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.
的上方 -
当我们
push
6次的时候发现所以是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
必须要用来open
flag文件,然后调用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的博客!