MEEPWN CTF 2018 - XSS

Lúc đầu nhìn tên bài mình cứ nghĩ là dạng binary .Net chứa mấy đề XSS giống ở SECCON, nên còn không buồn đọc đề. Đến lúc làm thì thấy là cũng liên quan thật.

File binary khi được chạy sẽ fork thêm 1 process nữa, rồi process mới này lại fork một process khác, tổng là 3 process. Mỗi process này sẽ dựa theo số lượng tham số truyền vào để xử lý theo nhánh riêng của nó (process đầu không có tham số, process thứ hai có 1 tham số là 1, process thứ ba có 2 tham số là 1 và 2). Mình debug bằng cách attach 3 cửa sổ IDA vào lần lượt 3 process.

Nếu cứ diễn giải lần lượt từng bước thì khá dài dòng, về cơ bản là các process sẽ tương tác với nhau bằng cách inject code và thực thi thông qua các hàm: OpenProcess, VirtualAllocExReadProcessMemoryWriteProcessMemory, WaitForSingleObject, ReleaseSemaphore,  và CreateRemoteThread. Các code được inject tương đối giống nhau, có dạng thế này (đây là code đọc input từ người chơi):

    v11 = 0;
    hObject = 0;
    while ( WaitForSingleObject(*(&dword_4F4FFC + (_DWORD)lpParameter), 1u) == 258 )
        ;
    arr1[0] = 97;
    arr1[1] = 98;
    arr1[2] = 99;
    arr1[3] = 100;
    arr1[4] = 97;
    arr1[5] = 98;
    arr1[6] = 99;
    arr1[7] = 100;
    arr1[8] = 97;
    arr1[9] = 98;
    arr1[10] = 99;
    arr1[11] = 100;
    arr1[12] = 97;
    arr1[13] = 98;
    arr1[14] = 99;
    arr1[15] = 100;
    arr2[0] = 106;
    arr2[1] = 21;
    arr2[2] = 109;
    arr2[3] = 11;
    arr2[4] = -99;
    arr2[5] = -16;
    arr2[6] = -62;
    arr2[7] = 52;
    arr2[8] = 116;
    arr2[9] = -118;
    arr2[10] = -44;
    arr2[11] = 79;
    arr2[12] = 80;
    arr2[13] = -124;
    arr2[14] = -96;
    arr2[15] = 127;
    for ( i = 0; i < 16; ++i )
    {
        v8 = getchar();
        arr1[i] = v8 ^ arr2[i];
    }
    while ( WaitForSingleObject(*(&hHandle + (_DWORD)lpParameter), 1u) == 258 )
        ;
    hProcess = OpenProcess(dwDesiredAccess, 0, *(&BaseAddress + (_DWORD)lpParameter));
    if ( !hProcess )
        exit(0);
    v13 = 0;
    lpBaseAddress = (LPCVOID)BaseAddress;
    Buffer = 0;
    while ( 1 )
    {
        v13 = ReadProcessMemory(hProcess, lpBaseAddress, &Buffer, 1u, 0);
        if ( !v13 )
            exit(0);
        if ( Buffer == 198 )
            break;
        lpBaseAddress = (char *)lpBaseAddress + 1;
    }
    lpBaseAddress = (char *)lpBaseAddress - 1;
    for ( j = 0; j < 16; ++j )
    {
        v13 = WriteProcessMemory(hProcess, (char *)lpBaseAddress + 4, &arr1[j], 1u, 0);
        if ( !v13 )
            exit(0);
        lpBaseAddress = (char *)lpBaseAddress + 4;
    }
    CloseHandle(hProcess);
    hObject = OpenProcess(dwDesiredAccess, 0, dword_4F502C[2]);
    if ( !hObject )
        exit(0);
    dwSize = 43632;
    v10 = VirtualAllocEx(hObject, 0, 0xAA70u, 0x1000u, 0x40u);
    if ( !v10 )
        exit(0);
    v6 = v10;
    lpBuffer = byte_4FFAA8;
    v11 = WriteProcessMemory(hObject, &BaseAddress, &v10, 4u, 0);
    if ( !v11 )
        exit(0);
    for ( k = 0; k < dwSize; ++k )
        byte_4FFAA8[k] ^= 0x66u;
    v11 = WriteProcessMemory(hObject, v6, lpBuffer, dwSize, 0);
    if ( !v11 )
        exit(0);
    lpStartAddress = (LPTHREAD_START_ROUTINE)v10;
    v4 = CreateRemoteThread(hObject, 0, 0, (LPTHREAD_START_ROUTINE)v10, lpParameter, 0, 0);
    if ( !v4 )
        exit(0);
    CloseHandle(hObject);
    return ReleaseSemaphore(*(&hSemaphore + (_DWORD)lpParameter), 1, 0);
}

