March 2012 Archives

У нас есть HTTP сервис 10.0.0.3:8080, отвечающий на запросы следующим образом:
GET / HTTP/1.0

HTTP/1.0 501 Unsupported method ('GET')
Server: BaseHTTP/0.3 Python/2.7.2+
Date: Sun, 18 Mar 2012 18:50:57 GMT
Content-Type: text/html
Connection: close

<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 501.
<p>Message: Unsupported method ('GET').
<p>Error code explanation: 501 = Server does not support this operation.
</body>
В глаза бросается Python и BaseHTTP, опыт подсказывает, что это XMLRPC сервер. Пробуем:
>>> import xmlrpclib
>>> s = xmlrpclib.ServerProxy('http://10.0.0.3:8080/')
>>> print s.system.listMethods()
['get_answer', 'get_server_var', 'system.listMethods', 'system.methodHelp', 'system.methodSignature']
отлично, вызовем get_answer.
>>> print s.get_answer()
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:get_answer() takes exactly 4 arguments (0 giv
en)">
а с параметрами?
>>> print s.get_answer(1,2,3,4)
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.ValueError'>:Wrong parameters">
попробуем узнать, что же за параметры нам нужны:
>>> print s.system.methodHelp('get_answer')
Returns an answer if params are correct
посмотрим вторую функцию.
>>> print s.system.methodHelp('get_server_var')
Gets a var from the server
попробуем достать ENV?
>>> print s.get_server_var('ENV')
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.NameError'>:name 'ENV' is not defined">
может быть ответ?
>>> print s.get_server_var('get_answer')
<function get_answer at 0x7f94aff416e0>
да ведь это eval! используя встроенные атрибуты, смотрим, что у нас есть интересного:
>>> print s.get_server_var('get_answer.func_globals')
{'__builtins__': <module '__builtin__' (built-in)>, '__file__': '/home/bay/task.py', 'xmlrpclib': <modu
le 'xmlrpclib' from '/usr/lib/python2.7/xmlrpclib.pyc'>, 'SimpleXMLRPCServer': <class SimpleXMLRPCServe
r.SimpleXMLRPCServer at 0x7f94aff376d0>, '__package__': None, 're': <module 're' from '/usr/lib/python2
.7/re.pyc'>, 'get_answer': <function get_answer at 0x7f94aff416e0>, 'get_server_var': <function get_ser
ver_var at 0x7f94aff3f9b0>, '__name__': '__main__', 'server': <SimpleXMLRPCServer.SimpleXMLRPCServer in
stance at 0x7f94b2110f38>, '__doc__': None}
>>> print s.get_server_var('get_answer.func_code.co_varnames')
('a', 'b', 'c', 'd', 'key')
>>> print s.get_server_var('get_answer.func_code.co_consts')
('Returns an answer if params are correct', 79011, 'Wrong parameters', 1702257177, 'The answer is %s')
>>> print s.get_server_var('get_answer.func_code.co_filename')
/home/bay/task.py
В константах ключа нет, вероятно он считается динамически, придется реверсить код функции get_answer
>>> import dis
>>> dis.dis(s.get_server_var('get_answer.func_code.co_code'))
дизассемблированный код функции. используя имена переменных и константы полученные ранее, восстановим ее псевдокод:
def get_answer(a, b, c, d):
    if( a + b != 79011): raise "Wrong parameters"
    if( a != b + c ): raise "Wrong parameters"
    if( c*d != 1702257177 ): raise "Wrong parameters"
    key = str(a) + str(b) + str(c) + str(d)
    return 'The answer is %s' % key
Найдем подходящие параметры.
>>> print s.get_answer(39506,39505,1,1702257177)
The answer is 395063950511702257177
Но данный ответ не корректен с точки зрения организаторов. Т.к. система уравнений имеет множество решений, попробуем поискать параметры, которые будут чем-то выделяться.
>>> def get_vals(a):
       b = 79011 - a
       c = a - b
       d = 1702257177/c
       if( c < 0 or 1702257177 % c != 0 ): return None
       return (a,b,c,d)
