# Buzzing Đề bài kêu ta chạy file readflag bằng cách sử dụng absolute path (/readflag). Tuy nhiên khi chạy nó thông báo rằng ta không có quyền! ![image](https://hackmd.io/_uploads/ry0ZMfEzbg.png) Khi chúng ta xem xét file ta thấy readflag có quyền suid tức là cho dù mình có là user nào thì khi thực thi file này thì bắt buộc phải chạy dưới quyền root -> nên khả năng permission của file không thể hiện chuẩn được vì sao file không chạy được Rất có thể có một cái gì đó không cho phép chạy readflag cho dù có là root! Nhìn sơ qua ta thấy có 1 file locker.bpf.o, bpf là một bộ lọc có thể giúp linux chạy chương trình một cách an toàn hơn bằng cách có thể ban/allow một số syscall nhất định chúng ta hãy thử xem các chuỗi trong file là gì ![image](https://hackmd.io/_uploads/HkBDQfVfbe.png) Thấy có dòng ``` .relkprobe/__x64_sys_execve ``` Nhìn vào ta thấy kprobe attach file của ebpf Suy đoán: từ dòng đó ta suy đoán có một bpf + kprobe hook vào syscall execve. Do đó khi chạy có thể bị chặn bởi quyền hạng bị từ chối Vì vậy chúng ta có thể viết 1 script C để chạy readflag sử dụng open và execveat Tuy nhiên trong máy không có gcc -> không thể viết script C được. Kiểm tra /usr/bin để kiểm tra coi thử có ngôn ngữ nào ta có thể dùng không ![image](https://hackmd.io/_uploads/BJM5Hf4Mbx.png) Nhìn nhanh ta thấy có perl là có thể dùng được vậy chúng ta cần viết 1 script perl ```perl #!/usr/bin/perl use strict; use warnings; use Fcntl qw(O_RDONLY); my $path = "/readflag"; sysopen(my $fh, $path, O_RDONLY) or die "open $path: $!"; my $fd = fileno($fh); my $arg0 = "readflag\0"; my $empty = "\0"; my $arg0_ptr = unpack('Q', pack('P', $arg0)); my $argv_buf = pack('Q2', $arg0_ptr, 0); my $argv_ptr = unpack('Q', pack('P', $argv_buf)); my $envp_buf = pack('Q', 0); my $envp_ptr = unpack('Q', pack('P', $envp_buf)); my $path_ptr = unpack('Q', pack('P', $empty)); my $SYS_execveat = 322; my $AT_EMPTY_PATH = 0x1000; # execveat(fd, "", argv, envp, AT_EMPTY_PATH) my $rc = syscall($SYS_execveat, $fd, $path_ptr, $argv_ptr, $envp_ptr, $AT_EMPTY_PATH); die "execveat failed"; ``` Sau đó chạy ``` perl execveat_readflag.pl ``` Nhận được flag ``` W1{just_4_s1mpl3_3bpf_l04d3r_buzz1n'_4r0und_fufu_76274bc788378a36b3345a49948045e9} ``` # Checker (Unsolved): Bài này thì em chưa giải ra chỉ mới tìm được part 1 và part 2 của flag cũng như phân tích được các hàm mã hóa được 1 tí. Dự định là part 3 sẽ được giấu trong đống mã hóa đó hiện tại em chưa giải được em xin trình bày cách giải part 1 và part 2 của em ---- Sau khi phân tích file checker.exe nó sẽ tìm kiếm các resource trong file tương ứng với flag_checker.exe cho part 1 và part 2 + 3. Sau đó chạy flag_checker.exe sau khi chạy xong nó sẽ xóa đi file resource đó. Để giữa nguyên không cho xóa chúng ta patch lại file ![image](https://hackmd.io/_uploads/BJS8sGEG-x.png) Thành ![image](https://hackmd.io/_uploads/r1QwjGVG-x.png) Sau đó khi sử dụng checker.exe file flag_checker.exe sẽ không bị xóa nữa và chúng ta có thể dịch ngược file đó. Đầu tiên là flag_checker cho part 1 ```C int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v3; // r8 _QWORD *v4; // rax __int64 v5; // r8 char v7; // [rsp+30h] [rbp-128h] BYREF _BYTE v8[3]; // [rsp+31h] [rbp-127h] BYREF unsigned int v9; // [rsp+34h] [rbp-124h] int v10; // [rsp+38h] [rbp-120h] __int64 v11; // [rsp+40h] [rbp-118h] _QWORD *v12; // [rsp+48h] [rbp-110h] __int64 v13; // [rsp+50h] [rbp-108h] _BYTE v14[8]; // [rsp+58h] [rbp-100h] BYREF _BYTE v15[16]; // [rsp+60h] [rbp-F8h] BYREF _BYTE dst[16]; // [rsp+70h] [rbp-E8h] BYREF _BYTE v17[24]; // [rsp+80h] [rbp-D8h] BYREF _BYTE v18[24]; // [rsp+98h] [rbp-C0h] BYREF _BYTE v19[24]; // [rsp+B0h] [rbp-A8h] BYREF _BYTE v20[24]; // [rsp+C8h] [rbp-90h] BYREF _BYTE v21[24]; // [rsp+E0h] [rbp-78h] BYREF _BYTE v22[24]; // [rsp+F8h] [rbp-60h] BYREF _BYTE v23[16]; // [rsp+110h] [rbp-48h] BYREF _BYTE v24[15]; // [rsp+120h] [rbp-38h] BYREF char v25; // [rsp+12Fh] [rbp-29h] BYREF if ( (word_140042018[2] & 1) != 0 ) printf_more(&offset_of_printf, "Flag checker 1 !!!!!!!!!!!\n", envp); else printf_more(&offset_of_printf, "Flag checker 1 !!!!!!!!!!\n", envp); printf_more(&offset_of_printf, "Your flag: ", v3); sub_1400040C0(&qword_140043580, &unk_1400432E8); v11 = unknown_libname_59(&v7); v12 = (_QWORD *)sub_1400099D0(&unk_1400432E8, v14); v4 = (_QWORD *)sub_140009480(&unk_1400432E8, v15); sub_140003F40(v22, *v4, *v12, v11); sub_140001460(v21, v22, &unk_1400432A0); sub_140001570(v20, v21, &unk_1400432B8); v9 = sub_140002370(1337LL); sub_1400039F0(v19, v20, v9); sub_140001C50((unsigned int)v18, (unsigned int)v19, (unsigned int)&unk_1400432D0, (unsigned int)&unk_140043288, 0); v24[0] = -4; v24[1] = 118; v24[2] = -44; v24[3] = 9; v24[4] = -93; v24[5] = -40; v24[6] = 80; v24[7] = 47; v24[8] = -71; v24[9] = -41; v24[10] = -70; v24[11] = -32; v24[12] = -80; v24[13] = 52; v24[14] = -78; v13 = unknown_libname_59(v8); qmemcpy(dst, (const void *)unknown_libname_63(v23, v24, &v25), sizeof(dst)); sub_140006820(v17, dst, v13); if ( (unsigned __int8)normal_compare(v18, v17) ) printf_more(&offset_of_printf, "Correct!\n", v5); else printf_more(&offset_of_printf, "Incorrect!\n", v5); v10 = 0; sub_1400074D0(v17); sub_1400074D0(v18); sub_1400074D0(v19); sub_1400074D0(v20); sub_1400074D0(v21); sub_1400074D0(v22); return v10; } ``` Nhìn sơ qua pseudocode thì chúng ta thấy sau khi nhập input (flag part 1) chương trình sẽ thực hiện 1 số loại mã hóa nào đó sau đó họ sẽ kiểm tra với 15 hằng số ở v24 như trong chương trình! Bởi vì v24 đã được cho trước nên chúng ta chỉ cần dịch ngược đoạn mã hóa Tuy nhiên có một nhận xét khá thú vị ở đây là thuật toán mã hóa là tuyến tính và độc lập. Nghĩa là việc biến đổi ký tự ở vị trí này không làm ảnh hưởng đến giá trị ở các vị trí khác. Chúng ta có thể kiểm chứng điều này bằng thực nghiệm! Đầu tiên nhảy vào hàm so sánh `normal_compare` và đặt breakpoint ở đầu hàm ![image](https://hackmd.io/_uploads/r1lh6MNGWl.png) Sau đó chúng ta nhập một input test `aaaaaaaaaaaaaaa` Đây là giá trị của input sau khi bị mã hóa ![image](https://hackmd.io/_uploads/BJxf0fVMZl.png) Thử một input khác `aaaxaaaaaaaaaaa` ![image](https://hackmd.io/_uploads/ByaECGNGZl.png) Chúng ta thấy nó chỉ khác duy nhất vị trí thứ 4 0x2B -> 0x32 đó là vị trí mà chúng ta thay đổi input Vì vậy sử dụng nhận xét này chúng ta có thể không cần dịch ngược hàm mã hóa! Vì vậy chúng ta có thể duyệt trâu tuyến tính với độ phức tạp là O(95 * n) với n là độ dài input (n = 15) Chương trình hiện tại hoạt động như một "oracle" chỉ trả về kết quả Đúng hoặc Sai, không cung cấp đủ thông tin để thực hiện brute force. Cần sửa đổi chương trình để tạo ra một phản hồi, cụ thể là cho biết độ dài prefix của input đã khớp bao nhiêu với flag chuẩn. Thực hiện Binary Patching lại hàm so sánh. Chiến thuật là tận dụng lại các instruction in ấn đã có sẵn trong binary để xuất thông tin này ra màn hình. ``` lea rdx, aCorrect lea rcx, offset_of_printf call printf_more ``` `Vì file bị stripped nên đây chỉ là tên được rename một cách ngẫu hứng` Ta sửa lại aCorrect một tí ![image](https://hackmd.io/_uploads/r1RSAd-fbe.png) Tóm tắt sơ quá ý tưởng * Khi mã hóa input -> sinh ra một cái list tương ứng * Nhiệm vụ của chúng ta là với mỗi một số trong list in ra số lượng ký C liên tiếp (khi đó độ dài chuỗi sẽ là giá trị của số đó) * Với mỗi một số xuống hàng Giả sử như sau khi mã hóa mảng nó là [2, 1, 5] thì chương trình sẽ in ra ``` CC C CCCCC ``` Dùng python để xử lý dữ liệu chúng ta khôi phục được mảng [2, 1, 5]. Lý do phải dùng cách rườm rà này là bởi vì khi patch tài nguyên của chúng ta có giới hạn (không đủ bytes nop để thay thế các instruction phức tạp hơn) nên chúng ta sử dụng những thứ có sẵn sau đó dùng python để xử lý dữ liệu sẽ ổn hơn khá nhiều Đây là sau khi ta patch lại ```asm .text:00007FF66BC422C0 normal_compare proc near ; CODE XREF: main+256↓p .text:00007FF66BC422C0 ; DATA XREF: .pdata:00007FF66BC850D8↓o .text:00007FF66BC422C0 .text:00007FF66BC422C0 var_50 = byte ptr -50h .text:00007FF66BC422C0 var_4F = byte ptr -4Fh .text:00007FF66BC422C0 var_4E = byte ptr -4Eh .text:00007FF66BC422C0 var_4D = word ptr -4Dh .text:00007FF66BC422C0 var_46 = dword ptr -46h .text:00007FF66BC422C0 var_40 = dword ptr -40h .text:00007FF66BC422C0 var_20 = qword ptr -20h .text:00007FF66BC422C0 var_18 = qword ptr -18h .text:00007FF66BC422C0 arg_0 = qword ptr 8 .text:00007FF66BC422C0 arg_8 = qword ptr 10h .text:00007FF66BC422C0 .text:00007FF66BC422C0 mov [rsp+arg_8], rdx .text:00007FF66BC422C5 mov [rsp+arg_0], rcx .text:00007FF66BC422CA sub rsp, 80 .text:00007FF66BC422CE mov rcx, [rsp+58h] .text:00007FF66BC422D3 call getLen .text:00007FF66BC422D8 mov [rsp+38h], rax .text:00007FF66BC422DD mov qword ptr [rsp+48], 0 .text:00007FF66BC422E6 jmp short start_loop .text:00007FF66BC422E8 ; --------------------------------------------------------------------------- .text:00007FF66BC422E8 .text:00007FF66BC422E8 increase_loop: ; CODE XREF: normal_compare+94↓j .text:00007FF66BC422E8 mov rax, [rsp+30h] .text:00007FF66BC422ED inc rax .text:00007FF66BC422F0 mov [rsp+30h], rax .text:00007FF66BC422F5 .text:00007FF66BC422F5 start_loop: ; CODE XREF: normal_compare+26↑j .text:00007FF66BC422F5 mov rax, [rsp+38h] .text:00007FF66BC422FA cmp [rsp+30h], rax .text:00007FF66BC422FF jnb short exittt .text:00007FF66BC42301 mov rdx, [rsp+48] .text:00007FF66BC42306 mov rcx, [rsp+88] .text:00007FF66BC4230B call getPos .text:00007FF66BC42310 movzx eax, byte ptr [rax] .text:00007FF66BC42313 .text:00007FF66BC42313 start_loop_1: ; CODE XREF: normal_compare+7B↓j .text:00007FF66BC42313 mov [rsp+50h+var_40], eax .text:00007FF66BC42317 nop .text:00007FF66BC42318 nop .text:00007FF66BC42319 nop .text:00007FF66BC4231A nop .text:00007FF66BC4231B nop .text:00007FF66BC4231C cmp eax, 0 .text:00007FF66BC4231F jz short label2 .text:00007FF66BC42321 lea rdx, aCorrect ; "C\x00\n\n\n\n\n" .text:00007FF66BC42328 lea rcx, offset_of_printf .text:00007FF66BC4232F call printf_more .text:00007FF66BC42334 mov eax, [rsp+50h+var_40] .text:00007FF66BC42338 dec eax .text:00007FF66BC4233A nop .text:00007FF66BC4233B jmp short start_loop_1 .text:00007FF66BC4233D ; --------------------------------------------------------------------------- .text:00007FF66BC4233D nop .text:00007FF66BC4233E nop .text:00007FF66BC4233F nop .text:00007FF66BC42340 nop .text:00007FF66BC42341 .text:00007FF66BC42341 label2: ; CODE XREF: normal_compare+5F↑j .text:00007FF66BC42341 lea rdx, aCorrect+2 ; "\n\n\n\n\n" .text:00007FF66BC42348 lea rcx, offset_of_printf .text:00007FF66BC4234F call printf_more .text:00007FF66BC42354 jmp short increase_loop .text:00007FF66BC42356 ; --------------------------------------------------------------------------- .text:00007FF66BC42356 .text:00007FF66BC42356 exittt: ; CODE XREF: normal_compare+3F↑j .text:00007FF66BC42356 add rsp, 50h .text:00007FF66BC4235A retn .text:00007FF66BC4235A normal_compare endp ``` Sau khi patch xong một script python đơn giản dùng để trâu phục hồi part1 của flag! ```python #!/home/ryou/.venvs/re/bin/python import subprocess BINARY_PATH = "./flag_checker.exe" def run_flag_checker(payload: str) -> str:\ result = subprocess.run( [BINARY_PATH], input=payload + "\n", capture_output=True, text=True ) return result.stdout def handle(inp: str): res = [int(len(i)) for i in inp.split('\n') if len(i) > 1] return res p = "testtesttest" handle(run_flag_checker(p)[38:]) v22 = [0xFC, 0x76, 0xD4, 0x09, 0xA3, 0xD8, 0x50, 0x2F, 0xB9, 0xD7, 0xBA, 0xE0, 0xB0, 0x34, 0xB2] flag = '' for _ in range(15): for ch in range(0x21, 0x7f): payload = 'A' * _ + chr(ch) + 'A' * (15 - _ - 1) # print(payload, handle(run_flag_checker(payload)[38:])[_], v22[_]) if handle(run_flag_checker(payload)[38:])[_] == v22[_]: flag += chr(ch) break # print(flag) print(flag) ``` ``` First part: W1{Ch4ng1ng_d4t ``` Part2 hoàn toàn tương tự ```asm .text:00000001400022C0 sub_1400022C0 proc near ; CODE XREF: main+256↓p .text:00000001400022C0 ; DATA XREF: .pdata:00000001400450D8↓o .text:00000001400022C0 .text:00000001400022C0 var_40 = dword ptr -40h .text:00000001400022C0 var_20 = qword ptr -20h .text:00000001400022C0 var_18 = qword ptr -18h .text:00000001400022C0 arg_0 = qword ptr 8 .text:00000001400022C0 arg_8 = qword ptr 10h .text:00000001400022C0 .text:00000001400022C0 mov [rsp+16], rdx .text:00000001400022C5 mov [rsp+8], rcx .text:00000001400022CA sub rsp, 80 .text:00000001400022CE mov rcx, [rsp+88] .text:00000001400022D3 call sub_14000A3F0 .text:00000001400022D8 mov [rsp+56], rax .text:00000001400022DD mov qword ptr [rsp+48], 0 .text:00000001400022E6 jmp short loc_1400022F5 .text:00000001400022E8 ; --------------------------------------------------------------------------- .text:00000001400022E8 .text:00000001400022E8 loc_1400022E8: ; CODE XREF: sub_1400022C0+94↓j .text:00000001400022E8 mov rax, [rsp+48] .text:00000001400022ED inc rax .text:00000001400022F0 mov [rsp+48], rax .text:00000001400022F5 .text:00000001400022F5 loc_1400022F5: ; CODE XREF: sub_1400022C0+26↑j .text:00000001400022F5 mov rax, [rsp+56] .text:00000001400022FA cmp [rsp+48], rax .text:00000001400022FF jnb short loc_140002356 .text:0000000140002301 mov rdx, [rsp+48] .text:0000000140002306 mov rcx, [rsp+88] .text:000000014000230B call sub_140007930 .text:0000000140002310 movzx eax, byte ptr [rax] .text:0000000140002313 .text:0000000140002313 loc_140002313: ; CODE XREF: sub_1400022C0+7B↓j .text:0000000140002313 mov [rsp+16], eax .text:0000000140002317 nop .text:0000000140002318 nop .text:0000000140002319 nop .text:000000014000231A nop .text:000000014000231B nop .text:000000014000231C cmp eax, 0 .text:000000014000231F jz short loc_140002341 .text:0000000140002321 lea rdx, aC ; "C" .text:0000000140002328 lea rcx, qword_140043620 .text:000000014000232F call sub_1400043D0 .text:0000000140002334 mov eax, [rsp+16] .text:0000000140002338 dec eax .text:000000014000233A nop .text:000000014000233B jmp short loc_140002313 .text:000000014000233B ; --------------------------------------------------------------------------- .text:000000014000233D db 4 dup(90h) .text:0000000140002341 ; --------------------------------------------------------------------------- .text:0000000140002341 .text:0000000140002341 loc_140002341: ; CODE XREF: sub_1400022C0+5F↑j .text:0000000140002341 lea rdx, asc_14002F482 ; "\n\n\n\n\n" .text:0000000140002348 lea rcx, qword_140043620 .text:000000014000234F call sub_1400043D0 .text:0000000140002354 jmp short loc_1400022E8 .text:0000000140002356 ; --------------------------------------------------------------------------- .text:0000000140002356 .text:0000000140002356 loc_140002356: ; CODE XREF: sub_1400022C0+3F↑j .text:0000000140002356 add rsp, 50h .text:000000014000235A retn .text:000000014000235A sub_1400022C0 endp ``` ```python #!/home/ryou/.venvs/re/bin/python import subprocess BINARY_PATH = "./flag_checker.exe" def run_flag_checker(payload: str) -> str: result = subprocess.run( [BINARY_PATH], input=payload + "\n", capture_output=True, text=True ) res = result.stdout return res.split(':')[1].strip() def handle(inp: str): res = [int(len(i)) for i in inp.split('\n') if len(i) > 1] return res p = "aAAAAAAAAAAAAAAA}" print(run_flag_checker(p)) v22 = [0x4E, 0xDD, 0x81, 0xCD, 0x84, 0x8F, 0xDD, 0x2B, 0xC2, 0x5A, 0xA6, 0xB7, 0xA9, 0x1A, 0xF7, 0xED, 0xF3] flag = '' for _ in range(14, 17): for ch in range(0x21, 0x7f): payload = 'A' * _ + chr(ch) + 'A' * (17 - _ - 1) print(payload, handle(run_flag_checker(payload)[38:])[_], v22[_]) if handle(run_flag_checker(payload))[_] == v22[_]: flag += chr(ch) break print(f'[+] Current Flag is {flag}') print(flag) ``` ``` Second part: 4_1n_run71me_Is_q ``` ### Một cách khác Một cách chúng ta có thể dùng để tìm part1 và part2 mà em nhận ra trong lúc làm là ![image](https://hackmd.io/_uploads/r1R_IXEGbg.png) Trong hàm này ở 2 chương trình chúng ta thấy `sub_14000AC40()` giống như một lệnh `push_back` và chuỗi `((n1337 % 0x61 + 31 * kk) ^ (unsigned __int64)(unsigned __int8)(Buf1a >> (kk % 24))) % 0x5F + 32)` luôn luôn nằm trong phạm vi [0x20, 0x7E] `ký tự in được` do vậy chúng ta có thể đặt breakpoint ở đây và khám nghiệm từng giá trị một! Lưu ý ở part2 có phần check_debugger ![image](https://hackmd.io/_uploads/r1kevmNzZx.png) `Có thể tìm bằng cách CTRL + F và search gs:60h` và hàm check_debugger này có thể làm thay đổi giá trị của chúng ta khi chạy debug nên chúng ta cần patch lại để không bị check ---- Bây giờ là phần part3 phần mà em chưa tìm ra được nhưng em sẽ trình bày hướng đã suy nghĩ được của em! Hướng đầu tên em nghĩ đó chính là trong file checker.exe vẫn còn 1 resource nữa dành cho flag_checker.exe part 3 mà có thể chương trình đã giấu vì thế ta cần check coi thử còn resource ẩn nào trong chương trình không ```python import pefile pe = pefile.PE("checker.exe") if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'): print(f"{'TYPE':<15} {'NAME/ID':<15} {'LANG':<10} {'SIZE (Bytes)':<10}") print("-" * 55) for resource_type in pe.DIRECTORY_ENTRY_RESOURCE.entries: try: if resource_type.name is not None: name = str(resource_type.name) else: name = pefile.RESOURCE_TYPE.get(resource_type.struct.Id, str(resource_type.struct.Id)) except: name = str(resource_type.struct.Id) for resource_id in resource_type.directory.entries: for resource_lang in resource_id.directory.entries: data_rva = resource_lang.data.struct.OffsetToData size = resource_lang.data.struct.Size print(f"{str(name):<15} {str(resource_id.struct.Id):<15} {str(resource_lang.struct.Id):<10} {size:<10}") else: print("File này không có Resource directory.") ``` ![image](https://hackmd.io/_uploads/BJFSY7VMZx.png) Vậy không có resource ẩn. Checker.exe đã làm hết nhiệm vụ của mình, theo em nghĩ part 3 chắc chắn được giấu trong flag_checker.exe sau Ngoài ra em còn có giả thuyết có khi nào part3 được ẩn giấu tức là chỉ được check thì điều kiện có phải đạng chạy debugger không? cho nên cũng thử patch lại chương trình sao cho khi chạy file bình thường thì chương trình xác định đang chạy debugger để dùng script brute force ở trên nhưng cũng không thành công Vì không còn manh mối nào em chỉ còn cách dịch ngược chương trình để tìm manh mối tiếp theo Cấu trúc của hàm main như sau ```C v8 = sub_140002970(1337LL); // sinh seed pseudo-random sub_140004000(v15, v16, v8); // XOR với PRNG kiểu LCG sub_140002140(v14, v15, &unk_140044600, &unk_140044588, 0); // ChaCha-like / block transform unknown_libname_1(v14, &unk_1400446A0); // so sánh với “đáp án” ``` Hiện tại em chỉ mới hiểu được và khái quát được cách thức mã hóa của nó một cách khái quát, do có quá nhiều phép toán nên em không kịp để hình dung một cách cụ thể chương trình được. Em xin hết