Like Share Discussion Bookmark Smile

J.J. Huang   2019-07-14   OllyDBG   瀏覽次數:

OllyDBG - 第十章 | 反匯編練習 (二) 下

這篇將延續OllyDBG - 第九章 | 反匯編練習 (二) 中,在上一篇中找到了計算序號的區域,這邊試著去了解計算過。

這篇會稍微有點枯燥乏味,因為在不懂組合語言的情況下,一定會有很多疑問;所以過程中會需要很多其他的資料來做協助。
而且這邊的過程將會盡量一行一行去做了解及解釋,然後試著融會貫通了解其算法。

檔案下載

相關的目的程式和使用的工具,請直接至OllyDBG - 第八章 | 反匯編練習 (二) 上,這邊下載

分析

延續上一篇的分析過程

1
2
3
4
5
004011DB   .  53            push ebx                                 ;  輸入的用戶名長度
004011DC . 8D8424 A00000>lea eax,dword ptr ss:[esp+0xA0]
004011E3 . 52 push edx ; 用戶名
004011E4 . 50 push eax ; 序列號
004011E5 . E8 56010000 call TraceMe.00401340 ; 關鍵,計算序號的地方

我們有發現計算序號的Call位置在00401340,以下提供代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
00401340  /$  55            push ebp
00401341 |. 8B6C24 0C mov ebp,dword ptr ss:[esp+0xC] ; ntdll_12.77609F72
00401345 |. 56 push esi
00401346 |. 57 push edi
00401347 |. 8B7C24 18 mov edi,dword ptr ss:[esp+0x18]
0040134B |. B9 03000000 mov ecx,0x3
00401350 |. 33F6 xor esi,esi
00401352 |. 33C0 xor eax,eax ; kernel32.BaseThreadInitThunk
00401354 |. 3BF9 cmp edi,ecx
00401356 |. 7E 21 jle short TraceMe.00401379
00401358 |. 53 push ebx
00401359 |> 83F8 07 /cmp eax,0x7
0040135C |. 7E 02 |jle short TraceMe.00401360
0040135E |. 33C0 |xor eax,eax ; kernel32.BaseThreadInitThunk
00401360 |> 33D2 |xor edx,edx ; TraceMe.<ModuleEntryPoint>
00401362 |. 33DB |xor ebx,ebx
00401364 |. 8A1429 |mov dl,byte ptr ds:[ecx+ebp]
00401367 |. 8A98 30504000 |mov bl,byte ptr ds:[eax+0x405030]
0040136D |. 0FAFD3 |imul edx,ebx
00401370 |. 03F2 |add esi,edx ; TraceMe.<ModuleEntryPoint>
00401372 |. 41 |inc ecx
00401373 |. 40 |inc eax ; kernel32.BaseThreadInitThunk
00401374 |. 3BCF |cmp ecx,edi
00401376 |.^ 7C E1 \jl short TraceMe.00401359
00401378 |. 5B pop ebx ; kernel32.757333AA
00401379 |> 56 push esi ; /<%ld> = 0x0
0040137A |. 68 78504000 push TraceMe.00405078 ; |Format = "%ld"
0040137F |. 55 push ebp ; |s = 0018FF94
00401380 |. FF15 9C404000 call dword ptr ds:[<&USER32.wsprintfA>] ; \wsprintfA
00401386 |. 8B4424 1C mov eax,dword ptr ss:[esp+0x1C]
0040138A |. 83C4 0C add esp,0xC
0040138D |. 55 push ebp ; /String2 = "?"
0040138E |. 50 push eax ; |String1 = "?U嬱吷厎."
0040138F |. FF15 04404000 call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
00401395 |. F7D8 neg eax ; kernel32.BaseThreadInitThunk
00401397 |. 1BC0 sbb eax,eax ; kernel32.BaseThreadInitThunk
00401399 |. 5F pop edi ; kernel32.757333AA
0040139A |. 5E pop esi ; kernel32.757333AA
0040139B |. 40 inc eax ; kernel32.BaseThreadInitThunk
0040139C |. 5D pop ebp ; kernel32.757333AA
0040139D \. C3 retn

