0day in {REA_TEAM}

Phân tích RCA crackme bằng Olly và IDA+HexRays September 11, 2008

Trên REA lão RCA có code một cái crackme nhỏ và ra đề như sau :

+ Crackme level 1.1 by Rongchaua.
- Level : Very very easy.
- Aim :
. Thực tập cách set breakpoint.
. Thực tập patch.
. Mã Ascii.

Rảnh rỗi ngồi phân tích thử xem thế nào!

Dùng PeID check thử, hú hồn may quá lão Rồng nhà ta không pack, thoát khỏi bước đầu tiên. Đọc Rulz và run thử Crackme thì thấy nút Checkit bị Disable mất tiêu. Soi lại list các hàm API thường dùng cho Window thì thấy có em EnableWindow (đây mới chỉ là nghi thôi)

The EnableWindow function enables or disables mouse and keyboard input to the specified window or control. When input is disabled, the window does not receive input such as mouse clicks and key presses. When input is enabled, the window receives all input.

BOOL EnableWindow(
HWND hWnd, // handle to window
BOOL bEnable // flag for enabling or disabling input
);

Parameters
hWnd
Identifies the window to be enabled or disabled.
bEnable
Specifies whether to enable or disable the window. If this parameter is TRUE, the window is enabled. If the parameter is FALSE, the window is disabled.

Okie đã có thông tin cho sol đầu tiên “Patch to enable the button”, load crackme vào trong Olly. Tại Olly , chuột phải và chọn Search all intermodular calls, ta sẽ thấy được một list các danh sách API mà lão Rồng dùng :

Found intermodular calls
Address Disassembly Destination
00401000 Cr PUSH 0 (Initial CPU selection)
004010FE CALL user32.CreateDialogParamA
00401035 CALL user32.DialogBoxParamA
0040107D CALL user32.EnableWindow
0040108C CALL user32.EnableWindow
004011D5 CALL user32.EnableWindow
004011EF CALL user32.EnableWindow
00401113 CALL user32.EndDialog
00401125 CALL user32.EndDialog
0040123C CALL user32.EndDialog
0040103B CALL kernel32.ExitProcess
0040106C CALL user32.GetDlgItem
004011C8 CALL user32.GetDlgItem
004011E2 CALL user32.GetDlgItem
004010BA CALL user32.GetDlgItemTextA
004010D1 CALL user32.GetDlgItemTextA
00401002 CALL kernel32.GetModuleHandleA
00401017 CALL user32.LoadIconA
0040118C CALL kernel32.lstrcatA
004011A3 CALL kernel32.lstrcmpA
0040115F CALL kernel32.lstrlenA
004011BB CALL user32.MessageBoxA
00401205 CALL user32.MessageBoxA
00401140 CALL ntdll.RtlZeroMemory
0040114F CALL ntdll.RtlZeroMemory
0040105F CALL user32.SendMessageA
0040122A CALL user32.SendMessageA
0040117A CALL user32.wsprintfA

_Ái chà có tới 4 hàm EnableWindow lận, vậy là không còn nghi ngờ gì nữa rồi.Chuột phải vào 1 hàm EnableWindow bất kì và chọn Set BP on every call to EnableWindow. Kết thúc quá trình Set BP, bây h nhấn F9 để Run cracke. Bụp, Olly đã break tại 1 hàm EnableWindow và đây chính là hàm cần Patch. Lý do tại sao tôi khẳng định được là vì khi form crackme của lão Rồng được load lên thì hàm EnableWindow sẽ được load theo để Disable cái nút nhấn Checkit :

00401087 |> \6A 00 PUSH 0 ; /Enable = FALSE <== Patch here
00401089 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] ; |hWnd
0040108C |. E8 D3010000 CALL ; \EnableWindow <== Break here

_Dịch lên 1 chút ta thấy PUSH 0, và thấy Olly comment bên cạnh. Do đó ta sẽ patch lại thành PUSH 1 . (0 và 1 ở đây thực chất là các cờ dùng cho việc set disable hoặc enable)

_Patch xong save lại và Run .. Done nút Checkit đã được Enable lên. Vậy là đã giải quyết được bài tập thứ nhât!

_Tiếp theo là đến bài tập tìm Real Serial. Như trong danh sách các APIs mà lão Rồng dùng ở trên, chúng ta để ý thấy có hàm GetDlgItemTextA. Check lại info về hàm này :

The GetDlgItemText function retrieves the title or text associated with a control in a dialog box.
UINT GetDlgItemText(
HWND hDlg, // handle of dialog box
int nIDDlgItem, // identifier of control
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum size of string
);

_Okie vậy là ta set 1 BP tại GetDlgItemTextA. Sau đó nhấn F9 để run Crackme và nhập thông tin về UserName và Fake Serial vào. Cuối cùng nhấn CheckIt, ta sẽ break tại đây trong Olly :

004010A8 |. 68 00010000 PUSH 100 ; /Count = 100 (256.)
004010AD |. 68 68304000 PUSH CrackMe.00403068 ; |Buffer = CrackMe.00403068
004010B2 |. 68 EC030000 PUSH 3EC ; |ControlID = 3EC (1004.)
004010B7 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004010BA |. E8 B7010000 CALL ; \GetDlgItemTextA <== Break here

Tại cửa sổ Stack :

0012FB1C 000C019E |hWnd = 000C019E ('CrackMe Level 1.1 by Rongchaua',class='#32770')
0012FB20 000003EC |ControlID = 3EC (1004.)
0012FB24 00403068 |Buffer = CrackMe.00403068
0012FB28 00000100 \Count = 100 (256.)

_UserName nhập vào của chúng ta sẽ được lưu tại Buffer trên. F8 để trace qua hàm , tại cửa sổ Dump ta có :

00403068  6B 69 65 6E 6D 61 6E 6F 77 61 72 00 00 00 00 00  kienmanowar.....

_Tương tự với hàm tiếp theo ta cũng có được kết quả FakeSerial được lưu vào một Buffer khác :

00403368  31 31 31 31 31 39 38 32 00 00 00 00 00 00 00 00  11111982........

_Sau khi Trace xong ta tới đây :


004010D6 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /Arg1
004010D9 |. E8 52000000 CALL CrackMe.00401130 ; \CrackMe.00401130 <== Trace Into

_ Trace Into vào hàm Call ở trên, ta sẽ tới quá trình tính toán sinh ra Real Serial.

00401130 /$ 55 PUSH EBP
00401131 |. 8BEC MOV EBP,ESP
00401133 |. 83C4 F8 ADD ESP,-8
00401136 |. 68 00010000 PUSH 100 ; /Length = 100 (256.)
0040113B |. 68 68314000 PUSH CrackMe.00403168 ; |Destination = CrackMe.00403168
00401140 |. E8 55010000 CALL ; \RtlZeroMemory
00401145 |. 68 00010000 PUSH 100 ; /Length = 100 (256.)
0040114A |. 68 68324000 PUSH CrackMe.00403268 ; |Destination = CrackMe.00403268
0040114F |. E8 46010000 CALL ; \RtlZeroMemory
00401154 |. BE 68324000 MOV ESI,CrackMe.00403268 ; ASCII "6b"
00401159 |. BF 68304000 MOV EDI,CrackMe.00403068 ; <== FU
0040115E |. 57 PUSH EDI ; /String => "kienmanowar"
0040115F |. E8 48010000 CALL ; \lstrlenA
00401164 |. 8BD8 MOV EBX,EAX ; <== Len = Length(User Name)
00401166 |. 33C0 XOR EAX,EAX ; <== i == 0
00401168 |> 50 /PUSH EAX ; <== i
00401169 |. 53 |PUSH EBX ; <== Len
0040116A |. 57 |PUSH EDI ; <== FU
0040116B |. 0FBE0C38 |MOVSX ECX,BYTE PTR DS:[EAX+EDI] ; <== Temp = FU [i]
0040116F |. 51 |PUSH ECX ; /<%x>
00401170 |. 68 59304000 |PUSH CrackMe.00403059 ; |Format = "%x"
00401175 |. 68 68314000 |PUSH CrackMe.00403168 ; |s = CrackMe.00403168
0040117A |. E8 D3000000 |CALL ; \wsprintfA
0040117F |. 83C4 0C |ADD ESP,0C
00401182 |. 68 68314000 |PUSH CrackMe.00403168 ; /StringToAdd = "69"
00401187 |. 68 68324000 |PUSH CrackMe.00403268 ; |ConcatString = "6b"
0040118C |. E8 0F010000 |CALL ; \lstrcatA
00401191 |. 5F |POP EDI
00401192 |. 5B |POP EBX
00401193 |. 58 |POP EAX
00401194 |. 40 |INC EAX ; <== i++
00401195 |. 3BC3 |CMP EAX,EBX ; <== While i < Len
00401197 |.^ 7C CF \JL SHORT CrackMe.00401168 ; <== Continue
00401199 |. 68 68334000 PUSH CrackMe.00403368 ; /String2 = "11111982"
0040119E |. 68 68324000 PUSH CrackMe.00403268 ; |String1 = "6b"
004011A3 |. E8 FE000000 CALL ; \lstrcmpA
004011A8 |. 0BC0 OR EAX,EAX
004011AA |. 75 4A JNZ SHORT CrackMe.004011F6
004011AC |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004011AE |. 68 00304000 PUSH CrackMe.00403000 ; |Title = "Reverse Engineering Association"
004011B3 |. 68 20304000 PUSH CrackMe.00403020 ; |Text = "Congratulation! You've done with it"
004011B8 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004011BB |. E8 C2000000 CALL ; \MessageBoxA
004011C0 |. 68 EC030000 PUSH 3EC ; /ControlID = 3EC (1004.)
004011C5 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011C8 |. E8 A3000000 CALL ; \GetDlgItem
004011CD |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
004011D0 |. 6A 00 PUSH 0 ; /Enable = FALSE
004011D2 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] ; |hWnd
004011D5 |. E8 8A000000 CALL ; \EnableWindow
004011DA |. 68 ED030000 PUSH 3ED ; /ControlID = 3ED (1005.)
004011DF |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011E2 |. E8 89000000 CALL ; \GetDlgItem
004011E7 |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX
004011EA |. 6A 00 PUSH 0 ; /Enable = FALSE
004011EC |. FF75 F8 PUSH DWORD PTR SS:[EBP-8] ; |hWnd
004011EF |. E8 70000000 CALL ; \EnableWindow
004011F4 |. EB 14 JMP SHORT CrackMe.0040120A
004011F6 |> 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004011F8 |. 68 00304000 PUSH CrackMe.00403000 ; |Title = "Reverse Engineering Association"
004011FD |. 68 44304000 PUSH CrackMe.00403044 ; |Text = "No,no! Try it again!"
00401202 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
00401205 |. E8 78000000 CALL ; \MessageBoxA
0040120A |> C9 LEAVE
0040120B \. C2 0400 RETN 4

