41

个人PWN入坑常见方法总结

 5 years ago
source link: https://www.freebuf.com/articles/rookie/200207.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0×01 概述

本文介绍个人学习pwn过程中的一些总结,包括常用方法,网上诸多教程虽然有提供完整的exp,但并未解释exp为什么是这样的,比如shellcode写到哪里去了(这关系到跳转地址),ROP链怎么选择的。对于pwn,本人也是新手,其中有总结错误的,欢迎各位大佬指正。

文中用到的测试程序都在: https://github.com/silience/pwn

0×02 PWN常用的基本知识

首先拿到一个PWN程序,可以先使用file命令,判断是32位还是64位。

YFFfyyZ.jpg!web

可以使用objdump读取plt和got表,plt和got网上都有详细的介绍,再此不再赘述。

vyy6riA.jpg!web

F3UBRfn.jpg!web

这边要提一下数据在寄存器中的存放顺序,这个在格式化字符串漏洞中要格外注意,特别是64位,32位的先后顺序是eax->edx->ecx->ebx,64位的先后顺序是rdi->rsi->rdx->rcx->r8->r9。

刚开始学习的时候,个人经常把pop和push经常搞反,因此在此把这两个指令的介绍说一下:push [reg]/[num] 是将reg寄存器中的值或是数字num压入堆栈中,而pop [reg]是将堆栈栈顶的值弹出到reg寄存器中,并将这个值从堆栈中删去。

有时候要查看寄存器中的值,可以用到如下命令:

print $esp:打印esp的值
x/10x $esp:打印出10个从esp开始的值
x/10x $esp-4:打印出10个从偏移4开始的值
x/10gx $esp:以64位格式打印

UjAbmqy.jpg!web

下面先使用hello练练手,首先使用IDA的F5大法可以看到内部有个getshell函数,可以直接跳转到该函数getshell。

jUnEZjA.jpg!web

使用工具pade可以很方便的计算出偏移量,pattern create 100。

67fuUr7.jpg!web

pattern offset 0×41284141,计算出偏移量为22。

RbuAzuV.jpg!web

查看汇编代码,获取getshell的地址,也就是要跳转的地址。

EFBrAvA.jpg!web

最后得到完整的exp如下。

UBRzUfq.jpg!web

0×03 shellcode

生成方式

1、在shellcode数据库网站找一个shellcode, http://shell-storm.org/shellcode/

2、使用kali的msfvenon生成shellcode,如命令msfvenon -p linux/x86/exec CMD=/bin/sh -f python

3、使用pwntools自带的函数如asm(shellcraft.sh())

但有时候不知道shellcode写到哪里去了,在回答这个问题前,要提一下bss段、data段、text段、堆(heap)、栈(stack)的一些区别。

1、bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,bss段属于静态内存分配。

2、data段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域,数据段属于静态内存分配。

3、text段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

4、堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

5、栈(stack):栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场。

下面以ret2shellcode,同样使用IDA看下代码,很明显,shellcode写入到bss段。

aM7JRnV.jpg!web

使用命令readelf -S ret2shellcode查看获取bss段地址为0x0804a040。

6NnEfiU.jpg!web

还必须保证bss段有可执行权限,shellcode才能运行,可用gdb调试的vmmap命令查看,发现bss段可读可写可执行。范围是0x0804a000到0x0804b000,bss段地址0x0804a040在这个区间,且必须保证shellcode长度不超过这个区间即可,但到目前为止,shellcode具体地址依然不知道。

FraqEzR.jpg!web

这时可以去调用它的函数strncpy前查看汇编代码,一般通过push或者move进行参数传递,参数传递顺序是从右到左,可以定位到shellcode地址0x804a80。

uIbUzef.jpg!web

最后exp如下。

qaaIF3a.jpg!web

shellcode地址的位置其实是一个坑。因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢?有两种方法,一种是 开启core dump这个功能,另外一种是使用GDB的attach功能。

可以使用level1练手,有时checksec显示PIE关闭。

B36vqmn.jpg!web

其实用ldd会发现,地址依然会随机变化。

YvqemyA.jpg!web

可使用命令echo 0 > /proc/sys/kernel/randomize_va_space关掉整个linux系统的ASLR保护,再进行调试;开启core dump这个功能,开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

0×04 格式化字符串漏洞

这要讲一下字节序。

大端就是:存储最高有效字节在最小的地址(网络传输文件存储常用)。

小端就是:存储最低有效字节在最小的地址(计算机内部存储)。

