Pwnable.kr 「random (Toddler's Bottle)」Writeup
シードが固定された rand() の値を予測し,XOR 条件を満たす入力を与える問題
pwnable_kr-random
Summary
本問は,シードが固定された rand() の値を予測し,XOR 条件を満たす入力を与える問題です.
- Category: Pwn
- Description: Daddy, teach me how to use random value in programming!
- Tools & TechStack:
- C
- gdb
- Release:
N/A
階層構造
1
2
3
4
5
6
7
8
9
.
├── Dockerfile
├── flag
├── random
├── random.c
├── readme
└── run.sh
1 directory, 6 files
バイナリ防御機構
1
2
3
4
5
6
7
8
9
❯ checksec --file=random
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
ソースコードの解析
乱数値 random と任意値 key のXOR結果が 0xcafebabe を満たす場合,flagが入手できるようになっています.\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;
}
C言語の rand() の性質
C言語の rand() は,srand() を呼ばない場合,規定により乱数のシードは 1 に固定されます.
そのため,同じ実装であれば何回実行しても同じ値が決定的に出力されます.1
また,同じバージョンの glibc であれば同じ値になるはずなので,ローカルで random の値を読むことで乱数の値を確定することができます.
random の値を読む
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) disas main
0x0000000000001209 <+0>: endbr64
0x000000000000120d <+4>: push rbp
0x000000000000120e <+5>: mov rbp,rsp
0x0000000000001211 <+8>: push rbx
0x0000000000001212 <+9>: sub rsp,0x18
0x0000000000001216 <+13>: mov rax,QWORD PTR fs:0x28
0x000000000000121f <+22>: mov QWORD PTR [rbp-0x18],rax
0x0000000000001223 <+26>: xor eax,eax
0x0000000000001225 <+28>: mov eax,0x0
0x000000000000122a <+33>: call 0x1110 <rand@plt>
0x000000000000122f <+38>: mov DWORD PTR [rbp-0x1c],eax # [rbp-0x1c] がrandom変数
0x0000000000001232 <+41>: mov DWORD PTR [rbp-0x20],0x0 # bp
0x0000000000001239 <+48>: lea rax,[rbp-0x20]
0x000000000000123d <+52>: mov rsi,rax
0x0000000000001240 <+55>: lea rax,[rip+0xdc1] # 0x2008
0x0000000000001247 <+62>: mov rdi,rax
0x000000000000124a <+65>: mov eax,0x0
0x000000000000124f <+70>: call 0x1100 <__isoc99_scanf@plt>
0x0000000000001254 <+75>: mov eax,DWORD PTR [rbp-0x20]
0x0000000000001257 <+78>: xor eax,DWORD PTR [rbp-0x1c]
0x000000000000125a <+81>: cmp eax,0xcafebabe
#...
x86-64 では関数の返り値はレジスタで返され,整数・ポインタ系は
rax(下位32ビットがeax) に入ります.
rand() の返り値の型は int (32bit) なので,*main+38 の [rbp-0x1c] が random 変数のアドレスであることが分かりました.
1
2
3
4
5
6
7
pwndbg> b *main+41
Breakpoint 1 at 0x1232
pwndbg> r
pwndbg> x/wx $rbp-0x1c
0x7fffffff7144: 0x6b8b4567
直後の *main+41 にbpを張り,値を読むと random = 0x6b8b4567 であることも分かりました.
排他的論理和の性質を用いて,入力すべき key を求める
排他的論理和には,交換法則・結合法則 が成り立ちます.
$b \oplus b = 0$ (同じ値同士は
0)
$a \oplus 0 = a$ (0との XOR は変化なし)
このプログラムでの条件式は以下の式 (1) と同様です.
\[key \oplus random = 0xcafebabe\tag{1}\]ここで,定数 0xcafebabe と 変数 random が既知なため,key を求めるために以下の式 (2) が成り立ちます.
この式に則り,計算します.
1
2
3
4
5
$ python3
Python 3.13.15 (main, Aug 5 2026, 12:25:43) [GCC 15.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x6b8b4567 ^ 0xcafebabe
2708864985
入力すべき key の値が,2708864985 であることが求まりました.
1
2
3
4
$ nc pwnable.kr 10005
2708864985
Good!
<REDACTED>
Post-Mortem & Dead ends
簡単だけど面白かったぁ~
