32C3 CTF - PICA

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 相關的檔案

1. 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 裝置進行,因此將目標轉移至 ring0

2. 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 hande
並透過 acpi_evaluate_object() 呼叫其中的 FLAG method 來檢查 ring3 傳來的 flag 字串

看來無法在 Driver 層簡單解決,只好再繼續深入到 ACPI

3. ACPI

ACPI (Advanced Configuration and Power Interface) is an open industry specification co-developed by Hewlett-Packard, Intel, Microsoft, Phoenix, and Toshiba.

Linux 下可以直接在 ring3 dump 出 ACPI 的 DSDT(Differentiated System Description Table):

sudo cat /sys/firmware/acpi/tables/DSDT > dsdt.aml

dsdt.amlAML(ACPI Machine Language) 檔案,可以透過 isal 工具做 disassemble:

sudo apt-get install iasl
iasl -d dsdt.aml

在 dsdt 中可以找到 CTF Device 的 code:

 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 要符合的條件:

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 中每一輪的 mn , 配合 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

Reference: