逆向分析技术

1 . 函数参数传递

调用约定 _cdecl _stdcall  _fastcall
传参顺序 左->右 右->左 寄存器和堆栈传参
平衡堆栈者 调用者 子程序 子程序
int _cdecl Add(char a, long b, int c, int d)

{
return (a + b + c + d);
}

push 4
push 3
push 2
push 1
call Add (1161A60h)
add esp,10h //调用者平衡堆栈

int _stdcall Add(char a, long b, int c, int d)
{
    return (a + b + c + d);
}

push 4 
push 3 
push 2 
push 1 
call Add (1161A60h) //子程序Add里面平衡堆栈

int _fastcall Add(char a, long b, int c, int d)

{
return (a + b + c + d);
}

push 4
push 3
mov edx,2 //寄存器传参
mov cl,1 //寄存器传参
call Add (11F1A60h) //子程序Add里面 平衡堆栈

_fastcall调用约定不同编译器传参不同:
1. MS VC: 最左边2个参数依次 ecx, edx
2. borland delphi/C++:最左边2个参数 eax, edx,ecx
3. watcom C: 最左边2个参数 eax, edx,ebx,ecx
4. 剩下的参数的全部esp传参

thiscall 也是寄存器传递参数, C++非静态成员函数的默认调用约定,
对象的每个成员函数隐含接受this指针。通过ecx传递this指针

class CSum
{
public:
int Add(int a, int b)
{
return (a + b);
}
};
void ClllDlg::OnBnClickedOk2()
{
CSum sum;
sum.Add(1, 2);
}

push 2
push 1
lea ecx,[sum]
call CSum::Add (1221B10h)

2. 函数返回值:

2.1函数返回值一般在eax中,如果返回值超过了eax容量,高32位存放在edx中

2.2.通过参数按传引用的方式返回值

以下代码就是通过参数按传引用的方式返回值
void max(int *a, int *b);
int main( )
{
int a=5,b=6;
max(&a, &b);
printf("a、b中较大的数是%d",a); //将最大的数显示出来
return 0;
}
void max( int *a, int *b)
{
if(*a < *b)
*a=*b; //经比较后,将较大的数放到a变量之中
}

3. 局部变量

3.1.利用堆栈存放局部变量,分配局部变量空间

形式1: 形式2:   形式3: 
sub esp, n
add esp, n
add esp,-n
sub esp, -n

push reg
pop reg

参数相对于 ebp是正的

局部变量相对于 esp是负的

3.2.利用寄存器存放局部变量

除了堆栈存放 局部变量外,6个通用寄存器也可以存放局部变量。

4. 全局变量
1. 全局变量一般存放在.data区块, 它是一个固定值,采用硬编码方式
mov dword ptr [4944A0h],7 //4944A0h就是全部变量的地址
mov eax,dword ptr [b]

5. 数组
5.1. 数组是连续数据的集合,汇编下访问数组是基址+变量的形式访问

#include <stdio.h>
int main(void)
{
static int a[3]={0x11,0x22,0x33};
int i,s=0,b[3];
for(i=0;i<3;i++)
{
s=s+a[i];
b[i]=s;
}

for(i=0;i<3;i++)
{
printf("%d\n",b[i]);
}
return 0;
}

mov eax,dword ptr [i]
mov ecx,dword ptr [s]
add ecx,dword ptr a (493078h)[eax*4]//493078h数组基址

 6.虚函数

虚函数是在程序运行时刻定义的函数, 虚函数的地址不能在编译时刻确定,所以虚函数引用
通常放在一个专有数组里-虚函数表(VTBL), 每一个虚函数对象都具有虚函数表指针(VPTR),
虚函数通过指向虚函数表的指针间接调用。

#include <stdio.h>
class CSum
{
public:
	virtual int Add(int a, int b)
	{
		return(a + b);
	}
	virtual	int	Sub(int a, int b )
	{
		return(a - b);
	}
};
void main()
{
	CSum*	pCSum = new CSum ;
	pCSum->Add(1,2);
	pCSum->Sub(1,2);
}

  