Mình sẽ không nói chi tiết về việc binary dùng SemaphoreCreateRemoteThread như thế nào để điều khiển luồng thực thi cho đúng, vì nói thật nó loằng ngoằng và mình cũng không cố note lại. Tuy nhiên, để làm được bài thì vẫn cần biết chương trình làm những gì, nên sau khi F8 tỉ mỉ một vài lượt inject code đầu tiên, nhận thấy có sự tương đồng như đã nói khi nãy, mình đẩy nhanh tiến độ bằng cách đặt breakpoint ở hàm kernel32_CreateRemoteThread(trong cả 3 process) và cứ thế chạy, mỗi khi dừng ở breakpoint, mình dựa vào tham số lpStartAddress để biết địa chỉ phần code chuẩn bị được thực thi ở đâu, rồi mở xem code đó nó làm gì (đáng lẽ cần phải xem tham số hProcess để biết là process nào nữa, nhưng mình lười nên cứ Alt+Tab lần lượt 3 cửa sổ IDA để thử thôi). Cũng có một chút anti-decompile nhưng có thể fix bằng tay được. Nhìn chung chương trình hoạt động như thế này:

Các đoạn code giống nhau về cấu trúc, có sẵn hai mảng 16 ký tự là arr1 và arr2, trong đó mảng arr1 luôn là abcdabcdabcdabcd. Khi được thực thi bằng hàm CreateRemoteThread, code sẽ xử lý 2 mảng arr1 và arr2 (bằng một trong các thao tác xor, aesencaesenclast hoặc so sánh), sau đó, ghi đè output  vào mảng arr1 của đoạn code thực thi sau đó thông qua hàm WriteProcessMemory (đoạn code này vốn cũng đã thực thi rồi, nhưng đang bị dừng bởi Semaphore). Bên cạnh việc xử lý input thì mỗi thread cũng kiêm thêm việc ghi code cho các thread sau. Để dễ hình dung (vì nếu nói chính xác thì các đoạn code không chạy một mạch từ đầu đến cuối mà sẽ bị gián đoạn bởi Semaphore), có thể tưởng tượng với đầu vào là input mà ta nhập, chương trình cho nó đi qua một dây chuyền sản xuất, ở mỗi bước giá trị của input lại bị thay đổi một tí, xong được đẩy sang bước tiếp theo. Có tất cả là 1 bước xor, 9 bước aesenc, 1 bước aesenclast (AES128) và cuối cùng là 1 bước so sánh (để hiện thông báo Correct! hoặc Try Again!). Chúng ta có toàn bộ hardcoded key được dùng trong các bước này và tất nhiên là cả giá trị so sánh cuối cùng.

xor_key = '6A156D0B9DF0C234748AD44F5084A07F'

aes_keys = [
'609D4731B5DD367EEF997AD8495C4523',
'FAECDBBB93B23AEF68E4BE6D2FF66B4C',
'3E43953C69D073672297D1B1A361FD4A',
'45337E3CF07F2DAC33443B75482AC546',
'4DF0EC1A3D04A9DBF5D5081A80709306',
'BAF0AB1FAC2F5881F125B159F979DE03',
'34AFFF57513A0FEC8BA0E65F8C986078',
'2EEBCF4605AE3D94BA8CCCF44CA11D4C',
'74F9C5427F7A6EE2B11F2CC21804B8F7',
'0A98631D8469820807CA31F71D335629',
]

target = '68CEDFDD586C37E4C4E1ACB4097F97A4'

Đến đây thì phần RE đã xong, phần còn lại là Crypto, mình hỏi một chuyên gia mật mã đầu ngành của team và nhận lại flag vài phút sau đó.

P.S: Có một bí ẩn xung quanh challenge này, nhưng bây giờ cảm giác tội lỗi với Linh Ka khiến mình không tập trung làm được việc gì cả. Nếu sau này có thời gian tìm hiểu thêm thì mình sẽ bổ sung vào bài viết sau.

Quên mất, cá nhân mình đánh giá đây là một đề mất không ít thời gian để xây dựng, nên kudos to người ra đề!