_ Tóm tắt đoạn code này như sau :
Đây là một vòng lặp dùng để chuyển từng kí tự trong chuỗi user name mà chúng ta nhập vào sang dạng mã Hexa của kí tự đó ( ví dụ : k — 0×6b). Sau quá trình trace khỏi vòng lặp ta được kết quả như sau :

EAX 0000000B <== length(FU)
ECX 77E74BAE kernel32.77E74BAE
EDX 00000003
EBX 0000000B
ESP 0012FB18
EBP 0012FB20
ESI 00403268 ASCII "6b69656e6d616e6f776172" <== after converted
EDI 00403068 ASCII "kienmanowar" <== String to convert

_Chuỗi sau khi tính toán sẽ được đem đi so sành với Serial mà chúng ta nhập vào. Đúng thì Ok, còn sai thì đi bụi

00401199 |. 68 68334000 PUSH CrackMe.00403368 ; /String2 = "11111982"
0040119E |. 68 68324000 PUSH CrackMe.00403268 ; |String1 = "6b69656e6d616e6f776172"
004011A3 |. E8 FE000000 CALL ; \<== Compare two Strings

Kết quả cuối cùng :

User Name : kienmanowar
Serial    : 6b69656e6d616e6f776172

Phần tiếp theo là dùng IDA+HexRays :

Cái tên HexRays sau khi xuất hiện trên site của lão Ilfak Guilfanov (http://www.hexblog.com/) đã làm cho giới RE kinh thiên động địa mặc dù lúc đó nó mới chỉ là trong ý tưởng.Đến sau này khi lão release sản phầm và public trên một site khác (http://www.hex-rays.com/) càng làm cho “giang hồ” thèm muốn. Nhưng cái giá mà lão đưa ra để có được HexRays thì quá chua chát : “A single user license of the Hex-Rays Decompiler is 2299USD (1500EUR)”. Với số tiến như thế này thì có mà thách đố, chắc đi cày đến hết đời mới mua nổi em nó về dùng. Cũng có một số team có ý định và đã bỏ tiền ra mua HexRays về sử dụng…. nhưng rồi đến một ngày, một ngày mà cả giới RE phải online để download hàng. Đó chỉnh là hôm YAG Team public hexrays cho bà con download….. Hơ hơ nhưng mà tool xịn có trong tay rồi, giờ dùng sao ta ? Không lẽ down về để đó ngó chơi…… hơn 2 ngàn $ chứ có ít đâu . Hôm nay tập dùng HexRays bèn lấy em crackme của lão Rồng ra vọc thử .. Thấy đại ca Còm dùng HexRays hay quá nên ráng đua đòi ..nhưng cũng chỉ dừng ở mức cưỡi ngựa xem hoa chứ chưa dám chạy trước . Và đây là kết quả cuối cùng sau một hồi ngồi phân tích bằng IDA + HexRays :

int __stdcall DialogFunc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
HWND v5; // eax@2
HWND hWnd; // [sp+0h] [bp-4h]@2

switch ( a2 )
{
case 0x110u:
SendMessageA(a1, 0x80u, 1u, lParam);
v5 = GetDlgItem(a1, 1006);
hWnd = v5;
if ( v5 )
EnableWindow(hWnd, FALSE); // disable checkit button
else
EnableWindow(hWnd, TRUE);
break;
case 0x111u:
switch ( a3 )
{
case 0x3EEu:
GetDlgItemTextA(a1, 1004, szName, 256);
GetDlgItemTextA(a1, 1005, szSerial, 256);
convert_szName_to_hex(a1);
break;
case 0x3F0u:
CreateDialogParamA(hInstance, (LPCSTR)0x3F1, a1, sub_40120E, 0);
break;
case 0x3EFu:
EndDialog(a1, 0);
break;
}
break;
case 0x10u:
EndDialog(a1, 0);
break;
}
return 0;
}

void __stdcall convert_szName_to_hex(HWND hDlg)
{
int i; // eax@1
int iLen; // ebx@1
int iIndex; // ST14_4@2
HWND v4; // eax@4
HWND v5; // eax@4

RtlZeroMemory(szBuf, 256); // Set a block of memory with 0's.
RtlZeroMemory(szHex, 256);
iLen = lstrlenA(szName);
i = 0;
do
{
iIndex = i;
wsprintfA(szBuf, "%x", szName[i]); // convert szName[i] to Hexa and store in a szBuf
lstrcatA(szHex, szBuf); // append szBuf to szHex
i = iIndex + 1;
}
while ( iIndex + 1 < iLen );
if ( lstrcmpA(szHex, szSerial) ) // compare szHex with szSerial
{
MessageBoxA(hDlg, "No,no! Try it again!", "Reverse Engineering Association", 0);
}
else
{
MessageBoxA(hDlg, "Congratulation! You've done with it", "Reverse Engineering Association", 0);
v4 = GetDlgItem(hDlg, 1004);
EnableWindow(v4, 0);
v5 = GetDlgItem(hDlg, 1005);
EnableWindow(v5, 0);
}
}

 

Dùng thử IDA 5.2 và HexRays September 10, 2008

Filed under: Dùng thử IDA 5.2 và HexRays, IDA Tutorials — kienmanowar @ 7:59 am

Download được IDA 5.2 và HexRays rồi mà mãi đến hôm nay mới có thời gian ngồi “nghịch”… từ trước đến nay thỉnh thoảng vẫn vọc IDA, nhưng mỗi lần load target để IDA analyze xong là “choáng”. Choáng vì khả năng analyze của IDA kinh quá, choáng vì khi nhìn vào kết quả mà IDA phân tích chỉ có nước hoa mày chóng mặt.Choáng vì Yag Team giang hồ quá , thế nên anh em mới có hàng mà dùng !! Chưa hết choáng với IDA thì lại choáng với HexRays, có câu “nghề chơi cũng lắm công phu”. Các công cụ hàng đầu có trong tay nhưng mà không biết dùng thì cũng đến nước mà vứt vào sọt rác, hoặc cùng lắm là chỉ “cưỡi ngựa xem hoa”. Nếu như biết xài IDA “đúng cách và có nghệ thuật” thì HexRays đúng là trợ thủ đặc lực trong việc xây dựng lại “mã giả” khiến cho đoạn code trở nên rõ ràng và dễ đọc hơn. Tuy nhiên để có thể làm được điều này thì cần phải có thời gian luyện tập rất nhiều và cũng phải có cả kiến thức về coding nữa . Hic coding thì bỏ lâu quá rồi nến khi đặt tên biến thấy gượng gạo quá mà có khi còn sai bét …….Sau một hồi nghịch bung bét dòm kết quả thấy cũng tàm tạm, không đến nỗi nào ………. gọi là tạm hài lòng


void __userpurge WndProc(int a1, HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam)
{
int v5; // eax@21
int v6; // ST0C_4@21

if ( Msg == 2 )
goto end_program;
if ( Msg != 516 && Msg != 5 && Msg != 1 && Msg != 513 )
{
if ( Msg == 36 )
{
*(_DWORD *)(lParam + 24) = 280;
*(_DWORD *)(lParam + 28) = 160;
*(_DWORD *)(lParam + 32) = 280;
*(_DWORD *)(lParam + 36) = 160;
return;
}
if ( Msg != 273 )
{
DefWindowProcA(hWndParent, Msg, wParam, lParam);
return;
}
if ( wParam == 103 )
{
DialogBoxParamA(hInstance, “DLG_ABOUT”, hWndParent, DialogFunc, 0);
return;
}
if ( wParam != 101 )
{
if ( wParam == 102 )
{
if ( DialogBoxParamA(
hInstance,
“DLG_REGIS”,
hWndParent,
(INT_PTR (__stdcall *)(HWND, UINT, WPARAM, LPARAM))sub_401253,
0) )
{
process_chars_of_szName__((char)szName); // v5 = process_chars_of_szName__((char)szName);
v6 = v5;
calculate_with_szSerial((char)szSerial);
if ( v6 == a1 )
show_good_work();
else
show_no_luck();
}
}
return;
}
end_program:
PostQuitMessage(0);
}
}

 

IDA Pro Advanced_N0w 0r N3v3r September 10, 2008