.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 argc = dword ptr 4
.text:00401000 argv = dword ptr 8
.text:00401000 envp = dword ptr 0Ch
.text:00401000
.text:00401000 push esi
.text:00401001 push 4
.text:00401003 call [email protected]@Z ;new为创建对象分配4个字节
.text:00401008 add esp, 4
.text:0040100B test eax, eax
.text:0040100D jz short loc_401019
.text:0040100F mov dword ptr [eax], offset off_4050A0 ;将4050A0写入创建对象的实例中,4050A0是虚函数表的指针。
.text:00401015 mov esi, eax
.text:00401017 jmp short loc_40101B
.text:00401019 ; ---------------------------------------------------------------------------
.text:00401019
.text:00401019 loc_401019: ; CODE XREF: _main+Dj
.text:00401019 xor esi, esi
.text:0040101B
.text:0040101B loc_40101B: ; CODE XREF: _main+17j
.text:0040101B mov eax, [esi]
.text:0040101D push 2
.text:0040101F push 1
.text:00401021 mov ecx, esi
.text:00401023 call dword ptr [eax]
.text:00401025 mov edx, [esi]
.text:00401027 push 2
.text:00401029 push 1
.text:0040102B mov ecx, esi
.text:0040102D call dword ptr [edx+4]
.text:00401030 pop esi
.text:00401031 retn
.text:00401031 _main endp

查看虚函数表 4050A0的数据
004050A0 40 10 40 00 50 10 40 00 FF FF FF FF 2E 11 40 00 @[email protected]@[email protected]
004050B0 42 11 40 00 5F 5F 47 4C 4F 42 41 4C 5F 48 45 41 [email protected]__GLOBAL_HEA

VTBL的2组数据
[VTBL] = 401040h
[VTBL+4] = 401050h
进一步看看这2个指针的内容

sub_401040 proc near
arg_0= dword ptr 4
arg_4= dword ptr 8
mov eax, [esp+arg_4]
mov ecx, [esp+arg_0]
add eax, ecx
retn 8
sub_401040 endp

sub_401050 proc near
arg_0= dword ptr 4
arg_4= dword ptr 8
mov eax, [esp+arg_0]
mov ecx, [esp+arg_4]
sub eax, ecx
retn 8
sub_401050 endp

原来 虚函数是通过虚函数表的指针 间接调用

时间: 2024-11-15 05:55:06

逆向分析技术的相关文章

《C++反汇编与逆向分析技术揭秘》--认识启动函数,找到用户入口

