攻防世界逆向高手题之EASYHOOK

99 篇文章 33 订阅

攻防世界逆向高手题之EASYHOOK

继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的EASYHOOK
在这里插入图片描述

这是我第一次遇到HOOK类型的题目,我很是兴奋,我也花了相当长的时间才把该HOOK流程全部弄懂,希望能给日后更多经验。
直接入重点,打开IDA查看main函数,具体分析我写在代码里:
在这里插入图片描述

 sub_401370(aPleaseInputFla);
  scanf("%31s", input_flag);
  if ( strlen(input_flag) == 19 )
  {
    sub_401220();		//未知名函数,一开始当然不会注意
    v4 = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);		//创建文件函数
    WriteFile(v4, input_flag, 19u, &NumberOfBytesWritten, 0);		//写入函数,这些系统函数通常不是重点,如果影响理解代码的话直接查API即可,  WriteFile(句柄, 写入字符串, 写入字节, 指向写入直接的指针, 0)
    sub_401240(input_flag, &NumberOfBytesWritten);
    if ( NumberOfBytesWritten == 1 )	//判断函数
      sub_401370(aRightFlagIsYou);
    else
      sub_401370(aWrong);
    system(Command);
    result = 0;
  }
  else
  {
    sub_401370(aWrong);
    system(Command);
    result = 0;
  }
  return result;
}


好了,疑惑开始,首先当然直接跟踪sub_401240函数:
在这里插入图片描述

犯下第一个错误:(被HOOK了)
查看该函数代码让我疑惑的事情发生了,v4[a1 - v4 + result] == v4[result] 这条神仙代码无解啊,a1是我输入的字符串地址,作为下标运算就算了,v4还是从0开始的,完全走不通啊!(PS:这题还算好了,给了我一个走不通的HOOK,要是这个HOOK还是走得通的话就更花时间了!)



然后我又看了一下生成的Your_input文件,???生成的不是我写入的:
在这里插入图片描述



然后就去查资料了,期间学到了很多东西,我们先用静态调试细致分析:首先这一系列非预期行为基本说明了存在其他操作,那前面非系统函数就只剩下sub_401220()了,跟踪加代码分析:

int sub_401220()
{
  HMODULE v0; // eax
  DWORD v2; // eax

  v2 = GetCurrentProcessId();			//系统函数,获取进程ID,就是当前程序的ID
  hProcess = OpenProcess(0x1F0FFFu, 0, v2);		//系统函数,返回现有进程对象的句柄。
  v0 = LoadLibraryA(LibFileName);			//系统函数,将指定的可执行模块映射到调用进程的地址空间,这里LibFileName双击跟踪存入的是kernel32.dll模块,就是导入了它
  *(_DWORD *)WriteFile_0 = GetProcAddress(v0, ProcName);		//系统函数,返回指定的导出动态链接库(DLL)函数的地址。 ProcName存放的是WriteFile函数名,也就是导入WriteFile函数。
  lpAddress = *(LPVOID *)WriteFile_0;	//获取WriteFile函数地址
  if ( !*(_DWORD *)WriteFile_0 )		//无用函数,因为kernel32.dll模块的WriteFile函数一定存在
    return sub_401370(&unk_40A044);	//sub_401370函数类型puts函数,是通过主函数分析得到的,&unk_40A044处的字符串是获取原API入口地址出错,不用管他。
  unk_40C9B4 = *(_DWORD *)lpAddress;		//这里获取WriteFile函数的地址
  *((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4);		//这里地址后四位也保持一样,不知道有什么用
  byte_40C9BC = 0351;		//这里连同第二句是我犯下的第二个错误:这里转十六进制不是E9,是JMP的机器码指令,而不一开始并没有机器码的相关知识。
  dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;		//这里是一个偏移地址,而且还是满足HOOK的连续地址。之所以这样写是因为汇编语言JMP address被编译后会变成机器指令码,E9  偏移地址,偏移地址=目标地址-当前地址-5(jmp和其后四位地址共占5个字节)。所以前面直接用E9,这里直接用偏移地址就省去编译生成机器码那一步。这也是HOOK的原型。
  return sub_4010D0();		返回一个函数,继续跟踪,因为前面并没有什么修改程序的行为
}


跟踪sub_4010D0()函数:

BOOL sub_4010D0()
{
  DWORD v1; // [esp+4h] [ebp-8h] BYREF
  DWORD flOldProtect; // [esp+8h] [ebp-4h] BYREF

  v1 = 0;
  VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);		//这里犯下第三个错误就是我一开始看不懂这三行代码
  函数理解:VirtualProtectEx(进程,修改地址,修改区域大小,[修改其中权限,1,2,4对应执行,写,读],一个保存修改前权限的变量 ),所以这三行作用是对lpAddress所存储的地址处进行了5字节的权限修改操作,先改成读写再往此处写入以前面E9 偏移量地址处的5个字节(即上面的跳板指令),最后恢复权限,完成修改。
  WriteProcessMemory(hProcess, lpAddress, &byte_40C9BC, 5u, 0);		
  return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);



