2025西湖论剑
PWN
Vpwn
Vpwn_分析1
- 这题折磨人,QAQ
- 先查看一下这个,先查看一下这个程序的保护机制。发现保护全开。

- 然后打开使用
IDA对该程序进行逆向分析,先从main函数开始分析- 在
main函数中,先是有一个菜单,该菜单表示着对栈的一些操作push、pop、print、Edit、exit - 然后还发现字节数组
v8这边是存储栈上的数据,我们输入的数据是int32类型。并且在v8[24]这个地方保存着push的栈数量(即控制着这个数组的索引值) - 这里就会发现一个问题,当我们对第7次
push的时候,我们push的值就会就会改变索引值,从而导致不正常的索引
- 在


- 之后查看其他函数,先来查看
update函数,我们先输入需要修改的索引值,让后将这个索引值和字节数组v8都传入进行- 这边先会判断是否超出索引,超出索引后会抛出异常,并且结束该函数,继续新一轮循环
- 如果没出现异常,就会进行更新值的操作。

- 接下来查看
push这个函数的操作,我们输入要压入栈中的值,然后就可以进行压栈操作

pop函数也是一样,我们调用pop函数的时候直接进行出栈操作,并且在弹栈之前会先判断栈是否为空。如果栈是空的话就会抛出异常

- 接下来
print函数,就是打印栈上的内容

- 之后就是错误选择的处理和退出选项

Vpwn_分析2
-
接下来进行动态调试,在动态调试的过程中会发现,存储栈索引的位置在如下图中的位置即
glibcxx.的上方 -
当我们
push6次的时候发现所以是6,是正常的情况

- 但是当我们
push第7次的时候就会出现如下情况,原本索引值为7的,接下来变成了0x20

- 这时我们就可以对栈进行非法的操作,我们使用
print操作,将栈上的某些地址泄露出来,并且可以使用push操作,修改一些返回地址 - 我们先来查看栈上的数据,看看这些数据有什么情况,我们发现这个位置,距离返回地址和程序地址比较近,并且通过计算,如果索引为
0x20泄露栈__libc_start_call_main+128的地址是可以的,并且还可以泄露程序的地址0x556ed6e03329,这样程序地址和返回地址就可以泄露出来了,通过计算偏移就可以得到程序基地址,和libc的基地址。

- 泄露地址的时候这里要注意一下,字符串格式化输出的的
int类型的数据是有正负的,如果直接对该数字字符转成Python中的int类型的数据,会出现一点问题,这时候需要位操作来处理接收后的数据
1 | def change(low,high): |

- 这时泄露之后,我们就可以想办法修改返回值通过计算
0x20和v8字节数组的偏移,就可以发现,v8+0x20*4所在的地址比返回地址高,这时我们就可以使用pop操作,将栈上的数据弹出一点。(也可以使用edit编辑栈上数据),使得我们可以修改返回地址

- 可以修改返回地址后,我们就可以布置rop链,这里我使用打
ogg的方法

- 这时发现
r12和r13这个寄存器并不是NULL,还有一点很重要rbp指针在最后leave的时候会变成1导致rbp-0x48不可写,这时还要构造rop链修改rbp为.bss段上的合适的地址,再修改r12和r13指针为NULL

- 通过
libc文件和程序文件寻找到rop链最后执行到ogg,即可getshell,注意在push操作的时候还要注意将Python中的整型,通过转换转为int32类型的数据,对于该题目canary如存在,算是已经在修改索引的时候绕过了
1 | def change2(low): |
exp
- 最终的exp如下:
1 | from pwn import * |
Heaven’s door
Heaven’s door_分析1
- 先查看一下保护机制,发现有栈溢出保护,但是没有开PIE

- 现在使用IDA进行逆向分析,一打开程序,发现是一题沙箱题,显然要写
shellcode

- 现在就来查看一下
sandbox,发现是一个白名单沙箱,只允许用如下的系统调用。并且白名单中没有read()这个系统调用。

- 现在继续回来分析源代码,该程序通过创建子进程,将程序分成两个任务,子进程主要完成的就是执行
shellcode的任务,而父进程是随机输出字符串数组中的某个字符串,一共会输出14次,所以并不需要考虑父进程的操作。

- 现在主要分析子进程,子进程会开辟一段内存空间,然后让我们输入
0xC3范围内的数据,并对数据进行检查,检查之后开启沙箱,开启完执行我们所输入的数据。

- 现在来查看一下
count_syscall_instructions会检查什么,该函数是检查是否有15 5两个是否相邻的个数。其实15 5这个字节码就是syscall这个系统调用的字节码,该函数的功能就是检查syscall的数量 - 会返回该
shellcode中syscall的个数,如果大于2个就会退出程序(在main函数的操作)

