高级语言逆向基础

第一节、函数及函数调用过程
一、函数简介
函数的组成:
        (1)、函数名:其定义规则和变量名的定义规则一样,是一个符合C++语法要求的标识符,但尽量不要用下划线开头,以避免和编译器的变量或函数重名。
        (2)、参数:可以有0或多个,用于向函数传送数值或从函数带回数值。每个参数都有自己的类型。
        (3)、返回值:指定函数返回的值。若没有返回值,则返回类型位void。
        (4)、函数体:花括号中完成函数功能的语句。
        举例,编写一个函数,用于获取两个整数中的较大值:

int max(int a, int b) {
	if (a > b)
		return a;
	else
		return b;
}

二、函数调用过程
函数的一般调用过程如下:
        (1)、将函数参数入栈,第一个参数在栈顶,最后一个参数在栈底(由右向左压栈)。
        (2)、执行CALL指令,调用该函数,进入该函数代码空间。
        (3)、将CALL指令下一行代码的地址入栈。
        (4)、进入函数代码空间后,将基址指针EBP入栈,然后让基址指针EBP指向当前栈的栈顶,并用它访问存在栈中的函数输入参数及栈中的其他数据。
        (5)、栈指针ESP减少一个值,即向低位移动一段距离,用来预留一段空间给该函数作为临时存储区。
        (6)、函数正式被执行。
        (7)、函数执行完毕返回调用处继续执行后继指令。
        执行完被调用的函数之后,将当前EBP的值传给堆栈指针ESP,恢复ESP,基址指针EBP出栈,恢复EBP,最后执行RET指令,返回调用点的下一条指令。
        例如:调用一个带有两个参数的函数:
push 参数2
push 参数1
call 函数地址
push ebp
mov ebp esp
sub esp,xxx
函数返回时,则有
add esp,xxx
pop ebp
retn
        在函数的调用过程中,会有相应的函数调用约定。常用的调用约定有:C调用约定、标准调用约定、FastCall调用约定和C++调用约定。
1、C调用约定
        C语言调用约定又称cdecl调用约定,是x86体系结构中C编译器使用的默认调用约定。该调用约定规定调用方将参数按照从右往左的顺序入栈。在调用完成之后同样由调用方将参数从堆栈中清除。
        举例如下,在主程序输入两个数值,调用上述函数输出较大值:

#include<iostream>
using namespace std;
int max(int a, int b);
int main() {
	int x, y,above;
	cout << "请输入两个整数(用空格隔开):";
	cin >> x >> y;
	above = max(x, y);
	cout << "您输入的较大的值为:" << above << endl;
	system("pause");
	return 0;
}
int max(int a, int b) {
	if (a > b)
		return a;
	else
		return b;
}

        用IDA打开可执行文件,找到函数的调用过程如下:参数从右到左入栈,然后调用函数sub_41132A
在这里插入图片描述
2、标准调用约定
        标准调用约定:是指在函数声明过程中使用了_stdcall修饰符。如:
int _stdcall max(int a,int b);
        它和C调用约定唯一的区别就是,标准调用约定在调用完成之后调用方无需从堆栈中删除参数,由被调用方在调用完成时删除堆栈中的函数参数。
        再次运行生成可执行文件,然后用IDA打开,如下,函数调用之后,比上面的少了一条指令:add esp,8
在这里插入图片描述
3、FastCall调用约定
        FastCall调用约定可以看作是stdcall约定的一种改进版,它向寄存器(ECX和EDX)最多传递两个参数,若参数大于两个,则按照stdcall约定,参数入栈。函数返回时由被调用方从堆栈中删除参数。
        求三个数的最大值,程序如下:

#include<iostream>
using namespace std;
int max(int a, int b);
int _fastcall Max(int a, int b, int c);
int main() {
	int x, y,z,above;
	cout << "请输入三个整数(用空格隔开):";
	cin >> x >> y >> z;
	above = Max(x, y, z);
	cout << "您输入的较大的值为:" << above << endl;
	system("pause");
	return 0;
}
int max(int a, int b) {
	if (a > b)
		return a;
	else
		return b;
}
int _fastcall Max(int a, int b, int c) {
	int t;
	t = max(a, b);
	if (c > t)
		return c;
	else
		return t;
}

用IDA打开如下:
在这里插入图片描述
        将最后一个参数入栈,第二个参数送入edx,然后把第一个参数送入ecx.
第二节、启动函数和入口点
一、启动函数

        操作系统实际上并不调用我们所写的入口点函数,而是调用由C/C++运行库实现并在链接时使用-entry:命令行选项来设置的一个C/C++运行时启动函数。所有启动函数所做的工作大致相同。启动函数执行的功能如下:
        (1)获取指向新进程的完整命令行的一个指针;
        (2)获取指向新进程的环境变量的一个指针;
        (3)初始化C/C++运行库的全局变量;
        (4)初始化C运行库内存分配函数以及其他的一些底层的I/O例程使用的堆;
        (5)调用所有全部和静态C++类对象的构造函数。
二、入口点
        完成启动函数的所有操作之后,C/C++就会调用相应的入口点函数。在PE文件中入口点是一个内存地址。
        入口点函数的原型如下:
int WINAPI WinMain(
HINSTANCE hInstanceExe, //实际值为一个内存基地址
HINSTANCE, //用于16位系统,对32位系统都位NULL
PTSTR pszCmdLine, //用来运行程序的命令行
int nCmdShow); //用来指明程序最初如何显示
第三节、控制语句
一、if-else语句
        编写代码,判断输入的年份是否正确,如下:test.cpp

#include <windows.h>
#include<iostream>
using namespace std;
void main()
{
	int year;
	cout << "请输入年份:";
	cin >> year;
	if (year == 2018) {
		MessageBox(NULL, "恭喜你,输入正确...", "验证通过", MB_OKCANCEL);
	}
	else {
		MessageBox(NULL, "输入错误...", "验证失败", MB_OKCANCEL);
	}
}

运行程序:
在这里插入图片描述
在这里插入图片描述
用IDA打开程序,程序结构如下:在IDA中,红色箭头表示False,绿色箭头表示TRUE。通过如下视图可清晰看到程序的两个分支结构,且不存在循环:
在这里插入图片描述
通过观察汇编代码,可发现关键跳转:jnz short loc_4123FC.在破解时只需修改该跳转即可绕过验证。
在这里插入图片描述
也可以利用IDA的F5插件功能,得到程序的伪代码,从程序中便可直接看到明文给出的验证数字2018如下:
在这里插入图片描述
在反汇编时,会发现for循环和while循环的操作过程和if-else语句类似,只是该语句的循环进行。

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读