帮助记忆的法子:小端就是存储先存最小有效字节,大端就是先存最大有效字节。

fqyAFjq.jpg!web

printf函数的格式化字符串常见的有 %d,%f,%c,%s(用于读取内存数据),%x(输出16进制数,前面没有0x),%p(输出16进制数,前面带有0x);%n是一个不经常用到的格式符,它的作用是把前面已经打印的长度写入某个内存地址,用于修改内存,除了%n,还有%hn,%hhn,%lln,分别为写入目标空间4字节,2字节,1字节,8字节。

去读内存,假如当偏移量为5时:

./a.out "`printf "\0x78\x56\x34\x12"`.%08x.%08x.%08x.%08x.%08s"

或者直接使用读取地址0×12345678的内容:

./a.out "`printf "\0x78\x56\x34\x12"`.%5\$s"

比如要将跳转地址0x0804a048改data为0×12345678,可使用%hhn;因为使用的是小段序,高字节保存在高地址。

AvAvAfv.jpg!web

所以poc如下,偏移量要从6开始,应为\x4b\xa0\x04\x08保存在偏移地址6。

./a.out "`printf "%18c%6\$hhn"."%34c%7\$hhn"." %34c%8\$hhn "."%34c%9\$hhn "."\x4b\xa0\x04\x08"."\x4b\xa0\x04\x08"."\x4b\xa0\x04\x08"."\x4b\xa0\x04\x08"

但是为什么依次是%18c、%34c、%34c、%34c;第一个是0×12,很简单,变成十进制就是18;第二个是0×34,十进制52,第二次总写入数包括第一次的,即18+34=52;后面两次依此类推。

实际使用中,可以直接使用pwntools的函数fmtstr_payload,或者fmt_str(offset,size,addr,target)(其中offset表示要覆盖的地址最初的偏移,size表示机器字长,addr表示将要覆盖的地址,target表示我们要覆盖为的目的变量值)直接覆盖。

可以以湖湘杯2017的pwn200进行练手,使用IDA,发现很明显的格式化字符串漏洞。

nEFn2qb.jpg!web

首先输入AAAA.%X.%X.%X.%X.%X.%X.%X.%X,可以发现在第七个%X输出41414141,A的ascii码是41(有时是61616161,a的ascii码61,因为程序把输入转换成小写),可知偏移量是7,首先使用%s获取puts函数的真实地址,然后计算出system的真实地址,后面再利用函数fmtstr_payload,将atoi的地址替换为system地址,当执行atoi时,就会这些system函数,从而获取shell。

meAJbuI.jpg!web

0×05 libc

libc中提供了大量的函数,gdb调试时可直接使用如下命令获取地址,如果未提供,可以去网站 http://libcdb.com/ 下载对应的文件。

可依次执行以下命令,快速getshell。

print system#获取system函数地址。

print __libc_start_main
find 0xb7e393f0, +2200000, "/bin/sh"#获取参数"/bin/sh"的地址

ENfIRbm.jpg!web

以level2为例,exp如下,利用链:偏移数据+system地址+返回地址+参数地址,本例是通过system获得shell,不需要做其他操作,所以返回地址可以随便写。

7bquyyb.jpg!web

0×06 ROP

Rop链顺序,首先是跳转地址,比如要调用的内置函数write泄露出system地址,然后是返回地址(如果泄露的地址要重复使用,则返回地址是write地址或者它前面的地址),再就是传递的参数是从右往左入栈。

以ret2syscall为例,rop链构造如下:因为要调用execve(“/bin/sh”,NULL,NULL),该系统函数的调用号为0xb,因此首先要将0xb给eax寄存器,可使用ROPgadget –binary ret2syscall –only “pop|ret” | grep “eax”进行查找。

NBJNbyf.jpg!web

因为函数execve有三个参数,接着可以使用命令。

ROPgadget –binary ret2syscall –only “pop|pop|pop|ret” | grep “ebx”,不能选包含esi(esi是下条指令执行地址)或者ebp(栈基址寄存器)。

jyimU3a.jpg!web

使ROPgadget –binary ret2syscall –string ‘/bin/sh’,可查找参数/bin/sh 的地址。

RN3EB3A.jpg!web

最后再跳转到int 0×80的地址就可执行对应的系统调用,也就是execve函数,可通过ROPgadget –binary ret2syscall –only ‘int’,找int 0×80的地址。

QvA32iv.jpg!web

最后完整的exp如下。

iiaQN36.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK