1 利用指针数组实现多个函数劫持技术
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include"detours.h"
#pragma comment(lib, "detours.lib")
int newAdd(int a, int b) //static文件只能限制在本文件使用
{
printf("+++++++\n");
return a + b;
}
int newMinis(int a, int b)
{
printf("-----------\n");
return a - b;
}
int newMulti(int a, int b) //函数名是一个常量地址
{
printf("***********\n");
return a*b;
}
int newDel(int a, int b)
{
printf("///////////");
return a / b;
}
int add(int a, int b) //static文件只能限制在本文件使用
{
return a + b;
}
int minis(int a, int b)
{
return a - b;
}
int multi(int a, int b) //函数名是一个常量地址
{
return a*b;
}
int del(int a, int b)
{
return a / b;
}
static int(*OldFunction[4])(int, int) = { add, minis, multi, del }; //被劫持函数数组
int(*NewFunction[4])(int, int) = { newAdd, newMinis, newMulti, newDel }; //劫持函数数组
//开始拦截
void Hook()
{
DetourRestoreAfterWith(); //恢复原来的状态
DetourTransactionBegin(); //拦截开始
DetourUpdateThread(GetCurrentThread()); //刷新当前线程
//这里可以连续多次调用DetourAttach,表明Hook多个函数
for (int i = 0; i < 4; i++)
DetourAttach((void**)&OldFunction[i], NewFunction[i]); //实现函数拦截, 如果想拦截多个指令,只需添加多个拦截函数即可
DetourTransactionCommit(); //拦截生效
}
void main() //函数名实际上保存着这个函数的地址
{
int(*p)(int a, int b) = add; //函数指针
printf("1 + 2 = %d\n", add(1, 2));
p = minis;
printf("1 - 2 = %d\n", minis(1, 2));
Hook(); //开始劫持
int(*pp[4])(int a, int b) = { add, minis, multi, del }; //函数指针数组,注意函数的返货类型要一致
for (int i = 0; i < 4; i++)
{
printf("%d\n", pp[i](10, 2));
}
system("pause");
}
运行结果:主函数中的函数被替换为自己定义的函数。
2 函数指针和指针函数
辨别指针函数与函数指针最简单的方式就是看函数名前面的指针*号有没有被括号包含,如果被包含就是函数指针,反之则是指针函数。
2.1 指针函数
指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针
类型标识符 *函数名(参数表)
int *f(x,y);
首先它是一个函数,只不过这个函数的返回值是一个地址值。指针函数一定有函数返回值,而且在主调函数中,函数返回值必须赋给同类型的指针变量。例如
float *fun();
float *p;
p = fun(a);
当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。
由于返回的是一个地址,所以类型说明符一般都是int。例如:
int *GetDate();
int * aaa(int,int);
2.2 函数指针
函数指针是指向函数的指针变量,即本质是一个指针变量。
int (*f)(int x); /*声明一个函数指针 */
f=func; /*将func函数的首地址赋给指针f */
指向函数的指针包含了函数的地址,可以通过它来调用函数。声明格式如下:
类型说明符 (*函数名)(参数)
例如:
void (*fptr)();
把函数的地址赋值给函数指针,可以采用下面两种形式:
(1)fptr = &Function;
(2)fptr = Function;
取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址。
如果是函数调用,还必须包含一个圆括号括起来的参数表。
通过指针调用函数,可以采用如下两种方式:
(1)x=(*fptr)();
(2)x=fptr();
第二种格式看上去和函数调用无异,但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。下面举一个例子:
void (*funcp)();
void FileFunc(),EditFunc();
main()
{
funcp = FileFunc;
(*funcp)();
funcp = EditFunc;
(*funcp)();
}
void FileFunc()
{
printf(FileFunc\n);
}
void EditFunc()
{
printf(EditFunc\n);
}
运行结果:
FileFunc
EditFunc
3 劫持函数
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
void printf1()
{
printf("11111");
}
void printf2()
{
printf("22222");
}
void printf3()
{
printf("33333");
}
void newprintf1()
{
MessageBoxA(0, "11111", "11111", 0);
}
void newprintf2()
{
MessageBoxA(0, "22222", "22222", 0);
}
void newprintf3()
{
MessageBoxA(0, "33333", "33333", 0);
}
void main()
{
void(*p[3])() = { printf1, printf2, printf3 };//函数指针数组
printf("%p", p);//数组的地址
void(*px[3])() = { newprintf1, newprintf2, newprintf3 };
for (int i = 0; i < 3; i++)
{
printf("\n%p", px[i]);//打印三个函数的地址
}
while (1)
{
for (int i = 0; i < 3; i++)
{
printf("\n");
p[i]();//挨个调用函数
printf("\n");
}
Sleep(3000);
}
}
void main1()
{
void(*p)() = printf1;
printf("\n%p", &p);
printf("\n%p,%p", printf1, newprintf1);//打印地址
while (1)
{
printf("\n");
p();
Sleep(3000);
}
}
劫持模块
#include<stdlib.h>
#include<stdio.h>
//导出函数,可以加载的时候调用
_declspec(dllexport) void go()
{
//改变一个变量,需要变量的地址
//改变一个指针,需要指针的地址
void(**p)() = (void(**)())0x2af8ac;
*p =(void(*)()) 0x2d1010;
}
_declspec(dllexport) void go1()
{
void(*p[3])(); //指针数组,批量管理函数地址
p[0] = 0x291030;
p[1] = 0x291050;
p[2] = 0x291070;
void(**pp)() = 0x22fa54;//
for (int i = 0; i < 3; i++)
{
*(pp+i) = p[i];//批量改变指针
}
}