- 这时我们编写shellcode就要考虑以下几点:
- 白名单没有
read()这个函数,所以要另外寻找其他的系统调用,将flag放入内存中 - 程序限制
syscall只能两次。
- 白名单没有
Heaven’s door_分析2
- 这时对于
read()我们可以使用沙箱名单的mmap系统调用进行代替,将文件映射到内存中。 - 对于
syscall的限制,这两次syscall必须要用来openflag文件,然后调用mmap将该文件映射到内存中。 - 对于如何将
flag输出出来,父程序有稍微提示一点,由于没有开启pie地址,我们还可以调用printf()函数,或者puts()函数将flag输出出来。所以我们就可以写如下shellcode
1 | mov rbx,0x000067616c662f2e |
exp
- 完整exp如下:
1 | from pwn import * |

非预期解
-
看到了这篇博客后才知道有非预期解:2025西湖论剑-PWN | StarrySky
-
execeve的系统调用竟然没有禁,可以直接getshell

babytrace(复现)
- 来复现一下这题,这题其实是基于
ptrace系统调用实现了一个简易的syscall沙盒,由父进程检测子进程,子进程与用户进行交互。参考博客:西湖论剑 2025 PWN Writeup | Y² 的博客
babytrace_分析1
- 查看一下保护机制发现保护全开。

- 查看一下libc版本,发现是
glibc2.35版本比较高。

- 先打开附件分析一下
main函数部分,main函数是Linux系统编程的风格。对于main函数的运行逻辑需要好好分析一下。打开先恢复结构体

- 首先就是这一段程序:
- 先进行初始化输入输出,使用
fork()函数在当前进程中创建一个子进程 - 接下来的
if语句先是调用了prctl,当父进程终止时,自动向子进程发送SIGKILL信号,直接结束子进程 - 之后又是调用
ptrace(),其实是一个反调试技术。如果有别的调试器附加到该程序中,该程序就会直接被kill。 kill(pid,19),实现的是手动让子进程暂停,让父进程可以被PTRACE_TRACEME后调用后waitpid()来控制该进程fork_main这个函数其实就是子进程与用户交互的逻辑。(具体逻辑等分析完main函数之后再来分析)
- 先进行初始化输入输出,使用

- 接下来一段代码如下:
- 首先有一个错误检查
waitpid(pid,&stat_logc,0)<0,这个是父进程等待子进程发送一个信号,当子进程创建失败或者不存在的时候就会进行处理,并发送出waitpid error1的错误提示 aralrm(0xFu)也是一个反调试技术,程序运行0xF秒后不论运行结束还是没结束都会强制结束程序。- 父进程还会先调用
ptrace(PTRACE_SETOPTIONS,....),就是让子进程在Syscall之前中止,让父进程先检查检查。 - 接下来是
do while的循环,PTRACE_SYSCALL用于追踪子进程的Syscall操作,当子进程每次进出系统调用时,父进程就会收到SIGTRAP信号。 PTRACE_GETREGS,在父进程接收到子进程Syscall操作的时候,使用ptrce(PTRACE_GETREGS,....)获取寄存器的值,并将这些寄存器的值存放到v7(一个user_regs_struct结构体,保存着子进程的寄存器值)- 在获取子进程的值后就会对
user_regs_struct中的rax寄存器进行检查,当系统调用号为1、231、5、60的时候就不会触发if语句 - 当不符合目标系统调用号,
v7.orig_rax会被设置为1,父进程就会执行ptrace(PTRACE_SETREGS,...)主要就是将rax这个系统调用号修改成-1,导致程序系统调用错误 - 并且最后还会调用
ptrace(PTRACE_SYSCALL,...)让子进程继续运行,之后就是waitpid进行等待和检测进程是否存在。 - 最后当子进程停止的时候,父进程才会退出循环。
- 首先有一个错误检查

- 接下来就是分析子进程与用户的交互逻辑,也就是
fork_main这个函数:有三个选项提供给我们选择,其中第三个选项是退出

- 选项1这里只能任意写
8字节需要调试一下如何利用:

- 选项2这里可以泄露任意栈上存储的
8字节数据(这里可以泄露俩次),也需要考虑一下泄露哪个数据比较好:

babytrace_分析2
- 由于该题存在反调试,所以先使用ida将反调试的地方给
NOP掉,然后再apply_patch,使用patch过后的二进制文件即可。

- 并且
ptrace沙箱是一个白名单,只允许1 write、231 exit_group、5 fstat 、60 exit这几个系统调用。但是注意这个沙箱是个模拟沙箱,实际上error()函数是这样的,并不是真正的白名单沙箱,system("/bin/sh")还是能打的。(这个沙箱其实是如有)

exp
Crypto
iot
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!

