- Category: Pwn
- Score: 500/500
- Solves: 1/247
A simple password generator written in C.
Server runs on Ubuntu 22.04.
這題沒提供 source code,需要自己 reverse binary
這題 binary 是 O0 編譯的,所以不管是 IDA 或是 Ghidra 直接反編譯出來都和 source 差不多,所以其實當成是有 source code 的情況就好。
基本上就一個 password generator,可以使用裡面預先定義好的 charset 或是自己輸入 charset,然後再指定一個長度就會隨機生成一個 password。
有個非常明顯的 buffer overflow 在讀取 charset 的地方: scanf("%s", charset);
,但這題有 PIE 所以 ROP 沒那麼容易。
另一個關鍵在於 rand64
其實是寫壞的:
long rand_num;
uint64_t rand64() {
syscall(SYS_getrandom, &rand_num, sizeof(rand_num), 0);
return &rand_num;
}
可以發現它回傳的不是 rand_num
,而是 rand_num
的 address,所以如果能得到 rand64()
回傳值的話就能知道 program base。另外這個就算沒從 code 看出來,只要生成多個相同長度的 password 就會發現它們都是一樣的,這樣也很容易看出它根本就不 random。
至於要 leak rand64()
的回傳值主要要關注這邊:
for (int i = 0; i < len; i++) {
password[i] = charset[(rand64() * i + len) % strlen(charset)];
}
在 i == 0
的時候無法知道任何關於 rand64()
的資訊,但 i == 1
時代表 charset.index(password[1]) == (rand64() + len) % strlen(charset)
,其中 len
已知所以也能推得 rand64() % strlen(charset)
的值。
這邊 rand64()
是個 64 bits 的數 1,所以如果要一次 leak 整個值出來的話勢必要讓 strlen(charset)
超過 2**64
,但這明顯不可能做到。
這邊的預期作法是透過蒐集多個 rand64() % strlen(charset)
在不同 strlen(charset)
下的值,這樣可以列出一組系統:
其中 strlen(charset)
,$a_i$ 就是 rand64() % strlen(charset)
,$x$ 就是 rand64()
。
看到這個就能發現只要使用 Chinese remainder theorem 了就能解出 rand64()
的值了。要最有效率的取得 rand64()
的值的話要最大化
之後在知道 prog base 的情況下就能串 ROP 了,而拿 shell 的方法也很簡單,就是先用一串 ROP 去 puts(puts@got)
再回到 main 可得 libc base,接著再用一串 ROP ret2libc 就能拿到 shell 了。
這題題目靈感來源來自 ImaginaryCTF 2021 - inkaphobia
Footnotes
-
實際上因為 address 的範圍有限,未知的部分少於 48 bits ↩