首页
社区
课程
招聘
[原创]Java程序员和Windows hook(二)
发表于: 2019-3-4 14:11 4788

[原创]Java程序员和Windows hook(二)

2019-3-4 14:11
4788

听完黑洛大佬的建议之后,突然发现了一个比较严重的问题,C/C++都不了解,怎么可能轻易的去修改或者破解他们的写出来的程序,于是大致浏览了网上的资源,《完美C++》还在路上,先写一点

以下几个概念让我在刚开始学习的时候产生了很大的障碍,后来我决定先把他们啃掉,变成可以可以客串以下c++程序员的java程序员

1.指针

个人认为。理解指针最好的方法其实是CE中那两个找指针的教程,很直接

指针变量保存的是地址,而地址本质上是一个整数,然后作为一名基础很差(几乎都不知道程序是怎么跑起来的,JVM以下一脸懵逼)的java狗,看到这里就懵了

为什么要地址,我们从来都是面向对象

Object obj = new Object();
obj.run();
然后我并不想浪费时间去从正面阐述指针是咋来的是什么个东西,我想的是,为什么我要指针,然后我在知乎看到了这样一条答案

到这里,我想到了jdk8新出的函数指针,当时理解这个东西非常费劲也每太搞懂,现在从这个指针的角度出发来看上面的代码。

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这个首地址其实就是上面回答中的运单

obj.run()这个过程实际上是让快递小哥拿着运单,把礼物送到运单上的地址

从这里可以看出Java和C++的差别,java在一个函数的运行时,除非利用反射或者jdk8的函数指针的机制或者一些ClassLoader的办法,从语言层面本身是很难去改变这个运单上的地址的。但是C++就不一样了,他可以运用指针去改变这张运单上的地址。这也就引出了另一个关键点,我们只要通过修改运单地址,就能改变程序原本执行的顺序达到我们的目的,这里让我想到了IAT HOOK


关于指针,由于接触的时间和理解问题,我上面的说法是非常粗略甚至可能是错误的,但是对于理解hook流程,应该是有一定帮助的


2.struct(结构体) 


下面的大部分都是在看完这里之后的一些笔记

http://c.biancheng.net/view/2235.html


先来看一下c语言的struct

struct Student{
    Student(char *name, int age, float score);
    void show();
    char *m_name;
    int m_age;
    float m_score;
};

和java的类不同之处在于 struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。

归纳一下就变成

struct  = java中没有成员函数的class

我之前以为,结构体是个非常非常神秘的东西,得出上面的结论之后,虽然很片面,但是对于我前期浏览到相关内容的时候,可以让我有一个大概的理解,可以让我顺利的略过他。之后肯定会得到纠正的

3.虚函数


先来看一个造了一百年的轮子,人类和老师类,下面的代码即使是一个java程序员,看起来也是相当舒服的,真的是黑洛说的好像回家的感觉

以下案例来自

http://c.biancheng.net/view/2294.html

侵删

#include <iostream>
using namespace std;
//基类People
class People{
public:
    People(char *name, int age);
    void display();
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}
//派生类Teacher
class Teacher: public People{
public:
    Teacher(char *name, int age, int salary);
    void display();
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}
int main(){
    People *p = new People("王志刚", 23);
    p -> display();
    p = new Teacher("赵宏佳", 45, 8200);
    p -> display();
    return 0;
}

C++中的派生类其实就是Java中的子类,但是这段代码的输出确是这样的

王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。

这个就很尴尬了,第二行的输出不应该是"赵宏佳今年45岁了,是一名教师,每月有8200元的收入"么。看来,同样的写法,Java会自动根据函数名去重载父类的方法,C++却并不会,难道C++不能重载父类函数么,显然并不是,这个时候就要引入虚函数的概念了

只要该上面的display()函数前面加上*virtual*就可以实现重载

#include <iostream>
using namespace std;
//基类People
class People{
public:
    People(char *name, int age);
    virtual void display();  //声明为虚函数
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}
//派生类Teacher
class Teacher: public People{
public:
    Teacher(char *name, int age, int salary);
    virtual void display();  //声明为虚函数
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}
int main(){
    People *p = new People("王志刚", 23);
    p -> display();
    p = new Teacher("赵宏佳", 45, 8200);
    p -> display();
    return 0;
}

这时候的输出就是

王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入


===2019.3.5更新
写完上面这些之后,我决定hook一下自己的代码,我把char *换成了string,下面是完整的代码
// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <string>

