Post

Pwnable.kr 「random (Toddler's Bottle)」Writeup

シードが固定された rand() の値を予測し,XOR 条件を満たす入力を与える問題

Pwnable.kr 「random (Toddler's Bottle)」Writeup

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) が成り立ちます.

\[random \oplus 0xcafebabe = key \tag{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

簡単だけど面白かったぁ~

References

This post is licensed under CC BY 4.0 by the author.