逆向-數組

對於數組而言,我們需要理解其數組的尋址公式,而其公式又與其內存的分佈息息相關,所以當你瞭解了其內存分佈,那麼這個公式自然而然也能推出來。

首先數組有如下兩點性質

連續性-排列連續
一致性-類型一致

因爲有其連續的特點,所以其內存模型也就比較好理解,下面我們來看一下如下代碼

int arr[5] = {0,1,2,3,4}

上面就是其內存模型,對於一致性的特點,可以推定其每個元素佔用的空間大小一致,上面的話就是每個元素佔4字節(int)

所以我們假設數組開始的首地址爲0x10000,那麼其各個元素在內存中佔的地址是多少呢

arr[0] 0x10000
arr[1] 0x10004
arr[2] 0x10008
arr[3] 0x1000c
arr[4] 0x10010

其實很明顯,上面就是一個等差數列,所以其一維數組的尋址公式爲

ary+sizeof(type)*n

ary[3] ->  0x10000 + 4 * 3 = 0x1000C

此時就可以很方便的定位到每一個元素了。下面來看一下對應的一維數組的反彙編

int main(int argc, char* argv[])
{
    int arr[5] = {1,2,3,4,5};
    printf("%d",arr[2]);
    printf("%d",arr[argc]);
    printf("%d",arr[argc%7]);
    for(int i=0;i < 5;++i)
    {
        printf("%d\r\n",arr[i]);
    }
    return 0;
}

對應的反彙編代碼

21:       int arr[5] = {1,2,3,4,5};
0040D7A8 C7 45 EC 01 00 00 00 mov         dword ptr [ebp-14h],1  //說明數組首地址爲ebp-0x14
0040D7AF C7 45 F0 02 00 00 00 mov         dword ptr [ebp-10h],2
0040D7B6 C7 45 F4 03 00 00 00 mov         dword ptr [ebp-0Ch],3
0040D7BD C7 45 F8 04 00 00 00 mov         dword ptr [ebp-8],4
0040D7C4 C7 45 FC 05 00 00 00 mov         dword ptr [ebp-4],5
22:       printf("%d",arr[2]); //arr[2] -> ebp-14 + 4 * 2 = ebp-0c
0040D7CB 8B 45 F4             mov         eax,dword ptr [ebp-0Ch]
0040D7CE 50                   push        eax
0040D7CF 68 1C 20 42 00       push        offset string "%d" (0042201c)
0040D7D4 E8 27 39 FF FF       call        printf (00401100)
0040D7D9 83 C4 08             add         esp,8
23:       printf("%d",arr[argc]);  // arr[argc]  ->  ebp-14h+argc*4
0040D7DC 8B 4D 08             mov         ecx,dword ptr [ebp+8]
0040D7DF 8B 54 8D EC          mov         edx,dword ptr [ebp+ecx*4-14h]  //換個寫法 [ebp-14h+ecx*4]
0040D7E3 52                   push        edx
0040D7E4 68 1C 20 42 00       push        offset string "%d" (0042201c)
0040D7E9 E8 12 39 FF FF       call        printf (00401100)
0040D7EE 83 C4 08             add         esp,8
24:       printf("%d",arr[argc%7]);
0040D7F1 8B 45 08             mov         eax,dword ptr [ebp+8]
0040D7F4 99                   cdq
0040D7F5 B9 07 00 00 00       mov         ecx,7
0040D7FA F7 F9                idiv        eax,ecx  //先計算結果此時,結果在edx
0040D7FC 8B 54 95 EC          mov         edx,dword ptr [ebp+edx*4-14h]  //尋址公式和上面相同
0040D800 52                   push        edx
0040D801 68 1C 20 42 00       push        offset string "%d" (0042201c)
0040D806 E8 F5 38 FF FF       call        printf (00401100)
0040D80B 83 C4 08             add         esp,8
25:       for(int i=0;i < 5;++i)
0040D80E C7 45 E8 00 00 00 00 mov         dword ptr [ebp-18h],0
0040D815 EB 09                jmp         main+90h (0040d820)
0040D817 8B 45 E8             mov         eax,dword ptr [ebp-18h]
0040D81A 83 C0 01             add         eax,1
0040D81D 89 45 E8             mov         dword ptr [ebp-18h],eax
0040D820 83 7D E8 05          cmp         dword ptr [ebp-18h],5
0040D824 7D 17                jge         main+0ADh (0040d83d)
26:       {
27:           printf("%d\r\n",arr[i]);
0040D826 8B 4D E8             mov         ecx,dword ptr [ebp-18h]
0040D829 8B 54 8D EC          mov         edx,dword ptr [ebp+ecx*4-14h]  //ebp-14h+ecx*4
0040D82D 52                   push        edx
0040D82E 68 20 20 42 00       push        offset string "%d\r\n" (00422020)
0040D833 E8 C8 38 FF FF       call        printf (00401100)
0040D838 83 C4 08             add         esp,8
28:       }