using namespace std;
//基类People
class People {
public:
	People(string name, int age);
	virtual void display();  //声明为虚函数
protected:
	string m_name;
	int m_age;
};
People::People(string name, int age) : m_name(name), m_age(age) {}
void People::display() {
	cout << m_name << "今年" << m_age << "岁了,是个无业游民。" << endl;
}
//派生类Teacher
class Teacher : public People {
public:
	Teacher(string name, int age, int salary);
	virtual void display();  //声明为虚函数
private:
	int m_salary;
};
Teacher::Teacher(string name, int age, int salary) : People(name, age), m_salary(salary) {}
void Teacher::display() {
	cout << m_name << "今年" << m_age << "岁了,是一名教师,每月有" << m_salary << "元的收入。" << endl;
}

void getUd(People *peple) {
	peple->display();
}
int main() {

	cout << "函数地址:" << &getUd << endl;

	People *p = new People("王志刚", 23);
	getUd(p);
	cout << "*p地址:" << p << endl;
	

	string s;
	cin >> s;
	getUd(p);

	p->display();
	p = new Teacher("赵宏佳", 45, 8200);

	p->display();

	

	
	return 0;
}
上面的代码中增加了getUd这个函数,这个就是用来hook的
     首先,用Detour进行Hook,上面我偷偷打印了函数的地址,然后用注入dll的方法进行hook
// dllmain.cpp : 定义 DLL 应用程序的入口点。




#include <windows.h>  
#include <string>  
#include <iostream>
#include "../include/detours.h"

using namespace std;

typedef int (__cdecl *pFunc) (int);//要hook函数的指针,从ida里面复制出来的
pFunc FuncToDetour = (pFunc)(0x011316C7); //设置要hook函数的地址

/*
int __cdecl sub_11316C7(int a1)
{
  return sub_1139840(a1);
}
*/
int  __cdecl MyFunc_getUd(int i)
{
	cout << "get Point";
	MessageBox(NULL, "get Point", "msg", MB_OK);
	int ret = FuncToDetour(i);
	return ret;
}


BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)FuncToDetour, MyFunc_getUd);
		DetourTransactionCommit();
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}


/*
这里需要注意的地方是typedef int (__cdecl *pFunc) (int);这个函数原型
*/
上面的被hook的原型是我直接ida反编译F5弄出来的,在这过程中遇到的障碍是我之前一直想用免费版的IDAPro5,但是他在win10上好像并不能F5,于是我就换成了IDA7.0才弄出来的,直接根据上面偷偷打印的地址跳转过去,然后复制出来。还有在上面的hook函数的时候,如果原型不正确或者没有继续调用原函数FuncToDetour,是会报错的。报错如下


==========小插曲,在找资料的时候,我又发现了一个新的库frida,不知道为啥,一看python就觉得搞这个有戏,官网一查还真有windows的demo。按照我目前的水平,只能以API为单位进行hook,如果要hook任意地址,要么重写DetourAttach的源码,要么直接在代码里面插入难看的如下内容
_asm{
    jum xxx;
    xxxx
}
然后把寄存器都存到变量里面,hook结束再还原回去。这个就有点难受了,看完Frida的Demo我感觉有戏,试了一下代码还真可以,
import frida
import sys

def on_message(message, data):
    print("[%s] => %s" % (message, data))

session = frida.attach('ConsoleApplication1.exe')
script = session.create_script('''
    var f1 = ptr('0x013816C7');
    Interceptor.attach(f1, {
        onEnter:function(args){
                console.log("hook success",args[0]);//第一个值的指针地址
                console.log("registor",this.context.eax);//寄存器内容
        }
    });

''')
script.on('message', on_message)
script.load()

用OD附加之后观察之后,发现他是在hook的函数头添加了jmp指令并用nop填充。虽然运行起来感觉性能有点慢,可以明显感觉到顿一下(ps:应该不可能是我机器的原因),但是这种方式搞起来还是蛮爽的,瞬间又回到了熟悉的世界
附上看雪的Frida官方手册
https://zhuanlan.kanxue.com/article-352.htm

ps:ida能f5能反编译出伪代码是清风告诉我的,之前没说是他告诉我的被他骂臭不要脸,社会,社会~ 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-3-6 10:34 被flameonyou编辑 ,原因: 添加hook内容
收藏
免费 0
支持
分享
最新回复 (4)
雪    币: 1885
活跃值: (57)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主还需要更接近本质   比如struct C和C++的不同 struct 和class的不同  虚函数地址放在哪
2019-3-4 15:36
0
雪    币: 1152
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
AykEpt 楼主还需要更接近本质 比如struct C和C++的不同 struct 和class的不同 虚函数地址放在哪
好嘞,多谢指点
2019-3-4 22:11
0
雪    币: 223
活跃值: (222)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
看楼主是想学习逆向汇编,这个最好的熟悉办法就是先逆向自己写的c++程序,看看你写的代码在汇编下是如何实现的,看多了心中就有个底,再看别人的汇编代码就会觉得某些地方很熟悉.
2019-3-5 09:42
0
雪    币: 351
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习了
2019-3-5 10:41
0
游客
登录 | 注册 方可回帖
返回
//