使用OllyDBG分析

  • 啟動OllyDBG
  • 按下快捷鍵F3
  • 選擇TraceMe.exe
  • 根據分析我們需要在00401340的地址下斷點

    註:記得先移除或是停用之前設定的斷點

  • 在反匯編視窗按下Ctrl + G
  • 輸入00401340
  • 然後在00401340 /$ 55 push ebp上面設定斷點

  • 按下F9執行程式
  • 輸入用戶名abcdefg、序列號12345678
  • 按下Check按鈕
  • 會斷點在00401340 /$ 55 push ebp
  • F8一步一步分析

** 注意:以下的過程會牽涉到大量的組合語言以及CUP暫存器的一些相關知識 **


補充知識

在開始之前,我先補充一些我們可能會需要先了解的知識,這樣在下方的解釋過程中,可以加速大家理解

  • 暫存器(寄存器)
    可以比喻為CPU身上的口袋,可以在過程中對這些暫存器進行存/取的動作。
    當然每個暫存器的用途特性都不盡相同。
    每個暫存器的的大小都是32位元,也就是四個字元(Byte);可容納的資料範圍為0 ~ FFFFFFFF (無符號數)。

    註:更詳細的可以參考x86組合語言 - 第二章 | x86架構及暫存器解釋

  • EIP 暫存器?
    EIP暫存器包含下一條將要執行的指令的位址。
    EIP只能在一個call指令後從堆疊讀出。

    註:白話文就是,把下一次要執行的指令位置,先放在這個暫存器立面。

  • Call xxxxxxxx 是什麼?
    等同於 push eip 然後 jmp xxxxxxxxx

  • C/C++資料長度
    BYTE = 8 BIT
    CHAR = 1 BYTE
    INT = 4 BYTE
    DOUBLE = 8 BYTE
    LONG = 4 BYTE
    SHORT = 2 BYTE
    WORD = 2 BYTE
    DWORD = 4 BYTE = 32BIT

  • 有號數運算(比較)後使用的條件跳越指令

指令 意義 示意 條件
jl
jngl
若低於則跳越
若不大於或等於則跳越
x < y SF≠OF
jle
jng
若低於或等於則跳越
若不大於則跳越
x≦y ZF=1或SF≠OF
  • 暫存器

指令分析

(以下為不負責任分析,在沒有非常了解暫存器和組合語言的情況下,可能有部分的解釋上沒這麼正確或是錯誤)

1
00401341  |.  8B6C24 0C     mov ebp,dword ptr ss:[esp+0xC]

當前esp = 0018F678
0018F678 + 0xC = 0x18F684
0x18F684 的值 = 61 62 63 64 65 66 67 00 3B 00 00 00 08 00 00 00 abcdefg.;......

註:在資料視窗那邊按下Ctrl + G輸入0x18F684就可以找到值了

mov ebp, dword ptr ss:[0x18F684]
解釋:將0x18F684位置放在ss的堆疊指標暫存器,然後dword ptr,表示取得4 BYTE(四個位元組)的資料,也就是0018F6D8 61 62 63 64 65 66 67 00 abcdefg.然後mov ebp,也就是將這個存到ebp裡面

所以這行指令執行完後,ebp = 0018F6D8 ; ASCII “abcdefg”


1
00401347  |.  8B7C24 18     mov edi,dword ptr ss:[esp+0x18]

當前esp = 0018F670
0018F670 + 0x18 = 0x18F688
0x18F688 的值 = 07 00 00 00 01 00 00 00 ......

註:在資料視窗那邊按下Ctrl + G輸入0x18F688就可以找到值了

mov edi,dword ptr ss:[esp+0x18]
解釋:將0x18F688位置放在ss的堆疊指標暫存器,然後dword ptr,表示取得4 BYTE(四個位元組)的資料,也就是0018F688 07 00 00 00 01 00 00 00 ......然後mov edi,也就是將這個存到edi裡面

所以這行指令執行完後,edi = 00000007


1
0040134B  |.  B9 03000000   mov ecx,0x3