Filed under: IDA Pro Advanced_N0w 0r N3v3r, IDA Tutorials — kienmanowar @ 4:55 am

IDA Pro Advanced_N0w 0r N3v3r

Author: _[kienmanowar]_

“Sóng gió cuộc đời chợt đến có ai chờ đợi.

Biết đâu một ngày phận người que diêm trước gió.

Lụi tàn trong một sớm không ngoài ai.

Nhưng với một triệu người

Hơi ấm sẻ chia từng người

Những đôi tay trần dìu nhau qua cơn khốn khó

Tầm hồn luôn rộng mở rất chân thành”

Rock mang đến cho tôi sự hứng khởi, lòng đam mê, tình yêu cháy bỏng, sự cuồng nhiệt và niềm tin vào cuộc sống. Giúp tôi vững bước trước mọi khó khăn sóng gió cuộc đời.”

I. Intro :

Chào tất cả các anh em REA, chúng ta lại gặp nhau trong phần tiếp theo tôi viết về IDA. Đây là bài viết của tác giả Medardus, có thể coi đây như là một “đơn đặt hàng” của anh Be, và tôi là người đứng ra nhận “bảo kê” cho “đơn đặt hàng” này. Thực tế tôi đã có ý định viết bài này từ rất lâu nhưng… hii lại có từ nhưng, vì thời gian bận bịu quá, sắp tới công việc lại có một số thay đổi mà chưa biết sẽ thế nào. Như đã hứa với anh em, bài viết trước tôi đã giới thiệu sơ qua một số tính năng của IDA, đủ để thuyết phục những người dù là khó tính nhất.Trong bài viết này tôi sẽ ứng dụng sử dụng IDA trong quá trình Reverse một Crackme.Hii trình độ tôi vẫn còn kém cỏi, không biết có đủ sức để hầu chuyện anh em không nữa :) . Okie, N0w L3t’s g0!!!!

II. IDA or W32Dasm…. N0w 0r N3v3r!!

Từ những ngày đầu tiên chập chững bước vào tìm hiểu thế giới Cracking & Reversing, những tut đầu tiên mà tôi đọc là của bậc đàn anh Ngô Vĩnh Hoàng hay còn được biết đến với nickname là NVH(c), nhớ lại cái thủa ban đầu ngơ ngác ấy tôi chẳng biết cái quái gì cả, chỉ biết đọc, down công cụ và làm theo một cách hoàn toàn dập khuôn , cứ 74->75, 75->74, không được nữa thì hehe Nop, nop, nop mà ít đầu tư vào suy nghĩ tại sao lại như thế.Mà hầu hết các tut thời đó đều sử dụng W32Dasm, Hiew, SoftIce v…v.. Sh1t!! nghĩ lại cái lúc hì hục cái SoftIce đến nhục, đọc và làm theo y như hướng dẫn mà lần nào Debug không treo máy thì lại bị Crash, hic cú quá tôi nghỉ chơi với SoftIce. Rồi sau này đọc các tut của lão nhỏ, tham gia vào REA ngay từ những ngày đầu tiên tôi dần dần tìm hiểu từ từ từng tí một. Các tut thời gian đó cũng hầu hết tập trung vào SoftIce, W32Dasm nhưng có sự xuất hiện của một Tool debugger mới mà hiện nay các bạn đang sử dụng đó, không nói chắc các bạn cũng biết đó chính là “em” Ollydbg. Hiii sân chơi của REA lúc đó đâu có được đông như bây giờ, chỉ có vài anh em, cầm đầu là anh Còm, anh Moon, lão Zom, bác RongchauA, bác deux, chú Little, trong đó người có nhiều bài viết nhất là anh Moon, khà khà tôi, anh Be, lão Q là một trong những member đầu tiên và cũng là những người trụ lại lâu nhất trong REA. Có thể nói REA lúc đó là một tập thể tuy chưa hiểu hết về nhau nhưng đã có cảm giác như anh em một nhà, rất gắn bó, trao đổi thẳng thắn, giúp đỡ nhau nhiệt tình và cho đến bây giờ vẫn vậy. Có thể tự hào mà nói rằng REA là một trong những 4room có “chất” nhất ở Việt Nam.

Hehe hơi lạc đề thì phải, như các bạn thấy thời đó công cụ Disassembly được ưa chuộng nhất là W32Dasm, có thể nói nó là công cụ của mọi Newbies, đơn giản, dễ học và dễ sử dụng. Nhưng sau này khi được tiếp xúc nhiều hơn với Cracking & Reversing, tôi thấy có một công cụ được nhắc đến khá nhiều đó là IDA được dùng cho cả hai giới Reversing lẫn Security.Lúc đó tôi tự hỏi “IDA là công cụ gì mà kinh thế?” nhưng rất tiếc không có câu trả lời. Sau này khi W32Dasm không còn được phát triển nữa, nó không đáp ứng được những chương trình có mức độ Protect cao cũng như những chương trình bị Packed, hic hic không còn cách nào khác tôi đành nhắm mắt đưa chân, download em IDA về “nghịch” thử xem thế nào. Phải nói rằng cảm giác đầu tiên khi mở IDA lên là một cảm giác choáng ngợp đến khỏ tả, vừa khó dùng lại vừa lắm chức năng, rối rắm và phức tạp J.

IDA là một chương trình Disassembler được phát triển bởi hãng DataRescure. Những ai đã từng sử dụng qua W32Dasm thì chắc hẳn sẽ biết Disassembler là như thế nào.Đối với những người chưa biềt gì thì có thể nói: Disassembler là một chương trình, mà theo đó nhờ vào sự giúp đỡ, hỗ trợ của chương trình này nó sẽ chuyển đổi code của một file (exe hay dll) về dạng mã assembler, được sắp xếp lại theo một khuôn dạng dễ hiểu và dễ đọc hơn. IDA hỗ trợ đầy đủ

các tính năng có trên W32Dasm, thêm vào đó nó còn hộ trợ thêm những tính năng cao cấp khác mà W32Dasm không có, ví dụ như : FLIRT (Nearly Library Identification and Recognition Technology), giúp đỡ cho việc nhận diện các hàm phù hợp từ các thư viện khác nhau và hỗ trợ các chú thích cung cấp cho việc mô tả.Sẽ rất ít người trả lời cho bạn tại sao IDA lại được sử dụng nhiều đến thế, chỉ còn mỗi một cách là tự mình tìm hiểu và chắt lọc. Vậy còn chần chừ gì nữa mà không tìm hiểu về IDA.

III. Play with Crackme

Oki, tốn bao giấy mực mới đến phần quan trọng nhất của bài viết này, đó là sử dụng IDA để thực hành với 1 Crackme.Tại sao lại là Crackme vì đơn giản nó có kích thước nhỏ và dễ dàng trong việc

Demo những kiến thức, thêm nữa là IDA sẽ disassembly rất nhanh còn nếu không bạn phải chờ dài cổ khà khà. Trong bài ngày hôm nay tôi sẽ sử dụng Crackme là CoSH Crackme #2.exe. Run thử Crackme này lên xem thế nào, đây chính là một bước quan trọng trong quá trình Reverse, tương tự như ta tiếp xúc với một người, ta phải quan sát thái độ, hành vi của người đó trước để còn liệu đường mà cử xử lại cho phải phép :) .

Download toàn bộ bài viết tại đây :

ida_now_or_never

 

Understanding Code! September 10, 2008

Filed under: IDA Tutorials, Understanding Code — kienmanowar @ 2:06 am

Understanding Code

Author: _[kienmanowar]_

“Cuộc sống luôn đầy cám dỗ

Ngày ngày níu kéo con người

Làm sao thắng được chính mình…Làm sao…

Ngựa non lớn lên thường háu đá

Tuổi trẻ nào suy tính gì

Nên đi tới con đường bế tắc

Chìm sâu… trong bóng tối”

I. Intro :

Chào tất cả anh em REA, chúng ta lại gặp nhau trong bài viết này của tôi viết về “Understanding Code”. Đây là bài viết của tác giả Kwazy Webbit, đề cập tới vấn đề đọc hiểu code của các chương trình được Disassembly bằng W32Dasm, IDA v..v.. Tại sao tôi lại chọn nó là chủ đề cho bài viết thứ 3 này bởi vì một lý do hết sức đơn giản, có đọc hiểu code chúng ta mới biết được chương trình đang làm gì, đoạn code mà chúng ta đang RE được dùng vào mục đích gì, đề từ đó có những thay đổi chỉnh sửa sao cho hợp lý để phục vụ mục đích của chúng ta. Hầu hết những anh em tôi quen biết đến với RE, Cracking theo những cách thức khác nhau, có người muốn chỉ trong một thời gian ngắn có thể Crack được một phần mềm mà không cần tìm hiểu xem tại sao lại làm như thế, nhiều người chỉ thực hiện theo các tut một cách dập khuôn, ăn sẵn để rồi cuối cùng vẫn luẩn quẩn với những câu hỏi “Tại sao lại làm như thế ?” , “Tại sao tôi làm thế không được ?” v…v… mà không hề có một chút đầu tư nghiên cứu tìm hiểu, nhưng ngược lại có những người đi từ những viên gạch đầu tiên, từ từ từng bước một, những viên gạch của quá trình lạo động tìm hiểu nghiêm túc, tốn nhiều mồ hôi, nước mắt để rồi bù lại họ có được những kiến thức làm tôi kinh ngạc.Có những người còn rất trẻ và những người lớn tuổi hơn tôi, có người chuyên về IT và cũng có người không. Nhưng những người đó đã để lại cho tôi lòng khâm phục bởi tinh thần làm việc nghiêm túc, lòng đam mê, sự chia sẻ kiến thức tới cộng đồng không hề vụ lợi.

