HackIM 2016 – donfos_reversing

Lâu lắm rồi mới quay lại chơi CTF, theo dự kiến thì thời gian tới sẽ chơi rất nhiều, nhưng sẽ chỉ chơi reverse thôi vì mình cảm thấy nó phần kiến thức cần phải thành thạo.

Đợt HackIM vừa rồi mình ngồi làm bài reverse cùng thằng em, hai anh em mất cả ngày trời cuối cùng cũng đã giải được bài donfos này. Cách làm phần cuối thì hơi ảo nhưng kệ ra được flag là mừng rồi.

Đề bài: re500 donfos_reversing

https://github.com/ctfs/write-ups-2016/tree/master/nullcon-hackim-2016/re/donfos-500

Đầu tiên, mình cũng down file về rồi vứt vào Ubuntu chạy thử, nó là file elf 64 bit. Khi chạy thì hỏi key, nhập lăng nhăng không ra flag gì cả. Giống như bài re300, mình strings thử thì biết được nó cũng dùng o-llvm để obfuscate code. Sau đó thì mình đem vào IDA để đọc code tĩnh trước. Nhưng mà bài này code hàm main nhìn phức tạp quá, xem chừng không thể ngồi đọc giả code với hợp ngữ được. Mình đoán là do hàm gốc đã phức tạp rồi cộng với việc dùng o-llvm để obfuscate nữa nên mới vậy.

graphview

Quyết định tạm dừng đọc trên IDA, mình quay ra tìm hiểu kĩ hơn về o-llvm và cách deobfuscate, xem thử xem có ai làm chưa. Nó có wiki tại đây https://github.com/obfuscator-llvm/obfuscator/wiki . Cái obfuscator này dựa trên llvm, một trình biên dịch open source – https://en.wikipedia.org/wiki/LLVM . Đại ý thì để dịch một chương trình C bằng llvm thì nó gồm ba pha: frontend, optimization passes và backend. (Xem hình ảnh minh họa).

llvm-compile

  • Pha frontend sẽ dịch mã nguồn sang ngôn ngữ trung gian (LLVM IR)
  • Pha optimization passes sẽ tối ưu hoặc làm gì đó (obfuscate chẳng hạn) với ngôn ngữ trung gian này
  • Pha backend dịch mã trung gian này sang mã hợp ngữ hoặc mã máy,… mình chắc chắn lắm, đại loại là mã chạy được cho một kiến trúc cpu cụ thể nào đó.

Đấy là llvm, còn o-llvm thì dựa trên llvm, nó sẽ tác động vào pha optimization, thực hiện obfuscate mã trung gian llvm IR bằng nhiều cách khác nhau chẳng hạn thêm một số lệnh linh tinh, hoặc thay thế các lệnh tương đương. Cụ thể thì nó gồm 3 kĩ thuật:

Mình có link tham chiếu ở đây https://github.com/obfuscator-llvm/obfuscator/wiki/Features , mình nghĩ nên đọc kĩ các kĩ thuật của nó trước. Có bạn này còn code hẳn một cái obfuscator mẫu, gần giống nhưng đơn giản o-llvm : https://github.com/0vercl0k/articles/blob/master/Obfuscation%20of%20steel%20meet%20Kryptonite.pdf . Mình cũng đọc bài này mới hiểu rõ hơn về o-llvm. Mình cũng chưa đọc hết, càng về sau thì nó càng khó hiểu + code nhiều nên mình chỉ đọc được ý tưởng chính.

Quay trở lại bài re ban đầu, nhìn sơ qua giả code hàm main thì thấy có nhiều vòng lặp while cộng với if else, một số đoạn đọc input với hiển thị flag thì dễ đọc.

obfuscator

Đoạn code đọc key từ stdin

if ( (_DWORD)status == 521280924 )// Read input from stdin
{
    __isoc99_scanf(4202605LL, &buf[index]);
    sprintf(s, "%d", (unsigned int)buf[index], status);
    strcat(flag, s);
    LODWORD(status) = 2142471201;
}