單純把ecx存成00000003


1
2
00401350  |.  33F6          xor esi,esi
00401352 |. 33C0 xor eax,eax

將esi 和 eax的暫存器,變成00000000


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
00401356  |. /7E 21         jle short TraceMe.00401379
00401358 |. |53 push ebx
00401359 |> |83F8 07 /cmp eax,0x7
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
|指令...
00401376 |.^|7C E1 \jl short TraceMe.00401359
00401378 |. |5B pop ebx ; user32.GetDlgItemTextA
00401379 |> \56 push esi ; /<%ld> = 0x0

當前edi = 00000007
當前ecx = 00000003

cmp edi,ecx 表示比較edi和ecx是否相同

jle short TraceMe.00401379 表示edi如果等於或是小於eax就跳轉實現,就會跳到00401379的位置
所以這邊是沒有跳轉,會繼續執行下面的的指令...

註:這邊表示你輸入的用戶名長度必須要大於3個字元 (程式有要求須要大於四個字元以上,這是在之前就有被判斷了)


** 以下將會是這個序號的計算的核心過程,會比較複雜一點,我盡量透過文字將其解釋清楚 **

1
2
3
4
5
6
7
8
9
10
11
12
13
00401359  |> /83F8 07       /cmp eax,0x7
0040135C |. |7E 02 |jle short TraceMe.00401360
0040135E |. |33C0 |xor eax,eax
00401360 |> |33D2 |xor edx,edx
00401362 |. |33DB |xor ebx,ebx
00401364 |. |8A1429 |mov dl,byte ptr ds:[ecx+ebp]
00401367 |. |8A98 30504000 |mov bl,byte ptr ds:[eax+0x405030]
0040136D |. |0FAFD3 |imul edx,ebx
00401370 |. |03F2 |add esi,edx
00401372 |. |41 |inc ecx
00401373 |. |40 |inc eax
00401374 |. |3BCF |cmp ecx,edi
00401376 |.^\7C E1 \jl short TraceMe.00401359

首先這段指令算是一個迴圈,透過用戶名來做計算序號,從哪看出是個迴圈?
最後兩行cmp ecx,edijl short TraceMe.00401359
注意到jl short TraceMe.00401359是向上跳轉的,而cmp ecx,edi是在比較,目前用戶名的個字元,是否都已經都被計算過了,就是Count的概念。

好的以下開始講解每一行指令


1
2
3
4
00401359  |> /83F8 07       /cmp eax,0x7
0040135C |. |7E 02 |jle short TraceMe.00401360
0040135E |. |33C0 |xor eax,eax
00401360 |> |33D2 |xor edx,edx

當前eax = 00000000
0x7 = 00000007

cmp eax,0x7 表示比較edi和ecx是否相同
jle short TraceMe.00401360 表示eax如果等於或是小於00000007就跳轉實現,就會跳到00401360的位置
也就是說跳過xor eax,eax 不做eax的暫存器,變成00000000的動作

註:為什麼要做eax變為00000000的動作,是因為他的序號計算規則的限制,這個實際在分析過程,我相信你會了解他的用意

xor eax,eax
xor edx,edx

將eax 和 edx的暫存器,變成00000000

注:eax的動作是有條件的,如上面剛剛分析的。


1
2
3
4
5
6
7
8
00401364  |. |8A1429        |mov dl,byte ptr ds:[ecx+ebp]
00401367 |. |8A98 30504000 |mov bl,byte ptr ds:[eax+0x405030]
0040136D |. |0FAFD3 |imul edx,ebx
00401370 |. |03F2 |add esi,edx
00401372 |. |41 |inc ecx
00401373 |. |40 |inc eax
00401374 |. |3BCF |cmp ecx,edi
00401376 |.^\7C E1 \jl short TraceMe.00401359

第一次進來的時候
ecx = 00000003
ebp = 0018F6D8

00000003 + 0018F6D8 = 0x18F6DB
0x18F6DB 的值 = 64 65 66 67 00 3B 00 00 defg.;..

