pwnable.kr [Toddler's Bottle II]

Albert Huang MED student | CS enthusiast

random

連到機器後來看一下檔案內容:

1
2
3
4
random@ubuntu:~$ ls
flag random random.c
random@ubuntu:~$ cat flag
cat: flag: Permission denied

沒有權限,來看一下 random.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xcafebabe ){
printf("Good!\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

這題的問題主要在於 rand() 的使用,在使用之前應該設定 srand() 來讓亂數隨機產生,否則每次程式執行產生的亂數都會一樣。既然亂數相同,我們就可以直接利用 XOR 可逆的特性求出 key 的值。

直接使用 C 實作:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(){
unsigned int random;
random = rand();
int key = random ^ 0xcafebabe;
printf("%u", key); // unsigned
}

獲得 key 之後送出到檔案就可以拿到 flag。

1
2
3
4
random@ubuntu:~$ ./random
2708864985
Good!
[REDACTED]

input2

連到機器一樣看一下檔案內容:

1
2
3
4
input2@ubuntu:~$ ls
flag input2 input2.c
input2@ubuntu:~$ cat flag
cat: flag: Permission denied

沒有權限,來看一下 input2.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#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;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
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
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}

很明顯地,這是一個闖關題,而我們需要符合所有的條件才能獲得 flag,以下分段來用 Python 解決:

  • Stage 1
1
2
3
4
5
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

很直接,就是設定好參數即可,C 語言的鍵值會被轉換為 ascii 編碼,如下所示:

1
2
3
4
5
6
7
from pwn import *

argv = ['' for _ in range(100)]
argv[ord('A')] = "\x00"
argv[ord('B')] = "\x20\x0a\x0d"

p = process('./input2', argv=argv)
  • Stage 2
1
2
3
4
5
6
7
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

這次要設定 I/O, fd=0 的時候很直觀,就是一般的讀取輸入,但 fd=2 是 stderr,一般情況下並不能直接讀取,因此需要使用 pipe 來指定資料流的走向。如下所示:

1
2
3
4
5
6
7
8
import os

r, w = os.pipe() # w --> pipe --> r

p = process('./input2', stderr=r) # r = fd 2 (stderr)

p.send(b"\x00\x0a\x00\xff") # normal stdin
os.write(w, b"\x00\x0a\x02\xff") # w --> pipe --> r(=stderr)
  • Stage 3
1
2
3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

蠻簡單的,設定好 env 的參數即可:

1
p = process('./input2', env={'\xde\xad\xbe\xef' :'\xca\xfe\xba\xbe'})
  • Stage 4
1
2
3
4
5
6
7
// 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");

這次必須讀到指定檔案的指定內容,因此我們按照要求建立一個檔案即可:

1
2
with open("\x0a", 'w') as f:
f.write("\x00\x00\x00\x00")
  • Stage 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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");

這段 code 看起來有點嚇人,但其實他只做兩件事:

  1. 從攻擊者給的 argv['C'] 拿到 port 的數值,並把該 port 開啟監聽
  2. 確認該 port 拿到的數值是否與要求相符

所以可以直接輕鬆用 pwntools 解決:

1
2
3
4
argv[ord('C')] = 1234

r = remote('localhost', 1234)
r.send("\xde\xad\xbe\xef")

將五個關卡的 code 整合起來作為 exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
import os

argv = ['' for _ in range(100)]
argv[ord('A')] = "\x00"
argv[ord('B')] = "\x20\x0a\x0d"

r, w = os.pipe() # w --> pipe --> r

with open("\x0a", 'w') as f:
f.write("\x00\x00\x00\x00")

argv[ord('C')] = '1234'

p = process(executable='/home/input2/input2', argv=argv, stderr=r, env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})

p.send(b"\x00\x0a\x00\xff") # normal stdin
os.write(w, b"\x00\x0a\x02\xff") # w --> pipe --> r(=stderr)