Việc chúng ta tìm hiểu xem một đoạn binary biểu diễn ý nghĩa gì là một công việc quan trọng.Các đoạn mã thực thi và dữ liệu được biểu diễn ở mức thấp nhất đó là tập hợp của các bit 0 và 1.Bạn có thể cố gắng cho thực thi một đoạn dữ liệu như một đoạn mã, nhưng hầu hết trong các trường hợp việc làm này sẽ dẫn đến crash.Lấy ví dụ, việc cố gắng để sử dụng một đoạn mã thực thi như là một Picture data cũng sẽ là không hợp lệ hoặc ngược lại, nhưng đó chỉ là một sự ngẫu nhiên nào đó mà thôi. Đó là bởi vì có một cấu trúc để biểu diễn chúng khiến cho chúng không chỉ đơn thuần là binary mà còn bao hàm nhiều ý nghĩa khác. Để giúp ích cho bạn, bạn cần phải tìm hiểu về cấu trúc này và từ đó sẽ diễn dịch được nó theo đúng cách.

Để cụ thể hơn, tôi sẽ lấy một ví dụ, chẳng hạn tôi có 4 con số như sau :

112, 43, 149, 184

Như các bạn thấy chúng có thể mang rất nhiều ý nghĩa. Nếu như tôi nói với bạn rằng đây là ví dụ về một đường thẳng, và hãy tưởng tượng rằng đây là một đường thẳng 2 chiều, bắt đầu tại tọa độ thứ nhất là (112, 43) và kết thúc tại tọa độ thứ hai là (149, 184). Tuy nhiên nếu như có ai đó lại nói với bạn là đây chính là một hình vuông, liệu bạn có tin không? Tôi thì tin liền khà khà bởi vì đơn giản tôi nghĩ đây là một hình vuông với 4 các tọa độ của nó. Như các bạn thấy, 4 con số trên có rất nhiều ý nghĩa đúng không, tất cả chúng đều phụ thuộc vào sự suy diễn của bạn và tôi. Điều này sẽ dẫn đến nhiều vấn đề, làm sao chúng ta có thể phán đoán hết được ý nghĩa của chúng, và làm sao chúng ta chọn được một phương án đúng nhất trong đó? Máy tính sẽ dùng cách thức nào để có thể hiểu được? Làm thế nào chúng ta biết được điều gì đang thực sự xảy ra ? Trong bài viết này, tôi sẽ không đi vào tìm hiểu ngữ nghĩa của data, bởi vì các cấu trúc dữ liệu là quá nhiều (như các bạn đã thấy trong ví dụ ở trên). Mỗi một định dạng file sẽ có một cấu trúc dữ liệu. Chương trình sử dụng các phần mở dụng của file (.exe, .dll v..v..) như là một ám hiệu để biết cách cư xử với từng cấu trúc.

Thay vào đó, tôi và các bạn sẽ tập trung vào các đoạn code thực thi, đặc biệt là các đoạn code cho x86 processor. Chúng ta sẽ bắt đầu từ binary và kết thúc với ngôn ngữ C.

II. Binary to Hexadecimal

Như tôi đã nói ở trên, mức biểu diễn thấp nhất của thông tin (trong một môi trường máy tính) là binary. Các đoạn mã mà máy tính có thể hiểu được được biểu diễn bằng những bit 0 và 1 dài vô tận.Điều này dẫn đến con người khó có thể hiểu được những chuỗi 0 và 1 mà họ nhìn thấy thể hiện cái gì, và điều gì sẽ xảy ra. Nếu như bạn có hứng thú trong việc tìm hiểu nguyên lý hoạt động của các mạch trong CPU, tôi gợi ý bạn nên tìm đọc các quyển sách điện tử.Còn đối với tôi, tôi không biết nhiều về chúng để có thể giải thích cho bạn một cách chi tiết về nguyên lý hoạt động (Mặc dù có một thời gian tôi đã từng làm việc với những bộ vi xử lý đơn giản). Nhằm mục đích giảng giải, binary là một định dạng khó hiểu, vì nếu số lượng các số binary là quá lớn sẽ khiến cho chúng ta khó khăn trong việc quan sát. Đó chính là lý do tại sao các bạn thấy rằng thông thường chúng ta không bao giờ chỉnh sửa bất cứ gì theo định dạng binary, mà thay vào đó chúng được chuyển sang một định dạng dễ hiểu hơn mà tôi và các bạn đều biết, đó chính là Hexadecimal.Nếu như các bạn thấy trong hệ binary chỉ có 2 số 0 và 1, hệ decimal thì có 10 số (0,1,2,3,4,5,6,7,8,9) còn hệ Hexa sẽ có 16 (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F). Bạn có thể sẽ rất ngạc nhiên là tại sao định dạng Hexa lại được chọn để biểu diễn mà không phải là hệ Decimal đã quá thân thuộc ngay từ khi lọt lòng. Câu trả lời hết sức đơn giản.Đó là bởi vì tất cả các số khi được chuyển đổi vẫn nằm dưới định dang binary.Sử dụng 4bits thì tại một thời điểm bạn có thể tạo ra chính xác 16 giá trị khác nhau từ 4 bits này, bắt đầu từ 0 cho tới 15, theo hệ Hexa thì là từ 0 cho tới F. Điều này khiến cho hệ thống xử lý một cách dễ dàng hơn đơn giản chỉ bằng thay thế 4 bits bằng một số trong hệ Hexa. Dưới đây là bảng minh họa quá trình chuyển đổi giữa các hệ, giúp các bạn phần nào hiểu thêm về những gì mà tôi đã nói :

Binary

Decimal

Hexadecimal

0000

0

0

0001

1

1

0010

2

2

0011

3

3

0100

4

4

0101

5

5

0110

6

6

0111

7

7

1000

8

8

1001

9

9

1010

10

A

1011

11

B

1100

12

C

1101

13

D

1110

14

E

1111

15

F

10000

16

10

Như các bạn đã thấy trên bảng trên, tại giá trị Binary là 1000 thì giá trị ở hệ Hexa là (10), đó là vì con số 1 đầu tiên có thể được biểu diễn thành 0001, trong khi số 0 thứ 2 được biêu diễn bằng (0000). Do đó chúng ta có kết quả là 00010000, giá trị này rất phù hợp với hệ số Binary.

III. Hexadecimal to Assembly Code

Việc sử dụng kí pháp hexadecimal giúp chúng ta viết các đoạn Binary code một cách nhanh hơn, và cho phép chúng ta nhiều cái nhìn tổng quát hơn. Tuy nhiên nó vẫn không có ý nghĩa nhiều lắm cho con người bởi vì thực chất nó vẫn chỉ là những con số. Chúng ta hãy quan sát một ví dụ về đoạn code Hexa dưới đây :

83EC20535657FF158C40400033DBA39881400053

Như tôi đã nói, đây chỉ là một phương pháp biểu diễn nhanh, ngắn gọn cho các số binary.Điều đó có nghĩa là nó không cho chúng ta thấy bất kì một ý nghĩa gì cả, cũng như nhìn vào đó chúng ta chẳng hiểu nó định làm gì, nhưng cái lợi của nó là ngắn gọn hơn so với việc biểu diễn dưới dạng binary rất dài với toàn số 0 và 1. Đoạn Hexa ở trên bao gồm 40 kí tự, trong khi đó nếu chúng ta biểu diễn ở dạng binary chúng ta sẽ có được 160 (40 * 4 bits)

Đoạn mã ở trên không phải là một instruction lớn (thuật ngữ “instruction” ở đây để cập tới đoạn bytes code thực sự). Trên một vài bộ vi xử lý thì mỗi một instruction sẽ có một kích thước nhất định(ví dụ : 2 bytes) vì vậy chúng ta có thể chia code thành các phần một cách dễ dàng theo kích thước để có được các câu lệnh khác nhau (Giả sử rằng bạn sẽ có được một điểm bắt đầu hợp lệ trong đoạn code). Bộ xử lý x86 ít phức tạp hơn nhiều và có các kích thước instruction khác nhau. Bây giờ bạn có thể ngạc nhiên làm thế nào chúng ta có thể luôn luôn tách được các instructions theo cách này.Ý tưởng là như sau, chúng ta lấy byte đầu tiên, nhìn vào giá trị của nó, và byte này sẽ cho bạn biết cách tiến hành như thế nào. Một vài điều có thể xảy ra như sau :

_Nó có thể là một single byte instruction: ví dụ 90h là câu lệnh NOP (No Operation) và kích thước của nó chỉ là 1 byte.

_Có thể câu lệnh đó chưa được hoàn chỉnh: ví dụ Các lệnh (Instructions) mà được bắt đầu bằng 0Fh , chúng ta phải cần thêm các bytes vào sau để nó tạo thành một câu lệnh có nghĩa.

_Câu lệnh được định nghĩa bởi một byte độc lập, nhưng vẫn cần có tham số, ví dụ : 8Bh chuyển một thanh ghi vào trong một thanh ghi khác.Những byte mà theo sau 8Bh sẽ miêu tả nó được chuyển đến từ đâu và nó được chuyển đên đâu.

_Câu lệnh chưa hoàn chỉnh và cần thêm các tham số.

