# Cryptocrack ![](https://hackmd.io/_uploads/SJyVfKvN3.png) Load file vào ida: ![](https://hackmd.io/_uploads/S1nw4FDEn.png) Ta có thể thấy chương trình yêu cầu ta nhập vào 1 chuỗi input(`flag`), nếu như chuỗi mà ta nhập vào có số kí tự khác với 35 thì sẽ trả về "Nah" và kết thúc chương trình Xem tiếp thì chương trình sẽ thực hiện hàm `fork()` ![](https://hackmd.io/_uploads/HyIsPYvVn.png) Nói qua về hàm `fork()` thì `fork()` là 1 hàm phân luồng tiến trình trong linux, sau khi hàm `fork()` được gọi thì chương trình sẽ có 1 tiến trình cha và 1 hoặc nhiều tiến trình con ta có thể xem thêm ở [đây](https://daynhauhoc.com/t/ham-fork-trong-he-dieu-hanh-linux-hoat-dong-nhu-the-nao/22893/2) Tiếp theo chương trình sẽ check pid, nếu khác 0, nghĩa là đang ở tiến trình cha và sẽ chạy vào đoạn code dưới: ```c= if ( pid ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { do { if ( waitpid(pid, &stat_loc, 0) == -1 || count > 35 ) goto LABEL_24; } while ( (unsigned __int8)stat_loc != 0x7F ); if ( BYTE1(stat_loc) != 5 ) break; ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &v9); v12 = buff[count]; v11 = input[count]; if ( (count & 1) != 0 ) v13 += 13LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &v9); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } if ( BYTE1(stat_loc) != 11 ) break; ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &v9); v3 = count++; if ( v10 == key[v3] ) { v13 -= 9LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &v9); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } else { LABEL_21: ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 15LL); } } if ( BYTE1(stat_loc) == 4 ) { ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &v9); v4 = count++; if ( v10 != key[v4] ) goto LABEL_21; v13 -= 21LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &v9); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } } } ``` Còn trong tiến trình con pid=0 nên bỏ qua đoạn trên và nhảy vào hàm `start()`: ![](https://hackmd.io/_uploads/HJIn19wV3.png) oke vậy bây giờ ta sẽ tiến hành phân tích chương trình .trước tiên là về cách mà tiến trình cha được thực thi. mình thấy có 1 số biến mà ma giả gen ra cho mình đọc như `v9`, `v11`, `v12`, `v13` làm mình cảm thấy khá khó hiểu, nên mình quyết định tìm hiểu về cách hoạt động của hàm `ptrace()` trước giải thích qua về hàm `ptrace()` ## Syntax ```c= ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data) ``` Trong đó: `request` là một giá trị kiểu enum __ptrace_request xác định yêu cầu của bạn đối với hàm ptrace(). `PTRACE_GETREGS` sẽ lấy các giá trị của thanh ghi, `PTRACE_SETREGS` sẽ sử dụng các thanh ghi để lưu giá trị, `PTRACE_CONT` sẽ tiếp tục thực thi tiến trình `pid` là một giá trị kiểu pid_t xác định tiến trình mà bạn muốn theo dõi hoặc điều khiển. `addr` là một con trỏ kiểu void * xác định vị trí bộ nhớ mà bạn muốn đọc hoặc ghi trong tiến trình được chỉ định bởi pid. `data` là một con trỏ kiểu void * trỏ đến dữ liệu mà bạn muốn đọc hoặc ghi trong tiến trình được chỉ định bởi pid. Vậy thì từ đây mình suy luận được ra rằng các biến mà mình thắc mắc ở bên trên chính là các thanh ghi trong chương trình. nên mình đã define lại các biến đó như sau Vào mục `structures` dí chuột phải ![](https://hackmd.io/_uploads/SkNMtcvNh.png) Chọn `Add struct type` ![](https://hackmd.io/_uploads/BJwx5cwV2.png) Chọn `Add standard struct` Ta tìm `pt_regs` ![](https://hackmd.io/_uploads/r1EMc5DN2.png) Sau đó về lại luồng thực thi của tiến trình cha và define lại biến `v9` vì hàm `ptrace()` chỉ lấy giá trị của địa chị mà v9 trỏ tới ![](https://hackmd.io/_uploads/ryDP95vNn.png) Sửa `int` thành `pt_regs` bằng cách nhấn `Y` và sửa luôn `v9` thành `reg` bằng cách nhấn `N` Ngoài ra còn các giá trị `stat_loc` 5, 11, 4 ở đây là các trạng thái của tiến trình con trả về cho tiến trình cha, ta có thể define lại bằng cách thêm `enum`. Đầu tiên chọn vào giá trị, ấn `M`(hoặc chuột phải chọn `enum`), ban đầu không có giá trị sẵn mình sẽ search giá trị 5 là `SIGTRAP`, và tương tự 2 giá trị còn lại khi ấn M là tự hiện ra 11 là `SIGSEGV` và 4 là `SIGILL`. Giải thích qua một chút những giá trị này: * **SIGILL** (Illegal Instruction Signal): Tín hiệu này được gửi khi một tiến trình cố gắng thực thi một lệnh (instruction) không hợp lệ, ví dụ như cố gắng thực thi một lệnh không được hỗ trợ bởi CPU hoặc cố gắng thực thi một vùng nhớ không hợp lệ. * **SIGSEGV** (Segmentation Fault Signal): Tín hiệu này được gửi khi một tiến trình cố gắng truy cập một vùng nhớ ngoài phạm vi được cấp phép (vi phạm quyền truy cập) hoặc truy cập một vùng nhớ không hợp lệ. * **SIGTRAP** (Trace/Breakpoint Trap Signal): Tín hiệu này được sử dụng để hỗ trợ việc gỡ rối (debugging) và được gửi khi một tiến trình gặp một điểm ngắt (breakpoint) hoặc khi một tiến trình được gỡ rối theo dõi (trace) một sự kiện cụ thể. Ta được code mới như sau ```c= if ( pid ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { do { if ( waitpid(pid, &stat_loc, 0) == -1 || count > 35 ) goto LABEL_24; } while ( (unsigned __int8)stat_loc != 0x7F ); if ( BYTE1(stat_loc) != SIGTRAP ) break; ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &reg); reg.rdi = buff[count]; reg.rsi = input[count]; if ( (count & 1) != 0 ) reg.rip += 13LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &reg); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } if ( BYTE1(stat_loc) != SIGSEGV ) break; ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &reg); v3 = count++; if ( reg.rax == key[v3] ) { reg.rip -= 9LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &reg); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } else { LABEL_21: ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 15LL); } } if ( BYTE1(stat_loc) == SIGILL ) { ptrace(PTRACE_GETREGS, (unsigned int)pid, 0LL, &reg); v4 = count++; if ( reg.rax != key[v4] ) goto LABEL_21; reg.rip -= 21LL; ptrace(PTRACE_SETREGS, (unsigned int)pid, 0LL, &reg); ptrace(PTRACE_CONT, (unsigned int)pid, 0LL, 0LL); } } } ``` Chúng ta qua xem hàm `start()`: ![](https://hackmd.io/_uploads/B1jvKJOEh.png) Hàm `start()` có 3 đoạn gây ra 3 lỗi tương ứng với 3 `stat_loc` được xét trong tiến trình cha: `int 3` -> `SIGTRAP` ``` xor r12,r12 mov r12,[r12] -> SIGSEGV ``` `jmp near ptr loc_4016F3+1` -> `SIGILL` Như vậy tiến trình cha sẽ check những lỗi của tiến trình con trả về, sau đó thực hiện theo ý muốn của tác giả. Để có thể phân tích tĩnh được luồng của chương trình, mình sẽ sử kỹ thuật `LD_PRELOAD hook`. Vậy thì, `LD_PRELOAD` là gì? Ta sẽ làm rõ 2 vấn đề sau Ta có thể tham khảo thêm ở 2 link [đây](https://tbrindus.ca/correct-ld-preload-hooking-libc/) và [đây](https://www.mike-gualtieri.com/posts/hooking-linux-libraries-for-post-exploitation-fun) ### Shared library(thư viện mở) và thực thi ứng dụng trong Linux Trước khi giới thiệu biến môi trường `LD_PRELOAD` là gì và sử dụng nó như thế nào, ta cần hiểu sơ lược về cách một chương trình ứng dụng được biên dịch, được nạp và được khởi chạy như thế nào trong môi trường Linux. Đây cũng là một kiến thức hữu dụng cần biết khi lập trình ứng dụng trên Linux. Ví dụ, chúng ta có một chương trình KCSC Training! viết bằng C. Nội dung của chương trình này, như mọi chương trình Hello World khác, sử dụng hàm puts() để in dòng chữ “KCSC Training!” ra màn hình. Mình có mẫu 1 đoạn code như sau: ```c= #include <stdio.h> int main() { puts("\t\tKCSC Training!\n"); return 0; } ``` Mình sử dụng câu lệnh `gcc -Wall kcsc.c -o kcsc.o` để tại 1 file thực thi `kcsc.o` Để chạy chương trình này ta sẽ sử dụng lệnh như sau: ![](https://hackmd.io/_uploads/Hy7QdpdE3.png) Thông thường, một ứng dụng sẽ sử dụng các hàm hay code được cung cấp bởi các library (thư viện), đặc biệt là shared library (hay thư viện liên kết động hay nói đơn giản là thư viện mở). Chương trình `kcsc.c` đơn giản ở trên ít nhất đã dùng hàm `puts()` của thư viện libc. Trong quá trình biên dịch để tạo ra file thực thi, phần code trong shared library sẽ không được include vào trong file thực thi. Có nghĩa là trong file `kcsc.o` không hề chứa phần code định nghĩa hàm `puts()`. Thay vào đó, những phần code đó sẽ được hệ thống nạp vào tại thời điểm chương trình được thực thi, khi cần thiết. Để xem một file thực thi sử dụng những shared library nào, ta có thể dùng công cụ ldd: ![](https://hackmd.io/_uploads/H10au6OE3.png) Ta có thể thấy được các shared library được file thực thi `kcsc.o` load vào khi thực thi, trong đó có cả `libc`. Danh sách các thư viện được xác định ở compile time dựa trên mã nguồn của chương trình. Khi 1 chương trình được thực thi, 1 phần khác trong hệ thống Linux, là dynamic linker/loader(bộ liên kết động) sẽ thực hiện công việc là nạp các thư viện mà chương trình cần để thực thi chương trình và các map code của các thư viện vào không gian địa chỉa mà process đó tạo ra khi thực thi. Quá trình này được thực hiện ở runtime, hình dưới đây sẽ mô tả quá trình đó: ![](https://hackmd.io/_uploads/ByWY5TO42.png) Ví dụ với chương trình `kcsc.o` thì tại run time, sau khi libc được map vào không gian địa chỉ của process, phần code định nghĩa hàm `puts()` trong libc có thể được gọi tới và thực thi. ### Biến môi trường LD_PRELOAD Như mình đã đề cập ở phía trên, bộ liên kết động sẽ thực hiện nạp vào các thư viện mà chương trình cần cho việc thưc thi. Thứ tự mà các thư viện được nạp vào sẽ khác nhau theo từng trường hợp Trong Linux, biến môi trường `LD_PRELOAD` là một biến chứa đường dẫn tới các shared library. Các thư viện này sẽ được nạp vào trước bất ki thư viện nào, kể cả `libc` Việc một thư viện được nạp vào trước giúp cho những hàm chứa trong thư viện này được tìm tới và sử dụng trước, thay cho những hàm có trùng tên ở trong các thư viện được nạp sau nó (nếu có). ***Điều này có nghĩa là ta có thể thực hiện việc intercept hay overwrite các hàm thư viện có sẵn bằng các hàm do chính mình tự viết*.** Ví dụ, bạn có thể tự viết một phiên bản hàm `puts()` của riêng mình và dùng nó thay cho hàm `puts()` có sẵn trong libc để mỗi lần hàm này được sử dụng, nội dung của đoạn text sẽ được log vào một file nào đó chẳng hạn. Khả năng intercept hay overwrite này đưa tới nhiều ứng dụng khác Mình sẽ ví dụ bằng 1 chương trình sinh ra 5 số ngẫu nhiên liên tiếp: ```c= #include <stdio.h> #include <stdlib.h> #include <time.h> int main() { srand(time(NULL)); int i = 5; while(i--) printf("%d\n", rand()%100); return 0; } ``` Khi chạy chương trình thì sẽ có trả về như sau: ![](https://hackmd.io/_uploads/ByRUpTO43.png) Giờ mình sẽ define lại 1 lib sẽ chỉ in ra số` 99` ```c= int rand(void) { return 99; } ``` mình sẽ sử dụng lệnh này để tạo ra 1 shared library `gcc -Wall -fPIC -shared -o randomlib.so randomlib.c -ldl` Mình sử dụng lệnh này `LD_PRELOAD=./randomlib.so ./rand.o` và kết quả trả về như sau: ![](https://hackmd.io/_uploads/S1YbJAuVh.png) Bạn có thể “thay thế” một hàm thư viện có sẵn bằng một hàm của mình, như hàm `rand()` trong ví dụ trước, nhưng điều tuyệt vời hơn nữa là bạn vẫn có thể gọi tới hàm gốc để thực hiện công việc gốc một cách bình thường. Ta sẽ áp dụng vào bài này. Dưới đây là code để hook hàm `waitpid()` và hàm `ptrace()` ```c= // How to hook PTRACE.... // Compile : gcc -Wall -fPIC -shared -o hookPtrace.so hookPtrace.c -ldl #define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <inttypes.h> #include <sys/types.h> #include <unistd.h> #include <stdint.h> enum __ptrace_request{ PTRACE_GETREGS = 12, PTRACE_SETREGS = 13, PTRACE_CONT = 7}; //define kiểu dữ liệu enum const char *ptrace_types[] = { "PTRACE_TRACEME", "PTRACE_PEEKTEXT", "PEEKDATA", "PTRACE_PEEKUSER", "PTRACE_POKETEXT", "POKEDATA", "6", "CONT", "8", "9", "10", "11", "GETREGS", "SETREGS", "14", "15", "ATTACH", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "CONT|SYSCALL" }; //define các mode của hàm ptrace typedef struct pt_regs // khai báo các thanh ghi trong 64bit { unsigned long long r15; unsigned long long r14; unsigned long long r13; unsigned long long r12; unsigned long long rbp; unsigned long long rbx; unsigned long long r11; unsigned long long r10; unsigned long long r9; unsigned long long r8; unsigned long long rax; unsigned long long rcx; unsigned long long rdx; unsigned long long rsi; unsigned long long rdi; unsigned long long orig_rax; unsigned long long rip; unsigned long long cs; unsigned long long eflags; unsigned long long rsp; unsigned long long ss; }pt_regs; long int ptrace(int request, pid_t pid, void *addr, void *data) //Khai báo hàm ptrace { void *hLibc; // Khai báo một con trỏ void hLibc để lưu trữ handle của thư viện được tải động. long int (*real_ptrace)(enum __ptrace_request request, pid_t pid, void *addr, void *data); //Khai báo một con trỏ hàm real_ptrace có các tham số tương tự như hàm waiptrace() hLibc = dlopen("libc.so.6", 1); // Load thư viện libc.so.6 bằng lệnh dlopen() *(long int**)(&real_ptrace) = dlsym(hLibc, "ptrace"); //Dùng hàm dlsym() để gán giá trị cho con trỏ hàm mà ta đã khai báo. Hàm dlsym() trả về địa chỉ của một symbol trong các thư viện được nạp. Trong trường hợp này, ta truyền 2 tham số cho hàm dlsym(). Tham số đầu tiên hLibc có nghĩa là tìm symbol tiếp theo trong các thư viện sau thư viện hiện tại, tham số thứ 2 chính là tên symbol cần tìm, ở đây chính là hàm “ptrace”. Như vậy, hàm dlsym() sẽ trả về địa chỉ của hàm ptrace() gốc trong thư viện libc. pid_t cur_pid = getpid(); // Lấy PID của tiến trình hiện tại bằng cách sử dụng hàm getpid() và gán giá trị này vào biến cur_pid long int result = real_ptrace(request, pid, addr, data); // Gọi lại hàm waitpid() thông qua con trỏ hàm real_ptrace để thực hiện chức năng của ptrace(). Kết quả trả về được lưu trữ trong biến rs printf("%s, pid = %d, data = 0x%.8x\n",ptrace_types[request], pid, data); //in ra mode hiện tại của hàm ptrace, process id, địa chỉ hiện tại if(ptrace_types[request] == "SETREGS") //Kiểm trà xem mode của hàm ptrace có phải SETREGS hay không { printf("SETREGS_MODE\n"); pt_regs* regs = (pt_regs*)data; printf("RAX: %p\n", regs->rax); printf("RBX: %p\n", regs->rbx); printf("RCX: %p\n", regs->rcx); printf("RDX: %p\n", regs->rdx); printf("RDI: %p\n", regs->rdi); printf("RSI: %p\n", regs->rsi); printf("RIP: %p\n", regs->rip); printf("R12: %p\n\n", regs->r12); } return result; } int waitpid(pid_t pid,int* stat_loc,int options) //Khai báo biến hàm waitpid() { void *hLibc; // Khai báo một con trỏ void hLibc để lưu trữ handle của thư viện được tải động. int (*real_waitpid)(pid_t pid,int* stat_loc,int options); //Khai báo một con trỏ hàm real_waitpid có các tham số tương tự như hàm waitpid() hLibc = dlopen("libc.so.6",1); // Load thư viện libc.so.6 bằng lệnh dlopen() *(int**)(&real_waitpid) = dlsym(hLibc, "waitpid"); //Dùng hàm dlsym() để gán giá trị cho con trỏ hàm mà ta đã khai báo. Hàm dlsym() trả về địa chỉ của một symbol trong các thư viện được nạp. Trong trường hợp này, ta truyền 2 tham số cho hàm dlsym(). Tham số đầu tiên hLibc có nghĩa là tìm symbol tiếp theo trong các thư viện sau thư viện hiện tại, tham số thứ 2 chính là tên symbol cần tìm, ở đây chính là hàm “waitpid”. Như vậy, hàm dlsym() sẽ trả về địa chỉ của hàm waitpid() gốc trong thư viện libc. pid_t cur_pid = getpid(); // Lấy PID của tiến trình hiện tại bằng cách sử dụng hàm getpid() và gán giá trị này vào biến cur_pid int rs = real_waitpid(pid,stat_loc,options); // Gọi lại hàm waitpid() thông qua con trỏ hàm real_waitpid để thực hiện chức năng của waitpid(). Kết quả trả về được lưu trữ trong biến rs printf("\n___________Status_loc : %x\n",(*stat_loc)); //n giá trị của biến stat_loc, là giá trị trạng thái của tiến trình con return rs; //Trả về kết quả hàm waitpid() } ``` Sau khi nhập vào, đọc giá trị của các thanh ghi: ![](https://hackmd.io/_uploads/S1np7B54h.png) `RAX mang giá trị của key[i]` `RSI mang giá trị của input[i]` `RDI mang giá trị của buff[i]` Thêm một điều nữa là ta không thế debug chương trình này bởi vì lý do sau: ![](https://hackmd.io/_uploads/S1C27Q5Eh.png) Ở đây chúng ta thấy hàm `ptrace` và mode `TRACEME` ![](https://hackmd.io/_uploads/rJBtHXqEn.png) Hiểu một cách đơn giản nhất thì khi hàm `ptrace` với mode `TRACEME` được gọi, tiến trình con sẽ được giám sát bởi tiến trình cha, và giá trị trả về của hàm sẽ là tiến trình con có đang được giám sát bới tiến trình cha hay không. Vậy nên khi mà ta thực hiện debug chương trình tiến trình con sẽ không được giám sát bới tiến trình cha nữa mà bới trình debug. Chương trình sẽ trả về mà hình chuỗi "Daddyyyyy this person is trying to debug me !!" Tổng kết lại , khi check đến input[odd] thì chương trình sẽ thực hiện phép toán xor `input[i]^buff[i]` rồi check với `key[i]`, còn với input[even] thì chương trình sẽ truyền `input[i]` và `buff[i]` vào và gọi hàm `some_func()` sau khi đọc và sửa lại tên của biến thìcode của hàm `some_func()` sẽ như sau: ```c= _int64 __fastcall some_func(char buff[i], unsigned __int8 input[i]) char v7; int v5 = 0; for (int count = 0; count <= 7; ++count ) { if ( (input[i] & 1) != 0 ) v5 ^= input[i]; v7 = buff[i] & 0x80; buff[i] *= 2; if ( v7 ) buff[i] ^= 0x1Bu; input[i] >>= 1; } return v5; ``` mình đã viết 1 script giải như sau: ```python= buff=[ 0xA3, 0x97, 0xA2, 0x55, 0x53, 0xBE, 0xF1, 0xFC, 0xF9, 0x79, 0x6B, 0x52, 0x14, 0x13, 0xE9, 0xE2, 0x2D, 0x51, 0x8E, 0x1F, 0x56, 0x08, 0x57, 0x27, 0xA7, 0x05, 0xD4, 0xD0, 0x52, 0x82, 0x77, 0x75, 0x1B, 0x99, 0x4A, 0xED] key=[ 0xD2, 0xFF, 0x8E, 0x39, 0x70, 0xD3, 0xEA, 0x88, 0x06, 0x0A, 0x68, 0x15, 0xAD, 0x4C, 0x9E, 0xAD, 0x1D, 0x0E, 0x85, 0x2B, 0x35, 0x38, 0x8C, 0x6E, 0x7A, 0x40, 0x19, 0x8F, 0x1B, 0xB3, 0x8E, 0x34, 0x07, 0xFD, 0x4D, 0xED] flag=[0]*35 for k in range(1,35,2): flag[k]=buff[k]^key[k] for k in range(0,35,2): for j in range(0x20,0x80,1): v5=0 tmp=j tmpbuff=buff[k] for i in range(8): if((j&1)!=0): v5^=tmpbuff v7=tmpbuff&0x80 tmpbuff*=2 if(v7): tmpbuff^=0x1b j>>=1 if((v5&0xff)==(key[k]&0xff)): flag[k]=tmp break for i in range(len(flag)): print(chr(flag[i]&0xff),end="") ``` sau khi chạy script, chương trình trả về kết quả: ![](https://hackmd.io/_uploads/rkiq_yuNh.png) > flag: shellmates{Gg_yOu_n4N0mITES_w1ZARd}