r = remote('localhost', 1234)
r.send(b"\xde\xad\xbe\xef")

p.interactive()

有點麻煩的是,我們預設在 /home/input2 是沒有寫入權限的,因此我們需要在 /tmp 建立一個資料夾來執行這段 code,但我們在 /tmp 沒有 flag 的檔案,因此需要使用 ln -s 來進行文件鏈結:

1
2
3
input2@ubuntu:/tmp$ mkdir m3t30r
input2@ubuntu:/tmp$ cd m3t30r
input2@ubuntu:/tmp/m3t30r$ ln -s /home/input2/flag flag

接著把 exploit 也送上去執行即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
input2@ubuntu:/tmp/m3t30r$ python3 exploit.py
[+] Starting local process '/home/input2/input2': pid 4057224
[+] Opening connection to localhost on port 1234: Done
[*] Switching to interactive mode
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
[REDACTED]

leg

連到機器時輸出了一堆看起來是開機的 log,來看一下他給的檔案內容:

1
2
3
ls
bin dev flag linuxrc root sys
boot etc leg proc sbin usr

給了一個 flag 跟 leg 檔案,接下來看題目的原始碼部分:

  • leg.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
  • leg.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
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
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.

看起來很明確,我們要找出 key1()+key2()+key3() 的值。一個一個來看:

  • key1
1
2
3
int key1(){
asm("mov r3, pc\n");
}
1
2
3
4
5
6
7
8
9
10
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.

前兩行是函數的 prologue,代表函數開始的地方,而後三行是函數的 epilogue,代表函數結束的地方,因此不用理他們,從第三行開始:

  • mov r3, pc:把 pc 暫存器(Program Counter,程式計數器)的值複製到 r3 暫存器
  • mov r0, r3:把 r3 暫存器的值複製到 r0 暫存器,而 calling convention 會把 r0 的值作為 return 的值

所以回傳值就是 pc 的值,但 pc 的值是多少呢?

在 CPU 的執行順序中,會採用 Fetch 擷取 -> Decode 解碼 -> Execute 執行 的架構,因此代表當某一行指令執行時,下一行指令正在解碼,而下下行指令正在擷取。而 pc 則永遠指向正在擷取的位址,因此在這個情況下,就是 mov r3, pc 執行時下下行的位址:0x00008ce4,這就是 key1。

  • key2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disass key2
Dump of assembler code for function key2:
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
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.

一樣從第三行開始看,他 push 了一個 r6 到 stack 上,但後面看起來跟 r3 一點關係都沒有,所以只是個幌子,不理他,直接看 r3 的地方:

  • mov r3, pc:把 pc 暫存器(Program Counter,程式計數器)的值複製到 r3 暫存器
  • adds r3, #4:把 r3 暫存器的值加 4

後面是一些 stack 的處理,一樣不用理他,而他最後一樣 mov r0, r3,所以最終的回傳值就是 r3 的值。執行 pc 時的值指向下下行的 0x00008d08,因此最後的回傳值就是 0x00008d08 + 4 = 0x00008d0c。

  • key3
1
2
3
int key3(){
asm("mov r3, lr\n");
}
1
2
3
4
5
6
7
8
9
10
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.

前兩行一樣不看,從第三行開始:

  • mov r3, lr:把 lr 暫存器(Link Register,連結暫存器)的值複製到 r3 暫存器
  • 把 r3 暫存器的值複製到 r0 暫存器,而 calling convention 會把 r0 的值作為 return 的值

所以我們的目標就是找到 lr 暫存器的值,但 lr 暫存器的值應該是多少呢?

lr 暫存器是用來存放函數的 return address,因此就是跳回 main 時應該跳回的位址,來看一下 main 的組合語言:

1
2
0x00008d7c <+64>:	bl	0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0

上述組合語言中,0x00008d7c 呼叫了 key3 函式,因此應該跳回的位址就是下一行的 0x00008d80,也就是 lr 的存放值。因此最終的回傳值就是 0x00008d80。