Bởi vì chúng ta sẽ cần phải biết đó là câu lệnh nào để mà tách ra, chúng ta sẽ kết hợp quá trình tách các câu lệnh khác nhau với việc chuyển chúng sang một định dạng mà con người có thể đọc hiểu được một cách tương đương. Ngôn ngữ mà con người có thể đọc hiểu được đó chính là “Assembly Language”, thường được viết tắt là ASM. Quá trình chúng ta chuyển dịch một chương trình từ Code thô (Raw code) sang ASM, được gọi là quá trình “Disassembling”. Việc làm này sẽ cho chúng ta khả năng để đọc hiểu ASM.Để có thể thực hiện được chúng ta cần phải có một số kinh nghiệm.

Vì rằng rõ ràng không có hệ thống nào để hiều một đoạn hexadecimal code thực hiện công việc gì , đó là một công việc cực kì chán ngắt. Tuy nhiên, việc hiểu được nó làm việc thế nào là rất quan trọng.Tôi sẽ chứng minh điều này thông qua ví dụ mà các bạn đã thấy ở trên.

Hãy quan sát lại đoạn Hexadecimal code :

83EC20535657FF158C40400033DBA39881400053

Chúng ta sẽ giả sử rằng byte đầu tiên chính là điểm bắt đầu hợp lệ và chúng ta sẽ bắt đầu phân tích từ đó.Đầu tiên tôi sẽ lấy byte này ra, nó là 83h , sau đó chúng ta thực hiện công việc tra cứu dựa trên một bảng và bảng này tôi để trong phần Phụ lục A1. Khi xem trong bảng này chúng ta thấy nó cần phải có thêm byte khác để mô tả nhiệm vụ của nó một cách đầy đủ nhất, và byte cần sẽ được hình thành từ một “mod R/M” byte. Để có được những gì đầy đủ về nhiệm vụ của câu lệnh chúng ta sử dụng thông tin từ byte này và tra cứu thêm bảng phụ lục thứ 2 (Phụ lục A2) để tìm kiếm thông tin thông qua “group #1”.Trong trường hợp này, byte đó chính là ECh. Một mod R/M byte bao gồm trường 3 bits sau:

Bit :

7

6

5

4

3

2

1

0

Meaning :

mod

reg

R/M

Để phân tách các trường này, chúng ta quay trở lại với binary bằng cách biểu diễn lại ECh :

EC = 1110 1100 = 11 101 100

Sử dụng bảng phụ lục A2, chúng ta sẽ thấy rằng những gì chúng ta biểu diễn ở trên phù hợp với giá trị xx101xxx, và đây chính là cậu lệnh SUB. Hai bit khác sẽ dùng để miêu tả toán hạng đầu tiên của câu lệnh SUB. Chúng ta lại xem tiếp trong một bảng phụ lục thứ 3 (Phụ lục A3), chúng ta tìm thấy 11 có nghĩa là chúng ta sẽ sử dụng trực tiếp một thanh ghi, và giá trị 100 ở trên chính là biểu diễn cho thanh ghi ESP. Quay trở lại bảng phụ lục A1 chúng ta thấy rằng cần phải có một toán hạng nữa để điền vào, đó chính là ‘Ib’ (Input byte). Rất dễ dàng để chúng ta thấy rằng byte tiếp theo đó chính là 20h.

Ghép tất cả những gì chúng ta vừa phân tích ở trên lại với nhau ,chúng ta sẽ có được một câu lệnh ASM đầu tiên :

83EC20 SUB ESP, 20

Okie như các bạn đã thấy, khá phức tạp phải không nào. Chúng ta phải tra đi tra lại mới ra được còn máy tính thì thực hiện quá nhanh J. Tiếp theo chúng ta sẽ tiếp tục quá trình phân tích với câu lệnh kế tiếp, bắt đầu tại giá trị 53h . Tra cứu trên bảng A1 nó cho chúng ta biết đây là một byte độc lập mà không có tham số :

PUSH rBX (= PUSH EBX)

Vậy cuối cùng chúng ta có được kết quả với 4 bytes đầu tiên được biểu diễn bằng ASM :

83EC20 SUB ESP, 20

53 PUSH EBX

Như các bạn đã thấy việc làm này đã tiêu tốn của chúng ta rất nhiều thời gian phải không. Tuy nhiên chúng ta thật may mắn khi có những công cụ đã thực hiện điều này cho chúng ta (ví dụ : HIEW) :

83EC20 sub esp,020

53 push ebx

56 push esi

57 push edi

FF158C404000 call d,[0040408C]

33DB xor ebx,ebx

A398814000 mov [00408198],eax

53 push ebx

Hoặc một cách khác cũng có thể giúp cho công việc của chúng ta đơn giản hơn đó là nhờ đến Ollydbg, chúng ta cũng có được đoạn code tương tự :

Tuy nhiên nhiều khi nhìn vào chúng ta không thể hiểu ngay được ý nghĩa của chúng, vi dụ như địa chỉ ở trên tham chiếu đến hàm nào, có trỏ tới một String nào không v..v.. Để giúp cho chúng ta các chương trình Disassemblers như IDA và W32DASM đã hỗ trợ rất nhiều. Sử dụng IDA, chúng ta sẽ có được nhiều thông tin hơn :

sub esp, 20h

push ebx

push esi

push edi

call ds:GetProcessHeap

xor ebx, ebx

mov hHeap, eax

push ebx ; lpModuleName

Như bạn đã thấy, IDA đã thực hiện thật tuyệt.Nó đã nhận ra được hàm call sẽ gọi tới API nào và nó cũng hiểu được giá trị trả về từ hàm đó, đó là hàm (GetProcessHeap) và do đó nó sẽ đổi tên biến thành hHeap. Đây chỉ là minh họa nhỏ cho thấy những gì IDA có thể làm được, nhưng cũng đủ để thấy rằng nó cung cấp cho chúng ta rất nhiều thông tin hơn là những gì chúng ta quan sát trong HIEW. Điều này thật là tuyệt vời và nó giúp cho chúng ta tiết kiệm được rất nhiều thời gian hơn vào việc làm bằng tay, và bên cạnh đó nó cũng giúp cho chúng ta có một điểm bắt đầu tốt cho quá trình phân tích code về sau.

Nhưng nhiệm vụ tiếp theo và rất quan trọng của chúng ta là làm cách nào để biểu đạt đoạn code dưới dạng ASM đó thành đoạn code dưới dạng cú pháp của một ngôn ngữ bậc cao (ví dụ như C).

IV. Assembly code to C

Bây giờ giả sử rằng tôi và các bạn có một đoạn code ASM, và chúng ta có thể đọc hiểu được nó để biết được chương trình đang làm gì.Tuy nhiên, vì hầu hết các câu lệnh ASM chỉ thực hiện một nhiệm vụ thông thường, do đó rất khó cho chúng ta biết được tổng quát nhiệm vụ của chương trình đang thực hiện cái gì. Hãy xem một đoạn mã ASM dưới đây :

.004122F0: 55 push ebp

.004122F1: 8BEC mov ebp,esp

.004122F3: 83EC48 sub esp,048 ;”H”

.004122F6: 53 push ebx

.004122F7: 56 push esi

.004122F8: 57 push edi

.004122F9: C745F800000000 mov d,[ebp][-08],000000000 ;”

.00412300: EB09 jmps .00041230B —–¯ (1)

.00412302: 8B45F8 mov eax,[ebp][-08]

.00412305: 83C001 add eax,001 ;”J

.00412308: 8945F8 mov [ebp][-08],eax

.0041230B: 8B4508 mov eax,[ebp][08]

.0041230E: 50 push eax

.0041230F: FF1584A34300 call lstrlenA ;KERNEL32.dll

.00412315: 3945F8 cmp [ebp][-08],eax

.00412318: 7D2E jge .000412348 —–¯ (2)

.0041231A: 8B4508 mov eax,[ebp][08]

.0041231D: 0345F8 add eax,[ebp][-08]

.00412320: 8A08 mov cl,[eax]

.00412322: 884DFF mov [ebp][-01],cl

.00412325: 0FB645FF movzx eax,b,[ebp][-01]

.00412329: 83F861 cmp eax,061 ;”a”

.0041232C: 7C18 jl .000412346 —–¯ (1)

.0041232E: 0FB645FF movzx eax,b,[ebp][-01]

.00412332: 83F87A cmp eax,07A ;”z”

.00412335: 7F0F jg .000412346 —–¯ (2)

.00412337: 0FB645FF movzx eax,b,[ebp][-01]

.0041233B: 83E820 sub eax,020 ;” ”

.0041233E: 8B4D08 mov ecx,[ebp][08]

.00412341: 034DF8 add ecx,[ebp][-08]

.00412344: 8801 mov [ecx],al

.00412346: EBBA jmps .000412302 —–­ (3)

.00412348: 5F pop edi

.00412349: 5E pop esi

.0041234A: 5B pop ebx

.0041234B: 8BE5 mov esp,ebp

.0041234D: 5D pop ebp

.0041234E: C3 retn

Bạn thấy đấy, trên đây là một đoạn mã ASM sử dụng rất nhiều các câu lệnh đơn giản kết hợp với nhau và cuối cùng là để thực hiện một nhiệm vụ nào đó mà chính chúng ta cần phải tìm hiểu. Chúng ta sẽ bắt đầu làm việc từ câu lệnh đầu tiên và cứ như thế cho đến hết, cố gắng để có một cái nhìn tổng quan nhất về những gì sẽ xảy ra bằng việc sử dụng một “Pseudo-C” notation (kí pháp Giả ngôn ngữ C), và cuối cùng là để chuyển nó về chính xác ở C code.

