逆向-数组

Posted 嘻嘻兮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆向-数组相关的知识,希望对你有一定的参考价值。

对于数组而言,我们需要理解其数组的寻址公式,而其公式又与其内存的分布息息相关,所以当你了解了其内存分布,那么这个公式自然而然也能推出来。

首先数组有如下两点性质

连续性-排列连续
一致性-类型一致

因为有其连续的特点,所以其内存模型也就比较好理解,下面我们来看一下如下代码

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,对于多维数组的情况其实和上面都是一致的,这里就大概说说思想,如那三维数组来说,那么我们将其看成多个二维数组即可,或者说没一行就是一个二维,那么因为每行需要占用的空间固定,也就是二维数组的首地址就能确定了,下面就是老套路,调用二维的寻址公式,简单来说对于多维数组的寻址,就是一级一级的降维寻址。

以上是关于逆向-数组的主要内容,如果未能解决你的问题,请参考以下文章

逆向知识十三讲,汇编中数组的表现形式,以及还原数组

逆向知识十三讲,汇编中数组的表现形式,以及还原数组

解题报告hdu3308 LCIS

逆向-字符串

逆向-字符串

51Nod 1050 循环数组最大子段和 dp