Saturday, August 8, 2015

[WhiteHat contest 10] Re400 writeup

Trap, trap everywhere.....
Thực sự đối với bài này mình cũng không biết nên đánh giá khó hay dễ. Dễ vì thực ra thuật toán check flag 'thực sự' rất đơn giản (chỉ vài dòng code là ra). Khó vì nếu đi sai 1 hướng thì sẽ mất rất nhiều thời gian để thoát ra khỏi mớ bòng bong. Do đi sai đường nên mình phải mất tới 2 ngày mới solved được nó.
Bài này sử dụng khá nhiều kỹ thuật anti debug và nên dùng song song cả ida và ollydbg 2 để debug. (Vì sao lại là ollydbg2 mà không phải là v1 mình sẽ nói ở bên dưới). Đồng thời do bài này nếu giải thích kỹ sẽ rất dài nên một số đoạn mình sẽ bỏ qua (thực ra là do lười writeup kỹ). :D. Chỉ tập chung chính vào cách check key của bài này.
Lòng vòng nhiều quá, bắt đầu nào.
Load vào IDA, ngay ở đoạn đầu tiên của hàm main:
if ( argc > 0 && !_stricmp("Reverse", *argv) )
  {
    sub_D31A90();
    return 0;
  }
 Mục đích của đoạn này là sẽ chạy sub_D31A90() nếu tên tham số argv = "Reverse". Khi chạy một file thì tham số truyền vào argv sẽ là đường dẫn đến file đó, trong khi argv ở đây lại không có đường dẫn? Có 1 cách để làm việc này, ở ngay đoạn tiếp theo
if ( !sub_D31870() )
  {
    if ( !CreateProcessA(&Filename, "Reverse", 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation) )
      printf("CreatePrcess Error");
    v5 = 0;
    do
    {
      byte_D41044[v5] ^= 0x11u;
      ++v5;
    }
 Bỏ qua sub_D31870() đoạn này chỉ nhằm mục đích xem file đã đang chạy hay chưa.
CreateProcessA(&Filename, "Reverse", 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation) 
Hàm này sẽ sinh ra một process mới,argv = "Reverse".  Và tất nhiên process này sẽ run
sub_D31A90(); Check sâu vào trong hàm này, khá dài dòng nhưng mục đích chính là process mới tạo ra sẽ lắng nghe trên cổng 8888 localhost, sau khi nhận được dữ liệu sẽ tiến hành check. Để capture các gói tin loopback bạn có thể dùng công cụ Rawcap.
Antidebug: Trong hàm này thực hiện 2 kỹ thuật antidebug: GetTickCount và CheckRemoteDebuggerPresent (các bạn hãy hỏi anh gồ để biết thêm thông tin về 2 kỹ thuật này, riêng đối với CheckRemoteDebuggerPresent thì chỉ có tác dụng khi sử dụng win XP).
Khi debug file này bằng IDA và ollydbg1 sẽ không quan sát và debug được process con mới tạo ra, ollydbg2 thì đã hỗ trợ sẵn (Vào options-> debugging->events, check chọn Debug child process). Sau hàm createprocessA một cửa sổ ollydbg mới sẽ xuất hiện
Trở về với process chính của chương trình:
scanf("%500[^\n]s", byte_D41B90);
 if ( v6 >= 7 && !strncmp(byte_D41B90, "FLAG{", 5u) && byte_D41B8F[v6] == '}' )
    {
      memcpy(&byte_D41D88, &unk_D41B95, v6 - 6);
      if ( strlen(&byte_D41D88) <= 40 )

     {
         v8 = sub_D31470();
-> Nhập vào key với định dạng: FLAG{xxxx....}, len <=40, chỉ copy nguyên 'xxxx...' (mình tạm gọi là S) vào byte_D41D88, nếu đúng định dạng thì sẽ chạy tiếp func sub_D31470() để kiểm tra. Func này mục đích là kiểm tra các ký tự bên trong cặp {} phải nằm từ [0-9], và [A-F] [a-f]
if ( v9 == (strlen(&byte_D41D88) >> 1) - 1 && *(dword_D41B54 + v9) != '_' )
S được chia thành từng cặp 2 ký tự. Cặp ký tự cuối của S =5F
if ( v10 == 2596 )
dword_D41B50 = sub_D31960();
 v13 = CreateThread(0, 0, StartAddress, 0, 0, 0);

WaitForSingleObject(v13, 0xFFFFFFFF);
Ví dụ S = 011F -> v10 = 0x01 + 0x1F
sub_D31960(): Từ cặp trong S sẽ được xor với len(S)/2
 v13 = CreateThread(0, 0, StartAddress, 0, 0, 0);
-> Trong StartAddress chứa 2 kỹ thuật antidebug nữa, mời các bạn đọc thêm phần openprocess và Parent processes ở đây
if ( _stricmp(&byte_D41D88, "Whitehat_Contest_Is_Amazing") && sub_D31700() )
Trap1 -> đoạn này không có tác dụng gì, chỉ có mục đích đánh lừa
if ( dword_D42174 != dword_D41F7C && sub_D31730(strlen(&byte_D41D88)) )
Trap2 -> khi không bị debug thì dword_D42174 == dword_D41F7C và việc kiểm tra sub_D31730 là vô nghĩa
 CreateThread(0, 0, sub_D319A0, &Parameter, 0, 0);
Tạo ra một thread mới, đầu vào chính là S (sau khi đã xor )
Sub_D319A0:
 v1 = lpThreadParameter;
  operator new(8u);
  WSAStartup(0x202u, &WSAData);
  v2 = socket(2, 1, 6);
  name.sa_family = 2;
  *&name.sa_data[2] = inet_addr("127.0.0.1");
  *&name.sa_data[0] = htons(0x22B8u);
  if ( connect(v2, &name, 16) )
  {
    do
    {
      Sleep(0x1388u);
      connect(v2, &name, 16);
    }
    while ( connect(v2, &name, 16) );
    v1 = lpThreadParameter;
  }
  send(v2, *v1, *(v1 + 1), 0);
Connect tới cổng 8888 của localhost và send S lên đó.
Process được tạo ra ngay đầu tiên sẽ xử lý S. Khá dài dòng và khá nhiều trap nữa (với mục đích giả vờ check key).
Tương tự tại process chính cũng có khá nhiều đoạn giả check key (xuất hiện các string giả).
When you have eliminated the impossible whatever remains, however improbable, must be the truth. - Shelock Homes.
Và cuối cùng đây là thuật toán tạo key.
final =[0x41,0x17,0x25,0xC7,0xCB,0x55,0x5F,0x9B,0xC7,0x7C,0xC2,0xD6,0x7C,0x12,0xAF,0x80]
key =[0]*16
key[15] = 0x4F
flag ="FLAG{"
for i in xrange(len(key)-2,-1,-1):
    key[i] = final[i] ^ key[i+1]
key = list(map((lambda x:x^0x10),key))
for i in key:
    flag +=hex(i)[2:]
flag += "}"
print flag.upper()