April 2011 Archives

File: rce300.zip
Задание на первый взгляд интересное и необычное, у нас есть игра для Nintendo DS, которая проверяет введенную строку и сообщает success или fail.
Для начала нам нужно загрузить образ в IDA для анализа. В сети есть loader для IDA, но он скомпилирован под старую версию. Можно скомпилировать с новым sdk, но мы пошли по быстрому пути посмотрели нужные поля в заголовке и загрузили прошивку руками.

snk-box:rce300 snk$ ./dump-hdr crackme.nds
gamecode: ####
devicetype: 0x0
devicecap: 0x2
arm9_rom_offset: 0x200
arm9_entry_address: 0x2000000
arm9_ram_address: 0x2000000
arm9_size: 0x41e7c
arm7_rom_offset: 0x42200
arm7_entry_address: 0x37f8000
arm7_ram_address: 0x37f8000
arm7_size: 0xf71c
fnt_offset: 0x51a00
fnt_size: 0x9
fat_offset: 0x51c00
fat_size: 0x0
arm9_overlay_offset: 0x0
arm9_overlay_size: 0x0
application_end_offset: 0x52440
rom_header_size: 0x200

После того как загрузили, поищем интересные строки в памяти. Быстро найдем вот такой кусок:
RAM:020003BE       LDR     R0, =a______________ ; " ______________________________\n"
RAM:020003C0       BL      printf
RAM:020003C4       LDR     R0, =asc_2040858 ; "|                              |\n"
RAM:020003C6       BL      printf
RAM:020003CA       LDR     R0, =aPassword  ; "|            password          |\n"
RAM:020003CC       BL      printf
RAM:020003D0       LDR     R0, =asc_2040858 ; "|                              |\n"
RAM:020003D2       BL      printf
RAM:020003D6       LDR     R0, =asc_20408A0 ; " ------------------------------\n\n"
RAM:020003D8       BL      printf
RAM:020003DC       LDR     R0, =aRootNdh2011 ; "root@ndh2011:# "
RAM:020003DE       BL      printf
RAM:020003E2       LDR     R1, [SP,#8]
RAM:020003E4       LDR     R0, =aS         ; "%s"
RAM:020003E6       BL      scanf
RAM:020003EA       ADD     R3, SP, #16     ; R3 - String

По тому какие параметры передаются очень похоже что это вызов фукций printf и scanf. Чуть ниже идет несколько проверок и вычисление хеша от введенной строки.
rce300.png
Хэш-функция располагается по адресу sub_02003530. Её псевдокод и код полной проверки строки в rce300_src.tgz

snk-box:rce300 snk$ ./brute
Key: DsLrox

rce300-win.png
Задание на первый взгляд было сложным, но как оказалось решалось очень быстро, даже разбираться в устройстве Nintendo не пришлось :(

File: rce200.zip
Представляет из себя приложение для Android. С помощью dex2jar конвертируем из Dalvik bytecode в Java bytecode и c помощью jad или JD-GUI декомпилируем. Внутри у нас три класса: a,b и c, последний представляет из себя хранилище для зашифрованных строк.

a=f9dd11ff6857af73ac9a944dfc52f41b
b=google_sdk
c=MD5
d=android.speech.action.RECOGNIZE_SPEECH
e=SHA-1

Логика приложения простая, оно ждет пока мы скажем голосом слово, проверяет что MD5 от него равен f9dd11ff6857af73ac9a944dfc52f41b, а ключом является SHA1 от него.

По Rainbow tables легко найти MD5(salope) = f9dd11ff6857af73ac9a944dfc52f41b значит ключ:
SHA1(salope)=913beccad686975f8c686d9b3b1ee6bb97c22d6f

Вуа ля :)

File: rce100.zip
Задание представляет из себя Win32 PE бинарный файл запакованный простеньким пакетом. Сперва распаковываем подручными средствами или снимаем дамп, восстанавливаем импорт. Я использовал IDA Universal PE unpacker и IDAStealth, кто-то использует свои любимые средства.
Первым делом в распакованном бинарнике смотрим строки и API вызовы. Вскоре находим функцию которая вызывает RegisterClassExA - это sub_403260. Вот примеры её вызовов:
  sub_403260("CWindow", CWindow_WindowProc, 0x7F00);
  sub_403260("CImage", CImage_WindowProc, 0x7F00);
  sub_403260("CEdit",  CEdit_WindowProc, 0x7F00);
  sub_403260("CButton", CButton_WindowProc, 0x7F89);
Окей уже неплохо, а теперь посмотрим граф вызовов и его же но с переименованными функциями:
rce100-1.png rce100-2.png

Код функции WinMain:
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  HRSRC v4; // eax@1
  HWND v5; // esi@1
  HRSRC v6; // eax@1
  int v7; // eax@1
  HRSRC v8; // eax@1
  int v9; // eax@1
  int v10; // esi@1

  sub_401000(hInstance);
  sub_401080(0, 0x101010u, 0x999999u);
  v4 = sub_402CB0(0x73u);
  v5 = NewWindows(0, 10, 10, 1, v4, -1, "test", sub_41ED20, 0);
  v6 = sub_402CB0(0x75u);
  v7 = sub_401BA0(v6);
  CreateCImage(v5, 220, 180, 4, v7);
  v8 = sub_402CB0(0x74u);
  v9 = sub_401BA0(v8);
  CreateCImage(v5, 220, 190, 3, v9);
  sub_403580(4);
  sub_403580(3);
  CreateCEdit(v5, 390, 240, 200, 20, 2, 2);
  NewButton(v5, 405, 270, 80, 20, 100, "Login", 10);
  NewButton(v5, 495, 270, 80, 20, 101, "Quit", 10);
  sub_403750(100, 1, 50);
  sub_403750(101, 1, 50);
  sub_403550(1);
  sub_403870(1, 128);
  v10 = sub_4010B0();
  sub_4010F0();
  return v10;
}