總和以上三個 key,就可以算出我們應該給的值:

送出到 leg 檔案即可獲得 flag:

1
2
3
4
/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
[REDACTED]

mistake

連到機器後來看一下檔案內容:

1
2
3
4
5
6
mistake@ubuntu:~$ ls
flag mistake mistake.c password
mistake@ubuntu:~$ cat flag
cat: flag: Permission denied
mistake@ubuntu:~$ cat password
cat: password: Permission denied

有一個 password 和 flag 檔案,來看一下 mistake.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}

int main(int argc, char* argv[]){

int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}

printf("do not bruteforce...\n");
sleep(time(0)%20);

char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}

char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);

// xor your input
xor(pw_buf2, 10);

if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
setregid(getegid(), getegid());
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}

close(fd);
return 0;
}

乍看之下看起來沒什麼漏洞,但仔細觀察後可以發現,他在 if 使用的地方出現了一些問題:

1
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){

正常情況下,這個條件式想要判斷的是讀取檔案有沒有成功,如果讀取失敗就會讓 open("/home/mistake/password",O_RDONLY,0400) 的值為 -1,讓判斷式成立。但在上述情況下,使用方法應該如下:

1
if((fd=open("/home/mistake/password",O_RDONLY,0400)) < 0){

如果使用題目的寫法,就會牽涉到一個運算子優先順序(Operator Precedence)的問題:在 C 語言中,比較運算子(< 或 >)的優先序預設是高於賦值運算子(=),因此會先進行比較再把值存到 fd,如此就會與原本的目的不同。

在這個情況下,因為 password 檔案存在因此讀取會成功,open("/home/mistake/password",O_RDONLY,0400) 的回傳值不會 < 0,因此判斷式的結果為 0,不執行 if 條件式中的內容,並且讓 fd = 0。

接著看下面的 code:

1
2
3
4
5
6
7
8
9
10
printf("do not bruteforce...\n");
sleep(time(0)%20);

char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}

在中斷 0~20 秒不等之後,又執行了一段 if 條件式,詳情如下:

1
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){

首先,這段 code 原本是為了讀取剛剛打開的 password 檔案,所以使用了剛剛讀取使用的 fd 變數。但因為運算子的使用錯誤,讓 fd = 0,變成一般的 stdin 讀取,如此就不是讀取檔案了,是讀取使用者的輸入,讓使用者可以控制 pw_buf 的內容。

接著是與剛剛一樣的問題,原本使用者是想確認有讀到 password 檔案,因此使用 len 來確定長度不小於 0,但正確的使用方式為:

1
if(!((len=read(fd,pw_buf,PW_LEN)) > 0)){

在題目的情況下,> 的優先序大於 =,而 read() 函式回傳的是讀取到的字串大小,因此只要我們有輸入,回傳值就會 > 0,加上前面的 ! 讓整個判斷式為 0,也不會執行 if 判斷式內的內容。

最後,他會比對我們輸入的 pw_buf2 XOR 指定的 XORKEY=1 之後是否相符,因此我們只要輸入 XOR 後會符合的兩組密碼即可。來用 Python 試試看:

1
2
>>> chr(ord("A")^1)
'@'

直接輸入兩組密碼即可過關(注意 PW_LEN=10):

1
2
3
4
5
6
mistake@ubuntu:~$ ./mistake
do not bruteforce...
AAAAAAAAAA
input password : @@@@@@@@@@
Password OK
[REDACTED]
  • Title: pwnable.kr [Toddler's Bottle II]
  • Author: Albert Huang
  • Created at : 2026-09-07 10:42:30
  • Updated at : 2026-09-09 21:35:05
  • Link: https://blog.albert-web.tw/2026/09/07/pwnable-kr-writeup-2/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
pwnable.kr [Toddler's Bottle II]