鱼C论坛

 找回密码
 立即注册
查看: 4701|回复: 1

扫盲逆向基础

[复制链接]
发表于 2018-1-26 23:20:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 网友 于 2018-1-26 23:20 编辑

如果有错误请各位指正

参考书籍:

C++反汇编与逆向分析技术揭秘

汇编教学 by charme

老码识途


主要内容(不分先后顺序):

1.寻址Main函数入口点(为什么找3个push 1个call)

2.指令地址是如何计算出来的

3.所谓程序入口地址Main函数

4.调用约定(很重要)

5.汇编指令并非固定1+1=2(没兴趣可以无视)



使用到的工具:

OD

编译环境:VS2013 Debug



直接上代码:

非常经典的一个程序Hello World,麻雀虽小五脏俱全

很简单直接调用一个函数打印Hello World

oid Hello()
{
printf("Hello World\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("恭喜你找到了Main函数入口地址\n");
Hello();
return 0;
}

1.寻址Main函数入口点

找到Main函数入口点就要知道Main函数的入口特征,我们可以通过VS2013调试。F10走出Main函数
1.png
我们发现Main函数其实也是一个函数,并非程序一开始就调用Main(IDA可以看的更直观)
2.png
main函数定义有三个参数

main(int argc,char *argv[],char *envp[])

1.argc:命令行参数个数,整数

2.argv:命令行信息,保存字符串数组首地址的指针变量,是一个指向数组的指针。

3.envp:环境变量的信息,和argv类型相同

参数argv与envp就是两个指针数组,当数组作为参数时,实际上以指针方式进行数据传递,这两个参数可以转换为char**二级指针类型。

默认情况下argv第0项是保存路径字符串的首地址

1.1:OD打开EXE程序

方法1:
3.png
F7跟进遇到了两个call,先去看看第一个call有没有我们要找的特征(3个push  1个call)
4.png
很遗憾并不在第一个call

F7跟进第二个call找找看(找3个push 1个call的函数)
5.png

小知识(所谓程序入口地址Main函数):

       我们还可以发现程序并非从Main函数开始运行的,main函数也是一个参数,也需要被人调用。main函数只是我们语法规定的用户入口,而不是应用程序入口。


F7跟进历尽千辛万苦终于找到了
6.png

方法2:使用OD自带的字符串搜索法
11.png
12j.png
13.png

2:解析汇编代码

  我们用VS2013来调试比较方便
14.png
补充知识:

补码表示:既其正整数值求反加1。比如,-1就是1求反,它只有最后一位是0,其他位均为1,再加上1,那么所有位均为1,即0xFFFFFFFF。对有符号数,最高bit为1,则说明它是一个负数如果对一个补码形式的负数求正整值,就是求反加1。比如,对-1求其整数值,就是对0xFFFFFFFF求反,即0。再加1,就是1。

指令地址是如何计算出来的:

      E8是call的机器码,占一个字节,后面4个字节是call的地址,小端在内存中存放的是倒序的所以是FFFFFD8B。其实计算机寻址方式分为两种,一种是call 123432(绝对值),另外一种是相对定位就是用偏移量。偏移量是区分正与负(往前往后跳)。

      FF FF FD 8B最高位是1,所以是负数。将其取反+1后,结果是275,再用call当前的地址00381455-275=3811E0+5(call字节数)=3811E5H


2.1:一些基础知识

基础汇编知识:

        eax一般是当作函数的返回值

           push esp-4

           pop esp+4

Call指令= push 下一条指令(程序从上往下走,jmp到一个地方执行完代码你要记得你回去的路吧)

                jmp 标号处         

Ret指令= 将栈顶保存的地址弹入指令寄存器EIP,相当于"pop eip",从而让程序跳转到该地址。执行ret指令后,寄存器eip存储了栈顶保存的那个地址和ESP在(32位X86+4)的值有变化。

栈的理解:

         可以把栈理解成一个箱子,先放进去的东西最后才能拿出来(遵循先进后出的原则)

图片:

第一个先拿出来的是英语书
21.png
第二本拿出来的是数学书
22.png
最先进去的语文书,是最后拿出来的
23.png
2.2:函数汇编方式展示出来的样子

     我们必须要了解的是,调用call函数进去的时候它原来是什么样子,执行完call你要给它还原回去,所以每次进去函数就会看见push xxx,因为函数内部要用到
24.png
汇编代码详解:

003813C0  push        ebp                                                      //保存ebp,ebp就相当于esp的备份

003813C1  mov         ebp,esp                                              //esp后面要进行操作,所以要让ebp先帮它保存原来的值。这样esp怎么改变都不怕影响到后面的操作

003813C3  sub          esp,0C0h                                            //开辟空间

003813C9  push        ebx                                                     //保存寄存器,因为后面要用到

003813CA  push        esi                                                   

003813CB  push        edi                                                  

/*************主要功能把分配的空间全部填CC****************/

003813CC  lea            edi,[ebp-0C0h]                                 

003813D2  mov         ecx,30h

003813D7  mov         eax,0CCCCCCCCh

003813DC  rep stos    dword ptr es:[edi]   

详解:

      stos是串存储指令,它的功能是将eax中的数据放入edi所指的地址中,同时edi增加4个字节。rep使指令重复指令ecx中填写的次数。方括号表示存储器,这个地址实际上是edi的内容所指向地址。这里的stos其实对应的是stosd,其他还有stosb,stosw,对应的处理的是4,1,2字节,这里对堆栈中30h*4个字节初始化为0CC也就是int3指令,这样发生意外时执行堆栈里面的内容会引发调试中断      

/*************主要功能把分配的空间全部填CC****************/

printf("Hello World\n");

003813DE  mov         esi,esp

003813E0  push        385858h

003813E5  call        dword ptr ds:[389114h]

003813EB  add         esp,4                                                  //堆栈平衡后面会详解(调用约定)

003813EE  cmp         esi,esp                                              //VS自带的堆栈平衡检查

003813F0  call        __RTC_CheckEsp (0381140h)         //检查可以直接无视

003813F5  pop         edi                                                     //恢复寄存器 ,遵循先进后出的原则                        

003813F6  pop         esi

003813F7  pop         ebx

003813F8  add         esp,0C0h                                         //用完了记得还原分配的栈空间

003813FE  cmp         ebp,esp                                           //无视

00381400  call        __RTC_CheckEsp (0381140h)        //VS自带的检查

00381405  mov        esp,ebp                                           //ebp还给esp

00381407  pop         ebp                                                   //恢复ebp

00381408  ret                                                                    //ret返回

总结:

      其实很多汇编代码都是系统帮我们生成的,或则是VS自带的检查代码。不要看到汇编代码多就恐惧,很多代码都是套路。


3.调用约定

常用的调用约定分为四种:

_stdcall(windowsAPI默认调用方式):参数压栈方式右到左,函数内平衡,函数结束ret xxx

_cdecl(c/c++默认调用方式):参数压栈方式右到左,函数外平衡堆栈,call后面跟着add esp,xxx

_fastcall:参数压栈方式右到左,寄存器方式传参,函数内平衡堆栈

_thiscall:参数压栈方式右到左,ecx传递this指针,函数内平衡堆栈

渣渣代码:
/*
简要说明:
    this指针应属于指针类型,在32位环境下占4个字节大小,保存的数据为地址信息,"this"可翻译为"这个",因此经过字面的分析可以认为this指针保存了所属对象的首地址。
*/
#include "stdafx.h"
class TestClass
{
public:
TestClass(int Number)
{
m_nNumber = Number;
}
int m_nNumber;
};
void _fastcall Show_fastcall(int nNumA, int nNumB)
{
printf("_fastcall调用方式%d %d %d %d\n", nNumA, nNumB);
}
void _cdecl  Show_cdecl(int nNumA, int nNumB)
{
printf("_cdecl调用方式%d %d\n", nNumA, nNumB);
}
void _stdcall Show_stdcall(int nNumA, int nNumB)
{
printf("_stdcall调用方式%d %d\n", nNumA, nNumB);
}
int _tmain(int argc, _TCHAR* argv[])
{
Show_stdcall(7, 8);
Show_cdecl(10, 11);
Show_fastcall(22, 33);
TestClass Test;
Test.ShowThis(argc);
return 0;
}

_stdcall:参数压栈方式右到左,函数内平衡,函数结束ret xxx

Show_stdcall(7, 8);

00B137A8  push        8                                                                             //压栈方式右到左

00B137AA  push        7

00B137AC  call        Show_stdcall (0B1106Eh)

00B11580  ret         8                                                                              //函数内结束的时候ret 8  平衡堆栈
31.png   

_cdecl(c/c++默认调用方式):参数压栈方式右到左,函数外平衡堆栈,call后面跟着add esp,xxx

Show_cdecl(10, 11);

00B137B1  push        0Bh

00B137B3  push        0Ah

00B137B5  call        Show_cdecl (0B111D1h)

00B137BA  add         esp,8                                                  //函数外平衡堆栈


_fastcall:参数压栈方式右到左,寄存器方式传参,函数内平衡堆栈

好处是:传递效率高,因为是使用寄存器所以函数结束不用清空栈
坏处是:就只能用两个寄存器ecx,edx大于2就只能乖乖的用栈传递参数
Show_fastcall(22, 33);
00BF1553  mov         edx,21h
00BF1558  mov         ecx,16h
00BF155D  call        Show_fastcall (0BF1154h)
        return 0;
00BF1562  xor         eax,eax

例如这个例子:
_fastcall而寄存器比较少,它只使用了ecx,edx保存第一个和第二个参数,其余的放在堆栈操作
Show_fastcall(22, 33,44);
012D1553  push        2Ch       //大于2个参数只能用栈
012D1555  mov         edx,21h
012D155A  mov         ecx,16h
012D155F  call        Show_fastcall (012D11EAh)


call内部

012D1499  mov         esp,ebp
012D149B  pop         ebp
012D149C  ret         4         //函数内平衡堆栈


_thiscall:参数压栈方式右到左,ecx传递this指针,函数内平衡堆栈
32.png
33.png
4.善变的汇编语言(如果没兴趣可以直接无视)

   我记得有一天上课薛老师给我们发了一套某公司的面试题目是:栈的分配内存方式除了sub esp,xxx还有哪些?其实无非就是考你思维灵不灵活。并非只有1+1=2,条条大路通罗马。

分配栈内存主要是对esp做减法。

     push xxx

     pushad

     add esp,负数

     dec esp

例如call的调用

正常的call调用:

        push 0

        push 0

        push offset szText

        push 0

        call MessageBox

非正常call调用:

        push 0

        push 0

        push offset szText

        push 0

        push offset aa                        //返回地址

        push offset MessageBox       //调用函数

        lea esp,[esp+4]                      //+4等于返回地址

        jmp dword ptr [esp - 4]         //jmp DWORD ptr [esp-4]并不改变ESP的值等于调用函数MessageBox,但是esp值并没有改变还是指向返回地址

aa:

        xor eax,eax                             //垃圾代码,只是为了测试



另外发点小牢骚,我发帖保存的不能继续编辑有点蛋疼。发了我三四次,每次断网又要重来
致谢

感谢15PB老师们的辛勤栽培!   
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-10-19 17:24:10 | 显示全部楼层
感谢分享。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-22 01:42

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表