GHCTF-2025-pwn
GHCTF my_vm
ida分析
main函数分析
1.存在backdoor()函数,点开发现system(“/bin/sh\x00”).可以直接利用这个地址0x400877.
2.funcptr会调用my_print,如果可以修改my_print 为backdoor。那就很完美了
3.memory中保存着我们的指令,execute 会按序执行我们的指令,查看这个函数。
execute函数分析
1.首先看,对op的处理,和对op的限制
2.寻找漏洞。option == 0x90 时,可以对memory上的数据作修改
3.基于此,如果reg[dest]设置为负数,那么可以完成对其他数据的修改
4.从第三张图,查看option == 0x90 时的汇编,发现赋值指令是movzx(有符号低扩展为有符号高),所以可以在reg[]中写入负数,完成数组的向上越界
构造思路
1.首先是,ip和sp。ip从0开始,也就从我们读入的第一个指令执行。sp设置为1,大于0就行
2.接着读入op。我们需要对op作一点处理,便于控制每一个字节
1 | def Code(op,dest,src1,src2): |
通过样的处理,我们可以控制每个字节,便于准确的控制
3.需要找到要覆盖的目标地址,dest_addr. 这一题中可以覆盖func的内容为backdoor.另外,常见的手法可以覆盖got表内容为backdoor .此题中我采用了后者的方法
4.计算对应dest_addr的偏移,这里从汇编中可以看出来,此题中的memory和reg均是以rax*4 来寻址。可知,均是4字节数组.所以对应偏移需要除以4,才能被数组寻到
5.得到偏移之后,利用0x90控制数据,注意到,数据会被改写为src1.因此,在调用前需要将某个reg内写入我们的backdoor
6.最后,因为我们不能直接往reg里写入任意数据,有字节和大小的限制。所以我们需要通过题目提供的运算操作,一步一步修改内容.
exp
修改puts_got
1 | from pwn import * |
覆盖func
1 | from pwn import * |
GHCTF ret2libc2
ida分析
func函数分析

1.程序很简单,main函数里只有init和func,这里直接看到func函数
2.可以看到存在一个格式化字符串漏洞和溢出漏洞.
3.程序没有提供system和/bin/sh\x00,需要泄露libc,完成ret2libc.
func汇编分析

1.从汇编中可以看到更多信息.
2.首先是在leave ret 之前,lea rax [rbp+buf]. 实际上是将我们的输入的起始位置的内容交给了rax.而且可以注意到,无论是printf还是两个puts,都是通过rax来设置rdi。那么也就说我们的输入,可以给printf传递参数,也就是可以实现我们的格式化字符串漏洞.
3.同时,leave ret 留下了栈迁移的隐患。
gdb调试分析

1.通过gdb动调寻找栈上可以泄露出libc的函数.将func的返回地址覆盖为0x401227,直接将printf的rdi修改成我们的输入,查看这一帧栈帧,在0x15的位置看到了__libc_start_main,计算偏移为21+6=27.
2.同时,在第一次溢出时,需要覆盖rbp为有效地址。否则,这次func执行最后,会崩溃掉。
构造思路
1.首先确定泄露libc的手段,格式化字符串.并且第一次溢出时需要栈迁移.在这里补充一点,除了使用格式化字符串以外,还有一种泄露的手法.观察func函数,0x401223处,会将rbp-0x10 的内容作为参数赋给rax,再下方又被赋给了rdi.那么如果[rbp-0x10]是某个got表,那就可以把got表的内容打印出来。所以我们只需要把某个got-0x10交给rbp,就可以完成第一次的栈迁移和libc的泄露。
2.因为程序本身是没有提供pop_rdi,但是题目给了libc.so.6文件,在泄露libc基址之后,利用libc.so.6中的pop rdi;ret,一样可以控制rdi寄存器。现在我们已经有了ret2libc的全部条件。只需要栈迁移的一个合适的地址,完成rop。
3.选择bss段的高地址完成这段rop。如果是采用第二种方法泄露libc的话,需要再栈迁移一次,而且为了保证程序的顺利执行,第二次溢出,需要注意维护got表的内容尤其是read,否则第三次溢出就会出错。
exp
格式化字符串
1 | from pwn import * |
迁移泄露
1 | from pwn import * |
GHCTF 你真的会布置栈吗?
ida分析
start函数分析

1.print了两段字符,然后调用sys_read()读取数据,溢出空间非常大
2.最后,不是leave ret,而是jmp rsp,var8 是 qword ptr -8 ,可以从汇编代码查看
print函数分析

1.print是通过sys_wirte(),实现写字符,最后也是jmp rsp.
gadgets 分析
1.gadgets都已经在上面的图中,可以看到,我们能直接控制的有rsi,rdi,rbx,r13,r15,最后还会jmp r15.
2.从print的汇编中可以看到,可以交换rax和r13 的值,因此可以间接控制rax.
3.同时,dispatch留有执行rbx中代码的功能.
4.下方还可以控制rdx,rsi,rdi 值为0.
构造思路
1.首先,在_start 函数中有很明显的溢出漏洞,并且通过jmp rsp 可以跳转到我们写入的地址。第一眼,考虑shellcode ,但是一下就可以排除。因为它不会执行shellcode,而是跳转地址。因为题目只有系统调用的函数,所以肯定是用syscall解题。
2.确定是用syscall写题之后,考虑要控制的寄存器。首先execve函数的系统调用号是0x3b,需要设置rax=0x3b,可以通过r13 和 xchg rax,r13 实现,接着是rsi 设置为0 ,rdx 设置为0 ,rdi设置为,"/bin/sh\x00" 的地址。但是程序中没有该字符串,所以需要,先调用一次read往程序上写入字符串。
3.read函数,需要控制rax=0,rsi为buf,即写入的地址,rdx为写入字符数。可以利用gadgets 设置rsi 完成任意地址写,利用本身的sys_read 设置字节为0x539。因为程序没有bss段,所以只能往data段上写入字符。
4.那么目前的思路就是,利用sys_read往data段写入字符,再执行execve,getshell
5.但是似乎忽略了一点。rdx,本身是0x539,我们没有修改,需要通过xor_rdx 来修改为0 ,但是这条指令进跟着的是jmp r15.意味着,我们不能设置r15 为xor_rdx。 考虑让r15 指向xchg rax,r13,将rsp 设置为xor_rdx,也陷入了循环。似乎无法跳出循环。
6.此时注意到dispatch,可以跳转到rbx中的指令,而且每次执行会加8,也就是可以执行下一条指令。这样一来,我们把r15 指向dispatch,同时设置rbx为之前sys_read时,buf的地址.然后,之前sys_read时在buf 里依次布置指令,xor_rdx,xchg rax,r13 的地址。这样,将rdx置0 后,程序会跳转到xchg rax,r13 ,将rax 设置为r13的值.最后将rsp 设置为,syscall,就可以完成这华丽的rop。
exp
花里胡哨的rop
1 | from pwn import * |



