0x00 Challenge
PICACHUUUUUUUUU!!!1!
If you switch to VTY2, you can log in as root without a password.
Note: Please use the supplied qemu. I'm not sure if everything works if you use a different version. The qemu was compiled under Ubuntu 14.04.
Reversing 題,給了 qemu-system-x86_64
跟 image: bzImage-initramfs-qemux86-64.bin
,另外還有一包 BIOS 相關的檔案
0x01 Boot Up
用 qemu 跑起 image ,開機完後會要求輸入 Flag ,用 CTRL + ALT + F3
切換到 VTY2
,會出現有皮卡丘的登入畫面:
░█▀▀▄░░░░░░░░░░░▄▀▀█
░█░░░▀▄░▄▄▄▄▄░▄▀░░░█
░░▀▄░░░▀░░░░░▀░░░▄▀
░░░░▌░▄▄░░░▄▄░▐▀▀
░░░▐░░█▄░░░▄█░░▌▄▄▀▀▀▀█
░░░▌▄▄▀▀░▄░▀▀▄▄▐░░░░░░█
▄▀▀▐▀▀░▄▄▄▄▄░▀▀▌▄▄▄░░░█
█░░░▀▄░█░░░█░▄▀░░░░█▀▀▀
░▀▄░░▀░░▀▀▀░░▀░░░▄█▀
░░░█░░░░░░░░░░░▄▀▄░▀▄
░░░█░░░░░░░░░▄▀█░░█░░█
░░░█░░░░░░░░░░░█▄█░░▄▀
░░░█░░░░░░░░░░░████▀
░░░▀▄▄▀▀▄▄▀▀▄▄▄█▀
PICA PICA PICA PICA PICA
PICA login:
根據題目提示,成功用 root
和空密碼登入
透過 ps
指令可以得知在 VTY1
要求輸入 Flag 所執行的程式為 /usr/bin/verify-flag
:
int main()
{
int buf; // [sp+14h] [bp-1Ch]@1
size_t n; // [sp+18h] [bp-18h]@1
char *line; // [sp+20h] [bp-10h]@1
int fd; // [sp+2Ch] [bp-4h]@3
line = NULL;
buf = 1;
printf("Gib flag! ");
if ( getline(&line, &n, stdin) ) {
fd = open("/dev/flag", 2);
if ( fd >= 0 ) {
write(fd, line, n);
read(fd, &buf, 4uLL);
close(fd);
free(line);
if ( buf )
puts("Wrong flag!");
else
puts("You of winning!");
}
else {
puts("Can't open flag device");
}
}
else {
puts("You needings of giving input");
}
return 0;
}
分析後發現,負責檢查 Flag 的關鍵部分並不在 verify-flag
中,而是透過 /dev/flag
裝置進行,因此將目標轉移至 Kernel
0x02 Driver
用 lsmod
指令列出 kernel module,會發現有個 svchost
相當可疑:
root@PICA:~# lsmod
Tainted: G
svchost 2668 0 - Live 0xffffffffa000b000 (O)
uvesafb 24359 1 - Live 0xffffffffa0000000
開始分析 /lib/modules/3.19.2-yocto-standard/extra/svchost.ko
從 init_module()
可以確定 /dev/flag
是由這隻 driver 建立的沒錯, 而較關鍵的函數則是 dev_write()
,當 /dev/flag
被寫入時會執行,捨去一些 Error Handling 簡化後大概如下:
dev_write(struct file *filp, const char *buf, size_t size, loff_t *f_pos) {
uint8_t user_input[60];
acpi_handle handle;
struct acpi_object_list obj_list;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
union acpi_object *result;
if ( !copy_from_user(user_input, buf, 45) ) {
if ( ACPI_SUCCESS(acpi_get_handle(0, "\\CTF.FLAG", &handle)) ) {
obj = kmalloc(sizeof(union acpi_object), GFP_KERNEL);
obj->type = ACPI_TYPE_STRING;
obj->buffer.length = 45;
obj->buffer.pointer = (u8 *) user_input;
obj_list.count = 1;
obj_list.pointer = obj;
if ( ACPI_SUCCESS(acpi_evaluate_object(handle, 0, &obj_list, &output) ) {
result = output.pointer;
if ( result->type == ACPI_TYPE_INTEGER ) {
g_has_new_data = 1;
g_result = result->integer.value;
}
kfree(result);
}
kfree(obj);
}
}
}
svchost.ko
會透過 acpi_get_handle()
取得 BIOS 中 ACPI 的CTF
device handle, 並透過 acpi_evaluate_object()
呼叫其中的 FLAG
method 來檢查寫入到 /dev/flag
的字串, 看來無法在 Kernel 簡單解決,只好再繼續深入到 ACPI
0x03 ACPI
ACPI (Advanced Configuration and Power Interface) is an open industry specification co-developed by Hewlett-Packard, Intel, Microsoft, Phoenix, and Toshiba.
Linux 下可以直接在 dump 出 ACPI 的 DSDT (Differentiated System Description Table)
:
$ sudo cat /sys/firmware/acpi/tables/DSDT > dsdt.aml
dsdt.aml
為 AML(ACPI Machine Language)
檔案,可以透過 isal
工具做 disassemble:
$ sudo apt-get install iasl
$ iasl -d dsdt.aml
在 dsdt.aml
中可以找到 CTF
Device 對應的程式碼:
Device (CTF)
{
Name (_HID, EisaId ("CTF32C3")) // _HID: Hardware ID
Name (FLG, Buffer (0x0F) {
0xA4, 0xB3, 0x28, 0x2D, 0x9B, 0x93, 0xFE, 0x35, 0x75, 0xDC, 0x78, 0xB4, 0x3C, 0x81, 0x37
})
Name (HEXC, Package (0x16) {
0x37, 0x65, 0x35, 0x66, 0x34, 0x39, 0x63, 0x33, 0x62, 0x32, 0x36, 0x30, 0x61, 0x31, 0x38,
0x64, 0x65, 0x37, 0x39, 0x63, 0x33, 0x62
})
Method (FLAG, 1, Serialized) {
If (LNotEqual (SizeOf (Arg0), 0x2D)) {
Return (One)
}
CreateDWordField (Arg0, Zero, HDR)
CreateByteField (Arg0, 0x04, HDR2)
If (LOr (LNotEqual (HDR, 0x33433233), LNotEqual (HDR2, 0x5F))) {
Return (One)
}
CreateField (Arg0, 0x28, 0x0140, BUFF)
Store (Buffer (0x14) {}, Local0)
Store (HEX (BUFF), Local0)
If (LEqual (Local0, Ones)) {
Return (One)
}
Store (Zero, Local1)
Store (Zero, Local7)
While (LLess (Local1, 0x0F)) {
Add (Local7, XOr (XOr (DerefOf (Index (Local0, Local1)), DerefOf (
Index (Local0, Add (Local1, 0x05)))), DerefOf (Index (FLG,
Local1))), Local7)
Increment (Local1)
}
Return (Add (Local7, DerefOf (Index (Local0, 0x13))))
}
Method (HEX, 1, NotSerialized)
{
Store (Buffer (0x14) {}, Local0)
Store (Zero, Local1)
While (LLess (Local1, SizeOf (Local0))) {
Subtract (Match (HEXC, MEQ, DerefOf (Index (Arg0, Local1)), MTR,
Zero, 0x02), 0x02, Local2)
Store (Match (HEXC, MEQ, DerefOf (Index (Arg0, Add (Local1, 0x14
))), MTR, Zero, Zero), Local3)
If (LOr (LEqual (Local2, Ones), LEqual (Local3, Ones))) {
Return (Ones)
}
XOr (Or (XOr (Local2, Local3), ShiftLeft (Local3, 0x04)), 0xC3, Index (Local0, Local1))
Increment (Local1)
}
If (LEqual (Add (Add (Add (DerefOf (Index (Local0, Zero)),
DerefOf (Index (Local0, One))), DerefOf (Index (Local0, 0x02))
), DerefOf (Index (Local0, 0x03))), Zero))
{
Return (Local0)
}
Return (Ones)
}
}
參考 ACPI 官方 Spec 逆向成 Python 形式:
HEXC = [0x37, 0x65, 0x35, 0x66, 0x34, 0x39, 0x63, 0x33, 0x62, 0x32, 0x36, 0x30, 0x61, 0x31, 0x38, 0x64, 0x65, 0x37, 0x39, 0x63, 0x33, 0x62]
FLG = [0xA4, 0xB3, 0x28, 0x2D, 0x9B, 0x93, 0xFE, 0x35, 0x75, 0xDC, 0x78, 0xB4, 0x3C, 0x81, 0x37]
def FLAG(flag):
if len(flag) != 45:
return 1
if flag[0:5] != "32C3_":
return 1
buf = HEX(flag[5:])
if buf == None:
return 1
sum = 0
for i in range(15):
sum += buf[i] ^ buf[i+5] ^ FLG[i]
return sum + buf[19]
def HEX(hash):
buf = []
for i in range(20):
m = HEXC.index(ord(hash[i]), 2) - 2
n = HEXC.index(ord(hash[i+20]))
buf.append(( ( m ^ n ) | ( n << 4 ) ) ^ 0xC3)
if buf[0] + buf[1] + buf[2] + buf[3] == 0:
return buf
return None
總結一下 Flag 要符合的條件:
- 由
verify-flag
得知FLAG
method 的回傳值必須為 0 - 長度為 45 , 以
32C3_
開頭
FLAG
method 會將 Flag 後 40 位傳給 HEX
做某種運算,得到一組長度 20 位的 data
HEX
method 中的結果 buf
需要符合 buf[0] + buf[1] + buf[2] + buf[3] == 0
, 可以視為 buf
的前四位都是 0x00
而 FLAG
method 中的迴圈所計算的總和與 buf[19]
所和必須為 0 因此 buf[i] ^ buf[i+5] ^ FLG[i]
及 buf[19]
也必須為 0
以這兩個條件可以進一步推出 buf
的完整內容為: [0, 0, 0, 0, 0x70, 0xa4, 0xb3, 0x28, 0x2d, 0xeb, 0x37, 0x4d, 0x1d, 0x58, 0x37, 0x4f, 0xf9, 0x21, 0xd9, 0]
再由 buf
反推出 HEX
method 中每一輪的 m
及 n
, 配合 HEXC
array 就可以組合出正確的 Flag:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from z3 import *
HEXC = [ 0x37, 0x65, 0x35, 0x66, 0x34, 0x39, 0x63, 0x33, 0x62, 0x32, 0x36, 0x30, 0x61, 0x31, 0x38, 0x64, 0x65, 0x37, 0x39, 0x63, 0x33, 0x62]
FLG = [0xA4, 0xB3, 0x28, 0x2D, 0x9B, 0x93, 0xFE, 0x35, 0x75, 0xDC, 0x78, 0xB4, 0x3C, 0x81, 0x37]
# Get buf
x = [BitVec("x%d" % i, 32) for i in range(20)]
s = Solver()
for i in range(4):
s.add(x[i] == 0)
s.add(x[19] == 0)
for i in range(15):
s.add(x[i]^x[i+5]^FLG[i] == 0)
if s.check() == sat:
ans = s.model()
buf = [ans[x[i]].as_long() for i in range(20)]
# Get m and n
m = [BitVec("m%d" % i, 32) for i in range(20)]
n = [BitVec("n%d" % i, 32) for i in range(20)]
s = Solver()
for i in range(20):
s.add(m[i] >= 0, m[i] < 18)
s.add(n[i] >= 0, n[i] < 20)
s.add(((m[i]^n[i]) | (n[i] << 4)) ^ 0xC3 == buf[i])
if s.check() == sat:
ans = s.model()
m = [ans[m[i]].as_long() for i in range(20)]
n = [ans[n[i]].as_long() for i in range(20)]
print "32C3_" + "".join(chr(HEXC[i+2]) for i in m) + "".join(chr(HEXC[i]) for i in n)
root@PICA:~# /usr/bin/verify-flag
Gib flag! 32C3_77776f235a1b941c0817aaaa0c3885db12dbf8ea
You of winning!
Flag: 32C3_77776f235a1b941c0817aaaa0c3885db12dbf8ea