2025 iscc
练武
签
通过两个函数地址确定libc版本
1 | #!/usr/bin/env python3 |
key
UAF + ret2libc 栈平衡
思路:
- 将释放的chunk再申请回来,并输入flag即可进入到漏洞处
- 在第一个write处堆溢出一字节覆盖canary的\x00,使它能够被printf打印出来
- 然后正常打ret2libc,调用system需要栈平衡
1 | #!/usr/bin/env python3 |
《魔导王的秘密》
glibc2.27
tcache_poisoning分配到free_hook,改free_hook为one_gadget
1 | #!/usr/bin/env python3 |
genius
function3()中栈溢出
思路:
- 绕过没用的东西
- 进入function3(),read()中读入覆盖canary的低字节,printf()泄露出来
- gets()溢出,rop
1 | #!/usr/bin/env python3 |
program
glibc2.31
uaf,堆溢出,增删改查都有
思路:
- 先free一个chunk进入unsortedbin,泄露canary
- tcache_poisoning申请到free_hook
- 改free_hook为system,free一个chunk即可get shell
1 | #!/usr/bin/env python3 |
Fufu
保护全开
1 | # ctf @ 51e4c2a03f9c in ~/pwn [0:52:46] |
分析程序:
这道题给出了一个菜单,有两个函数,分析如下:
submit_evidence()
ida中反编译的伪代码如下:
1 | unsigned __int64 submit_evidence() |
有三次输入:
- 第一次输入的时候,程序进行了判断 10*input <= 0x40 ,所以必须小于7
- 第二次输入,可以明显的发现是格式化字符串漏洞
- 第三次输入目前看来用处不大
trial_adjourned()
伪代码如下:
1 | unsigned __int64 trial_adjourned() |
很明显的栈溢出
思路
先找一个gadgets,如下:
1 | fufu/ $ ROPgadget --binary ./pwn --only "pop|ret" |
明确了有格式化字符串漏洞和栈溢出,并且前者可重复利用,fmt+ret2libc
思路:
- 通过两次格式化字符串,来泄露canary和程序基址(此处泄露的main)
- 通过2中的栈溢出泄露地址,并在该网站上查到system,bin_sh偏移
- 再让程序返回到输入这里,再次利用栈溢出来get_shell
- 注意:这道题glibc是2.34版本的,存在栈平衡检查,在调用system时要多加一个ret
exp:
1 | #!/usr/bin/env python3 |
mutsumi
还是vmpwn
通过mutsumi_jit函数解析用户输入的指令生成机器码并通过run_vm执行
vm结构体,如下:
1 | struct struc_VM{ |
vm结构体由 指针、指令类型、指令 组成,后续在mutsumi_jit()进行处理
在main()函数中首先分配了两个chunk,一个赋给了VM
这里分配了0x1000+0x10大小的空间给VM,通过下图观察VM的结构体
主程序中,程序接受的每次输入有三种形式:
- saki,ido,to name
- saki,ido nptr
- saki,stop
由于atoi(nptr),我们不能自由的生成指令 程序使用__isoc99_scanf(“%s”, s);读入,没有对长度进行检查
观察上图,我们可以通过溢出修改vm结构体 来控制要生成的指令 构造payload = b’saki,stop’.ljust(0x20,b’\x00’) + ( p64(target) + p64(type) + p64(value) ) *n
在mutsumi_jit中对vm结构体进行处理,先将指针的值和saki进行比较,再根据type进行跳转,当type==0 时,会调用imm2asm将value转换为机器码存入0x114000
思路:
- 通过溢出,修改指令
- 让程序执行mprotect(),再read()读入到mprotect()的区域
- read()读入shellcode来get_shell
exp:
1 | #!/usr/bin/env python3 |
Dilemma
栈迁移+fmt+orw
在自定义的init()函数中开启了沙箱
可以看到禁用了sys_number和execve,那就只能用orw来读flag
在看menu()函数,看到这个dummy()中什么都没有那就是设计的gadgets了
查看func_1()
这里有两次输入,而且第二个只能用一次,有溢出
在func_0中明显存在溢出
在iscc中flag文件名为flag或flag.txt
查找gadgets
缺少了rdx的gadgets,在libc文件中找通过泄露可以libc为libc6_2.35-0ubuntu3.8_amd64.so
思路:
- 在func_1()中泄露canary和libc
- 进入func_0()中栈迁移到bss段,将flag.txt写入
- 在rop回到func_0()中栈溢出写入orw
exp:
1 | #!/usr/bin/env python3 |
擂台
call
ret2libc,泄露的write地址
1 | #!/usr/bin/env python3 |
vm_pwn
逆向记录:
- 0x4090 memory
- 0x4098 read(0, opcode, 0x1000uLL);的返回值—->ax
read() 函数的返回值含义如下:
返回值为正整数:表示实际读取到的字节数。
返回值为0:表示已到达文件末尾(EOF),没有更多数据可读。
返回值为-1:表示读取失败,发生了错误(此时可以通过 errno 查看具体错误原因)。
- 0x4060 array
指令集逆向解析
1. 基础指令
Opcode | 指令名称 | 参数 | 功能描述 | 对应代码位置 |
---|---|---|---|---|
0x00 | LOAD_CONST |
目标寄存器地址 |
从内存加载常量到目标寄存器(sub_132C 读取8字节) |
case 0u |
0x01 | LOAD_MEM |
源地址, 目标地址 |
从内存地址加载值到寄存器(*reg[v12] 解引用操作) |
case 1u |
0x02 | STORE_MEM |
目标地址, 源值 |
将寄存器值存储到内存地址(*reg[fetch] = reg[v11] ) |
case 2u |
0x03 | MOV |
目标寄存器, 源值 |
寄存器间数据传输(直接复制值) | case 3u |
0x09 | NOP |
无 | 空操作 | case 9u |
2. 控制流指令
Opcode | 指令名称 | 参数 | 功能描述 | 对应代码位置 |
---|---|---|---|---|
0x04 | CALL |
无 | 调用子函数(sub_1393 压栈返回地址) |
case 4u |
0x07 | JMP |
目标地址 |
直接跳转到目标地址(修改PC指针) | case 7u |
0x08 | EXIT |
无 | 退出虚拟机(返回主函数) | case 8u |
3. 运算指令
Opcode | 指令名称 | 参数 | 功能描述 | 对应代码位置 |
---|---|---|---|---|
0x0A | ADD |
目标寄存器 |
对目标寄存器做加法(reg[v9] += sub_132C(reg) ) |
case 0xAu |
0x0B | SUB |
目标寄存器 |
对目标寄存器做减法(reg[v8] -= sub_132C(reg) ) |
case 0xBu |
4. 高级操作
Opcode | 指令名称 | 参数 | 功能描述 | 对应代码位置 |
---|---|---|---|---|
0x05 | PUSH |
值 |
将值压入栈(通过sub_1432 实现栈增长) |
case 5u |
0x06 | CALL_FUNCTION |
函数指针 |
调用外部函数(v14(*reg) 执行函数指针) |
case 6u |
在\x01和\x02存在数组溢出,got表不可写,先获得data段0x4008的地址此处指向是base+0x4008,可以间接计算got
1 | #!/usr/bin/env python3 |
迷途之子
迷宫问题
1 | import idc |
可以得到二进制文件maze.bin,然后通过下面脚本可以获取maze.txt
1 | #!/usr/bin/env python3 |
BFS算法走到终点
1 | from collections import deque |
find.py
从任意x,y走向任意a,b所需的步的记录
1 | #!/usr/bin/env python3 |
思路:
静态分析
game()
1 | unsigned __int64 start_game() |
del()
1 | ssize_t del_user() |
这里虽然说置零了,但指令的不是free的那个指针,存在uaf
add()
1 | ssize_t add_user() |
没有溢出,且只能向bk指针处写值
edit()
1 | ssize_t edit_user() |
同样没有溢出,且只能向bk指针处写值
思路:
漏洞只有uaf,且没有溢出
我们要打就要考虑如何将free_hook等链入tcachebins中
思路:
- 首先要通过游戏,拿到gift()中的read地址,计算出libc_base
- 申请3个chunk,并将free掉,通过game()向chunk0的fd低字节处写值
- 写值的地址哪里填上free_hook的地址,然后就好说了
exp:
1 | #!/usr/bin/env python3 |
book_manager
推测book的结构体如下:
1 | struct book{ |
静态编译的程序,用sig恢复部分符号表,是一个菜单题通过数组模拟了malloc等
思路:
仔细观察可以发现,所有与book有关的操作都是通过栈来执行的,猜测栈溢出,通过程序中文件读的特点实现load(/flag)
- 泄露canary,在search的2中有一个封装的read()的输入函数
sub_40206A
,在最后可以多覆盖一个字节加上\n
,就可以通过printf()来泄露canary - 在5中发现了从bss段上copy数据到栈上
- 填充一些垃圾数据,到canary出构造rop
- 最终可以将flag读出来
exp:
1 | #!/usr/bin/env python3 |
mini_pwn
以下是整理后的虚拟机指令集表格:
虚拟机指令集表格
指令名称 | 操作码 | 子操作码 | 目标寄存器 | 行为描述 |
---|---|---|---|---|
mov reg,[mem] | 0x01 |
0x00 |
A | 从内存地址[SP] 读取8字节到寄存器A,SP += 8 |
0x01 |
B | 从内存地址[SP] 读取8字节到寄存器B,SP += 8 |
||
0x02 |
C | 从内存地址[SP] 读取8字节到寄存器C,SP += 8 |
||
0x03 |
D | 从内存地址[SP] 读取8字节到寄存器D,SP += 8 |
||
0x04 |
E | 从内存地址[SP] 读取8字节到寄存器E,SP += 8 |
||
0x05 |
F | 从内存地址[SP] 读取8字节到寄存器F,SP += 8 |
||
0x06 |
SP | 从内存地址[SP] 读取8字节到SP寄存器,SP += 8 |
||
mov [mem], reg | 0x02 |
0x00 |
A | 将寄存器A的值写入内存地址[SP-8] ,SP -= 8 |
0x01 |
B | 将寄存器B的值写入内存地址[SP-8] ,SP -= 8 |
||
0x02 |
C | 将寄存器C的值写入内存地址[SP-8] ,SP -= 8 |
||
0x03 |
D | 将寄存器D的值写入内存地址[SP-8] ,SP -= 8 |
||
0x04 |
E | 将寄存器E的值写入内存地址[SP-8] ,SP -= 8 |
||
0x05 |
F | 将寄存器F的值写入内存地址[SP-8] ,SP -= 8 |
||
0x06 |
SP | 将SP寄存器的值写入内存地址[SP-8] ,SP -= 8 |
||
swapctx | 0x03 |
无 | 无 | 保存当前寄存器状态到内存块,并恢复另一上下文(协程/异常处理) |
restorectx | 0x04 |
无 | 无 | 从内存中恢复之前保存的寄存器状态(与swapctx 配对使用) |
syscall | 0x05 |
无 | 无 | 执行Linux syscall ,参数通过寄存器传递(A=系统调用号,B/C/D=参数) |
xor reg, reg | 0x06 |
0x00 |
A | 将寄存器A置零 |
0x01 |
B | 将寄存器B置零 | ||
0x02 |
C | 将寄存器C置零 | ||
0x03 |
D | 将寄存器D置零 | ||
0x04 |
E | 将寄存器E置零 | ||
0x05 |
F | 将寄存器F置零 | ||
0x06 |
SP | 将SP寄存器置零 | ||
add reg, 8 | 0x07 |
0x00 |
A | 寄存器A的值增加8(A += 8 ) |
0x01 |
B | 寄存器B的值增加8(B += 8 ) |
||
0x02 |
C | 寄存器C的值增加8(C += 8 ) |
||
0x03 |
D | 寄存器D的值增加8(D += 8 ) |
||
0x04 |
E | 寄存器E的值增加8(E += 8 ) |
||
0x05 |
F | 寄存器F的值增加8(F += 8 ) |
||
0x06 |
SP | SP寄存器的值增加8(SP += 8 ) |
||
sub reg, 8 | 0x08 |
0x00 |
A | 寄存器A的值减少8(A -= 8 ) |
0x01 |
B | 寄存器B的值减少8(B -= 8 ) |
||
0x02 |
C | 寄存器C的值减少8(C -= 8 ) |
||
0x03 |
D | 寄存器D的值减少8(D -= 8 ) |
||
0x04 |
E | 寄存器E的值减少8(E -= 8 ) |
||
0x05 |
F | 寄存器F的值减少8(F -= 8 ) |
||
0x06 |
SP | SP寄存器的值减少8(SP -= 8 ) |
关键寄存器说明
虚拟寄存器组
A
:xmmword_4080
B
:xmmword_4080
的高8字节C
:xmmword_4090
D
:xmmword_4090
的高8字节E
:xmmword_40A0
F
:xmmword_40B0
的高8字节
栈指针
SP
:xmmword_40B0
,操作内存时自动增减。
状态标志
qword_40C0
:用于控制上下文切换和系统调用的条件。