Okie có vẻ vẫn hơi mơ hồ, tôi sẽ cùng các bạn giải quyết. Đầu tiên chúng ta sẽ bắt đầu với những dòng lệnh sau :

.004122F0: 55 push ebp

.004122F1: 8BEC mov ebp,esp

.004122F3: 83EC48 sub esp,048 ;”H”

.004122F6: 53 push ebx

.004122F7: 56 push esi

.004122F8: 57 push edi

Hai dòng lệnh đầu tiên còn được biết đến với một cái tên là “stack frame”.Về bản chất đây là một ‘local’ stack bên trong của hàm, nơi mà chúng ta tưởng tượng như là một căn phòng đặc biệt dùng để chứa các biến cục bộ (local variables). Việc tạo ra căn phòng này có thể được thực hiện rất dễ dàng bằng cách đơn giản là giảm con trỏ stack đi một số bit nào đó, cụ thể là bao nhiêu bytes cần thiết cho việc lưu trữ các biến cục bộ.

Một trong những lợi thế chính của Stack frame chính là ở thanh ghi EBP, nó có thể được sử dụng như là một con trỏ cố định tới các biến tham chiếu (reference varibales) (Nằm ở trên thanh ghi EBP là các tham số, ở dưới nó thì là các biến cục bộ) (Đọc thêm các bài viết của anh Be)

Chú ý rằng các con trỏ Stack như (ESP và EBP) cần phải được phục hồi lại trước rời khỏi một hàm nào đó, để tránh cho việc lỗi Stack corruption.

.004122F0: 55 push ebp

.004122F1: 8BEC mov ebp,esp

.004122F3: 83EC48 sub esp,048 ;”H”

Trên đây là quá trình tạo Stack Frame và căn phòng được với không gian là 48 bytes dành cho việc lưu trữ các biến cục bộ.

Windows yêu cầu rằng một vài thanh ghi khác ngoài ESP và EBP cũng cần được bảo vệ trong suốt quá trình của một Callback function, đó là những thanh ghi EBX, ESI và EDI. Chúng được lưu trữ một cách an toàn trên Stack, và sẵn sàng để khôi phục lại đúng vị trí trước khi rời khỏi hàm.Điều này cho phép sự tự do khi sử dụng những thanh ghi này bên trong một hàm.

Đã có quá trình lưu giữ thanh ghi thì cũng phải có quá trình phục hồi chúng, điều này được thực hiện nhờ vào các câu lệnh rất đơn giản. Và nhìn vào đó ta biết ngay nó làm gì :

.00412348: 5F pop edi

.00412349: 5E pop esi

.0041234A: 5B pop ebx

.0041234B: 8BE5 mov esp,ebp

.0041234D: 5D pop ebp

.0041234E: C3 retn

Đầu tiên 3 thanh ghi được khôi phục từ stack của chúng ta. Sau đó Stack được phục hồi lại trạng thái của nó sau khi hàm đã được gọi và khi gặp câu lệnh Return. Chú ý rằng chúng ta không thể khôi phục Stack trước khi khôi phục 3 thanh ghi được, bởi vì các thanh ghi của chúng đã được lưu trên Stack.Chuyển tất cả đoạn code trên sang C là rất dễ dàng. Bây giờ chúng ta biết rằng đây có thể là một hàm, bởi vì dựa vào Stack Frame cũng như việc lưu trữ và phục hồi các thanh ghi, v..v.. :

void SomeFunction()

{

//…code…

}

Bây giờ tôi giả sử rằng đây là một void function, bởi vì không hề có bất kì một sự thay đổi nào trong thanh ghi EAX trước khi Return. Điều đó không có nghĩa là EAX đã không bị thay đổi. Nhưng cho đến bây giờ, chúng ta sẽ giả sử giá trị trong thanh ghi EAX bị lờ đi.

Tiếp theo chúng ta sẽ tiếp tục với thân của hàm này :

.004122F9: C745F800000000 mov d,[ebp][-08],000000000 ;”

.00412300: EB09 jmps .00041230B —–¯ (1)

.00412302: 8B45F8 mov eax,[ebp][-08]

.00412305: 83C001 add eax,001 ;”J

.00412308: 8945F8 mov [ebp][-08],eax

.0041230B: 8B4508 mov eax,[ebp][08]

.0041230E: 50 push eax

.0041230F: FF1584A34300 call lstrlenA ;KERNEL32.dll

.00412315: 3945F8 cmp [ebp][-08],eax

.00412318: 7D2E jge .000412348 —–¯ (2)

Chúng ta hãy để ý tới giá trị được tham chiếu đến :

d,[ebp][-08] == dword ptr[ebp-08] (in another notation)

Như tôi đã nói, bời vì nó nằm dưới thanh ghi EBP của chúng ta (thanh ghi EBP đang được lưu trên Stack), vì vậy hàm đang lưu trữ một biến cục bộ ở đó. Chúng ta biết được rằng nó có kích thước là DWORD và nó có thể là một giá trị có dấu (signed value), bởi nó được đem đi so sánh với kết quả của hàm lstrlenA, mà kết quả của hàm này là một signed int). Trên nền tảng win32, thì giá trị signed dword trong C là (signed) int. Chúng ta hãy đổi tên của nó thành int_locall để cho việc đọc hiểu trở nên dễ dàng hơn :

.004122F9: mov int_local1, 000000000

.00412300: jmps .00041230B —–¯ (1)

.00412302: mov eax, int_local1

.00412305: add eax,001

.00412308: mov int_local1, eax

.0041230B: mov eax,[ebp][08]

.0041230E: push eax

.0041230F: call lstrlenA ;KERNEL32.dll

.00412315: cmp int_local1,eax

.00412318: jge .000412348 —–¯ (2)

Okie đã thấy sáng sủa hơn một chút, tuy nhiên các bạn hãy cẩn thận ở đây, đừng nhầm lần giữa [ebp][08] với [ebp][-08] . Mặc dù là nhìn thoáng qua ta cũng thấy nó giống nhau đấy chứ, tuy nhiên đây lại là những địa chỉ hoàn toàn khác nhau. Biến tại địa chỉ [ebp][08] thì luôn luôn là một tham số đầu tiên được truyền vào hàm của chúng ta. Chính vì lí do đó chúng ta sẽ đổi tên của giá trị này thành dw_param1. Khà khà sau một hồi phân tích chúng ta đã xác định được biến cục bộ, và làm sáng tỏ được một số vấn đề , bây giờ chúng ta sẽ thử chuyển nó sang một đoạn mã giả C :

int_local1 = 0;

goto label_41230B;

eax = int_local1;

eax = eax + 1;

int_local1 = eax;

label_41230B:

eax = dw_param1;

eax = lstrlenA(eax); //lstrlenA returns its result in eax

if( int_local1 >= eax)

goto label_412348;

Vậy là phần nào chúng ta đã có một cái nhìn dễ dàng hơn với đoạn code trên, tuy nhiên đây mới chỉ là điểm khởi đầu.Việc tiếp theo chúng ta phải dùng tư duy của mình để tối ưu hóa lại đoạn code này, hãy nhìn lại 3 dòng sau :

eax = int_local1;

eax = eax + 1;

int_local1 = eax;

Chúng ta sẽ thấy rằng đoạn code trên là hơi thừa nó có thể được đơn giản hóa lại như sau :

int_local1++;

Điểm khác biệt duy nhất giữa hai cách thể hiện này là thanh ghi EAX không xuất hiện trong cách biểu diễn thứ hai. Chúng ta cần phải cẩn thận quan sát, vì rất có thể giá trị của thanh ghi EAX sẽ lại được sử dụng ở phía bên dưới thì sao J.

Tiếp theo ta đến dòng kế tiếp :

eax = dw_param1;

Điều này có nghĩa là những gì chúng ta làm ở trên là đúng bởi vì thanh ghi EAX đã được thay đổi bằng cách được gán 1 giá trị mới. Phần tiếp theo :

eax = dw_param1;

eax = lstrlenA(eax); // lstrlenA returns its result in eax

if( int_local1 >= eax)

goto label_412348;

Với đoạn code này chúng ta hoàn toàn có thể làm cho nó trở nên dễ dàng hơn, chúng ta có thể kết hợp những câu lệnh trên lại như sau :

if( int_local1 >= lstrlenA(dw_param1) )

goto label_412348;

Một lần nữa chúng ta phải quan sát xem thành ghi EAX có được sử dụng trong các đoạn code bên dưới không, để từ đó chúng ta không bỏ sót vị trí nơi mà giá trị của thanh ghi này đang được sử dụng. Trong các câu lệnh sau đó, giá trị của EAX bị thay đổi, do đó chúng ta không cần quan tâm về những thay đổi của chúng ta. Bởi vì chúng ta biết rằng hàm lstrlenA sẽ lấy đầu vào là một con trỏ trỏ tới một chuỗi, do đó chúng ta sẽ thay đổi tham số này thành pString , cuối cùng ta có được như sau :

int_local1 = 0;

goto label_41230B;

int_local1++;

label_41230B:

if( int_local1 >= lstrlenA(pString))

goto label_412348;

Quan sát toàn bộ đoạn code tiếp theo trong hàm này chúng ta thấy được dòng sau :

.00412346: EBBA jmps .000412302 —–­ (3)

Đây là một câu lệnh nhảy và nó nhảy trở về vị trí có câu lệnh int_local1++; , điều này chứng tỏ đây là một vòng lặp. Nếu như bạn đã quen thuộc với lập trình C, bạn có thể minh họa được cấu trúc này.Đây dường như là một vòng lặp for. Chúng ta sẽ cố gắng để biểu diễn lại nó, bằng cách thay đổi biến int_local1 thành i. Chúng ta sẽ viết lại dưới ngôn ngữ C như sau :