Đoạn code xử lý flag:


if ( (_DWORD)status == 772034162 )// change flag ki tu thu index
{
    flag[index] = *((_BYTE *)&v40 + 4 * index) & ~flag[index] | flag[index] & ~*((_BYTE *)&v40 + 4 * index);
    LODWORD(status) = 931089765;
}

Mình đoán ngay nó sử dụng kĩ thuật control flow flatten. Những khối while hoặc if else mục đích là để ẩn giấu đi những khối code gốc. Trong hàm luôn duy trì một biến status để điều khiển luồng thực thi.  Theo như thằng em bảo thì nó giống như một state machine trong đó status đóng vai trò là state, status sẽ thay đổi liên tục thông qua các khối if else hoặc while, cho tới khi vào được state chuẩn. Ví dụ hai đoạn code ở trên khi nào status bằng 521280924 thì sẽ nhảy vào đoạn code đọc key input, khi nào status bằng  772034162 thì sẽ nhảy vào đoạn code xử lý flag . Để mà lần theo toàn bộ state của hàm main này thì khá mết nên tạm thời mình vẫn chưa có định đọc giả code hàm main. Mình quay lại tìm kiếm google xem có bài nào tương tự hoặc có kĩ thuật deobfuscate nào không. Thì có tìm được một bài ở đây và cũng gần như là bài duy nhất tìm được http://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html . Thấy họ bảo sử dụng Miasm reverse engineering framework và kĩ thuật symbolic execution để deobfuscate. Họ có ví dụ và có phân tích ba kĩ thuật obfuscate của o-llvm, tuy nhiên bài viết hơi sâu, mình chưa thể nào hiểu được symbolic execution là gì? Quan trọng nữa cũng không có code đầy đủ ở bên dưới, ví dụ họ đưa ra cũng đơn giản. Mặc dù cuối bài họ có nói là cách làm này cũng hiệu quả ngay cả với chương trình lớn. Tiếc mỗi kiến thức hiện tại và thời gian không đủ để làm lại giống họ được. Thế mới thấy khi không có tools dùng sẵn thì nản như nào. Thời gian cứ trôi mà vẫn chưa thể nào tìm được giải pháp nào hay ho cả, search mãi cũng không ra được thêm gì, đọc lại mấy bài trước thì càng đọc càng khó hiểu.

Sau đó thì thằng em vứt cho mình link bảo thử sử dụng Pin tools vì có bài ctf gần giống cũng sử dụng cách này https://gist.github.com/yrp604/e61d4e08b7c13fdd5b63 . Ý tưởng là dựa vào việc đếm số lệnh thực thi ứng với kí tự đầu vào để bruteforce dần từng kí tự của key. Ý tưởng này khá hay nhưng không biết liệu có áp dụng được không? Điều kiện là chương trình phải kiểm tra key đầu vào theo từng kí tự của key, exit ngay nếu kí tự đó không đúng. Hai anh em có ngồi cái thử pin tools rồi chạy thử một số kí tự đầu vào, nhưng mà thấy không đúng lắm, cùng đầu vào là key ‘a’ mà số lệnh mỗi lần chạy khác nhau. Thử chán với một số các trường hợp khác cũng không được. Khả năng cao là thuật toán check key không phù hợp với điều kiện, có vẻ có yếu tố ngẫu nhiên gì đó hoặc phụ thuộc thời gian hệ thống,… mình cũng không biết nữa.

pintools

Bài toán lại quay trở lại bế tắc. Thôi mình đi đến quyết định đọc giả code hàm main, cũng không biết làm gì khác nữa.  Hàm main có một mảng để lưu trữ flag, có một mảng int buf[] để lưu trữ key đầu vào, biến index để dùng làm chỉ số truy cập mảng buf và mảng flag, một biến status để lưu trữ trạng thái, giá trị ban đầu của status là -1640168124.