Замечаем
v5 = NewWindows(0, 10, 10, 1, v4, -1, "test", sub_41ED20, 0);

sub_41ED20 - это оконная процедура, которая дальше вызывает функцию sub_41ECB0

int __cdecl sub_41ECB0()
{
  signed int v1; // [sp+0h] [bp-20Ch]@2
  char v2; // [sp+4h] [bp-208h]@1
  unsigned int v3; // [sp+208h] [bp-4h]@1

  v3 = &v2 ^ dword_4228B4;

  GetCEditString(2, &v2, 513);
  if ( calc_hash(&v2) == 0xC4B1801C )	// Bingo !
    v1 = 3;
  else
    v1 = 4;

  sub_403550(v1);
  sub_403580(2);
  sub_403580(100);
  return 0;
}

int __usercall calc_hash<eax>(const char *string<edi>)
{
  const char *v1; // ecx@1
  int hash; // esi@1
  unsigned int i; // edx@1
  char v4; // al@2

  v1 = string;
  hash = 0xDEADBEEFu;
  i = 0;
  do
    v4 = *v1++;
  while ( v4 );
  if ( v1 != string + 1 )
  {
    do
      hash = 0x5B86AFFE * string[i++] - 0x38271606 * hash;
    while ( i < strlen(string) );
  }
  return hash;
}

Теперь мы можем написать брутфорсер:
snk-box:rce100 snk$ ./brute
Key: pWn3D

Таких строк удовлетворяющих условию будет очень много. Но жюри приминает только две: pWn3D и H,v^^
Видим протокол: клиент цепляется к серверу, отправляет публичный ключ, сервер отправляет свой, на основе ключей генерируется сессионный ключ. Если публичный ключ разрешен на сервере, то получим флаг. Задача - иметь возможность представиться клиентом с разрешенным публичным ключом. Т.к. в генерации сессионного ключа используется приватный ключ, то для заданного публичного ключа попробуем найти приватный. В конструкторе класса BraidKey видим, что перемешиваются только 2 часть ключа длиной N инициализированного цифрами 0..N-1.
self.privkey = Braid(N)
if client:
    self.privkey.shuffle(offset=N/2)
Видим, что длина приватного ключа клиента 22 байта, а первые 11 байт всегда статичны. Для того чтобы найти вторую часть ключа, переберем 11!(~40M) вариантов перестановок. Leet More описали этот метод как неэффективный(en), но столь большое количество времени необходимо лишь из-за неоптимальной процедуры комбинации ключей в исходном коде. Развернутый код(thanks to bma!):
% time python b.py
Found!!! [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 17, 14, 13, 21, 20, 16, 19, 11, 12]
41.922u 0.000s 0:42.14 99.4%    1261+1261k 0+0io 0pf+0w
Исходник:
#!/usr/local/bin/python
# -*- coding: UTF-8 -*-
from sys import exit
from itertools import permutations

#def gen_pub(priv_key, key, len_key):
#    # reverse
#    privr_key = range(len_key)
#    pub_key = range(len_key)
#    temp0 = range(len_key)
#    for i in xrange(len_key):
#        privr_key[priv_key[i]] = i
#    # combine part one
#    for i in xrange(len_key):
#        temp0[i] = privr_key[key[i]]
#    # combine part two
#    for i in xrange(len_key):
#        pub_key[i] = temp0[priv_key[i]]
#    return pub_key


def main():
    key = [13, 18, 20, 4, 1, 8, 6, 15, 5, 12, 14, 2, 7, 3, \
            10, 21, 16, 9, 0, 11, 19, 17]
    npub = [15, 12, 17, 4, 1, 8, 6, 11, 5, 21, 14, 16, 0, 9, \
            10, 3, 13, 19, 18, 20, 2, 7]
    bpkey = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    spkey = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
    len_key = len(key)
    privr_key = range(len_key)
    pub_key = range(len_key)
    temp0 = range(len_key)

    iter = 0

    for p in permutations(spkey):
        iter += 1
        if iter % 100000 == 0:
             print iter

        pkey = bpkey + list(p)

        for i in xrange(len_key):
            privr_key[pkey[i]] = i
        # combine part one
        for i in xrange(len_key):
            temp0[i] = privr_key[key[i]]
        # combine part two
        for i in xrange(len_key):
            pub_key[i] = temp0[pkey[i]]

        if pub_key == npub:
            print "Found!!!", pkey
            return 0
    return 0

if __name__ == '__main__':
    try:
        import psyco
        psyco.full()
    except ImportError:
        print 'Psyco not installed, the program will just run slower'
    exit(main())

About this Archive

This page is an archive of entries from April 2011 listed from newest to oldest.

February 2011 is the previous archive.

June 2011 is the next archive.

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