具體的註釋都寫在裏面了,其實最主要的就是套用數組的尋址公式。對於release版下其尋址表現形式差不多,要說區別的話可能就是循環最了點優化。

.text:00401005                 mov     edi, 5
;....
.text:00401069                 lea     esi, [esp+1Ch+var_14]  //數組首地址
.text:0040106D
.text:0040106D loc_40106D:                             ; CODE XREF: _main+81↓j
.text:0040106D                 mov     eax, [esi]
.text:0040106F                 push    eax
.text:00401070                 push    offset aD       ; "%d\r\n"
.text:00401075                 call    sub_401090
.text:0040107A                 add     esp, 8
.text:0040107D                 add     esi, 4
.text:00401080                 dec     edi  //edi爲計數器
.text:00401081                 jnz     short loc_40106D

對應其高級代碼還原

    int arr[5] = {1,2,3,4,5};
    int count = 5;
    int *pAddr = arr;
    do 
    {
        printf("%d",*pAddr);
        pAddr++;
        count--;
    } while (count != 0);

 

下面再來看看二維數組,對於二維數組而言,簡單點來說就是多個一維數組而言,既然同樣也是數組,那麼必然也逃不了開始的兩點性質。先來看一下下面的代碼

int arr[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};

我們先來看一下我們人體中大腦中的表現形式,也就是邏輯層面

  當編寫二維數組時,我們只需腦海中浮現對應的二維表現形式,那麼這個元素值就很好定位了

而在內存中,其表現形式就是把每行都拼接到上一行尾部,所以其本質就是個一維數組

對於二維數組的尋址公式而言,其實我們只需藉助上面的邏輯圖就可,首先我們可以將二維數組看成多個一位數組,如上面的案例則可看成3個一維數組

3 * int[5]

那麼此時我們只需要解決如何定位到每個一維數組的首地址即可,由於數組的一致性,所以其每一行佔用到固定大小已知,所以,n行指向乘以每行佔用的內存即可定位

ary[i][j]

ary + i * sizeof(type) * j  //一行有j個,i表示第n行
也可如下表示
ary + sizeof(ary[0]) * i
ary + sizeof(int[j]) * i

上面哪種表現形式都可,下面我們來驗證一下,如計算ary[2]的首地址

ary[2]

ary + sizeof(int[j]) * i
0x10000 + sizeof(int[5]) * 2
0x10000 + 20 * 2
0x10000 + 0x28
0x10028

其實我們可以使用一維數組來驗證一下,ary[2]其實相當於就是ary[2][0]的地址,那麼對於ary[2][0],通過上表的內存圖可以發現就是ary[10],所以使用一維數組公式進行驗證

ary[10]
0x10000 + 10 * sizeof(type)
0x10000 + 10 * 4
0x10000 + 40
0x10028

可以發現其結果一致。好了,既然得到其首地址,那麼對於ary[2][3]來說,再次使用一維數組的尋址即可,使其尋址到[3]的位置,具體尋址公式就直接看下面吧

(ary + sizeof(ary[0])*i) + sizeof(type) * j
       獲取首地址       加上    一維數組尋址


ary[2][3] ->
0x10000 + sizeof(int[5]) * 2 + sizeof(int) * 3
0x10028 + 4 * 3
0x10028 + 12
0x10034

ary[2]因爲上面有同樣的公式計算過,所以就直接給出了地址進行下一步的運算。下面再次使用一維數組來驗證一下,對於ary[2][3]而言,如果將其看成一維數組,那麼應該爲ary[13](2*5+3=13)

ary[13]
0x10000 + 13 * 4
0x10000 + 52
0x10034

OK,下面我們再來看一個優化,這裏的優化指的是計算優化,對於二維數組的尋址公式其發現需要多次的運算

