参考blog(https://bbs.kanxue.com/thread-287806.htm#msg_header_h3_0)
Time
mian
1 | void __fastcall __noreturn main(int a1, char **a2, char **a3) |
sub_2A31()
1 | unsigned __int64 sub_2A31() |
sub_2B0F
1 | __int64 sub_2B0F() |
start_routine
1 | unsigned __int64 __fastcall start_routine(void *a1) |
这里在start_routine的printf(byte_50A0);有格式化字符串漏洞很明显我们要把读入的flag通过的格式化字符串泄露出来。
可是在sub_2B0F读入的文件名对flag进行了过滤。这里就要了解一下进程和线程的关系了。
线程与进程的基本关系(复习)
- 进程(Process):操作系统资源分配的基本单位。每个进程拥有独立的内存空间、文件描述符、环境变量等。进程之间相互隔离,通信需要通过 IPC(如管道、消息队列)。
- 线程(Thread):进程内的执行单元(轻量级进程)。多个线程共享同一进程的资源(如内存、文件描述符),但每个线程有自己的栈、寄存器和程序计数器。线程切换开销小,适合并发任务。
线程竞争(Race Condition)的定义和原因
- 定义:当多个线程同时读写共享资源(如
filename
),且没有同步机制(如锁)时,程序的执行结果依赖于线程调度的时序,导致不一致或错误。
1 | 时间 事件 |
再用格式化字符串泄露flag
1 | hello aaaa0x71a74c1fe8a0(nil)(nil)0x6(nil)0x10000000000x3000003e80x2000x36c63f9b4b69cc070x2bc422698ba00f710x80656d6974(nil)(nil)(nil)(nil)(nil)(nil)0x20(nil)0x36c63f9b4b69cc070x2bc422698ba00f710x10102464c457f(nil)0x1003e00030x12400x400x41980x380040000000000x1c001d0040000d0x4000000060x400x400x400x2d80x2d80x80x4000000030x318 ,your file read done! |
把16进制
1 | 0x80656d6974 ---> time |
来算一下它在那个位置
1 | hello aaaa |
文件在12的位置,但是我们不知道内容在什么位置,我在本地创建一个fake:aaaa来算一下
1 | hello aaaa |
在22的位置
1 | from pwn import * |
理论上是可以的但是没通,实际上也是可以的,因为别的师傅打出来,我没复现出来。
原作者的exp(https://bbs.kanxue.com/thread-287806.htm#msg_header_h3_0)
1 | from pwn import * |
官方exp
1 | from pwn import * |
smallbox
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
在这里我们可以看到父线程开启了sandbox的保护,子进程没有但是子进程是个无限循环。
1 | mmap((void *)0xDEADC0DE000LL, 0x1000uLL, 7, 50, -1, 0LL) == (void *)0xDEADC0DE000LL |
在0xDEADC0DE000上创建了RWX(可读,可写,可执行)
1 | $ echo -ne "expected_input" | seccomp-tools dump ./smallbox |
只能用ptrace
所以攻击思路就是在子进程里用ORW,在到父进程执行,具体怎么绕过无限循环能先看exp(大佬原创的,我纯黏贴)
1 | from pwn import * |
ptrace请求类型详解
PTRACE_ATTACH
(16)
1 | asm(shellcraft.ptrace(16, "r14")) |
- 作用:将当前进程附加到目标进程作为调试器
- 参数:
r14
:目标进程PID(子进程)
- 效果:
- 使子进程进入暂停状态(相当于发送SIGSTOP)
- 父进程成为子进程的调试器,可以完全控制子进程
- 系统调用号:
__NR_ptrace = 101
,请求类型为16
PTRACE_GETREGS
(12)
1 | asm(shellcraft.ptrace(12, "r14", 0, 0xDEADC0DE000+0x500)) |
- 作用:获取目标进程的寄存器状态
- 参数:
r14
:目标进程PID0
:忽略参数0xDEADC0DE500
:寄存器数据存储位置
- 效果:
- 将子进程的所有寄存器值复制到
0xDEADC0DE500
位置 - 结构体大小为
sizeof(user_regs_struct) = 216字节
- 将子进程的所有寄存器值复制到
- 目的:为后续修改寄存器做准备
PTRACE_POKETEXT
(5)
1 | asm(shellcraft.ptrace(5, "r14", 0xDEADC0DE000, 0x010101010101b848)) |
- 作用:向目标进程内存写入数据
- 参数:
r14
:目标进程PID0xDEADC0DE000
:写入地址0x010101010101b848
:写入的8字节数据
- 关键特性:
- 每次调用只能写入8字节数据
- 需要多次调用写入完整shellcode
4.PTRACE_SETREGS
(13)
1 | asm(shellcraft.ptrace(13, "r14", 0, 0xDEADC0DE000+0x500)) |
- 作用:设置目标进程的寄存器状态
- 参数:
r14
:目标进程PID0
:忽略参数0xDEADC0DE500
:寄存器数据来源位置
- 效果:
- 将
0xDEADC0DE500
处的寄存器数据恢复到子进程 - 关键点:虽然恢复了寄存器,但此时子进程的代码已被覆盖
- 将
PTRACE_DETACH
(17)
python
1 | asm(shellcraft.ptrace(17, "r14", 0, 0)) |
- 作用:分离调试器与目标进程
- 参数:
r14
:目标进程PID0
:忽略参数0
:发送给子进程的信号(0表示无信号)
- 效果:
- 子进程恢复执行
- 父进程不再控制子进程
阶段1: 附加和控制子进程
PTRACE_ATTACH(16)
- 父进程附加到子进程
- 子进程暂停执行
延时循环
1
2
3
4mov rcx,0x500000000
loop:
sub rcx,1
jnz loop- 目的:确保附加操作完成(替代waitpid)
- 原理:给内核时间处理附加请求
阶段2: 准备内存操作
PTRACE_GETREGS(12)
- 保存子进程当前寄存器状态
- 存储到共享内存的0x500偏移处
调整父进程栈指针
asm
1
2
3
4mov rsp,0xDEADC0DE588
mov rax, 0xDEADC0DE000
push rax
mov rsp,0xDEADC0DE800- 目的:避免后续操作破坏父进程栈
- 将栈移到共享内存的安全区域
阶段3: 注入恶意代码
8次PTRACE_POKETEXT(5)调用
向子进程内存写入64字节ORW shellcode
覆盖子进程原来的循环代码:
asm
1
2
3
4
5
6; 原始代码 (被覆盖)
while(1):
jmp $ ; 机器码: EB FE
; 覆盖后代码
movabs rax,0x101010101010101 ; 新指令
阶段4: 恢复执行
- PTRACE_SETREGS(13)
- “恢复”子进程寄存器
- 实际效果:RIP仍指向被覆盖的代码区域
- PTRACE_DETACH(17)
- 分离父进程和子进程
- 子进程从当前RIP开始执行(即ORW shellcode)
阶段5: 维持进程
父进程挂起
asm
1
jmp $ ; 无限循环
- 防止父进程退出导致程序终止
- 保持子进程继续运行
1 | 0xDEADC0DE000: |
我就了解到这里了,还是太菜,多学吧wuuu~~~
官方exp
1 | from pwn import * |