所以这里执行到WriteFile函数的时候就会变成一个跳转语句,跳转到目标地址sub_401080处,双击跟踪该函数:

int __stdcall sub_401080(HANDLE hFile, LPCVOID input_flag, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
{
  int v5; // ebx

  v5 = sub_401000((int)input_flag, nNumberOfBytesToWrite);	//真正加密函数,后面分析
  sub_401140();		//重写WriteFile函数地址处的内存,和前面4010D0处函数的三句类似,只是代码 WriteProcessMemory(hProcess, lpAddress, &unk_40C9B4, 5u, 0);写入的地址为&unk_40C9B4,这的确是WriteFile函数地址,就是为了下面那个WriteFile函数不受影响,真正是写入文件函数
  WriteFile(hFile, input_flag, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
  if ( v5 )
    *lpNumberOfBytesWritten = 1;		//这里*lpNumberOfBytesWritten已经赋值为1了,也是全局变量,你会猛的发现主函数的sub_401240(input_flag, &NumberOfBytesWritten);这个幌子函数就算对不上数组字符也不会设置*lpNumberOfBytesWritten = 0;所以只要这里真正加密的对了,后面错误退出*lpNumberOfBytesWritten还是等于1.
  return 0;
}




分析真加密函数 sub_401140():

int __cdecl sub_401000(int input_flag, int _19)
{
  char i; // al
  char v3; // bl
  char v4; // cl
  int v5; // eax

  for ( i = 0; i < _19; ++i )
  {
    if ( i == 18 )
    {
      *(_BYTE *)(input_flag + 18) ^= 19u;       // 这里犯下第五个错误,写脚本时这种单独的if语句应该直接在外面单独成行,不然就要多写几个elif语句了,界面就会很乱!
    }
    else
    {
      if ( i % 2 )
        v3 = *(_BYTE *)(i + input_flag) - i;
      else
        v3 = *(_BYTE *)(i + input_flag + 2);
      *(_BYTE *)(i + input_flag) = i ^ v3;
    }
  }
  v4 = 0;
  if ( _19 <= 0 )                               // 不会小于等于,所以不会在这里返回
    return 1;
  v5 = 0;
  while ( aAjygkfm[v5] == *(_BYTE *)(v5 + input_flag) )		//这里犯下第四个错误,前面的加密逻辑中我们没有可以参照的字符串,因为flag是推出来的,而真正的字符串在后面,所以逆向逻辑时首先要找到非输入的字符串才行!这里双击跟踪 aAjygkfm等于ajygkFm.\x7f_~-SV{8mLn
  {
    v5 = ++v4;
    if ( v4 >= _19 )
      return 1;
  }
  return 0;
}

所以最后脚本:

flag=list("-------------------")			#这是学别人的,因为我们要的是19个元素的数组,所以List函数很不错喔
print(len(flag))
key1="ajygkFm.\x7f_~-SV{8mLn"
flag[18]=chr(ord(key1[18])^19)
for i in range(18):
	v3=ord(key1[i])^i
	if i%2==1:		#把18的判断条件抽出去后里面就只有一层条件了,简便很多。
		flag[i]=chr(v3+i)
	else:
		flag[i+2]=chr(v3)
print(''.join(flag))

结果,改第一个字符为f即可:
在这里插入图片描述


引用别人博客的一句话:

现在程序流程就很明朗了,粗略来看程序流程是CreateFileA->(lpAddress里存的指令)WriteFile->sub_401240,但是在经过sub_401220()的处理以后,变成了CreateFileA->(lpAddress里存的指令)sub_401080->sub_401240。

那么最后的sub_401240又干了什么事呢,分析一下代码:(这里我重命名了一些变量名)
在这里插入图片描述

这里最有意思的是这个函数完全符合就会执行nNumberOfBytesToWrite = 1;,但是不符合也没有nNumberOfBytesToWrite = 0;的操作啊,我们前面真加密函数的时候就赋值*nNumberOfBytesToWrite = 1;了,也就是说不管这里对还是错都会输出you flag is right啊!而且动态调试的时候你会发现,这里循环执行一次就退出来了,因为while ( v4[input_flag - v4 + result] == v4[result] )这个代码根本不和逻辑。




最后动态调试就不想搞了,我记得IDA动态运行到主函数WriteFile处的时候用F7单步执行会看到一个jmp的指令,继续动态到sub_401240函数的时候也是正如我说的循环运行第一次就退出来了,但是照样输出you flag is right。
在这里插入图片描述
在这里插入图片描述



推荐一篇很详细的博客:

https://www.cnblogs.com/c10udlnk/p/14214057.html



总结:

1:
犯下第一个错误:(被HOOK了)
查看该函数代码让我疑惑的事情发生了,v4[a1 - v4 + result] == v4[result] 这条神仙代码无解啊,a1是我输入的字符串地址,作为下标运算就算了,v4还是从0开始的,完全走不通啊!(PS:这题还算好了,给了我一个走不通的HOOK,要是这个HOOK还是走得通的话就更花时间了!)

2:
这里连同第二句是我犯下的第二个错误:这里转十六进制不是E9,是JMP的机器码指令,而不一开始并没有机器码的相关知识。
//这里是一个偏移地址,而且还是满足HOOK的连续地址。之所以这样写是因为汇编语言JMP address被编译后会变成机器指令码,E9 偏移地址,偏移地址=目标地址-当前地址-5(jmp和其后四位地址共占5个字节)。所以前面直接用E9,这里直接用偏移地址就省去编译生成机器码那一步。这也是HOOK的原型。

3:
//这里犯下第三个错误就是我一开始看不懂这三行代码
函数理解:VirtualProtectEx(进程,修改地址,修改区域大小,[修改其中权限,1,2,4对应执行,写,读],一个保存修改前权限的变量 ),所以这三行作用是对lpAddress所存储的地址处进行了5字节的权限修改操作,先改成读写再往此处写入以前面E9 偏移量地址处的5个字节(即上面的跳板指令),最后恢复权限,完成修改。

4:
//这里犯下第四个错误,前面的加密逻辑中我们没有可以参照的字符串,因为flag是推出来的,而真正的字符串在后面,所以逆向逻辑时首先要找到非输入的字符串才行!这里双击跟踪 aAjygkfm等于ajygkFm.\x7f_~-SV{8mLn

5:
这里犯下第五个错误,写脚本时这种单独的if语句应该直接在外面单独成行,不然就要多写几个elif语句了,界面就会很乱!

解毕!敬礼!

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
目前最好的EasyHook的完整Demo程序,包括了Hook.dll动态库和Inject.exe注入程序。 Hook.dll动态库封装了一套稳定的下钩子的机制,以后对函数下钩子,只需要填下数组表格就能实现了,极大的方便了今后的使用。 Inject.exe部分是用MFC写的界面程序,只需要在界面上输入进程ID就能正确的HOOK上相应的进程,操作起来非常的简便。 这个Demo的代码风格也非常的好,用VS2010成功稳定编译通过,非常值得下载使用。 部分代码片段摘录如下: //【Inject.exe注入程序的代码片段】 void CInjectHelperDlg::OnBnClickedButtonInjectDllProcessId() { ////////////////////////////////////////////////////////////////////////// //【得到进程ID值】 UINT nProcessID = 0; if (!GetProcessID(nProcessID)) { TRACE(_T("%s GetProcessID 失败"), __FUNCTION__); return; } ////////////////////////////////////////////////////////////////////////// //【得到DLL完整路径】 CString strPathDLL; if (!GetDllFilePath(strPathDLL)) { TRACE(_T("%s GetDllFilePath 失败"), __FUNCTION__); return; } ////////////////////////////////////////////////////////////////////////// //【注入DLL】 NTSTATUS ntStatus = RhInjectLibrary(nProcessID, 0, EASYHOOK_INJECT_DEFAULT, strPathDLL.GetBuffer(0), NULL, NULL, 0); if (!ShowStatusInfo(ntStatus)) { TRACE(_T("%s ShowStatusInfo 失败"), __FUNCTION__); return; } } //【Hook.dll动态库的代码片段】 extern "C" __declspec(dllexport) void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO* InRemoteInfo) { if (!DylibMain()) { TRACE(_T("%s DylibMain 失败"), __FUNCTION__); return; } } FUNCTIONOLDNEW_FRMOSYMBOL array_stFUNCTIONOLDNEW_FRMOSYMBOL[]= { {_T("kernel32"), "CreateFileW", (void*)CreateFileW_new}, {_T("kernel32"), "CreateFileA", (void*)CreateFileA_new}, {_T("kernel32"), "ReadFile", (void*)ReadFile_new} }; BOOL HookFunctionArrayBySymbol() { /////////////////////////////////////////////////////////////// int nPos = 0; do { /////////////////////////////// FUNCTIONOLDNEW_FRMOSYMBOL* stFunctionOldNew = &g_stFUNCTIONOLDNEW_FRMOSYMBOL[nPos]; if (NULL == stFunctionOldNew->strModulePath) { break; } /////////////////////////////// if (!HookFunctionBySymbol(stFunctionOldNew->strModulePath, stFunctionOldNew->strNameFunction, stFunctionOldNew->pFunction_New)) { TRACE(_T("%s HookFunctionBySymbol 失败"), __FUNCTION__); return FALSE; } } while(++nPos); /////////////////////////////////////////////////////////////// return TRUE; } HANDLE WINAPI CreateFileW_new( PWCHAR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { TRACE(_T("CreateFileW_new. lpFileName = %s"), lpFileName); return CreateFileW( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐一 · 林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值