ary + i * sizeof(type) * j  +  sizeof(type) * j

//sizeof(type) * j
第一個j表示一行有j個
第二個j表示獲取xxx[j]

那麼既然有公共的部分,自然可以提取優化,最終可以發現其可以減少一個乘法運算

ary[3][5]  //爲了避免將兩個j混亂,這裏直接替換常量(真實做法也就是常量)

ary + i * sizeof(type) * j  +  sizeof(type) * j
ary + sizeof(type) * i * 5  +  sizeof(type) * j
ary + sizeof(type) * (5*i+j)

好了,這裏的優化公式會體現到release版的尋址上,下面我們就來分析一下反彙編,對於二維就直接分析release版吧,畢竟debug就是原原本本的套公式即可

int main(int argc, char* argv[])
{
    int arr[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
    int i,j;
    scanf("%d %d",&i,&j);
    printf("%d",arr[2][3]);
    printf("%d",arr[i][3]);
    printf("%d",arr[2][j]);
    printf("%d",arr[argc%2][argc/2]);
    printf("%d",arr[i][j]);
    for(i=0;i < 3;i++)
    {
        for(j=0;j < 5;++j)
        {
            printf("%d",arr[i][j]);
        }
    }
    return 0;
}

對應的反彙編代碼

.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main           proc near               ; CODE XREF: start+AF↓p
.text:00401000
.text:00401000 var_44          = dword ptr -44h
.text:00401000 var_40          = dword ptr -40h
.text:00401000 var_3C          = dword ptr -3Ch
.text:00401000 var_38          = dword ptr -38h
.text:00401000 var_34          = dword ptr -34h
.text:00401000 var_30          = dword ptr -30h
.text:00401000 var_2C          = dword ptr -2Ch
.text:00401000 var_28          = dword ptr -28h
.text:00401000 var_24          = dword ptr -24h
.text:00401000 var_20          = dword ptr -20h
.text:00401000 var_1C          = dword ptr -1Ch
.text:00401000 var_18          = dword ptr -18h
.text:00401000 var_14          = dword ptr -14h
.text:00401000 var_10          = dword ptr -10h
.text:00401000 var_C           = dword ptr -0Ch
.text:00401000 var_8           = dword ptr -8
.text:00401000 var_4           = dword ptr -4
.text:00401000 argc            = dword ptr  4
.text:00401000 argv            = dword ptr  8
.text:00401000 envp            = dword ptr  0Ch
.text:00401000
.text:00401000                 sub     esp, 44h
.text:00401003                 lea     eax, [esp+44h+var_40]
.text:00401007                 lea     ecx, [esp+44h+var_44]
.text:0040100B                 push    eax
.text:0040100C                 push    ecx
.text:0040100D                 push    offset aDD      ; "%d %d"
.text:00401012                 mov     [esp+50h+var_3C], 1  //首地址
.text:0040101A                 mov     [esp+50h+var_38], 2
.text:00401022                 mov     [esp+50h+var_34], 3
.text:0040102A                 mov     [esp+50h+var_30], 4
.text:00401032                 mov     [esp+50h+var_2C], 5
.text:0040103A                 mov     [esp+50h+var_28], 6
.text:00401042                 mov     [esp+50h+var_24], 7
.text:0040104A                 mov     [esp+50h+var_20], 8
.text:00401052                 mov     [esp+50h+var_1C], 9
.text:0040105A                 mov     [esp+50h+var_18], 0Ah
.text:00401062                 mov     [esp+50h+var_14], 0Bh
.text:0040106A                 mov     [esp+50h+var_10], 0Ch
.text:00401072                 mov     [esp+50h+var_C], 0Dh
.text:0040107A                 mov     [esp+50h+var_8], 0Eh
.text:00401082                 mov     [esp+50h+var_4], 0Fh
.text:0040108A                 call    _scanf
.text:0040108F                 push    0Eh  //arr[2][3] 直接獲取結果
.text:00401091                 push    offset unk_408030
.text:00401096                 call    sub_401160
.text:0040109B                 mov     eax, [esp+58h+var_44] //獲取i
.text:0040109F                 lea     edx, [eax+eax*4] //edx = i*5 一行有5個元素
.text:004010A2                 mov     eax, [esp+edx*4+58h+var_30] //var_30相對於var_3C已經往後數了三個,這裏使用的是 arr[3] + arr[i] (之前的公式倒一下)
.text:004010A6                 push    eax   //arr[i][3]
.text:004010A7                 push    offset unk_408030
.text:004010AC                 call    sub_401160
.text:004010B1                 mov     ecx, [esp+60h+var_40]  //獲取j
.text:004010B5                 mov     edx, [esp+ecx*4+60h+var_14]  //arr[2]被常量化,相對於一維數組的arr[10],其值就是[esp+60h+var_14]
.text:004010B9                 push    edx  //arr[2][j]
.text:004010BA                 push    offset unk_408030
.text:004010BF                 call    sub_401160
.text:004010C4                 mov     eax, [esp+68h+argc]
.text:004010C8                 mov     ecx, eax
.text:004010CA                 and     ecx, 80000001h
.text:004010D0                 jns     short loc_4010D7
.text:004010D2                 dec     ecx
.text:004010D3                 or      ecx, 0FFFFFFFEh
.text:004010D6                 inc     ecx
.text:004010D7
.text:004010D7 loc_4010D7:                             ; CODE XREF: _main+D0↑j
.text:004010D7                 cdq
.text:004010D8                 sub     eax, edx  //ecx=argc%2
.text:004010DA                 lea     ecx, [ecx+ecx*4] //優化公式先計算 i*5
.text:004010DD                 sar     eax, 1 //eax = argc/2
.text:004010DF                 add     ecx, eax  //i*5+j
.text:004010E1                 mov     edx, [esp+ecx*4+68h+var_3C]  //最後乘以類型大小4
.text:004010E5                 push    edx
.text:004010E6                 push    offset unk_408030
.text:004010EB                 call    sub_401160
.text:004010F0                 mov     eax, [esp+70h+var_44]
.text:004010F4                 mov     ecx, [esp+70h+var_40]
.text:004010F8                 lea     edx, [ecx+eax*4] //i*4 + j
.text:004010FB                 add     eax, edx  //i*4+j + i = i*5+j
.text:004010FD                 mov     eax, [esp+eax*4+70h+var_3C] //這裏也是雷同使用優化公式
.text:00401101                 push    eax
.text:00401102                 push    offset unk_408030
.text:00401107                 call    sub_401160
.text:0040110C                 add     esp, 34h
.text:0040110F                 xor     eax, eax
.text:00401111                 mov     [esp+44h+var_44], eax
.text:00401115
.text:00401115 loc_401115:                             ; CODE XREF: _main+14C↓j
.text:00401115                 xor     ecx, ecx
.text:00401117                 mov     [esp+44h+var_40], ecx
.text:0040111B
.text:0040111B loc_40111B:                             ; CODE XREF: _main+142↓j
.text:0040111B                 lea     ecx, [ecx+eax*4]
.text:0040111E                 add     eax, ecx  //i*5+j
.text:00401120                 mov     edx, [esp+eax*4+44h+var_3C] //(i*5+j)*4 + arr
.text:00401124                 push    edx
.text:00401125                 push    offset unk_408030
.text:0040112A                 call    sub_401160
.text:0040112F                 mov     ecx, [esp+4Ch+var_40]
.text:00401133                 mov     eax, [esp+4Ch+var_44]
.text:00401137                 add     esp, 8
.text:0040113A                 inc     ecx
.text:0040113B                 cmp     ecx, 5 //ecx爲j
.text:0040113E                 mov     [esp+44h+var_40], ecx
.text:00401142                 jl      short loc_40111B
.text:00401144                 inc     eax
.text:00401145                 cmp     eax, 3 //eax爲i
.text:00401148                 mov     [esp+44h+var_44], eax
.text:0040114C                 jl      short loc_401115
.text:0040114E                 xor     eax, eax
.text:00401150                 add     esp, 44h
.text:00401153                 retn
.text:00401153 _main           endp

上面是IDA中粘貼出來的代碼,相對來說其代碼沒有debug那麼容易理解,在探究的過程中,建議先使用debug版本看一遍然後再看這個release。

OK,對於多維數組的情況其實和上面都是一致的,這裏就大概說說思想,如那三維數組來說,那麼我們將其看成多個二維數組即可,或者說沒一行就是一個二維,那麼因爲每行需要佔用的空間固定,也就是二維數組的首地址就能確定了,下面就是老套路,調用二維的尋址公式,簡單來說對於多維數組的尋址,就是一級一級的降維尋址。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章