*(_OWORD *)flag = 0LL;
printf("Enter Key:\n", argv);
index = 0;
LODWORD(status) = -1640168124; // INIT STATE
while ( 1 )
{.....

Công việc bây giờ là lần theo trạng thái của status để đi đến trạng thái tiếp theo bằng cách là search xem -1640168124 được dùng ở những chỗ nào? Ví dụ trạng thái tiếp theo ở đây


if ( (_DWORD)status == -1640168124 ) // kiem tra index
{
    v29 = 521280924;
    if ( index >= 16 )
    v29 = 1493801594;
    LODWORD(status) = v29;
}

Nó kiểm tra index có lớn hơn hoặc bẳng 16 không? Nếu >= thì chuyển sang trạng thái 1493801594, nếu nhỏ hơn thì chuyển sang trạng thái 521280924. Ban đầu thì index = 0 nên nó nhảy sang trạng thái 521280924. Trạng thái 521280924 như dưới đây:


if ( (_DWORD)status == 521280924 )// Read input from stdin, (number)
{
    __isoc99_scanf(4202605LL, &buf[index]);
    sprintf(s, "%d", (unsigned int)buf[index], status);
    strcat(flag, s);
    LODWORD(status) = 2142471201;
}

Nó đọc một số nguyên từ stdin, lưu vào buf[index], đồng thời viết lại số nguyên này dưới dạng không dấu vào mảng flag. Sau đó thì chuyển sang trạng thái 2142471201 :


if ( (_DWORD)status == 2142471201 ) // Tang index
{
    ++index;
    LODWORD(status) = -1640168124;
}

Đến đây thì lờ mờ hiểu được, hàm main sẽ lặp 16 lần để đọc 16 số nguyên lưu vào mảng buf. Key của bài toán sẽ là một dãy 16 số nguyên. Mình tiếp tục quá trình đọc giả code này, mặc dù khá tốn thời gian nhưng càng về sau thì càng quen nên đọc cũng nhanh hơn. Mình copy ra notepad++ rồi search theo các trạng thái để đi lần lượt chương trình. Đoạn này có lẽ tốn thời gian nhất. Cụ thể thì chương trình sau khi đọc xong 16 số, nó nhảy sang bước kiểm tra. Nó lặp qua 16 số nguyên trong mảng buf, gọi hàm check (sub_400780). Hàm check lần gọi thứ index sẽ nhận hai tham số đầu vào: tham số thứ nhất là buf[index] và tham số thứ hai là một số nguyên thuộc [0-15] phụ thuộc vào index.  Thứ tự check sẽ như sau


Thu tu check:
index=0 check(buf[index], 11)
index=1 check(buf[index], 12)
index=2 check(buf[index], 6)
index=3 check(buf[index], 7)
index=4 check(buf[index], 4)
index=5 check(buf[index], 13)
index=6 check(buf[index], 5)
index=7 check(buf[index], 0)
index=8 check(buf[index], 1)
index=9 check(buf[index], 15)
index=10 check(buf[index], 8)
index=11 check(buf[index], 14)
index=12 check(buf[index], 10)
index=13 check(buf[index], 2)
index=14 check(buf[index], 9)
index=15 check(buf[index], 3)

Mình có lưu lại địa chỉ của các lần gọi hàm check để tiện cho việc debug sau này


Check ki tu 2 401EBC
Check ki tu 15 401F24
Check ki tu 12 401E36
Check ki tu 9 401CBD
Check ki tu 14 401C85
Check ki tu 8 401BE6
Check ki tu 1 401AEB
Check ki tu 3 401A16
Check ki tu 4 4018C3
Check ki tu 11 401820
Check ki tu 0 4017AD
Check ki tu 6 401562
Check ki tu 13 4014A6
Check ki tu 5 401470
Check ki tu 10 401379
Check ki tu 7 40112E

Sau khi check okie xong hết (tức là check trả về giá trị 0) thì coi kiểm tra key xong. Hàm main tính toán, xử lý với mảng flag rồi in ra flag. Chỗ xử lý flag này mình không đọc kĩ được, lúc đó hơi loạn đầu rồi. Chỉ biết sơ sơ nó lặp qua mảng kí tự flag (chú ý là mảng flag hiện tại là chuỗi số không dấu của key, chẳng hạn key là dãy 1, 45, 33 thì mảng flag là “14533”) rồi tính toán như này


if ( (_DWORD)status == 772034162 )// change flag ki tu thu index
{
    flag[index] = *((_BYTE *)&v40 + 4 * index) & ~flag[index] | flag[index] & ~*((_BYTE *)&v40 + 4 * index);
    LODWORD(status) = 931089765;
}

Tóm lại thì mấu chốt ở đây là hàm check, với mỗi lần gọi check làm nào để mỗi lần gọi check, ta cần tìm được số nguyên đầu vào để check trả về giá trị 0. Vậy thì bước tiếp theo là cần hiểu được hàm check làm gì. Đây là giả code của nó trong IDA:


__int64 __fastcall check(unsigned int input, int index)
{
int i; // ST18_4@1
int v3; // ST10_4@1
int v4; // ST10_4@1
int v5; // eax@1
int v6; // ecx@1
int v7; // ST10_4@1
int v8; // er14@1
int v9; // er8@1
unsigned int v10; // er8@1
unsigned int v11; // er8@1
int v12; // eax@1
int v13; // esi@1

i = index;
v3 = func(input, 3);
v4 = -(-((unsigned __int64)func(input, 2)
* ((~globalBytes[4 * i] & 0x6EFBE94D | globalBytes[4 * i] & 0x910416B2) ^ (~globalBytes[4 * i + 3] & 0x6EFBE94D | globalBytes[4 * i + 3] & 0x910416B2)))
- v3);
v5 = func(input, 2);
v6 = globalBytes[4 * index + 3];
v7 = v5 * (v6 & ~globalBytes[1 - -4 * i] | globalBytes[1 - -4 * i] & ~v6) + v4;
v8 = 1 - -4 * index;
v9 = v7
+ (unsigned __int64)func(input, 2)
* ((~globalBytes[4 * i + 2] & 0x43B37B1 | globalBytes[4 * i + 2] & 0xFBC4C84E) ^ (~globalBytes[4 * i + 3] & 0x43B37B1 | globalBytes[4 * i + 3] & 0xFBC4C84E))
+ input
* ((~globalBytes[4 * i] & 0x59C0DD4B | globalBytes[4 * i] & 0xA63F22B4) ^ (~globalBytes[4 * i + 3] & 0x59C0DD4B | globalBytes[4 * i + 3] & 0xA63F22B4))
* ((~globalBytes[v8] & 0xBA0892CF | globalBytes[v8] & 0x45F76D30) ^ (~globalBytes[4 * i + 3] & 0xBA0892CF | globalBytes[4 * i + 3] & 0x45F76D30));
v10 = v9
+ input
* ((~globalBytes[2 - -4 * i] & 0xEFD60EC8 | globalBytes[2 - -4 * i] & 0x1029F137) ^ (~globalBytes[4 * i + 3] & 0xEFD60EC8 | globalBytes[4 * i + 3] & 0x1029F137))
* ((~globalBytes[4 * i + 1] & 0x6F5BEF81 | globalBytes[4 * i + 1] & 0x90A4107E) ^ (~globalBytes[4 * i + 3] & 0x6F5BEF81 | globalBytes[4 * i + 3] & 0x90A4107E));
v11 = v10
+ input
* ((~globalBytes[4 * i] & 0x50C0D70D | globalBytes[4 * i] & 0xAF3F28F2) ^ (~globalBytes[4 * i + 3] & 0x50C0D70D | globalBytes[4 * i + 3] & 0xAF3F28F2))
* (globalBytes[4 * i + 3] & ~globalBytes[4 * i + 2] | globalBytes[4 * i + 2] & ~globalBytes[4 * i + 3]);
v12 = globalBytes[4 * index + 3];
v13 = ~globalBytes[4 * index + 3];
return v11
+ (v12 & ~globalBytes[4 * i] | v13 & globalBytes[4 * i]) * ((~globalBytes[4 * i + 1] & 0x73E6034A | globalBytes[4 * i + 1] & 0x8C19FCB5) ^ (~globalBytes[4 * i + 3] & 0x73E6034A | globalBytes[4 * i + 3] & 0x8C19FCB5)) * ((~globalBytes[4 * i + 2] & 0xAC23EFC0 | globalBytes[4 * i + 2] & 0x53DC103F) ^ (v13 & 0xAC23EFC0 | v12 & 0x53DC103F));
}

Hàm func (sub_400670) được gọi nhiều lần trong hàm check. Hàm func cũng bị obfuscate theo kĩ thuật control flow flatten, tuy nhiên do kích thước nó nhỏ nên mình có thể đọc được không quá khó khăn,cộng với việc vứt giả code vào codeblocks chạy thử với một số input thì xác nhận được hàm func chỉnh là hàm pow – tính lũy thừa. Như vậy trong hàm check có tính lũy thừa bậc ba ( func(input, 3) ) và lũy thừa bậc hai của input ( func(input, 2) ). Tuy vậy, đọc mãi thì mình vẫn chưa thể nào hiểu được rốt cuộc hàm check làm cái quái gì. Một đống các phép toán ^ & không hiểu có ý nghĩa gì. Không hiểu được rất khó tính ngược lại được. Mình copy giả code vứt vào codeblocks để chạy thử xem input,output như thế nào. Tuy nhiên kết quả chạy giả code bị sai so với lúc debug , mình có debug chỗ check lần đầu tiên trong chương trình để lấy một kết quả mẫu, làm đi làm lại kết quả chạy trên codeblocks vẫn không đúng. Mình đã nghi nghi IDA decompile bị sai, quay sang đọc hợp ngữ, đọc hợp ngữ thì không thấy được decompile sai ở chỗ nào. Không code lại được hàm check bằng C, đang có ý tưởng vứt code hợp ngữ sang nhúng vào C để gọi, nhưng vì kĩ thuật code hợp ngữ tù quá nên hơi ngại. Mình quay lại đọc giả code hàm check để xem có hiểu thêm được ít gì không. Ngồi loanh hoay mãi thì mình nhớ lại cái tools obfuscate o-llvm ban đầu. Mình nhận thấy trong hàm check có khá nhiều đoạn code giống nhau kiểu như này

  1. (a & ~b | ~a & b) ví dụ (v6 & ~globalBytes[1 – -4 * i] | globalBytes[1 – -4 * i] & ~v6) hoặc
  2. (~a & 0xee | a & 0xdd) ^ (~b & 0xee | b & 0xdd) ví dụ ((~globalBytes[4 * i + 2] & 0x43B37B1 | globalBytes[4 * i + 2] & 0xFBC4C84E) ^ (~globalBytes[4 * i + 3] & 0x43B37B1 | globalBytes[4 * i + 3] & 0xFBC4C84E))

Những đoạn code này mình cảm giác như code linh tinh nào đó do o-llvm thêm vào. Có vẻ như một phép toán nào đó đơn giản nhưng bị o-llvm thay thế bằng những phép toán phức tạp tương đương. Đọc lại kĩ thuật Instructions Substitution của o-llvm ( https://github.com/obfuscator-llvm/obfuscator/wiki/Instructions-Substitution)  đúng như mình dự đoán cái phép toán số 1 ở trên bản chất là a^b , phép toán số 2 cũng là a^b. Mình đã code thử lại trên codeblocks để verify lại điều này. Như vậy, bước tiếp theo ta cần đơn giản hóa hàm check lại. Chỗ này khá thú vị, mình cũng làm bằng tay thôi, đầu tiên đặt mấy cái biến global cho dễ tham chiếu


int g0 = globalBytes[4 * i];
int g1 = globalBytes[4 * i+1];
int g2 = globalBytes[4 * i+2];
int g3 = globalBytes[4 * i+3];

Sau đó rút gọn dần, mình chỉ viết kết quả cuối cùng, hàm check rất đơn giản là như này


int check(unsigned int input, int i)
{
    int g0 = globalBytes[4 * i];
    int g1 = globalBytes[4 * i+1];
    int g2 = globalBytes[4 * i+2];
    int g3 = globalBytes[4 * i+3];

    int a = g0^g3;
    int b = g1^g3;
    int c = g2^g3;
    //return func(input,3) + func(input,2) *(a+b+c) + input *(ab + bc + ca) + abc;
    // (x+a)(x+b)(x+c)
    return (input+a)*(input+b)*(input+c);
}

Hóa ra hàm này chính là công thức toán học quen thuộc, nói chính xác thì là một đa thức bậc ba. Đến đây thì dễ thấy cần truyền input là -a hoặc -b hoặc -c thì check sẽ return 0. a,b,c sẽ được tính từ mảng toàn cục globalBytes dựa theo tham số thứ hai i. Mình code thử lại check trên codeblocks để verify lại hàm này. Kết quả vẫn không khớp với những gì debug, hơi ảo. Mình kiểm tra lại thì phát hiện ra mình copy globalBytes sai, đúng là ngớ ngẩn. Lúc đầu mình tưởng globalBytes là mảng byte nên sử dụng lệnh này trong IDAPython để lấy [idc.Byte(0x603060 + i) for i in range(64)]. Đúng là sai một li đi một dặm, lấy chuẩn globalBytes, giả code của IDA chạy chuẩn luôn. Đến đây thì mình đã xác định được đầu vào hàm check hợp lệ. Nhưng vấn đề là mỗi lần check có 3 giá trị input thỏa mãn cho check == 0. Thế thì biết chọn input nào. Nếu bruteforce thì cần 3^16 lần chạy chương trình vì có 16 lần check. Đó cũng là một con số không hề nhỏ.

Mình quay lại xem xét hàm main. Chẳng lẽ lại có nhiều key thỏa mãn, nhưng mà flag lại được tính theo key, như thế chẳng lẽ có nhiều flag. Rõ ràng là mình đã reverse thiếu đoạn sau khi gọi các hàm check. Hình như trước khi xử lý flag, hàm main có kiểm tra gì thêm nữa, đọc  một hồi thì thấy nó xor tất cả dãy số input đầu vào với nhau và kiểm tra giá trị này có bằng 0 hay không. Mà hình như vẫn chưa hết, vẫn còn kiểm tra gì khác nữa. Đến đây thì mình ngai đọc tiếp quá rồi. Mình quay ra thử input bằng tay.

Trước hết liệt kê đầu vào có thể cho mỗi lần check

Index 0 : 4     45    72   
Index 1 : -93   -86   8    
Index 2 : -13   31    51   
Index 3 : -47   -9    8   
Index 4 : -54   2    29   
Index 5 : -37   10    24   
Index 6 : 0     41    42 
Index 7 : -6    3     27   
Index 8 : 10    54    60   
Index 9 : -40   42    73   
Index 10 : -35   -1    31 
Index 11 : 20    28    92   
Index 12 : -48   19     90   
Index 13 : 19     32    96   
Index 14 : -66   20    93   
Index 15 : -39   2     31

Để ý lại chỗ xử lý flag, và search trong hàm main cũng chỉ có chỗ này xử lý flag:


if ( (_DWORD)status == 772034162 )// change flag ki tu thu index
{
    flag[index] = *((_BYTE *)&v40 + 4 * index) & ~flag[index] | flag[index] & ~*((_BYTE *)&v40 + 4 * index);
    LODWORD(status) = 931089765;
}

Công thức phía trên bị obfuscate, công thức chuẩn là:


flag[index] = flag[index] ^ *((char*)v40[4*i];

Đơn giản là nó xor từng phần tử flag với từng phần tử của v40. Mà flag lại là kí tự printable. v40 mình cũng extract ra được. Quyết định thử làm bằng tay với một số input đầu tiên. Cách làm là với mỗi lần check, có 3 input thỏa mãn, thử lần lượt từng input, đem xor với mảng v40 tương ứng, nếu kết quả là các kí tự printable thì hợp lệ. Ở đây, input mình chỉ lấy số dương, vì nếu số âm, khi in số không dấu sẽ rất lớn, chẳng hạn -1 sẽ tương ứng 4294967295, khi đó chuỗi flag sẽ rất dài. Trong chương trình, flag chỉ dài 26 kí tự (vòng lặp xử lý flag kiểm tra index >= 26 thì dừng). Mình làm thử với 5 kí tự đầu thì thấy xuất hiện  d0n_f0  , chuẩn rồi đúng tên đề bài luôn. Cách làm này không hay lắm nhưng khả thi để ra flag. Đây là phần làm của mình:


Index 0 : 4(ok)     45    72   
Index 1 : -93   -86   8 (ok)   
Index 2 : -13   31 (ok)    51   
Index 3 : -47   -9    8 (ok)   
Index 4 : -54   2 (ok)    29   
Index 5 : -37   10 (ok)    24   
Index 6 : 0     41    42 (ok)  
Index 7 : -6    3 (ok)     27   
Index 8 : 10 (ok)    54    60   
Index 9 : -40   42 (ok)   73   
Index 10 : -35   -1    31 (ok)  
Index 11 : 20 (ok)    28    92   
Index 12 : -48   19 (ok)    90   
Index 13 : 19 (ok)    32    96   
Index 14 : -66   20    93   
Index 15 : -39   2     31

[80, 8, 93, 110, 94, 2, 4, 89, 90, 85, 108, 0, 5, 107, 6, 65, 69, 109, 68, 1, 102, 93, 10,      6, 66, 92]
4    8   31       8  2    10    42      3    10     42       31      20     19      19           20     2
d    0   n   _    f  0   5  i  n   g   _     15       _4    r  t    _t     0_       l3           4r     n

d0n   _    f  0   5  i  n   g   _     15       _4    r  t    _t     0_       l3           4r     n

Dãy số key cần tìm là :


4    
8   
31       
8  
2    
10    
42      
3    
10     
42       
31      
20     
19      
19           
20     
2

Thủ lại với chương trình:

flag

Đến giờ vẫn chưa hiểu flag này có ý nghĩa gì :). Thế là hết một ngày chủ nhật, hai anh em cũng may là làm ra trước 12h để còn được đi ngủ sớm.

Sau bài này thì mình rút ra: cần phải học lại hợp ngữ nhiều hơn, không thì sẽ rất ngại đọc hợp ngữ từ đó dẫn tới công việc reverse khá nhàm chán thêm nữa những bài viết như kcrypnite hay deobfuscate ở trên rất khó đọc, cái ý tưởng deobfuscate theo blog chắc sau này mới có thể xem lại được. Rồi thì llvm với o-llvm nữa, mình hơi tiếc vì ngày xưa học môn compiler rất lớt phớt, trong khi các bạn cùng lớp hoàn thành project compiler hoàn chỉnh còn mình thì bỏ giữa chừng.

One thought on “HackIM 2016 – donfos_reversing”

Leave a Reply

Your email address will not be published. Required fields are marked *