一、查看文件类型
PE32 无壳
二、HOOK原理
1.什么是hook(钩子)
对于Windows系统,它是建立在事件驱动机制上的,说白了就是整个系统都是通过消息传递实现的。hook(钩子)是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。所以说,我们可以在系统中自定义钩子,用来监视系统中特定事件的发生,完成特定功能,如屏幕取词,监视日志,截获键盘、鼠标输入等等。
钩子的种类很多,每种钩子可以截获相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 中。
所以说,hook(钩子)就是一个Windows消息的拦截机制,可以拦截单个进程的消息(线程钩子),也可以拦截所有进程的消息(系统钩子),也可以对拦截的消息进行自定义的处理。Windows消息带了一些程序有用的信息,比如Mouse类信息,就带有鼠标所在窗体句柄、鼠标位置等信息,拦截了这些消息,就可以做出例如金山词霸一类的屏幕取词功能。
2.hook分类
(1)线程钩子监视指定线程的事件消息。
(2)系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)中。这是系统钩子和线程钩子很大的不同之处。
3、HOOK(钩子)的工作原理
在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个线程钩子,您进程中的钩子函数将被调用。如果是一个系统钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。
当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。
几点需要说明的地方:
(1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
(2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
(3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
三、IDA静态分析和动态调试
1.查看main函数
程序逻辑分析:
将输入存储在Buffer中,长度为19,进行长度判断后,调用了函数sub_401220,当前此函数功能未知,接着调用CreateFileA和WriteFile创建文件并将输入写入本地文件夹中的文件,
双击文件名查看为
再调用sub_401240后,进行最后的flag判断,猜测sub_401240为关键加密函数
动态调试后发现,输入字符串“1234567891234567891”时,写入文件Your_input中的值发生了改变,其值如下:
只有两种可能,对输入的修改要么是在未知函数sub_401220中,要么是在WriteFile中,题目名为easyhook,故猜测在函数sub_401220进行了hook操作。
2.查看sub_401220
第9行通过GetProcAddress获取WriteFile函数的地址,
接着第12-16行进行一系列操作计算函数sub_401080和函数WriteFile的偏移地址,
第15行赋值的0xE9是jmp指令的机器码,
最后调用sub_4010D0后返回
3.查看sub_4010D0
此函数将byte_40C9BC的值写入lpAddress,byte_40C9BC的值在上层函数中定义了,经动态调试后发现为0xE9 892ED82Bh
即此时指令变为了jmp sub_401080
0xE9是jmp指令的机器码,确定sub_4010D0中进行了hook操作,执行WriteFile时,没有写文件,而是跳转到sub_401080
4.查看sub_401080
逻辑分析:
首先调用 sub_401000 函数,分析参数传递可知参数 lpBuffer 即是我们输入的 flag,那么很有可能就是在 sub_401000 里面对 flag 进行了修改。
然后调用 sub_401140 函数,接着又一次调用了WriteFile 函数。我们知道虽然 WriteFile 被 hook 了,但最后确实是把 flag 写入到了文件里面,所以很有可能是在 sub_401140 里面对 WriteFIle 进行了 hook 还原。
最后对 sub_401000 的返回值进行判断,如果非 0 则将NumberOfBytesWritten 置 1。
查看sub_401140
证实进行了hook还原
5.查看sub_401000
逻辑分析:
(1)我们输入的长度为 19 的 flag 经过第一个循环的处理之后,如果和 byte_40A030 指向的全局字符串相同,那么 sub_401000 返回 1。
(2)在 sub_401080 里面对sub_401000 的返回值进行判断,如果返回值为 1,则NumberOfBytesWritten 置 1。
(3)在最外层的 main函数里面进行判断,如果NumberOfBytesWritten 为 1,则输出正确的提示信息。
byte_40A030如下:
提取出来为:
unsigned char ida_chars[] =
{
0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F,
0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E, 0x00
};
6.查看main最后看似关键的函数sub_401240
逻辑分析或动态调试都发现其毫无作用
四、解题
关键加密函数sub_401000
将 byte_40A030 指向的字符串做一次 sub_401000 函数里面第一个循环处理的逆运算,并将byte_40A030 最后一位与0x13异或,就可以得到输入正确的 flag 了。
脚本如下:
a = [0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F,
0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E, 0x00]
flag = list("1234567891234567890") #长度为19
for i in range(0,18):
if i % 2 ==1:
flag[i] = chr((a[i]^i)+i)
else:
flag[i+2] = chr(a[i]^i)
flag[18]= chr(a[18]^0x13)
print("".join(flag))
#结果第一位有错误,可能是程序中缺失了,改为f
运行结果:
尝试提交后发现不对,将第一位改为f后提交,发现正确
flag为:flag{Ho0k_w1th_Fun}