大神论坛

找回密码
快速注册
查看: 619 | 回复: 0

[语言编程类] 大神论坛 逆向脱壳分析基础学习笔记二十二 汇编 指针(...

主题

帖子

5

积分

初入江湖

UID
20
积分
5
精华
威望
10 点
违规
大神币
68 枚
注册时间
2021-03-14 10:40
发表于 2021-03-14 23:52
本帖最后由 kay2kay 于 2021-03-14 23:52 编辑

本文为本人的滴水逆向破解脱壳学习笔记之一,为本人对以往所学的回顾和总结,可能会有谬误之处,欢迎大家指出。
陆续将不断有笔记放出,希望能对想要入门的萌新有所帮助,一起进步


所有笔记链接:

大神论坛 逆向脱壳分析基础学习笔记一 进制篇
大神论坛 逆向脱壳分析基础学习笔记二 数据宽度和逻辑运算
大神论坛 逆向脱壳分析基础学习笔记三 通用寄存器和内存读写
大神论坛 逆向脱壳分析基础学习笔记四 堆栈篇
大神论坛 逆向脱壳分析基础学习笔记五 标志寄存器 
大神论坛 逆向脱壳分析基础学习笔记六 汇编跳转和比较指令
大神论坛 逆向脱壳分析基础学习笔记七 堆栈图(重点)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记八 反汇编分析C语言
大神论坛 逆向脱壳分析基础学习笔记九 C语言内联汇编和调用协定
大神论坛 逆向脱壳分析基础学习笔记十 汇编寻找C程序入口(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十一 汇编C语言基本类型
大神论坛 逆向脱壳分析基础学习笔记十二 汇编 全局和局部 变量(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十三 汇编C语言类型转换(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十四 汇编嵌套if else(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十五 汇编比较三种循环(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十六 汇编一维数组(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十七 汇编二维数组 位移 乘法(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十八 汇编 结构体和内存对齐(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十九 汇编switch比较if else(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十 汇编 指针(一)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十一 汇编 指针(二)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十二 汇编 指针(三)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十三 汇编 指针(四)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十四 汇编 指针(五) 系列完结(需登录才能访问)

更多逆向脱壳资源,请访问  大神论坛

指针三

通过先前指针的学习,了解了指针和地址以及数据的关系,现在结合先前的知识继续学习巩固

指针遍历数组

有了先前的基础,再来看看如何用指针遍历数组

代码

#include "stdafx.h"
void function(){
short arr[5]={1,2,3,4,5};

short* p=&arr[0];
short* p2=arr;

if(p==p2){
printf("equal\n");
}else{
printf("not equal\n");
}

int i;
for(i=0;i<5;i++){
int j=*(p+i);
printf("addr:%x value:%d\n",p+i,j);
}
}
int main(int argc, char* argv[])
{
function();
return 0;
}

代码说明

稍微说明的一下代码部分

主要是声明了一个数组,然后用两种方法取得数组的首地址,一种是&arr[0],另一种则直接是arr

后面则是通过循环配合指针遍历数组成员并输出

按顺序依次涉及先前指针笔记的知识点

  1. 取变量地址
  2. 指针赋值
  3. 指针之间的比较
  4. 取地址中存储数据
  5. 指针的加减

运行结果

image-20210310153516983


可以看到通过&arr[0]和arr取数组首地址得到的结果是一致

并且能够通过指针来输出数组成员的地址和对应的数据

这里还会观察到数组里的每个地址都相差2(short类型的数据宽度),和先前数组的学习又匹配上了


反汇编代码

11:       short arr[5]={1,2,3,4,5};
0040D7A8 mov word ptr [ebp-0Ch],offset function+1Ch (0040d7ac)
0040D7AE mov word ptr [ebp-0Ah],offset function+22h (0040d7b2)
0040D7B4 mov word ptr [ebp-8],offset function+28h (0040d7b8)
0040D7BA mov word ptr [ebp-6],offset function+2Eh (0040d7be)
0040D7C0 mov word ptr [ebp-4],offset function+34h (0040d7c4)
12:
13: short* p=&arr[0];
0040D7C6 lea eax,[ebp-0Ch]
0040D7C9 mov dword ptr [ebp-10h],eax
14: short* p2=arr;
0040D7CC lea ecx,[ebp-0Ch]
0040D7CF mov dword ptr [ebp-14h],ecx
15:
16: if(p==p2){
0040D7D2 mov edx,dword ptr [ebp-10h]
0040D7D5 cmp edx,dword ptr [ebp-14h]
0040D7D8 jne function+59h (0040d7e9)
17: printf("equal\n");
0040D7DA push offset string "equal\n" (00422fbc)
0040D7DF call printf (0040d710)
0040D7E4 add esp,4
18: }else{
0040D7E7 jmp function+66h (0040d7f6)
19: printf("not equal\n");
0040D7E9 push offset string "not equal\n" (00422fb0)
0040D7EE call printf (0040d710)
0040D7F3 add esp,4
20: }
21:
22: int i;
23:
24: for(i=0;i<5;i++){
0040D7F6 mov dword ptr [ebp-18h],0
0040D7FD jmp function+78h (0040d808)
0040D7FF mov eax,dword ptr [ebp-18h]
0040D802 add eax,1
0040D805 mov dword ptr [ebp-18h],eax
0040D808 cmp dword ptr [ebp-18h],5
0040D80C jge function+0A8h (0040d838)
25: int j=*(p+i);
0040D80E mov ecx,dword ptr [ebp-18h]
0040D811 mov edx,dword ptr [ebp-10h]
0040D814 movsx eax,word ptr [edx+ecx*2]
0040D818 mov dword ptr [j],eax
26: printf("addr:%x value:%d\n",p+i,j);
0040D81B mov ecx,dword ptr [j]
0040D81E push ecx
0040D81F mov edx,dword ptr [ebp-18h]
0040D822 mov eax,dword ptr [ebp-10h]
0040D825 lea ecx,[eax+edx*2]
0040D828 push ecx
0040D829 push offset string "addr:%x value%d:\n" (00422f9c)
0040D82E call printf (0040d710)
0040D833 add esp,0Ch
27: }
0040D836 jmp function+6Fh (0040d7ff)
28:
29: }

反汇编分析

由于循环和数组等相关的知识在先前的笔记已经详细学习过了,这里就直接看指针相关的代码

25:           int j=*(p+i);
0040D80E mov ecx,dword ptr [ebp-18h]
0040D811 mov edx,dword ptr [ebp-10h]
0040D814 movsx eax,word ptr [edx+ecx*2]
0040D818 mov dword ptr [j],eax

1.将ebp-18h里的值赋值给ecx,这里的[ebp-18h]其实对应的就是 i

0040D80E   mov         ecx,dword ptr [ebp-18h] (i)

2.将ebp-10h里的值赋值给edx,这里的[ebp-10h]其实对应的是p,即数组首地址

0040D811   mov         edx,dword ptr [ebp-10h] (p)

3.movsx是带符号扩展赋值,将edx+ecx2,也就是p+i\数据宽度地址里存储的值赋给eax

0040D814   movsx       eax,word ptr [edx+ecx*2]

为什么要使用movsx指令?

内存对齐的结果,先前的笔记就提到过:char short 在计算时都会转变为dword宽度来进行计算        

相关笔记可以参考:

逆向基础笔记十三 汇编C语言类型转换

逆向基础笔记十八 汇编 结构体和内存对齐


4.将前面暂存在寄存器中的值赋给变量j

0040D818   mov         dword ptr [j],eax

指针翻转数组

翻转数组思想

翻转数组的思想就是从数组两端(数组首部和数组尾部)开始然后逐渐向中间靠拢,相互交换数组中的内容

image-20210310163600879


代码

#include "stdafx.h"
void function(){
int arr[5]={1,2,3,4,5};

int* begin=&arr[0];
int* end=begin+4;

while(begin<end){
int tmp=*begin;
*begin=*end;
*end=tmp;
begin++;
end--;
}

int i;
for(i=0;i<5;i++){
printf("%d\n",arr[i]);
}

}
int main(int argc, char* argv[])
{
function();
return 0;
}

代码分析

数组翻转的关键代码是:

int* begin=&arr[0];
int* end=begin+4;

while(begin<end){
int tmp=*begin;
*begin=*end;
*end=tmp;
begin++;
end--;
}

1.获取数组首地址和尾地址

int* begin=&arr[0];
int* end=begin+4;

2.循环直到所有数组成员交换结束

while(begin<end){
}

3.取出begin中的数据放在临时变量中

int tmp=*begin;

4.用end里存储的值覆盖begin

*begin=*end;

5.将原本备份的begin的变量tmp赋值给end,此时已经完成了交换

*end=tmp;

6.继续交换,让指针向数组中间靠拢

begin++;
end--;

运行结果

image-20210310165950306

可以看到,数组成功翻转了


反汇编实现翻转数组

前面使用指针实现了数组的翻转,为进一步了解其本质,自己手写汇编代码实现翻转数组

下面的汇编代码中省略了 dword ptr ds:[],默认就是取dword

代码

#include "stdafx.h"
void function(){
int arr[5]={1,2,3,4,5};
int len=sizeof(arr)/sizeof(int)-1;
__asm{
xor ecx,ecx
_begin:
mov eax,len
sub eax,ecx

lea edx,[arr+ecx*4]
push edx
mov edx,[edx]
lea ebx,[arr+4*eax]
push ebx
mov ebx,[ebx]
xchg [arr+ecx*4],ebx
pop ebx

mov [ebx],edx
pop edx
inc ecx

cmp edx,ebx
jb _begin

}
int i;
for(i=0;i<5;i++){
printf("%d\n",arr[i]);
}

}
int main(int argc, char* argv[])
{
function();
return 0;
}

运行结果

image-20210310184633838

能够正确地实现相同的功能O(∩_∩)O


反汇编代码分析

__asm{
xor ecx,ecx
_begin:
mov eax,len
sub eax,ecx

lea edx,[arr+ecx*4]
push edx
mov edx,[edx]
lea ebx,[arr+4*eax]
push ebx
mov ebx,[ebx]
xchg [arr+ecx*4],ebx
pop ebx

mov [ebx],edx
pop edx
inc ecx

cmp edx,ebx
jb _begin

}

1.将ecx清零,初始化ecx,ecx在这里是作为偏移量来使用的(刚开始为首地址的偏移,后来慢慢往中间靠拢)

xor ecx,ecx

2.声明一个程序段,后续跳转会用到

_begin:

3.将数组的长度减1的值赋给eax,因为数组从0开始,所以要减1

mov eax,len

4.用先前的eax减去ecx获得偏移(刚开始为尾地址的偏移,后来慢慢往中间靠拢)

sub eax,ecx

5.通过数组首地址加上ecx偏移取得地址,刚开始取得的为首地址,相当于edx=begin

lea edx,[arr+ecx*4]

6.将前面获得的地址edx放入堆栈中

push edx

7.取出edx中的值,这里相当于edx=*begin=tmp

mov edx,[edx]

8.通过数组首地址加上偏移eax取得地址,刚开始取得的为尾地址,相当于ebx=end

lea ebx,[arr+4*eax]

9.将前面获得的地址ebx放入堆栈中

push ebx

10.取出ebx中的值,这里对相当于ebx=*end

mov ebx,[ebx]

11.交换arr+ecx*4(*begin)和ebx(*end)里存储的值,这里相当于*begin=*end

xchg [arr+ecx*4],ebx

12.将先前的push的end的地址恢复到ebx,使得ebx=end

pop ebx

13.这里相当于*end=tmp,此时数组中的两个成员就已经交换完毕了

mov [ebx],edx

14.将先前push的begin的地址恢复到edx,使得edx=begin

pop edx

15.让ecx自增一,这里相当于begin++;end--; 因为这里end的偏移是通过len-begin的偏移得到的

inc ecx

16.比较edx和ebx

jb:jump below,小于则跳转(无符号),这里相当于while(begin<end)中的比较

如果begin<end则继续跳回去执行

cmp edx,ebx
jb _begin

总结

可以通过指针来存储数组的各成员地址,然后用指针来遍历数组,翻转数组


版权声明:本文由 lyl610abc 原创,欢迎分享本文,转载请保留出处

返回顶部