→ PHD CTF Afterparty

| 1 Comment | No TrackBacks

Появилась возможность немного пореверсить, разбор таска Artifact.

Win32 PE, не упакован. В строках есть "Base must be binary (MR_ALWAYS_BINARY defined in mirdef.h ?)", а в импорте "QueueUserAPC". Первое говорит о прилинкованной библиотеке Miracl. Второе - об асинхронных процедурах.

Делаем/применяем сигнатуры IDA для Miracl. В моем случае сигнатуры не сработали.

IDA находит _main по адресу 0x00403700, ее псевдокод:

hFile = CreateFileA("ctf-pass-file.txt", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
ReadFile(hFile, g_pFileBuffer, 0x10u, &NumberOfBytesRead, 0);
...
SleepEx(1u, TRUE);
if (g_pDecisionFunc(g_pFileBuffer))
{
    printf("fail!\n");
}
else
{
    printf("you did it!\n");
    ...
    szHash = GetHash();
    printf("%s\n", szHash);
}

Создаем файл со строкой 16 символов, патчим переход по адресу 0x00403799, чтобы всегда сваливаться на ""you did it!". Запускаем, получаем какой-то хэш, как ответ не подходит.

Ставим брейк на 0x00403791 на g_pDecisionFunc(g_pFileBuffer). Запускам под отладчиком, Olly падает.

Комбинация SleepEx(xxx, TRUE) и QueueUserAPC в импорте как бы намекает. Брейк на QueueUserAPC, попадаем ___tmainCRTStartup (0x00414A34) -> j_SetupUserAPC (0x00418660) -> SetupUserAPC (0x0041C340) -> QueueUserAPC. По адресу 0x4014A0 находится наш APCProc.

.text:004014A0                 push    ebp
...
.text:004014AD                 push    offset sub_42D1E8
.text:004014B2                 push    offset unk_421000
.text:004014B7                 call    GetDataFromMiracl
.text:004014BC                 add     esp, 8
.text:004014BF                 push    3
.text:004014C1                 call    GetSyscallIndex ; ZwProtectVirtualMemory
.text:004014C6                 add     esp, 4
.text:004014C9                 mov     [ebp+syscall], eax
.text:004014CC                 push    3
.text:004014CE                 call    GetNumberOfParam
.text:004014D3                 add     esp, 4
.text:004014D6                 mov     [ebp+syscall_param_count], eax
.text:004014D9                 mov     eax, [ebp+var_1C]
.text:004014DC                 xor     eax, 0FFFFFFFFh
.text:004014DF                 mov     [ebp+a4], eax
.text:004014E2                 mov     [ebp+a5], 2000h
.text:004014E9                 lea     ecx, [ebp+a7]
.text:004014EC                 push    ecx             ; a7
.text:004014ED                 push    40h             ; a6
.text:004014EF                 lea     edx, [ebp+a5]
.text:004014F2                 push    edx             ; a5
.text:004014F3                 lea     eax, [ebp+a4]
.text:004014F6                 push    eax             ; a4
.text:004014F7                 push    0FFFFFFFFh      ; param1
.text:004014F9                 mov     ecx, [ebp+syscall_param_count]
.text:004014FC                 push    ecx             ; param_count
.text:004014FD                 mov     edx, [ebp+syscall]
.text:00401500                 push    edx             ; syscall
.text:00401501                 call    DoSyscall
.text:00401506                 add     esp, 1Ch
...


Первый вызов по адресу 0x004014B7 расшифровывает/декодирует кусок кода, используя Miracl.
Следующие 3 вызова по адресам 0x004014C1, 0x004014CE и 0x00401501 соответственно подготавливают номер системного вызова, количество параметров и делает системный вызов.

Поиск системного вызова - 0x00405640 GetSyscallIndex(int nIndex). Внутри функции есть массив хэшей

Hashes[0] = 0x1232B8E2;
Hashes[1] = 0xF70D3051;
Hashes[2] = 0xEB7FEB5E;
Hashes[3] = 0x2E8DB3FA;
Hashes[4] = 0xD02ED5BC;
Hashes[5] = 0x7659BF62;
Hashes[6] = 0xF6A0F55F;
Hashes[7] = 0x2C4E4F12;
Hashes[8] = 0x64FACEBB;

и по адресу 0x00405697 вызов функции FindNtdllFuncByName, которая находит адрес функции по переданному хэшу. Внутри все просто, через PEB находится ntdll.dll, затем в директории экспорта перебираются все имена экспортируемых функций, вычисляется хеш, при совпадении возвращается адрес кода найденной функции.

Функция вычисления хеша:

int __cdecl GetHashOfFuncName(char *szFuncName)
{
    int hash;
    hash = 0;
    while (*szFuncName)
        hash = 51 * hash + (unsigned __int8)*szFuncName++;
    return hash;
}

Пишем скрипт, который посчитает хэши всех функций ntdll.dll. Получить список экспортируемых функций можно, например, с помощью dumpbin.exe.

0 ==> 0x1232b8e2 ==> ZwAllocateVirtualMemory
1 ==> 0xf70d3051 ==> ZwReadVirtualMemory
2 ==> 0xeb7feb5e ==> ZwWriteVirtualMemory
3 ==> 0x2e8db3fa ==> ZwProtectVirtualMemory
4 ==> 0xd02ed5bc ==> ZwOpenProcess
5 ==> 0x7659bf62 ==> ZwQuerySystemInformation
6 ==> 0xf6a0f55f ==> ZwCreateThread
7 ==> 0x2c4e4f12 ==> ZwQueryInformationProcess
8 ==> 0x64facebb ==> ZwClose

После того, как адрес Zw-функции найден, GetSyscallIndex добавляет к адресу 1 и возвращает DWORD по полученному адресу - а это и есть номер системного вызова. Например, для ZwAllocateVirtualMemory, под W7 номер 0x13:

772E52D8 > B8 13000000      MOV EAX,13
772E52DD   BA 0003FE7F      MOV EDX,7FFE0300
772E52E2   FF12             CALL DWORD PTR DS:[EDX]
772E52E4   C2 1800          RETN 18

Но вернемся к APCProc. Далее происходит копирование расшифрованного/декодированного буфера на адрес 0x00401580 и передача туда управления. После выполнения расшифрованного кода, он затирается memset'ом.

.text:00401509                 push    12EBh           ; size_t
.text:0040150E                 push    offset sub_42D1E8 ; void *
.text:00401513                 mov     eax, [ebp+var_1C]
.text:00401516                 xor     eax, 0FFFFFFFFh
.text:00401519                 push    eax             ; void *
.text:0040151A                 call    _memmove        ; dest = 00401580
.text:0040151A                                         ; srd =  0042D1E8
.text:0040151A                                         ; size = 000012EB
.text:0040151F                 add     esp, 0Ch
.text:00401522                 mov     ecx, [ebp+var_1C]
.text:00401525                 xor     ecx, 0FFFFFFFFh
.text:00401528                 mov     [ebp+a4], ecx
.text:0040152B                 mov     [ebp+a5], 2000h
.text:00401532                 mov     edx, [ebp+var_1C]
.text:00401535                 xor     edx, 0FFFFFFFFh
.text:00401538                 mov     [ebp+var_4], edx
.text:0040153B                 push    1
.text:0040153D                 call    [ebp+var_4]     ; 00401580
.text:00401540                 push    12EBh           ; size_t
.text:00401545                 push    0               ; int
.text:00401547                 mov     eax, [ebp+var_1C]
.text:0040154A                 xor     eax, 0FFFFFFFFh
.text:0040154D                 push    eax             ; void *
.text:0040154E                 call    _memset

Вся основная работа происходит в расшифрованной функции 0x00401580. Здесь получается список процессов через ZwQuerySystemInformation, затем каждый процесс открывается ZwOpenProcess, в нем выделяется страница памяти через ZwAllocateVirtualMemory, декодируется перехватчик ZwReadVirtualMemory и пишется в выделенную память. Затем сплайсится ZwReadVirtualMemory.
Код хука (после последней инструкции записан PID защищаемого процесса, номера вызовов ZwQueryInformationProcess и ZwReadVirtualMemory):

.data:004332E0 ZwReadVirtualMemory_Hook proc near
.data:004332E0
.data:004332E0 var_124         = dword ptr -124h
.data:004332E0 nRetLength      = byte ptr -108h
.data:004332E0 ProcessInformation= byte ptr -104h
.data:004332E0 var_F8          = dword ptr -0F8h
.data:004332E0 var_8           = dword ptr -8
.data:004332E0 arg_0           = dword ptr  4
.data:004332E0 arg_4           = dword ptr  8
.data:004332E0
.data:004332E0                 push    ebx
.data:004332E1                 call    getDelta
.data:004332E6                 add     ebx, 3
.data:004332E9                 mov     edx, [esp+4+arg_0]
.data:004332ED                 sub     esp, 104h
.data:004332F3                 lea     eax, [esp+108h+nRetLength]
.data:004332F6                 lea     ecx, [esp+108h+ProcessInformation]
.data:004332FA                 push    eax
.data:004332FB                 push    18h
.data:004332FD                 push    ecx
.data:004332FE                 xor     eax, eax
.data:00433300                 push    eax
.data:00433301                 push    edx
.data:00433302                 push    edx
.data:00433303                 mov     eax, [ebx+4]    ; ZwQueryInformationProcess
.data:00433306                 call    $+5
.data:0043330B                 add     [esp+124h+var_124], 8
.data:0043330F                 mov     edx, esp
.data:00433311                 sysenter
.data:00433313                 add     esp, 1Ch
.data:00433316                 mov     eax, [esp+108h+var_F8]
.data:0043331A                 mov     ecx, [ebx]      ; pid
.data:0043331C                 add     esp, 100h
.data:00433322                 cmp     ecx, eax
.data:00433324                 mov     eax, [ebx+8]    ; ZwReadVirtualMemory
.data:00433327                 pop     ebx
.data:00433328                 jnz     short call_ZwReadVirtualMemory
.data:0043332A                 call    $+5
.data:0043332F
.data:0043332F crash_debugger:
.data:0043332F                 pop     ecx
.data:00433330                 push    ecx
.data:00433331                 add     ecx, 0Ch
.data:00433334                 mov     [esp+8+var_8], ecx
.data:00433337                 mov     edx, esp
.data:00433339                 sysenter
.data:0043333B                 mov     ecx, [esp+8+arg_0]
.data:0043333F                 mov     edx, [esp+8+arg_4]
.data:00433343                 cmp     edx, 1
.data:00433346                 jz      short locret_43336A
.data:00433348                 push    esi
.data:00433349                 mov     esi, eax
.data:0043334B                 mov     eax, large fs:18h
.data:00433351                 mov     eax, [eax+24h]
.data:00433354                 xor     eax, 1234A965h
.data:00433359
.data:00433359 loc_433359:
.data:00433359                 xor     byte ptr (crash_debugger - 43332Fh)[edx+ecx], al
.data:0043335C                 inc     al
.data:0043335E                 mov     ah, al
.data:00433360                 shl     ah, 1
.data:00433362                 xor     al, ah
.data:00433364                 dec     edx
.data:00433365                 jnz     short loc_433359
.data:00433367                 mov     eax, esi
.data:00433369                 pop     esi
.data:0043336A
.data:0043336A locret_43336A:
.data:0043336A                 retn    14h
.data:0043336D ; ---------------------------------------------------------------------------
.data:0043336D
.data:0043336D call_ZwReadVirtualMemory:
.data:0043336D                 call    $+5
.data:00433372                 add     [esp+8+var_8], 8
.data:00433376                 mov     edx, esp
.data:00433378                 sysenter                ; ZwReadVirtualMemory
.data:0043337A                 retn    14h
.data:0043337A ZwReadVirtualMemory_Hook endp
.data:0043337A
.data:0043337D
.data:0043337D getDelta        proc near
.data:0043337D                 call    $+5
.data:00433382                 pop     ebx
.data:00433383                 retn
.data:00433383 getDelta        endp

Перехватчик проверяет PID, при совпадении портит TEB, а также запрошенный буфер, что приводит к крэшу Olly.
Так как в обработчике нет ничего полезного, правим функцию 0x00401580, чтобы она больше не делала противоотладку: по адресу 0x00401597 пишем JMP 0x00402051. И попадаем на вторую часть функции.

Здесь декодируется 16 байт считанных из файла в _main и записываются обратно.

syscall = GetSyscallIndex(1);       // ZwReadVirtualMemory
syscall_params_count = GetNumberOfParam(1);
DoSyscall(syscall, syscall_params_count, -1, g_pFileBuffer, pReadBuffer, 16, &a6);
for (l = 0; l < 16; ++l)
{
    symb = pReadBuffer[l];
    for (m = 0; m < 256; ++m)
    {
        if (m * symb % 257 == 1)
        {
            pReadBuffer[l] = m;
            break;
        }
    }
}
syscall = GetSyscallIndex(2);       // ZwWriteVirtualMemory
syscall_params_count = GetNumberOfParam(2);
DoSyscall(syscall, syscall_params_count, -1, g_pFileBuffer, pReadBuffer, 16, &a6);

Затем декодируется DecisionFunc и заполняется указатель g_pDecisionFunc.

signed int DecisionFunc(char *pFileBuffer)
{
    signed int idx;
    unsigned int current;
    int value;
    int aConstants[16];
    aConstants[2] = 86356;
    aConstants[3] = 86356;
    aConstants[0] = 468517;
    aConstants[1] = 56613;
    aConstants[4] = 243006;
    aConstants[5] = 254505;
    aConstants[6] = 373182;
    aConstants[7] = 32439;
    aConstants[8] = 506585;
    aConstants[9] = 271621;
    aConstants[10] = 32439;
    aConstants[11] = 207142;
    aConstants[12] = 56613;
    aConstants[13] = 506585;
    aConstants[14] = 284318;
    aConstants[15] = 3;
    idx = 0;
    while (1)
    {
        current = 3;
        if (pFileBuffer[idx] > 0)
        {
            value = pFileBuffer[idx];
            do
            {
                current = current * current % 0x7FFFF;
                --value;
            }
            while (value);
        }
        if (current != aConstants[idx])
            break;
        ++idx;
        if (idx >= 16)
            return 0;
    }
    return 1;
}

Используя этот код, пишет брутер в символов в диапазоне (-127, 128).
Получаем строку 'Hello, cracker!' + '\0'. Затем кодируем его по правилу из предыдущего листинга, получаем

19 1C BC BC 2C 6F F9 87 │ 7C 35 87 F5 1C 7C 94 00   ↓∟??,oщ╪|5╪х∟|"

Записываем эти 16 байт в ctf-pass-file.txt, кладем рядом с artifact.exe, запускам, получаем ответ

you did it!
1b0aacc9baf278a3f9e5bd0b7d746e22

No TrackBacks

TrackBack URL: http://smokedchicken.org/m/mt-tb.cgi/63

1 Comment

Круто, терь понятно, что там за магия в SleepEx происходила. Я просто сдампил sbox и добрутил вторую часть.

About this Entry

This page contains a single entry by Павел Збицкий published on January 3, 2012 5:53 PM.

hack.lu CTF Power security tokens was the previous entry in this blog.

SnD Reverser Tool is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.