Lazy Project
Published on

32C3 CTF - PICA

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.amlAML(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 中每一輪的 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