<C++反汇编与逆向分析>和<程序员的自我修养>都是以VC6的代码作为例子讲解的.这里是在vs2017下,CRT代码有些区别,但整体流程上都是初始化环境,设置参数,最后转到用户main函数. class COne { public: COne() { printf("COne \r\n"); } ~COne() { printf("~COne \r\n"); } }; COne g_One; int main() { printf("

《C++反编译与逆向分析技术揭秘》之学习笔记01--浮点数转IEEE编码

※浮点数转IEEE编码 1.float类型的IEEE编码(31,30~23,22~0=>符号位,指数位,尾数位)eg1:12.25 经过IEEE编码后的各位情况:符号位:0指数位:3+127, 10000010尾数位:100010000000000000000004字节二进制:0x41440000vc6.0截图: eg2:-0.125 经过IEEE编码后的各位情况:符号位:1指数位:-3+127, 01111100尾数位:000000000000000000000004字节二进制:0xBE000

《C++反编译与逆向分析技术揭秘》之学习笔记03--函数的调用方式

※函数的调用方式 EBP:扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部. ESP:(Extended stack pointer)是指针寄存器的一种,用于指向栈的栈顶. _cdecl:C/C++默认的调用方式,调用方平衡栈,不定参数的函数可以试用. 调用方:1.参数压栈.esp-=42.调用函数.3.实现栈平衡.esp+=4 此处的printf也是同样道理0x004010CB.0x004010CC两处压入参数,共8个字节

《C++反编译与逆向分析技术揭秘》之学习笔记02--结构体和类之内存分布

※结构体和类之内存分布 1.空类的大小空类:其实空类至少会占用1个字节的长度. 2.字节对齐在为结构体和类中的数据成员分配内存时,结构体中的当前数据成员类型长度为M,指定对齐值为N,那么实际对齐值位q=min(M,N),其成员的地址安排在q的倍数上. vc6.0缺省对齐8个字节sShort占用2个字节,所以安排的地址0x0012FF70为2的倍数.nInt占用4个字节,所以安排的地址0x0012FF74为4的倍数.因为结构体中最大的字段长度为4,所以对齐值调整为4个字节.因为test对象为8个字

《C++反汇编与逆向分析技术揭秘》之十——析构函数

局部对象 当对象所在作用域结束之后,销毁栈空间,此时析构函数被调用. 举例: 函数返回时自动调用析构函数: 堆对象 调用析构代理函数来处理析构函数: 为什么使用析构代理函数来调用析构函数?考虑到如果delete的对象是一个对象数组,可以使用析构代理函数依次析构所有的对象.举例: 调用了构造代理函数: vector deleting destructor函数中先对标志位进行判断: 如果没有跳转,表明delete的是一个数组,则会调用调用析构代理函数对对象进行逐一的析构.如果进行了跳转,则只进行一次

《C++反汇编与逆向分析技术揭秘》之十一——虚函数

虚函数的机制 当类中定义有虚函数时,编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表.编译器还会在类中添加一个虚表指针. 举例: CVirtual类的构造函数中没有进行任何操作,但是我们来看构造函数内部,还是有一个赋初值的操作: 这个地址指向的是一个数组: 这些数组中的内容就是虚函数的指针: 值得注意的是,如果没有虚指针的存在,那么CVirtual大小就是4字节.有了这个指针存在就是8字节. 本例子中,使用了一个空的构造函数,但是编译器自己擅自插入了代码,实现了对虚

《C++反汇编与逆向分析技术揭秘》--数据类型

  浮点数类型 IEEE标准从逻辑上采用一个三元组{S, E, M}来表示一个数N,它规定基数为2,符号位S用0和1分别表示正和负,尾数M用原码表示,阶码E用移码表示.根据浮点数的规格化方法,尾数域的最高有效位总是1,由此,该标准约定这一位不予存储,而是认为隐藏在小数点的左边,因此,尾数域所表示的值是1.M(实际存储的是M),这样可使尾数的表示范围比实际存储多一位.为了表示指数的正负,阶码E通常采用移码方式来表示,将数据的指数e 加上一个固定的偏移量后作为该数的阶码,这样做既可避免出现正负指数,

Android逆向分析之Xposed的hook技术

Android逆向工程里常用到的工具除了的dex2jar,jd-gui,  Apktool之外还有一个Xposed. 这个工具是一个在不修改APK的情况下,影响其运行过程的服务框架.可以根据自己的需求编写模块,让模块控制目标应用的运行. 因为本人也是新手,对于Xposed用法还有很多的不熟悉,所以只对其hook技术进行简单的介绍,并让hook技术应用到以后的逆向分析工程中. 至于什么是hook,不了解的话就先去百度一下,这里基于菜鸟有限的经验,我只能说是一种函数拦截技术~ 首先,下载Xposed

Android中的软件安全和逆向分析[二]—apk反破解技术与安全保护机制

在Android应用开发中,当我们开发完软件之后,我们不希望别人能够反编译破解我们的应用程序,不能修改我们的代码逻辑.实际上,在应用程序的安全机制考虑中,我们希望自己的应用程序安全性高,通过各种加密操作等来增大竞争对手的反编译破解成本.设想,竞争对手开发一个同样的应用程序需要10天,而破解我们的软件程序需要100天,那么势必会打消黑客程序员破解我们应用程序的念头.如何增加对手的破解成本,就需要考验我们应用程序的安全性有多高,加密技术有多强.一个优秀的应用程序,不仅能为用户带来利益,同时也能保护自