逆向攻防世界CTF系列41-EASYHOOK

看题目是一个Hook类型的,第一次接触,虽然学过相关理论,可以看我的文章

Hook入门(逆向)-CSDN博客

题解参考:https://www.cnblogs.com/c10udlnk/p/14214057.html和攻防世界逆向高手题之EASYHOOK-CSDN博客

int __cdecl main(int argc, const char **argv, const char **envp){
  HANDLE FileA; // eax
  DWORD NumberOfBytesWritten; // [esp+4h] [ebp-24h] BYREF
  char Buffer[32]; // [esp+8h] [ebp-20h] BYREF

  sub_401370(aPleaseInputFla);
  scanf("%31s", Buffer);
  if ( strlen(Buffer) == 19 ){
    sub_401220();
    // 创建文件 (文件名,表示文件以写入模式打开,文件不允许共享访问(即独占模式),不设置安全属性,创建新文件,文件属性设置为普通       文件,模板文件)
    FileA = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);
    // (文件句柄:表示目标文件,写入的数据,表示写入的字节数,写入的实际字节数存储位置,同步写入[不使用异步操作])
    WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, 0);
    sub_401240(Buffer, &NumberOfBytesWritten);// 验证函数
    if ( NumberOfBytesWritten == 1 )
      sub_401370(aRightFlagIsYou);
    else
      sub_401370(aWrong);
    system(Command);
    return 0;
  }
  else{
    sub_401370(aWrong);
    system(Command);
    return 0;
  }
}

跟进401240

int __cdecl sub_401240(const char *a1, _DWORD *a2)
{
  int result; // eax
  unsigned int v3; // kr04_4
  char v4[24]; // [esp+Ch] [ebp-18h] BYREF

  result = 0;
  strcpy(v4, "This_is_not_the_flag");
  v3 = strlen(a1) + 1;
  if ( (int)(v3 - 1) > 0 )
  {
    while ( v4[a1 - v4 + result] == v4[result] )
    {
      if ( ++result >= (int)(v3 - 1) )
      {
        if ( result == 21 )
        {
          result = (int)a2;
          *a2 = 1;
        }
        return result;
      }
    }
  }
  return result;
}

v4[a1 - v4 + result] == v4[result]??,完全不通啊

还看了汇编源码,没看懂 0.0

看下sub_401220先

int sub_401220()
{
  HMODULE LibraryA; // eax
  DWORD CurrentProcessId; // eax
  // 1. 获取当前进程句柄
  // GetCurrentProcessId:获取当前进程的进程 ID。
  // OpenProcess:使用当前进程 ID,以 0x1F0FFFu 权限打开当前进程
  CurrentProcessId = GetCurrentProcessId();
  hProcess = OpenProcess(0x1F0FFFu, 0, CurrentProcessId);
  // 2. 加载目标库和获取函数地址
  // 动态加载一个DLL文件。
  // 成功后返回该库的模块句柄 LibraryA。
  // GetProcAddress:从 LibraryA 中获取目标函数(ProcName)的地址,存储到 WriteFile_0。函数签名定义为 BOOL WriteFile(...)。
  LibraryA = LoadLibraryA(LibFileName);
  WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName);
  // 3. 检查函数是否获取成功
  lpAddress = WriteFile_0;
  if ( !WriteFile_0 )
    return sub_401370((int)aApi);
  // 4. 保存原函数的前几字节
  // 将 WriteFile_0 的前 5 字节保存到 unk_40C9B4 中。_DWORD:前 4 字节。_BYTE:第 5 字节。
  unk_40C9B4 = *(_DWORD *)lpAddress;
  *((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4);
  // 5. 计算跳转偏移量
  // byte_40C9BC = -23:一个额外的字节赋值(可能和跳转相关)
  // 计算一个偏移量,用于跳转到 sub_401080。
  byte_40C9BC = -23;
  dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;
  return sub_4010D0();
}

这里犯了个错,不是-23而是0XE9,是JMP的机器码指令

image-20241118215218470

byte_40C9BC和dword_40C9BD是相邻的,连起来就是 jmp xxxx四个字节

偏移地址=目标地址-当前地址-5(jmp和其后四位地址共占5个字节)。所以前面直接用E9,这里直接用偏移地址就省去编译生成机器码那一步。

看看sub_4010D0()

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

  v1 = 0;
  // hProcess:目标进程的句柄。
  // lpAddress:目标内存地址
  // 5u:操作的字节数。
  // 4u:新的内存保护属性(可读写)。
  // &flOldProtect:存储原始内存保护属性。
  VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);
  //  byte_40C9BC 的内容写入目标进程中 lpAddress 开始的 5 字节区域
  WriteProcessMemory(hProcess, lpAddress, &byte_40C9BC, 5u, 0);
  return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);
}

jmp那里会跳转到目标地址sub_401080处,双击跟踪该函数:

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

  v5 = sub_401000(lpBuffer, nNumberOfBytesToWrite);
  sub_401140();
  WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
  if ( v5 )
    *lpNumberOfBytesWritten = 1;
     // 这里才是真正的校验结果的验证
  return 0;
}

sub_401000();才是真正的加密函数

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

  v1 = 0;
  VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);
  WriteProcessMemory(hProcess, lpAddress, &unk_40C9B4, 5u, 0);
  return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);
}
```c
int __cdecl sub_401000(int a1, int a2)
{
  char i; // al
  char v3; // bl
  char v4; // cl
  int v5; // eax

  for ( i = 0; i < a2; ++i ){
    if ( i == 18 ){
      *(_BYTE *)(a1 + 18) ^= 0x13u;
    }
    else{
      if ( i % 2 )
        v3 = *(_BYTE *)(i + a1) - i;
      else
        v3 = *(_BYTE *)(i + a1 + 2);
      *(_BYTE *)(i + a1) = i ^ v3;
    }
  }
  v4 = 0;
  if ( a2 <= 0 )
    return 1;
  v5 = 0;
  while ( byte_40A030[v5] == *(_BYTE *)(v5 + a1) ){
    v5 = ++v4;
    if ( v4 >= a2 ) return 1;
  }
  return 0;
}

解密代码

enc = [
  0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F,
  0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E
]

flag=list("-------------------")

for i in range(len(enc)):
    if i == 18:
        enc[i] ^= 0x13
    else:
        v3 = enc[i] ^ i
        if i % 2 == 1:
            flag[i] = chr(v3 + i)
        else:
            flag[i + 2] = chr(v3)

for i in range(len(enc)):
    print(flag[i],end='')

引用别人博客的一句话:

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

sub_401240中即使不符合也不会给NumberOfBytesWritten置成0