for(i = 0; i < lstrlenA(pString); i++)

{

//…rest of code…

}

Mọi việc đang dần dần được rõ ràng J. Giờ chúng ta đã biết hàm này có một vòng lặp, với số lần lặp bắt đầu từ 0 cho tới chiều dài của chuỗi có được thông qua tham số đầu tiên (lstrlenA(pString)). Tiếp theo chúng ta cần biết những gì đang diễn ra bên trong thân vòng lặp :

.0041231A: mov eax, pString

.0041231D: add eax,i

.00412320: mov cl,[eax]

.00412322: mov [ebp][-01],cl

.00412325: movzx eax,b,[ebp][-01]

.00412329: cmp eax,061 ;”a”

.0041232C: jl .000412346 —–¯ (1)

.0041232E: movzx eax,b,[ebp][-01]

.00412332: cmp eax,07A ;”z”

.00412335: jg .000412346 —–¯ (2)

.00412337: movzx eax,b,[ebp][-01]

.0041233B: sub eax,020 ;” ”

.0041233E: mov ecx, pString

.00412341: add ecx, i

.00412344: mov [ecx],al

Trong đoạn code này chúng ta lại thấy có một biến cục bộ khác được sử dụng. Nó xuất hiện dưới kiểu unsigned char , bởi vì nó có kích thước là byte ( byte ptr) và được sử dụng nhưng là unsigned (bởi câu lệnh movzx). Trong đoạn mã giả C, ta có thể viết lại như sau :

eax = pString;

eax = eax + i;

cl = *(eax);

ch_local2 = cl;

eax = (DWORD) ch_local2;

if(eax < 0×61) // “a”

goto label_412346;

eax = (DWORD) ch_local2;

if(eax > 0×7A) // “z”

goto label_412346;

eax = (DWORD) ch_local2;

eax = eax – 0×20;

ecx = pString;

ecx = ecx + i;

*(ecx) = al;

Bây giờ tiếp tục, chúng ta sẽ làm cho đoạn code của chương trình rõ ràng hơn, tôi đổi tên kí tự thành c cho nó ngắn gọn :

c = pString[i];

if((c < ‘a’) || (c > ‘z’))

goto label_412346;

pString[i] = c-0×20;

Để ý rằng địa chỉ tại 412346 chỉ đơn giản là vị trí kết thúc vòng lặp, vì vậy chúng ta có thể thay thế ‘goto label_412346’ bằng ‘continue;’, hoặc chúng ta có thể đảo conditional jumps.

Chúng ta nhận thấy rằng chương trình kết thúc vòng lặp nếu (c<‘a’)||(c>‘z’), vậy thì nó sẽ không kết thúc vòng lặp nếu ta đổi thành (c>=’a’)&&(c<=’z’), điều này cho phép chúng ta thay đổi lại cấu trúc như sau :

c = pString[i];

if((c >= ‘a’) && (c <= ‘z’))

pString[i] = c-0×20;

//…end of loop

Các bạn thấy đó , mọi thứ đã sáng tỏ và dễ hiểu hơn rất nhiều. Bây giờ chúng ta đã bắt đầu hiểu những gì đoạn code này đang làm. Chúng ta hãy sắp xếp lại chúng lại thành đoạn code cuối cùng như sau :

void SomeFunction(char* pString)

{

int i; //Local variables have to be declared

unsigned char c; //at the start of the function.

for(i = 0; i < lstrlenA(pString); i++)

{

c = pString[i];

if((c >= ‘a’) && (c <= ‘z’))

pString[i] = c-0×20;

}

}

Cuối cùng chúng ta đã có được một đoạn code ngắn gọn hơn nhiều so với những gì chúng ta đã đọc với ASM code.Nó đã được chuyển đổi hoàn toàn sang ngôn ngữ C, nhiệm vụ của nó là lấy từng kí tự từ String đầu vào, và nếu kí tự đó nằm trong khoảng ‘a’ và ‘z’ (tức là các kí tự chữ cái thường) nó sẽ được trừ đi cho 0×20h. Mà phép trừ này biểu diễn cho quá trình chúng ta chuyển đổi nó từ chữ cái thường thành chữ cái hoa. Do đó chúng ta sẽ đặt tên cho hàm này một cách gợi nhớ hơn là ToUpperCase .

V. Lời kết

Toàn bộ quá trình mà tôi và các bạn đã làm ở trên được gọi với cái tên : RE (Reverse Engineering). Chúng ta thấy rằng nó không phải là một việc làm quá khó, nhưng nó đòi hỏi một lòng kiên nhẫn và một kiến thức nền tảng vững chắc. Để chỉ khi ta chỉ nhìn lướt qua đoạn code ta có thể hiểu ngay được nó làm gì. Có một công cụ giúp chúng ta đơn giản hóa công việc đi rất nhiều, một trong số đó chính là IDA mà tôi đã giới thiệu với anh em.Hi vọng bài viết này của tôi sẽ phần nào giúp mọi người hiểu được quá trình phân tích một đoạn code như thế nào, chỉ có một lời khuyên duy nhất đó là các bạn hãy thực hành thật nhiều mới có thể đạt được những điều mình mong muốn. Qua đây tôi cũng xin cảm ơn tác giả Webbit đã cho chúng ta một bài viết rất hay và bổ ích.

Thời gian trôi đi rất nhanh

Vì sao ta cứ mãi đứng yên

Ngoài kia bóng tối đang dần buông

Lại một ngày nữa sắp qua mất rồi.

PS : Hi vọng anh TQN, Thug và light.phoenix có thời gian viết vài bài cho anh em mở rộng tầm mắt J

Best Regards

_[Kienmanowar]_

 

IDA Pro Advanced changes our life September 9, 2008

Filed under: IDA Pro Advanced changes our lif3!, IDA Tutorials, My Tutorials — kienmanowar @ 2:30 am

Bài này được viết vào khoảng cuối tháng 3 năm 2006, tuy cũ nhưng những kiến thức của nó vẫn còn như mới :-)

IDA Pro Advanced changes our life

Author: _[kienmanowar]_

I. Intro :

Chào tất cả các anh em REA, đã lâu rồi tôi không có viết tut kể từ ngày lão nhỏ rút lui.Không hiểu lão nhỏ đang bận cái quái quỉ gì mà ngay cả YM cũng chẳng thấy thò mặt mũi lên lấy 1 lần.Mặc dù rất ngứa tay và muốn viết thật nhiều cho REA, nhưng khi đặt tay lên bàn phím tôi không biết phải bắt đầu từ đâu và viết về vấn đề gì, cộng thêm phải đi cày để gom tiền cưới vợ nên cũng chẳng có nhiều thời gian ..khà khà.Lão nhỏ ra đi để lại độc một cái tut về IDA cho anh em, mà trong khi đó chắc anh em cũng như tôi còn muốn nhiều hơn thế, nhưng thôi thì cũng phải thông cảm cho lão nhỏ chắc giờ này lão cũng đi cày để gom tiền như tôi thôi. Trong REA, hễ cứ thấy động tới IDA là y như rằng anh em lặng im phăng phắc chẳng thấy bàn tán gì nhiều, tôi cũng như anh em thôi cũng muốn voọc IDA lắm chứ nhưng …. Hôm rồi, thấy anh Be có tạo một Thread “Learning IDA online” tôi nghĩ rằng đây là một Thread rất cần thiết và bổ ích cho anh em muốn tìm hiểu về IDA như tôi, nhưng ngặt nỗi tài liệu về IDA khá khiêm tốn trong khi đó ứng dụng của nó trong Reverse lại là rất lớn.Hii anh Be đã lên tiếng thì thằng em này cũng cố theo, hôm nay mạn phép xin viết một bài rất cơ bản về IDA để anh em đọc chơi, anh em nào đã biết rồi thì xin góp ý cho bài viết để tôi còn biết đường mà sửa, còn những anh em nào chưa biết thì …. Let’s go J

Bài viết này tôi tổng hợp lại từ bài viết của tác giả BlackBird, đã viết từ năm 2000.Có thể anh em cho rằng nó quá cũ, nhưng có một câu “Cũ người mới ta”, thế giới đã đi trước chúng ta bao nhiêu năm thì giờ đây chúng ta phải chắt lọc những gì tính túy nhất để mà học hỏi.Bài viết này tôi sẽ giới thiệu cho các bạn một số chức năng của IDA, để các bạn thấy được tại sao IDA lại được đánh giá cao đến thế. Như các bạn thấy đối với những Newbies thì khi nhìn vào cửa sổ chương trình IDA, hehe điều đầu tiên họ làm là nhấn nút “X” ở góc trên phải màn hình, để làm gì thì khỏi nói các bạn cũng biết nhưng còn đối với những Elite Reverses thì IDA lại là một công cụ không thể thiếu được.Tại sao các Newbies mới ban đầu khi tiếp xúc với IDA lại có thái độ như trên, đó là bởi vì IDA có quá nhiều các hàm và các chức năng mở rộng đồng thời việc sử dụng IDA phức tạp hơn nhiều so với W32Dasm.Phần tiếp theo đây của bài viết sẽ đưa ra những lời giải thích ngắn gọn và trong sáng nhất về việc tại sao bạn nên sử dụng IDA trong khi bạn đã khá thành thạo trong việc sử dụng W32Dasm.

II. IDA: A power disassembler