註:在資料視窗那邊按下Ctrl + G輸入0x18F6DB就可以找到值了

mov dl,byte ptr ds:[ecx+ebp]
解釋:將0x18F6DB位置放在ds的資料區段暫存器,然後byte ptr,表示取得一個字元的資料,也就是0018F6DB 64 d然後mov dl,也就是將這個存到dl裡面

dl 是 8-bit的,所以這時候edx會變成 00000064

註:為什麼我是mov 到 dl 卻是 edx 變動?這時候請參考補充知識的最後一張圖,有清楚的表示dl和edx的關係


第一次進來的時候
eax = 00000000
eax + 00405030 = 00405030
00405030 的值 = 00405030 0C 0A 13 09 0C 0B 0A 08 .... .

註:在資料視窗那邊按下Ctrl + G輸入00405030就可以找到值了

mov bl,byte ptr ds:[eax+0x405030]
解釋:將00405030位置放在ds的資料區段暫存器,然後byte ptr,表示取得一個字元的資料,也就是00405030 0C .然後mov bl,也就是將這個存到bl裡面

bl 是 8-bit的,所以這時候ebx會變成 0000000C

註:為什麼我是mov 到 bl 卻是 ebx 變動?這時候請參考補充知識的最後一張圖,有清楚的表示bl和ebx的關係


經過第一次的指令過程中

edx = 00000064
ebx = 0000000C

imul edx,ebx 表示作為相乘,並放到edx裡,00000064 * 0000000C = 0x4B0
edx = 0x4B0

註:0x4B0 = 十進制的 1200


esi = 00000000
edx = 000004B0

add esi,edx 表示為相加,並放到esi裡,00000000 + 000004B0 = 0x4B0
esi = 0x4B0


inc ecx 表示將ecx + 1
inc eax 表示將eax + 1

註:這就像是程式裡面for迴圈的i++的概念


當前
ecx = 00000004
edi = 00000007

cmp ecx,edi 表示比較ecx和是否edi相同
jl short TraceMe.00401359 表示ecx如果小於edi就跳轉實現,就會跳到00401359的位置
也就是說還沒有把用戶名都計算完的意思

註:這邊如同一開始所說,是個迴圈,建議大家在OD裡面試著動手做分析,一定可以很清楚知道計算過程,我這邊只是把每一行在做什麼做個說明。


1
2
3
4
5
6
7
8
9
10
00401378  |.  5B            pop ebx
00401379 |> 56 push esi ; /<%ld> = 4B0 (1200.)
0040137A |. 68 78504000 push TraceMe.00405078 ; |Format = "%ld"
0040137F |. 55 push ebp ; |s = 0018F6D8
00401380 |. FF15 9C404000 call dword ptr ds:[<&USER32.wsprintfA>] ; \wsprintfA
00401386 |. 8B4424 1C mov eax,dword ptr ss:[esp+0x1C]
0040138A |. 83C4 0C add esp,0xC
0040138D |. 55 push ebp ; /String2 = "abcdefg"
0040138E |. 50 push eax ; |String1 = 00000001 ???
0040138F |. FF15 04404000 call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA

這邊就不一行一行去說明了,簡單來說就是上面把序號計算完畢後,把計算過後的序號放在ebp裡面
然後把你輸入的序號放在eax裡面。

最後用lstrcmpA來比對這兩個值是否相同,然後這個call回傳結果回去

總結

分析組合語言的過程,在你看不懂也沒有經驗的情況下,會非常痛苦!但是當你試著去了解每個指令和CPU暫存器的一些用途特性後,會發現其實沒這麼難,只是過程中會很花時間,但是當你分析成功後,絕對會獲得很大的成就感和很大進步!

這邊沒有根據計算方式去做出一個註冊機,是因為我對於組合語言或是C語言都還不熟,也還沒有去用網路上提供的註冊機產生器,所以這邊就沒有去實作這塊,這在未來有碰到,就會特別在寫文章。


註:我這邊提供一些可以參考的文章 (以上的練習我沒事先參考)
CSDN billvsme的专栏OllyDbg 使用笔记 (二)