>>> for i in range(1,79011):
...  if( get_vals(i) != None): print i
... 
39506
39507
39515
39534
39982
40935
48559
55174
66666
>>> get_vals(66666)
(66666, 12345, 54321, 31337)
Флаг: 66666123455432131337 thanks to @snk.
В трех словах:
виртуальная машина, которая выполняет код производящий операции над матрицей, матрица - это представление графа, а алгоритм это поиск кратчайшего пути.
Ответ: Dijkstra

В много слов:
Программа представляет из себя виртуальную машину, с основной функцей:
.text:08048C00 ; char __cdecl vm_exec(struct_vm *vm, int *vm_data)
.text:08048C00 vm_exec         proc near               ; CODE XREF: main+27p
Алгоритм интерпретатора довольно простой, в цикле читается однобайтовый опкод команды, после switch и вызывается функция обработчик нужной команды.
    switch ( *code )
    {
      case 1:
        v2 = code + 1;
        opcode_create_class_1(vm, (char *)vm_data + *(_DWORD *)v2);
        code = v2 + 4;
        break;
...
      case 6:
        v7 = code + 1;
        opcode_add(vm, (int *)vm + *v7, (int *)vm + v7[1]);
        code = v7 + 2;
        break;
...
Команд всего 19, после непродолжительного анализа мы можем написать свой дизассемблер.
def GetMem(start_ea,size):
  r = ""
  for ea in range(start_ea,start_ea+size):
    r += chr(Byte(ea))
  return r

def sign(a):
 if( a >> 15 ):
   return -(65536-a)
 return a

vm_area = 0x0804C080
opcode = [
 {'s':1, 'name':'exit',                 'u':'',         'd':lambda pc,a: "exit" },
 {'s':5, 'name':'class_matrix',         'u':'I',        'd':lambda pc,a: "class_matrix(0x%x) # %d" % (vm_area + a[0],a[0]) },  
 {'s':5, 'name':'class_list',           'u':'I',        'd':lambda pc,a: "class_list(0x%x)   # %d" % (vm_area + a[0],a[0]) },
 {'s':3, 'name':'call_vf1 (get)',       'u':'BB',       'd':lambda pc,a: "matrix%d->vf1_get_val(r%d)" % (a[0],a[1])      }, 
 {'s':3, 'name':'call_vf2 (set)',       'u':'BB',       'd':lambda pc,a: "matrix%d->vf2_set_val(r%d)" % (a[0],a[1])      },
 {'s':3, 'name':'cmp',                  'u':'BB',       'd':lambda pc,a: "cmp r%d,r%d" % (a[0],a[1]) },
 {'s':3, 'name':'add',                  'u':'BB',       'd':lambda pc,a: "add r%d,r%d" % (a[0],a[1]) },
 {'s':3, 'name':'sub',                  'u':'BB',       'd':lambda pc,a: "sub r%d,r%d" % (a[0],a[1]) },
 {'s':3, 'name':'call_vf4 (set col) ',  'u':'BB',       'd':lambda pc,a: "matrix%d->vf4_set_col(r%d)" % (a[0],a[1])    },
 {'s':3, 'name':'call_vf3 (set row)',   'u':'BB',       'd':lambda pc,a: "matrix%d->vf3_set_row(r%d)" % (a[0],a[1])    },
 {'s':2, 'name':'print',                'u':'B',        'd':lambda pc,a: "print r%d" % (a[0]) },
 {'s':3, 'name':'jmp',                  'u':'H',        'd':lambda pc,a: "jmp 0x%x (%d)" % (pc+1+sign(a[0]),a[0]) },    
 {'s':3, 'name':'jz',                   'u':'H',        'd':lambda pc,a: "jz 0x%x (%d)" % (pc+1+sign(a[0]),a[0]) },     
 {'s':3, 'name':'mov reg, #Imm',        'u':'BB',       'd':lambda pc,a: "mov r%d, %d" % (a[0],a[1]) },
 {'s':2, 'name':'inc reg',              'u':'B',        'd':lambda pc,a: "inc r%d" % (a[0]) }, 
 {'s':2, 'name':'dec reg',              'u':'B',        'd':lambda pc,a: "dec r%d" % (a[0]) }, 
 {'s':3, 'name':'list_has_key',         'u':'BB',       'd':lambda pc,a: "list%d->has_key(r%d)" % (a[0],a[1])}, 
 {'s':3, 'name':'list_add_item',        'u':'BB',       'd':lambda pc,a: "list%d->add_item(r%d)" % (a[0],a[1])}, 
 {'s':3, 'name':'mov reg,reg',          'u':'BB',       'd':lambda pc,a: "mov r%d, r%d" % (a[0],a[1]) },
]

def dis(pc = 0x0804C11C, end_pc = 0x0804C1EF):
  while(pc <= end_pc):
    op = Byte(pc)
    add = struct.unpack( opcode[op]['u'], GetMem(pc+1,opcode[op]['s']-1) )
    print "%x: %s" % (pc,opcode[op]['d'](pc,add) )
    pc += opcode[op]['s']
    if( op == 0 ): break
Опкоды 1 и 2 создают по новому экземпляру классов MatrixGraph и PointerToGraph, у меня они называются для простоты matrix и list. Так же есть несколько опкодов, которые вызывают виртуальные функции этих классов.
Запустим наш дизассемблер получим следующий листинг:
804c11c: class_matrix(0x804c084) # 4
804c121: class_list(0x804c118) # 152
804c126: mov r0, 0
804c129: matrix0->vf2_set_val(r0)
804c12c: mov r0, 6
804c12f: mov r3, 0
804c132: dec r3
804c134: mov r1, 0
804c137: mov r2, 5
804c13a: cmp r1,r2
804c13d: jz 0x804c174 (54)
804c140: list1->has_key(r1)
804c143: jz 0x804c16f (43)
804c146: mov r5, 0
804c149: dec r5
804c14b: cmp r5,r3
804c14e: jz 0x804c154 (5)
804c151: jmp 0x804c16c (26)
804c154: matrix0->vf3_set_row(r1)
804c157: matrix0->vf4_set_col(r1)
804c15a: matrix0->vf1_get_val(r4)
804c15d: matrix0->vf4_set_col(r3)
804c160: matrix0->vf3_set_row(r3)
804c163: matrix0->vf1_get_val(r5)
804c166: cmp r4,r5
804c169: jz 0x804c16f (5)
804c16c: mov r3, r1
804c16f: inc r1
804c171: jmp 0x804c13a (65480)
804c174: matrix0->vf3_set_row(r3)
804c177: matrix0->vf4_set_col(r3)
804c17a: matrix0->vf1_get_val(r4)
804c17d: mov r5, 0
804c180: dec r5
804c182: cmp r5,r4
804c185: jz 0x804c191 (11)
804c188: cmp r4,r5
804c18b: jz 0x804c191 (5)
804c18e: jmp 0x804c1db (76)
804c191: list1->add_item(r3)
804c194: mov r1, 0
804c197: mov r2, 5
804c19a: cmp r1,r2
804c19d: jz 0x804c1d2 (52)
804c1a0: matrix0->vf4_set_col(r3)
804c1a3: matrix0->vf3_set_row(r1)
804c1a6: matrix0->vf1_get_val(r2)
804c1a9: matrix0->vf3_set_row(r3)
804c1ac: matrix0->vf1_get_val(r5)
804c1af: matrix0->vf4_set_col(r1)
804c1b2: matrix0->vf3_set_row(r1)
804c1b5: matrix0->vf1_get_val(r4)
804c1b8: add r2,r5
804c1bb: cmp r2,r4
804c1be: jz 0x804c1ca (11)
804c1c1: matrix0->vf4_set_col(r1)
804c1c4: matrix0->vf3_set_row(r1)
804c1c7: matrix0->vf2_set_val(r2)
804c1ca: inc r1
804c1cc: mov r2, 5
804c1cf: jmp 0x804c19a (65482)
804c1d2: dec r0
804c1d4: mov r2, 0
804c1d7: cmp r0,r2
804c1da: jz 0x804c12f (65364)
804c1dd: mov r2, 0
804c1e0: mov r2, 5
804c1e3: matrix0->vf4_set_col(r2)
804c1e6: matrix0->vf3_set_row(r2)
804c1e9: matrix0->vf1_get_val(r0)
804c1ec: print r0
804c1ee: exit
После анализа приблизительный псевдокод будет следующим:
m = class_matrix(0x804c084);
list = class_list(0x804c118)
m[0,0]=0
for(j=0;j<=5;j++){
    r3 = 0xFFFFFFFF;
    for (r1 = 0; r1 <= 5; r1++) {
        if( list1->has_key(r1) ) continue
        if(0xFFFFFFFF <= r3){
            r3 = r1
        }else if(m[r1,r1] > m[r3,r3]){
            r3 = r1
        }
    }

    if( 0xFFFFFFFF > m[r3,r3] && m[r3,r3]>0xFFFFFFFF){
        while (1); //error !?
    }

    list->add_item(r3)

    for (r1; r1<=5; r1++) {
        if( m[r3,r1] + m[r3,r3] <= m[r1,r1] ){
            m[r1,r1] = r2
        }
    }
}

print m[0,0]
exit
Имея подсказку, в виде названия класса MatrixGraph, понимаем что матрица представляет из себя форму хранения графа (см http://en.wikipedia.org/wiki/Adjacency_matrix), а сам алгоритм это поиск кратчайшего пути или алгоритм Дейкстры. Ответ к задаче будет: Dijkstra.
В трех словах:
com файл, подмена прерываний, самораспаковка, считываем с клавиатуры пароль, расшифровываем строку
Ответ: Bi11_i5_pr0|_|d_0|=_y0|_|

В много слов:
Выполнение начинается с вызова функции
seg000:0245                 call    sub_1028D
которая является своеобразным шлюзом для вызова других функций из таблицы, которая расположена по адресу 27Fh
seg000:02A8                 mov     ax, [bx+27Fh]
seg000:02AC                 push    ax
сама таблица содержит 7 функций, но часть кода зашифровано
seg000:027F off_1027F       dw offset f1_save_int1c
seg000:0281                 dw offset f2_save_int9
seg000:0283                 dw offset f3_save_int20
seg000:0285                 dw offset f4_restore_int1c
seg000:0287                 dw offset f5_restore_int20
seg000:0289                 dw offset f6_restore_int9
seg000:028B                 dw offset f7_print_string
Вначале управление передается на функцию:
seg000:0179 proc            f1_save_int1c near
Действия её сводится к следующему:
  1. вызов sub_1028D, которая в свою очередь вызовет уже вторую функцию f2_save_int9 (сохранит вектор 9го прерывания)
  2. сохранит вектор 1C прерывания
  3. вызов sub_1028D -> f3_save_int20
  4. установка нового вектора прерывания таймера

Давайте посмотрим новую функцию таймера
seg000:0104 proc            int_1C near 
Первым делом она устанавливает новые обработчики прерываний 0x9 и 0x20, далее она считает контрольную сумму самого себя с адреса 0x248 по 0x27B.
seg000:0115                 mov     bx, offset sub_10248
seg000:0118                 mov     [check_sum], 0
seg000:011D
seg000:011D loc_1011D:                              ; CODE XREF: int_1C+26j
seg000:011D                 cmp     bx, offset unk_1027B
seg000:0121                 jz      short loc_1012C
seg000:0123                 mov     al, [bx]
seg000:0125                 add     [check_sum], al
seg000:0129                 inc     bx
seg000:012A                 jmp     short loc_1011D
seg000:012C                 mov     bl, [check_sum]
seg000:0130                 and     bl, 0Fh         ; 8
seg000:0133                 mov     [check_sum], bl
Полученной контрольной суммой расшифровываются (0x248,0x27B), (0x1AA,0x1E1) (0x205,0x214) ниже скрипт для расшифровки:
sum = 0
for ea in range(0x10248,0x1027B):
   sum += Byte(ea)
sum &= 0xF

for ea in range(0x10248,0x1027B):
  PatchByte(ea,Byte(ea)^sum)
for ea in range(0x101AA,,0x101E1):
  PatchByte(ea,Byte(ea)^sum)
for ea in range(0x10205,0x10214):
  PatchByte(ea,Byte(ea)^sum)
Дальше выполнение программы вернется на 0x248, где в цикле с клавиатуры считывается 16 символьный пароль, считается сумма всех символов и этим ключем расшифровывается строка расположенная по адресу 0x02C0. Если сумма расшифрованной программы равно 0ADh, то мы выводим её.
seg000:0260 loc_10260:                              ; CODE XREF: sub_10248+23j
seg000:0260                 mov     bx, offset unk_102BE
seg000:0263                 add     bx, cx
seg000:0265                 xor     [bx+1], dh
seg000:0268                 add     dl, [bx+1]
seg000:026B                 loop    loc_10260
seg000:026D                 cmp     dl, 0ADh
seg000:0270                 jnz     short locret_10279
seg000:0272                 mov     ah, 9
seg000:0274                 mov     dx, 2BEh
seg000:0277                 int     21h             ; DOS - PRINT STRING
seg000:0277                                         ; DS:DX -> string terminated by "$"
Поскольку размер ключа у нам всего 1байт, а последний символ должен быть '$' мы можем перебрать все значения (на самом деле его можем просто вычислить) и найти верный ключ, которым расшифровав строку получим '90 |)33P3r$'

Идем дальше, смотрим прерывания int9, прерывание от клавиатуры. Что оно делает? Читает с контроллера прерываний сканкод нажатой клавишы, после этого суммирует значение первых 16 нажатых клавиш и вызывает функцию расшифровки второй для второй строки, расположеной по адресу 0x02CD.
seg000:01AA proc            decrypt_str2 near       ; CODE XREF: seg000:01DEp
seg000:01AA                                         ; DATA XREF: int_1C:loc_1014Bo
seg000:01AA                 mov     cx, 19h
seg000:01AD
seg000:01AD loc_101AD:                              ; CODE XREF: decrypt_str2+16j
seg000:01AD                 mov     bx, 2CDh
seg000:01B0                 add     bx, cx
seg000:01B2                 mov     al, [bx]
seg000:01B4                 xor     al, [key1]
seg000:01B8                 rol     al, cl          ; rol(b[i]^key,i)
seg000:01BA                 mov     [bx], al
seg000:01BC                 add     [key2], al
seg000:01C0                 loop    loc_101AD
seg000:01C2                 retn
seg000:01C2 endp            decrypt_str2
def decrypt(s,key):
   r = []
   for i in range(0x19,-1,-1):
      r.append( rol(s[i]^key,i%8) )
   return r

s = []
for ea in range(0x102CD,0x102CD+26):
   s.append(Byte(ea))

for i in range(256):
  a = map(chr,decrypt(s,i))
  if( a[0] == '$'):
    a.reverse()
    print "".join(a)
На выходе получаем строку: '\x19i11_i5_pr0|_|d_0|=_y0|_|$'
Однако, asm код не расшифровает первый символ строки, поэтому оставив его без изменений получим правильный ответ: Bi11_i5_pr0|_|d_0|=_y0|_|

About this Archive

This page is an archive of entries from March 2012 listed from newest to oldest.

February 2012 is the previous archive.

April 2012 is the next archive.

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