Điều đầu tiên mà chúng ta nhận thấy một cách rõ ràng khi chạy IDA đó là giao diện của chương trình trông cực kì chuyên nghiệp và hơn hẳn W32Dasm. Có rất nhiều các tùy chọn cũng nhữ những tính năng cấp cao mà ở W32Dasm không có. Điều này có một thuận lợi đó là các bạn có thể Disassemble tốt hơn và chi tiêt hơn, nhưng đó cũng là điều không thuận lợi khiến IDA trở nên rất khó trong việc làm quen và sử dụng nó.

Tuy nhiên, trên thực tế IDA vẫn có đầy đủ các tính năng tương như W32Dasm như : Bạn có thể nhảy tới chính xác một đoạn code nào đó, bạn có thể quan sát vị trí nơi mà một lệnh nhảy nhảy tới một đoạn code, hay bạn có thể xem các String References v..v..

Điều khó khăn đầu tiên cho tất cả các Newbies đó chính là quá trình tìm kiếm các String References. Trong W32Dasm chúng ta có một nút bấm mà theo đó chúng ta có thể xem trực tiếp tất cả các String References rất dễ dàng. Trong IDA, tính năng này được đặt trong Menu: View à Names.

Giả sử chúng ta đã mở một file .exe trong IDA, một hộp thoại bật ra cung cấp cho chúng ta rất nhiều tùy chọn. Đừng có đụng tới bất cứ cái gì, chỉ việc nhấn ‘OK’. Sau đó IDA sẽ thực hiện công việc Analysis và sau khi thực hiện xong bạn hãy mở theo như hình trên chúng ta có được như sau :

Oki, chúng ta đang ở cửa sổ Names, vậy làm thế nào để nhận biết ra đâu là String References.Như đã nói ở phần trên W32Dasm có một nút bấm riêng phục vụ cho công việc này, nhưng còn trong IDA các string reference được gắn vào trước bởi một chữ ‘a’.Để tìm kiếm một string bạn chỉ việc nhấn ‘a’ trên bàn phím tại cửa sổ Names, tương tự như việc tìm kiếm các hàm API trong Ollydbg.Hoặc các bạn có thể cuộn chuột để tìm kiếm, cho đến khi thấy được như sau :

Đó chính là các String Reference mà IDA đã chỉ ra vị trí của chúng cho chúng ta thấy.Và đương nhiên tương tự như W32Dasm, khi tìm thấy một String đáng quan tâm chúng ta sẽ tìm đến vị trí của nó, trong IDA bạn nhấn Enter hoặc nhấp đúp vào String chúng ta sẽ đến vị trí mà String đó ở tại.

Kết quả chúng ta sẽ được như hình minh họa trên. Tai đây bạn quan sát sẽ thấy có một tham chiếu được đặt cạnh String của chúng ta, nó có dạng như sau : DATA XREF: .text:XXXXXXXX­o, chi tiết về các thành phần tôi sẽ đề cập trong bài viết sau. Ý nghĩa của cái Reference này cho chúng ta biết nơi mà String của chúng ta thực sử được sử dụng trong đoạn code của chương trình.Rê chuột vào địa chỉ XXXXXXXX, IDA sẽ cho chúng ta thấy được như sau điều mà ở W32Dasm không có được :

Để thực sử tới đoạn code sử dụng String chúng ta chỉ việc nhấn đúp chuột vào địa chỉ đó, nó sẽ đưa chúng ta đến nơi chúng ta cần đến :

Khà khà rất đơn giản phải không nào, đâu có gì là quá khó phải không các bạn. Tiếp theo chúng ta sẽ đề cập đến một số tính năng khác của W32Dasm mà cũng được IDA hỗ trợ rất tốt đó là các lệnh nhảy (Jump) và các lời gọi Call References.Tôi xin lấy 2 hình minh họa cho quá trình Disassembly một chương trình trong W32Dasm và IDA để các bạn nhận thấy được sự khác biệt.

Như các bạn thấy, IDA cung cấp các thông tin chi tiết và rõ ràng hơn nhiều.Tiếp theo ta sẽ xem một ví dụ về Reference. Trong W32Dasm các ban sẽ thấy như sau :

Còn trong IDA nó sẽ được thể hiện như sau :

Nhìn vào hình minh họa trong IDA, ta thấy rằng IDA cho chúng ta nhiều thông tin mặc dù trông nó có vẻ ngắn gọn hơn W32Dasm.Cụ thể như sau :

  1. Đây là một lệnh nhảy vì ta thấy có kí tự (j). Thêm nữa lệnh nhảy này nằm ở phía trên của địa chỉ 0×0040132D, tại sao lại biết được nó nằm phía trên đó là do ta thấy có hình mũi tên lên (­).
  2. Lệnh nhảy tới đoạn code bắt đầu tại địa chỉ 0×0040132D này nằm tại địa chỉ 0×0040131A (= DialogFunc + 29). Bạn sẽ hỏi làm sao tôi biết địa chỉ của DialogFunc, xin thưa rất đơn giản bạn chỉ cần nhấn chuột vào chữ DialogFunc và chọn Edit Function hay phím tắt là Alt+P, ta sẽ có được thông tin về địa chỉ như sau :

Vậy là 0×0040131A = 0×004012F1 + 0×29

  1. Lệnh nhảy này được nằm tại .CODE section.

Nếu như Reference là một lời gọi (CALL) thì trong IDA nó sẽ được thể hiện như sau :

Một trong những tính năng tuyệt vời khác của IDA đó chính là việc thể hiện các biến được dùng trong chương trình.Thậm chỉ bạn có thể nhấn đúp chuột vào chúng để đi tới địa chỉ nơi mà các biến được lưu trữ : Lea edi, [esp + 1B8h + var_104]

Một trong những tính năng khiến cho IDA trở thành một công cũ rất mạnh đó chính là việc sử dụng Flirt Signatures. Giải thích một cách đơn giản nhất đó là trong quá trình disassembly IDA cố gắng nhận diện chính xác các hàm thư viện có liên quan với trình biên dịch. Ví dụ :

Wow các bạn có nhận thấy sự khác biệt không? Riêng tôi thì IDA quả là tuyệt, nó nói cho ta tất cả mọi thứ, rõ ràng như ban ngày.

Một số tính năng khác tương tự với W32Dasm đó là “Goto code location” thì trong IDA là “Jump-Jump to Address”. Hay “Goto entrypoint” thì trong IDA sẽ là “Jump – Jump to entry point”.

Đó là một số tính năng quan trọng nhất khi chúng ta bắt đầu con đường chinh phục IDA. Ah tôi quên mất, trong IDA còn một tính năng khá thú vị nữa đó chính là thỉnh thoảng chúng ta thấy có rất nhiều đoạn như sau ‘db’ mà lại không thấy có các String References, nhưng đó lại chính là những đoạn code. Chỉ việc trỏ chuột vào và nhấn phím tắt C trên bàn phím, ngay lập tức IDA sẽ chuyển nó thành code. Ví dụ như sau :

Còn lý do tại sao chắc tôi sẽ viết một bài về việc làm thế nào để hiểu một đoạn code có ý nghĩa gì.Đến đây coi như là kết thúc quá trình tôi cùng các bạn tìm hiểu một số tính năng của IDA. Sau khi bạn đọc bài viết này của tôi có thể bạn sẽ đặt cho mình một câu hỏi “Liệu tôi có nên tiếp tục sử dụng W32Dasm nữa không”, câu trả lời nằm ở chính bạn.Còn riêng với cá nhân tôi, tôi sẽ vẫn giữ lại W32Dasm vì trong một số trường hợp đơn giản nó vẫn rất có ích.

III. Lời kết

Vậy là tut này đến đây là kết thúc, 7 trang giấy trong bài viết này không thể nói hết được những tính năng rất mạnh của IDA. Chỉ có thực sự bắt tay vào công việc chúng ta mới thấy được, mới tìm hiểu được những tính năng rất mạnh của nó. Không lý do gì mà IDA lại được giới Reverser cũng như giới Security yêu thích đến thế, chắc chắn nó sẽ có nhiều điều thú vị nữa khiến cho mọi người đều đam mê. Bài viết này của tôi chỉ là một phần nhỏ giới thiệu về IDA, hi vọng nó đã mang đến cho các bạn một cái nhìn khác về chương trình Disassembly rất mạnh này.Rất cảm ơn anh em đã dành thời gian đọc nó.

PS : Hi vọng anh TQN, Thug và light.phoenix có thời gian viết vài bài cho anh em mở rộng tầm mắt :)

Best Regards

_[Kienmanowar]_

–++–==[ Greatz Thanks To ]==–++–
My family, Computer_Angel, Moonbaby , Zombie_Deathman, Littleboy, Benina, QHQCrker, the_Lighthouse, Merc, Hoadongnoi, Nini … all REA‘s members, TQN, HacNho, RongChauA, Deux, tlandn, light.phoenix, dqtln, ARTEAM …. all my friend, and YOU.

–++–==[ Special Thanks To ]==–++–
- coruso_trac, pat, trm_tr. Thug4lif3, vn_blackrain, v..v.. and all brothers in VSEC

–++–==[ Thanks To ]==–++–

iamidiot, WhyNotBar, trickyboy, dzungltvn, takada, hurt_heart, haule_nth, hytkl v..v.. các bạn đã đóng góp rất nhiều cho REA. Hi vọng các bạn sẽ tiếp tục phát huy :-)


>>>> If you have any suggestions, comments or corrections email me: kienmanowar[at]reaonline.net