2025腾讯游戏安全PC初级(上)

Ring3

先进入Main函数,找能分析的逻辑

首先进入找到Main函数,然后发现有个vftable,判断是一个虚表

image

双击虚表,然后对虚表进行分析

image

sub_1400013D0(ServiceCreateStart )

__int64 __fastcall sub_1400013D0(__int64 a1, char *a2, __int64 a3)
{
  unsigned int v6; // edi
  wchar_t *v7; // rax
  signed __int64 v8; // rcx
  __int16 v9; // ax
  SC_HANDLE v10; // rax
  SC_HANDLE v11; // rbx
  WCHAR Filename[264]; // [rsp+20h] [rbp-658h] BYREF
  wchar_t Destination[528]; // [rsp+230h] [rbp-448h] BYREF

  v6 = 0;
  memset(Destination, 0, 0x412ui64);
  if ( a2 )
  {
    if ( a3 )
    {
      memset(Filename, 0, 0x20Aui64);
      if ( GetModuleFileNameW(0i64, Filename, 0x104u) )
      {
        v7 = wcsrchr(Filename, 0x5Cu);
        if ( v7 )
        {
          v8 = (char *)v7 - a2;
          do
          {
            v9 = *(_WORD *)a2;
            *(_WORD *)&a2[v8 + 2] = *(_WORD *)a2;
            a2 += 2;
          }
          while ( v9 );
          wcscpy_s(Destination, 0x209ui64, Filename);
          v10 = OpenSCManagerW(0i64, 0i64, 0xF003Fu);
          v11 = v10;
          if ( v10 )
          {
            if ( (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64, wchar_t *))(*(_QWORD *)a1 + 40i64))(
                   a1,
                   v10,
                   a3,
                   Destination)
              && (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 24i64))(a1, v11, a3) )
            {
              LOBYTE(v6) = (*(unsigned int (__fastcall **)(__int64))(*(_QWORD *)a1 + 56i64))(a1) == 0;
            }
            CloseServiceHandle(v11);
          }
        }
      }
    }
  }
  return v6;
}

核心功能:获取当前程序路径并拼接自定义字符串,通过 Windows 服务控制管理器创建 / 启动服务

命名:ServiceCreateStart // 核心:创建并启动服务

sub_140001520(ServiceRemove)

__int64 __fastcall sub_140001520(__int64 a1, __int64 a2)
{
  unsigned int v4; // esi
  SC_HANDLE v5; // rbx

  v4 = 1;
  v5 = OpenSCManagerW(0i64, 0i64, 0xF003Fu);
  if ( !v5 )
    return 1i64;
  if ( (*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 32i64))(a1, v5, a2) )
  {
    if ( !(*(unsigned int (__fastcall **)(__int64, SC_HANDLE, __int64))(*(_QWORD *)a1 + 48i64))(a1, v5, a2) )
      v4 = 0;
    CloseServiceHandle(v5);
    return v4;
  }
  else
  {
    CloseServiceHandle(v5);
    return 0i64;
  }
}

核心功能:通过服务控制管理器(SCM)执行服务的 “停止 / 删除 / 配置” 类操作

命名:ServiceRemove

sub_1400015C0(ServiceStart)

__int64 __fastcall sub_1400015C0(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
  unsigned int v3; // ebx
  SC_HANDLE v4; // rax
  SC_HANDLE v5; // rdi

  v3 = 1;
  v4 = OpenServiceW(a2, a3, 0xF01FFu);
  v5 = v4;
  if ( !v4 )
    return 0i64;
  if ( !StartServiceW(v4, 0, 0i64) && GetLastError() != 1056 )
    v3 = 0;
  CloseServiceHandle(v5);
  return v3;
}

核心功能:打开指定名称的 Windows 服务 → 尝试启动 → 兼容 “服务已运行” 的场景 → 返回操作结果

命名:ServiceStart

sub_140001640(ServiceStop)

SC_HANDLE __fastcall sub_140001640(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
  unsigned int v3; // ebx
  SC_HANDLE result; // rax
  SC_HANDLE v5; // rdi
  struct _SERVICE_STATUS ServiceStatus; // [rsp+20h] [rbp-38h] BYREF

  v3 = 1;
  result = OpenServiceW(a2, a3, 0xF01FFu);
  v5 = result;
  if ( result )
  {
    if ( !ControlService(result, 1u, &ServiceStatus) && ServiceStatus.dwCurrentState != 1 )
      v3 = 0;
    CloseServiceHandle(v5);
    return (SC_HANDLE)v3;
  }
  return result;
}

核心功能:打开指定 Windows 服务 → 发送停止控制指令 → 校验服务状态(兼容已停止场景) → 返回操作结果

最优命名:ServiceStop

sub_1400016C0(FSFilterServiceCreateAndConfigure)

__int64 __fastcall sub_1400016C0(__int64 a1, SC_HANDLE a2, const WCHAR *a3, const WCHAR *a4)
{
  unsigned int v7; // esi
  SC_HANDLE ServiceW; // rdi
  __int64 v9; // rbx
  __int64 v10; // rax
  DWORD dwDisposition; // [rsp+70h] [rbp-90h] BYREF
  HKEY hKey; // [rsp+78h] [rbp-88h] BYREF
  HKEY phkResult; // [rsp+80h] [rbp-80h] BYREF
  WCHAR SubKey[520]; // [rsp+90h] [rbp-70h] BYREF

  v7 = 1;
  memset(SubKey, 0, sizeof(SubKey));
  phkResult = 0i64;
  hKey = 0i64;
  ServiceW = CreateServiceW(
               a2,
               a3,
               a3,
               0xF01FFu,
               2u,
               3u,
               0,
               a4,
               L"FSFilter Activity Monitor",
               0i64,
               L"FltMgr",
               0i64,
               0i64);
  if ( ServiceW || GetLastError() == 1073 )
  {
    wsprintfW(SubKey, L"SYSTEM\\CurrentControlSet\\Services\\%s\\Instances", a3);
    if ( RegCreateKeyExW(HKEY_LOCAL_MACHINE, SubKey, 0, 0i64, 1u, 0xF003Fu, 0i64, &phkResult, &dwDisposition) )
    {
      v7 = 0;
    }
    else
    {
      wsprintfW(SubKey, L"%s Instance", a3);
      v9 = -1i64;
      v10 = -1i64;
      do
        ++v10;
      while ( SubKey[v10] );
      if ( RegSetValueExW(phkResult, L"DefaultInstance", 0, 1u, (const BYTE *)SubKey, 2 * v10) )
      {
        v7 = 0;
      }
      else
      {
        wsprintfW(SubKey, L"SYSTEM\\CurrentControlSet\\Services\\%s\\Instances\\%s Instance", a3, a3);
        if ( RegCreateKeyExW(HKEY_LOCAL_MACHINE, SubKey, 0, 0i64, 1u, 0xF003Fu, 0i64, &hKey, &dwDisposition) )
        {
          v7 = 0;
        }
        else
        {
          wcscpy_s(SubKey, 0x208ui64, L"312723");
          do
            ++v9;
          while ( SubKey[v9] );
          if ( RegSetValueExW(hKey, L"Altitude", 0, 1u, (const BYTE *)SubKey, 2 * v9) )
          {
            v7 = 0;
          }
          else
          {
            dwDisposition = 0;
            if ( RegSetValueExW(hKey, L"Flags", 0, 4u, (const BYTE *)&dwDisposition, 4u) )
              v7 = 0;
          }
        }
      }
    }
  }
  else
  {
    v7 = 0;
  }
  if ( hKey )
  {
    RegCloseKey(hKey);
    hKey = 0i64;
  }
  if ( phkResult )
  {
    RegCloseKey(phkResult);
    phkResult = 0i64;
  }
  if ( ServiceW )
    CloseServiceHandle(ServiceW);
  return v7;
}

核心功能:创建文件系统过滤驱动(FSFilter)服务 → 配置驱动所需的注册表项(实例、高度、标志) → 兼容服务已存在场景 → 返回操作结果

最优命名:FSFilterServiceCreateAndConfigure

sub_1400019A0(ServiceStopAndDelete)

__int64 __fastcall sub_1400019A0(__int64 a1, SC_HANDLE a2, const WCHAR *a3)
{
  SC_HANDLE v3; // rax
  SC_HANDLE v4; // rbx
  struct _SERVICE_STATUS ServiceStatus; // [rsp+20h] [rbp-38h] BYREF

  v3 = OpenServiceW(a2, a3, 0xF01FFu);
  v4 = v3;
  if ( v3 )
  {
    ControlService(v3, 1u, &ServiceStatus);
    DeleteService(v4);
    CloseServiceHandle(v4);
  }
  return 1i64;
}

核心功能:打开指定 Windows 服务 → 强制发送停止指令 → 删除服务 → 释放句柄(无失败校验,恒返回 1)

最优命名:ServiceStopAndDelete

sub_140001A10(FilterConnectACEDriverPort)

HRESULT __fastcall sub_140001A10(__int64 a1)
{
  return FilterConnectCommunicationPort(L"\\ACEDriver", 0, 0i64, 0, 0i64, (HANDLE *)(a1 + 8));
}

核心功能:调用 FilterConnectCommunicationPort 连接名为 \ACEDriver 的过滤驱动通信端口,将端口句柄存入 a1+8,并返回连接结果

最优命名:FilterConnectACEDriverPort

sub_140001A40(FilterSendMessageToACEDriver)

__int64 __fastcall sub_140001A40(
        __int64 a1,
        int a2,
        const void *a3,
        unsigned int a4,
        LPVOID lpOutBuffer,
        DWORD dwOutBufferSize,
        DWORD *a7)
{
  DWORD v11; // r15d
  _DWORD *v12; // rsi
  DWORD *v13; // rbx
  unsigned int v14; // edi
  DWORD BytesReturned; // [rsp+68h] [rbp+10h] BYREF

  BytesReturned = 0;
  v11 = a4 + 4;
  v12 = operator new(a4 + 4);
  memset(v12, 0, v11);
  *v12 = a2;
  if ( a3 && a4 )
    memmove(v12 + 1, a3, a4);
  v13 = a7;
  if ( a7 )
    *a7 = 0;
  v14 = FilterSendMessage(*(HANDLE *)(a1 + 8), v12, v11, lpOutBuffer, dwOutBufferSize, &BytesReturned);
  if ( v13 )
    *v13 = BytesReturned;
  j_j_free(v12);
  return v14;
}

核心功能:构造 “4 字节指令码 + 消息体” 的消息包 → 通过 FilterSendMessage 向 ACEDriver 过滤驱动发送消息 → 接收驱动返回数据 → 释放消息包内存 → 返回发送结果

最优命名:FilterSendMessageToACEDriver

sub_140001370(ACEDriverInitServiceParams)

__int64 __fastcall sub_140001370(__int64 a1)
{
  return (*(__int64 (__fastcall **)(__int64, const wchar_t *, const wchar_t *))(*(_QWORD *)a1 + 8i64))(
           a1,
           L"ACEDriver.sys",
           L"ACEDriver");
}

核心功能:封装调用 a1 对象虚函数表偏移 8 的函数,传入 ACEDriver.sys(驱动名)和 ACEDriver(服务名),并返回该虚函数的执行结果

最优命名:ACEDriverInitServiceParams

sub_140001390(ACEDriverCleanupAndCloseHandle)

int __fastcall sub_140001390(_QWORD *a1)
{
  int result; // eax
  void *v3; // rcx

  result = (*(__int64 (__fastcall **)(_QWORD *, const wchar_t *))(*a1 + 16i64))(a1, L"ACEDriver");
  v3 = (void *)a1[1];
  if ( v3 )
  {
    result = CloseHandle(v3);
    a1[1] = 0i64;
  }
  return result;
}

核心功能:调用 a1 对象虚函数表偏移 16 的函数处理 ACEDriver 资源,再关闭 a1 [1] 指向的句柄并清空,返回最终操作结果

最优命名:ACEDriverCleanupAndCloseHandle

sub_140001B10(ACEDriverSendTestHelloMessage)

__int64 __fastcall sub_140001B10(__int64 a1)
{
  __int64 v2; // rax
  int v5; // [rsp+40h] [rbp-148h] BYREF
  char v6[40]; // [rsp+48h] [rbp-140h] BYREF
  char v7[256]; // [rsp+70h] [rbp-118h] BYREF

  v5 = 0;
  strcpy(v6, "This is TestHello from r3");
  memset(v7, 0, sizeof(v7));
  v2 = -1i64;
  while ( v6[++v2] != 0 )
    ;
  return (*(__int64 (__fastcall **)(__int64, __int64, char *, _QWORD, char *, int, int *))(*(_QWORD *)a1 + 64i64))(
           a1,
           1392640i64,
           v6,
           (unsigned int)(v2 + 1),
           v7,
           256,
           &v5);
}

核心功能:构造测试字符串 "This is TestHello from r3",计算长度后调用 a1 对象偏移 64 的虚函数,向 ACEDriver 驱动发送测试消息(指令码 1392640),并返回虚函数执行结果

最优命名:ACEDriverSendTestHelloMessage

sub_140001BE0(ACEDriverSendCustomData)

__int64 __fastcall sub_140001BE0(__int64 a1, unsigned int a2, const void *a3)
{
  unsigned int v7; // [rsp+40h] [rbp-438h] BYREF
  char v8[1036]; // [rsp+44h] [rbp-434h] BYREF

  memset(v8, 0, 0x400ui64);
  v7 = a2;
  memmove(v8, a3, a2);
  return (*(__int64 (__fastcall **)(__int64, __int64, unsigned int *, __int64, _QWORD, _DWORD, _QWORD))(*(_QWORD *)a1 + 64i64))(
           a1,
           1392644i64,
           &v7,
           1028i64,
           0i64,
           0,
           0i64);
}

核心功能:将外部传入的自定义数据(a3+a2)拷贝到 1024 字节缓冲区,调用 a1 对象偏移 64 的虚函数,向 ACEDriver 驱动发送指令码 1392644 的自定义消息,并返回虚函数执行结果

最优命名:ACEDriverSendCustomData

虚表分析完毕

image

准备进入动态调试,但是发现一开启动态调试程序就会退出

我们先判断此程序可能有反调试功能,打开Strings,发现CheckRemoteDebuggerPresent函数

image

然后交叉引用跳过去

image

__int64 __fastcall sub_7FF752851C90(unsigned __int8 *a1)
{
  __int64 result; // rax
  HANDLE CurrentProcess; // rax
  __int64 perf_frequency; // rbx
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rdi
  __int64 v8; // rbx
  __int64 v9; // rax
  __int64 v10; // rbx
  __int64 v11; // rcx
  __int64 v12; // rax
  int v13; // r8d
  BOOL pbDebuggerPresent; // [rsp+20h] [rbp-58h] BYREF
  xtime v15; // [rsp+28h] [rbp-50h] BYREF

  for ( result = *a1; (_BYTE)result; result = *a1 )
  {
    pbDebuggerPresent = 0;
    CurrentProcess = GetCurrentProcess();
    CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
    if ( pbDebuggerPresent || NtCurrentPeb()->BeingDebugged == 1 || (unsigned int)sub_7FF752851EA0() )
      terminate();
    perf_frequency = Query_perf_frequency();
    v5 = sub_7FF752855344();
    if ( perf_frequency == 10000000 )
      v6 = 100 * v5;
    else
      v6 = 1000000000 * (v5 / perf_frequency) + 1000000000 * (v5 % perf_frequency) / perf_frequency;
    v7 = v6 + 1000000000;
    if ( v6 > 0x7FFFFFFFC46535FEi64 )
      v7 = 0x7FFFFFFFFFFFFFFFi64;
    while ( 1 )
    {
      v8 = Query_perf_frequency();
      v9 = sub_7FF752855344();
      v10 = v8 == 10000000 ? 100 * v9 : 1000000000 * (v9 / v8) + 1000000000 * (v9 % v8) / v8;
      if ( v7 == v10 || v7 < v10 )
        break;
      v11 = 100 * Xtime_get_ticks();
      v12 = v7 - v10;
      if ( (double)((int)v7 - (int)v10) <= 8.64e14 )
      {
        v13 = v11 + v12;
      }
      else
      {
        v12 = 864000000000000i64;
        v13 = v11 - 1391067136;
      }
      v15.sec = (v12 + v11) / 1000000000;
      v15.nsec = v13 - 1000000000 * LODWORD(v15.sec);
      Thrd_sleep(&v15);
    }
  }
  return result;
}

这段代码是反调试 + 延时循环保护函数,核心逻辑是持续检测调试器、并通过高精度计时器实现精准延时循环,直到外部条件(*a1)终止,是恶意代码 / 保护程序中常见的反调试 + 反分析逻辑

分析

  1. 反调试特征:
    • 同时检测CheckRemoteDebuggerPresentPEB->BeingDebugged,是经典的双层反调试逻辑;
    • sub_7FF752851EA0()大概率补充检测NtQueryInformationProcess(调试端口)、IsDebuggerPresent等;
  2. 延时逻辑目的:
    • 高精度延时(1 秒级)+ 无限循环 → 阻碍动态分析(调试时单步执行耗时极长)、防止程序被快速调试;
    • Thrd_sleep而非空循环 → 减少 CPU 占用,避免被 “高 CPU 占用” 特征检测;
  3. 循环终止条件:
    • *a1是外部控制的 “开关”,逆向时需找到修改*a1的代码(如特定条件满足后置 0),才能退出该循环;
  4. 潜在绕过思路:
    • 补丁CheckRemoteDebuggerPresent返回 0;
    • 补丁PEB->BeingDebugged置 0;
    • 补丁terminate()为空函数;
    • 直接修改*a1为 0,强制退出循环。
关键区别:“单步指令” vs “单步逻辑”
  • ❌ 不是 “每点击一次单步(F7/F8)就等 1 秒”:比如你单步执行反调试检测的代码(CheckRemoteDebuggerPresent),是瞬间完成的;
  • ✅ 是 “每执行到延时逻辑这一段,就必须等 1 秒才能过去”:延时逻辑是一个完整的代码块,只要执行到这个块,无论你是单步走还是直接运行,都绕不开 1 秒的等待。

核心功能:无限循环执行反调试检测(调试器附加 / PEB 标志),触发则终止程序;同时通过高性能计数器实现 1 秒级高精度延时,直到\a1置 0 时退出循环

最优命名:AntiDebugDelayLoop

然后再找AntiDebugDelayLoop函数的交叉引用

(我感觉就是找此函数 被其他函数调用的地方)

image

核心功能:分配线程参数(退出开关 + 反调试函数)→ 创建独立线程执行 AntiDebugDelayLoop → 错误处理 + 防护校验 → 保存线程信息并注册退出清理函数

最优命名:StartAntiDebugDelayThread

如何跳过反调试?

先找到函数结束地址

image

rip = rip + 0x6F 的作用

这个条件断点的逻辑是:

  1. 当程序执行到 0x7FF6847E10D4mov ecx, 10h)时触发断点;
  2. 调试器自动把当前 rip(指向 0x10D4)加上 0x6F,让 rip 直接跳到 0x1143
  3. 程序会从 0x1143继续执行,完全跳过中间所有代码:
    • operator new 内存分配
    • 线程参数构造
    • _beginthreadex 反调试线程创建
    • 全局变量校验
    • atexit 注册清理函数(也被跳过,直接到 ret

为什么是 0x6F 而不是 0x78

  • 0x10D4 + 0x6F = 0x1143,对应指令:lea rcx, sub_7FF684804820atexit 的参数准备)
  • 再下一条是 jmp atexit,之后就是 add rsp, 48h + ret
  • 0x6F 是为了刚好跳到 atexit 调用前,既跳过了反调试线程创建,又保留了程序退出时的清理逻辑(atexit 仍会执行),避免因资源泄漏导致程序崩溃。
  • 如果加 0x78 会直接跳到 0x114C,会跳过 atexit 注册,可能导致线程资源未释放,但对绕过反调试来说也可行,只是不够 “干净”。

重新回到main函数进行分析

image

回到main,发现要求他会检查输入的Flag,开头四个字节得是"ACE_"开头

然后一段一段分析

image

sub_7FF7528520D0

image

这个函数是自定义的 Base58 编码实现(区别于 Base64,使用 58 个字符集的编码算法),核心作用是将输入的二进制数据(或字符串)转换为 Base58 编码格式,同时包含内存分配、边界检查、数据反转等辅助逻辑,是 Flag 校验流程中 “编码转换” 的关键环节

Base58 编码的本质是 “将二进制数据视为大整数,反复除以 58 取余,余数映射到 Base58 字符集”,代码中对应逻辑:

// 核心循环:逐字节处理输入数据,转换为 Base58 余数
do {
  v24 = *v22;  // 取当前输入字节
  if ( v23 > 0 ) {
    // 多字节拼接:(前值 << 8) + 当前字节 → 模拟大整数
    v27 = ((unsigned __int8)*v25 << 8) + v24;
    *v25 = v27 % 58;  // 取余(Base58 核心:余数对应字符集索引)
    v24 = v27 / 58;   // 商继续参与计算
    ++v25;
    --v26;
  }
  // 循环除以 58,直到商为 0
  for ( ; v24 > 0; v24 /= 0x3Au ) {  // 0x3A = 58
    *((_BYTE *)v21 + v23) = v24 % 0x3Au;  // 存储余数
    ++v3;
    ++v23;
  }
  ++v22;
} while ( v22 != v12 );
Base58 核心特征 代码中的对应实现
基数 58 多处出现 0x3A(58)、% 58/ 58
大整数除法取余 循环拼接多字节为大整数,反复除以 58 取余
结果反转 调用 _std_reverse_trivially_swappable_1 反转字符序列
开头零补充 v13 统计开头 0 字节,补充对应 Base58 零字符
字符集映射 qword_7FF75288AFD8 读取 Base58 字符集

核心功能:该函数是 Base58 编码实现,将输入的二进制 / 字符串数据(如用户输入的 Flag)转换为 Base58 编码格式,包含内存分配、边界检查、字符集映射、结果反转等完整逻辑;

关键特征:基数 58(0x3A)、大整数取余、结果反转、Base58 字符集映射是识别该函数的核心标识

qword_7FF75288AFD8,动态调试,查看qword_7FF75288AFD8内存

abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ1234567890!@+/

虽然得到了63个字符,但只能用到前58个,因为源函数一直是对58取余

image

摸清楚前一段Base58后,再往下看

 Base58(v58, v50);
      Src[0] = (void *)'\0';
      v19 = '\0';
      *(_QWORD *)&v49 = '\0';
      v20 = '\x0F';
      *((_QWORD *)&v49 + 1) = '\x0F';
      v21 = '\0';
      v22 = (void **)Block[0];
      if ( v59 )
      {
        v23 = v55 >= 0x10;
        do
        {
          v24 = v58;
          if ( v60 >= 0x10 )
            v24 = (__int64 *)v58[0];
          v25 = Block;
          if ( v23 )
            v25 = v22;
          v26 = *((_BYTE *)v24 + v21) ^ *((_BYTE *)v25 + v21 % v54);
          if ( v19 >= v20 )
          {
            sub_7FF7D10C29A0(Src);
          }
          else
          {
            *(_QWORD *)&v49 = v19 + 1;
            v27 = Src;
            if ( v20 >= 0x10 )
              v27 = (void **)Src[0];
            *((_BYTE *)v27 + v19) = v26;
            *((_BYTE *)v27 + v19 + 1) = 0;
          }
          ++v21;
          v20 = *((_QWORD *)&v49 + 1);
          v19 = v49;
        }
        while ( v21 < v59 );
        v6 = v42;
      }
      if ( v60 >= 0x10 )
      {
        v28 = (void *)v58[0];
        if ( v60 + 1 >= 0x1000 )
        {
          v28 = *(void **)(v58[0] - 8);
          if ( (unsigned __int64)(v58[0] - (_QWORD)v28 - 8) > 0x1F )
            invalid_parameter_noinfo_noreturn();
        }
        j_j_free(v28);
      }
      v56 = *(_OWORD *)Src;
      v57 = v49;
      if ( v52 >= 0x10 )
      {
        v29 = (void *)v50[0];
        if ( v52 + 1 >= 0x1000 )
        {
          v29 = (void *)*((_QWORD *)v50[0] - 1);
          if ( (unsigned __int64)(v50[0] - (const void *)v29 - 8) > 0x1F )
            invalid_parameter_noinfo_noreturn();
        }
        j_j_free(v29);
      }
      v51 = 0i64;
      v52 = '\x0F';
      LOBYTE(v50[0]) = 0;
      if ( v55 >= 0x10 )
      {
        v30 = v22;
        if ( v55 + 1 >= 0x1000 )
        {
          v22 = (void **)*(v22 - 1);

这段代码是 Base58 编码后的数据加解密 / 校验逻辑 —— 先对输入做 Base58 编码,再通过异或(XOR) 与预设的 Block 数据做逐字节混淆,最后进行内存清理和边界校验,是 Flag 校验的核心环节(Base58 编码后的二次验证)

1.初始化阶段(准备异或操作)

Base58(v58, v50);  // 先对输入(Flag截取后的数据)做Base58编码,结果存在v58
Src[0] = (void *)'\0';
v19 = '\0';
*(_QWORD *)&v49 = '\0';
v20 = '\x0F';  // 缓冲区最大长度(15字节,0x0F=15)
*((_QWORD *)&v49 + 1) = '\x0F';
v21 = '\0';  // 异或循环的索引(从0开始遍历每个字节)
v22 = (void **)Block[0];  // Block是预设的异或密钥,v22指向密钥起始地址

关键:v20 = 0x0F 限定了异或处理的最大字节数为 15,超过则触发缓冲区扩容(sub_7FF7D10C29A0)。

2.核心:逐字节异或混淆(Flag 校验的关键)

if ( v59 )  // v59是Base58编码结果的长度,非0则执行异或循环
{
  v23 = v55 >= 0x10;  // v55是Block的长度,判断是否≥16字节(指针偏移兼容)
  do
  {
    // 步骤1:定位Base58编码后的当前字节(兼容指针偏移)
    v24 = v58;
    if ( v60 >= 0x10 )
      v24 = (__int64 *)v58[0];  // 长度≥16时,v58[0]是实际数据地址
    
    // 步骤2:定位Block密钥的当前字节(兼容指针偏移)
    v25 = Block;
    if ( v23 )
      v25 = v22;  // Block长度≥16时,v22是实际密钥地址
    
    // 步骤3:核心异或操作!Base58字节 XOR Block字节(取模避免密钥越界)
    v26 = *((_BYTE *)v24 + v21) ^ *((_BYTE *)v25 + v21 % v54);
    
    // 步骤4:将异或结果写入输出缓冲区Src(边界校验)
    if ( v19 >= v20 )  // 超过15字节则扩容
      sub_7FF7D10C29A0(Src);
    else
    {
      *(_QWORD *)&v49 = v19 + 1;  // 计数+1
      v27 = Src;
      if ( v20 >= 0x10 )
        v27 = (void **)Src[0];  // 长度≥16时,Src[0]是实际输出地址
      *((_BYTE *)v27 + v19) = v26;  // 写入异或结果
      *((_BYTE *)v27 + v19 + 1) = 0;  // 末尾补0(字符串结束符)
    }
    
    ++v21;  // 索引+1,处理下一个字节
    v20 = *((_QWORD *)&v49 + 1);
    v19 = v49;
  } while ( v21 < v59 );  // 遍历完Base58编码的所有字节
}

最核心逻辑:v26 = Base58字节 ^ Block字节

异或的特点是 “可逆”(A^B=C → C^B=A),程序会把异或后的结果 v26 与预设的 “正确结果” 对比,一致则 Flag 校验通过,否则失败。

取模操作 v21 % v54:如果 Base58 结果长度超过 Block 密钥长度,循环使用 Block 密钥(比如 Block 长度 5,第 6 个字节用 Block [0])。

3.内存释放阶段(避免泄漏 + 防溢出)

// 释放v58的临时内存(Base58编码时分配的)
if ( v60 >= 0x10 )
{
  v28 = (void *)v58[0];
  if ( v60 + 1 >= 0x1000 )  // 大内存(≥4096字节)特殊释放逻辑
  {
    v28 = *(void **)(v58[0] - 8);  // 取原始分配地址(对齐时记录的)
    if ( (unsigned __int64)(v58[0] - (_QWORD)v28 - 8) > 0x1F )
      invalid_parameter_noinfo_noreturn();  // 越界则终止程序
  }
  j_j_free(v28);  // 释放内存(等价于free())
}

// 释放v50的临时内存(同理)
if ( v52 >= 0x10 )
{
  v29 = (void *)v50[0];
  // 同上:大内存特殊释放 + 越界校验
  j_j_free(v29);
}

关键:程序对内存释放做了严格的边界校验(比如 >0x1F 则终止),是防止内存溢出攻击的保护逻辑。

4.收尾阶段(保存结果 + 重置变量)

v56 = *(_OWORD *)Src;  // 保存异或后的结果(16字节)到v56,用于后续校验
v57 = v49;  // 保存处理的字节数
v51 = 0i64;
v52 = '\x0F';
LOBYTE(v50[0]) = 0;  // 重置缓冲区

v56 是后续 Flag 校验的 “关键对比值”—— 程序会把 v56 与硬编码的正确异或结果对比,一致则 Flag 正确。

找到 Block 密钥

  • Block 是预设的异或密钥,定位 Block[0] 的地址(比如 qword_7FF7D10Fxxxx),提取其内存数据(比如 15 字节的固定值);
  • 动态调试:在 v26 = A ^ B 这行下断点,查看 *((_BYTE *)v25 + v21 % v54) 的值,记录完整的 Block 密钥。

简单来说:这段代码的核心是 Base58 编码 + 异或混淆 + 结果校验,破解的关键是提取 Block 密钥和正确的异或结果,再通过异或可逆性反向推导 Base58 原始值,最终还原 Flag。

下一段

image

memmove(v41, v31, v19);
      if ( (*(unsigned int (__fastcall **)(_QWORD *, _QWORD, void *))(*v6 + 96i64))(v6, (unsigned int)v49, v41) )
        v33 = "Flag not correct!";
      else
        v33 = "Flag is correct!";
      v34 = sub_7FF7D10C4A60(v32, v33);
      v4 = v47;
      v10 = (char *)v45[0];
      v3 = *((_QWORD *)&v57 + 1);
    }
    else
    {
LABEL_54:
      v34 = sub_7FF7D10C4A60(v11, "Flag not correct!");
      v5 = v41;
    }
    sub_7FF7D10C4CF0(v34);
    (*(void (__fastcall **)(_QWORD *))(*v6 + 80i64))(v6);
  }
  else
  {
    v8 = sub_7FF7D10C4A60(v7, "Driver loading failed, please check the environment.");
    sub_7FF7D10C4CF0(v8);
    v10 = (char *)v45[0];
  }

image

image

image

96i64 = 十六进制 0x60

对应表中 +0x60 条目:ACEDriverSendCustomData

然后我们进入ACEDriverSendCustomData这个函数查看

image

Ring3通信发送到ACEDriver,命令码是0x154004。核心是 Flag 校验相关数据

目的:让内核驱动完成最终的 Flag 对比,防止用户态逆向 / 篡改

简单说:用户态程序只负责处理 Flag 格式(ACE_开头、Base58、异或),但最终 “对不对” 的判定权在驱动手里,这也是这类程序难破解的核心 —— 关键对比逻辑在内核态,用户态逆向只能拿到 “处理流程”,拿不到 “正确结果”。

自此,完成Ring3层的分析

Ring0

驱动无法加载

image

把sys拖进ida,发现有回调函数

  1. 混淆,把程序逻辑转换成更晦涩但等价的形式。
  2. 加花,对抗反汇编引擎,利用反汇编工具的算法缺陷、漏洞来迫使分析者必须花费大量时间处理错误的反汇编结果,让诸如控制流视图之类的工具失效。

image

ConnectNotifyCallback

image-20260319082421641
一、这段代码的核心特征:花指令 + 动态计算跳转地址

这是典型的内核态反逆向混淆代码(ConnectNotifyCallback 是驱动的端口连接回调函数),核心目的是隐藏真实的函数入口 / 逻辑,防止静态分析。

1. 花指令(Junk Code)特征
  • 无意义指令:not r9xchg rax, r9 这类指令只是打乱寄存器状态,对业务逻辑无任何作用。
  • 数据插在代码中间:dw 5AE9hdb 0E9h,41h,58h... 是直接插入的垃圾数据,破坏反汇编器的指令解析,让 IDA 无法正确识别函数边界。
  • 冗余操作:pushfq + popfq 会保存并恢复标志寄存器,本质是干扰单步调试(破坏 TF 标志)。
2. 动态跳转(Obfuscated Control Flow)特征

代码里有两处典型的“先算地址,再跳转”逻辑:

第一处(rdx 分支):

mov     rdx, 1A11B4C41h
pushfq
add     rdx, 0FFFFFFFF9EE552D6h  ; 等价于 rdx -= 0x611AAAD2Ah
popfq
jmp     rdx

静态无法直接看出跳转目标,必须计算:

0x1A11B4C41h + 0xFFFFFFFF9EE552D6h = 0x1A11B4C41h - 0x611AAAD2Ah = 0xE0009171h(实际地址需结合模块基址修正)

目的:让静态分析器无法直接解析跳转目标,必须动态计算或调试才能看到真实代码。

第二处(r8 分支):

mov     r8, 11075C6FEh
pushfq
add     r8, 2F8AD83Eh
popfq
jmp     r8

计算后目标:0x11075C6FEh + 0x2F8AD83Eh = 0x140009F3Ch

这是跳转到下一段真实逻辑的入口,中间被花指令隔开。

二、ConnectNotifyCallback 的作用

在 Windows 驱动开发中,ConnectNotifyCallback过滤通信端口(Filter Communication Port) 的连接回调函数:

  • 当用户态程序调用 FilterConnectCommunicationPort 尝试连接驱动时,内核会触发这个回调;
  • 它的核心职责是:
    1. 验证连接请求的合法性(比如检查进程名 / 权限,防止恶意程序连接驱动);
    2. 分配连接上下文;
    3. 返回是否允许连接(STATUS_SUCCESS 或拒绝状态)。

这个回调是 用户态 EXE 连接 ACE 驱动的 “第一道门”,只有通过它的验证,用户态才能和驱动建立通信,后续才能发送 Flag 校验数据。

代码里的混淆 / 花指令,就是为了防止逆向者篡改验证逻辑(比如让任意程序都能连接驱动)。

解决花指令方法:
image-20260319084753162

步骤 1:按 u(Undefine,取消定义)

  • 操作:光标定位到 41E9h 对应的行,按下快捷键 u

  • 作用:

    1. 把 IDA 误识别成 “指令” 的 41E9h 还原成原始字节数据(不再解析为指令);
    2. 解除错误的指令绑定,为后续修正做准备。
    image-20260319084902994

步骤 2:跳过 E9 字节(定位到正确指令起始位)

  • 操作:

    1. 查看 41E9h 的字节布局:实际是两个字节 0xE9(第一个字节) + 0x41(第二个字节);
    2. 把光标移动到 0xE9下一个字节(即 0x41 之后的位置);
  • 原理:0xE9 是花指令插入的垃圾字节,必须跳过它,才能找到真实指令的起始位置。

    把鼠标光标放在5A之后:

image-20260319085033304

步骤 3:按 c(Code,重新定义为代码)

  • 操作:光标停在跳过 0xE9 后的正确位置,按下快捷键 c
  • 作用:
    1. IDA 会从这个位置重新开始反汇编,解析出正确的指令流;
    2. 原本被花指令打乱的 jmp r10/lea r10, [...] 等核心指令,会被正确识别;
    3. 花指令被 “剥离”,你能看到 ConnectNotifyCallback 的真实逻辑
image-20260319085123366
观察花指令
image-20260319085801341

MessageNotifyCallback

image-20260319090315446

然后截图处是一块,将地址计算完进行jmp,计算出来实际就是jmp到下面pop处,说明这一段是无用的,可以直接将push到pop全部nop即可。其他地方都和这地方混淆差不多类型,都直接跳过花指令后nop即可。

push r10
lea r10, cs:172AA3EECh
lea r10, [r10-32A99F2Dh]
jmp r10

计算目标地址:0x172AA3EECh−0x32A99F2Dh=0x140009FBFh而 0x140009FBFh 正好是下方 pop r10 指令的地址,

所以:jmp r10 本质就是 跳转到 pop r10,整个 push → lea → jmp 流程完全是冗余的。

批量 NOP 时,在 IDA 中选中这段地址范围,右键 → Edit → Patch program → Assemble,输入 nop 即可批量替换

image-20260319090704640 image-20260319091513000

计算目标地址:

0x1A1D0F0C9−0x61D050E3=0x140009FE6

0x140009FE6 正好是下方 mov edi, 3FFFFFF2h 指令的起始地址,所以这段代码本质是:

绕了一大圈,最后直接跳转到下一行正常指令。

为什么可以 NOP?
  • 无业务逻辑:这段代码没有读写任何全局变量、没有调用内核函数,只是做了一次无意义的地址计算和跳转,对程序功能完全没有影响
  • 栈平衡pushfqpopfq 成对出现,即使把中间所有指令(从 mov r11jmp r11)全部 NOP 掉,栈指针也不会失衡,程序能正常执行。
  • 纯混淆目的:和你之前清理的其他花指令一样,这段代码的唯一作用就是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。
具体操作
  • NOP 范围:从 0x140009FCFmov r11, ...)到 0x140009FE2jmp r11)的所有指令,包括中间的 pushfq/popfq
  • 清理后效果:IDA 会直接显示下方的 mov edi, 3FFFFFF2h,代码变得干净清晰,没有任何冗余混淆。
总结

db 0E8h,41h,5Bh插入的垃圾数据字节,而非 “IDA 解析错误”,所以:

  • u+c 只能改 IDA 的解析逻辑,改不了原始字节,运行时仍会出问题;
  • ✅ 直接 NOP 替换成空指令,既让 IDA 正确解析后续代码,又让 CPU 运行时跳过这些垃圾字节,是最彻底的处理方式。
下一段
image-20260319092515209

计算目标地址:

0x17DFA87AD−0x3DF99E744=0x17DFA87AD+0xFFFFFFFFFC206185C=0x14000A006

jmp rax 最终跳转到 0x14000A006,也就是下方 xchg r11, rcx 指令的起始位置。

结论:可以安全 NOP 掉

无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。

栈平衡pushfqpopfq 成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。

纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。

处理建议
  1. NOP 跳转块:从 0x140009FF3mov rax, ...)到 0x14000A006jmp rax)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A0080x14000A047dq ... 垃圾数据全部 NOP 掉。
下一段
image-20260319093122223

计算目标地址:

0x1722CBF2F−0x322C240CD=0x1722CBF2F+0xFFFFFFFFCDD3E133=0x14000A062

jmp rdx 最终跳转到 0x14000A062,也就是下方 add al, 17h 指令的起始位置。

结论:可以安全 NOP 掉,原因如下:

  1. 无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。
  2. 栈平衡pushfqpopfq 成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。
  3. 纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。

额外注意:中间的垃圾数据

下方 0x14000A061 开始的 db 0E8h,5Ah,...插入的垃圾数据,和之前的 db 0E8h,41h,5Bh 一样:

  • 这些字节会破坏指令流解析,必须和前面的跳转花指令一起批量 NOP 掉
  • 否则 IDA 会把这些垃圾数据误解析为指令,导致后续 add al, 17h 等代码解析错位。
处理建议
  1. NOP 跳转块:从 0x14000A04Cmov rdx, ...)到 0x14000A05Fjmp rdx)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A0610x14000A067db ... 垃圾数据全部 NOP 掉。
  3. 还原代码:处理后 IDA 会正确识别下方的 add al, 17h 等真实指令。
image-20260319093327158
下一段
image-20260319093406220

计算目标地址:

0x12278A45E+0x1D87FC28=0x14000A086

jmp r8 最终跳转到 0x14000A086,也就是下方 jnb loc_14000A2E8 指令的起始位置。

这段代码是否可以 NOP 掉?

结论:可以安全 NOP 掉,原因如下:

  1. 无业务逻辑:这段代码仅用于动态计算跳转,没有读写全局变量、没有调用内核函数,对程序功能无任何影响。
  2. 栈平衡pushfqpopfq 成对出现,NOP 掉后栈指针(RSP)不会失衡,程序能正常运行。
  3. 纯混淆目的:和之前清理的花指令一样,这段代码的唯一作用是干扰 IDA/WinDbg 的静态分析,让控制流变得混乱。

额外注意:中间的垃圾数据

下方 0x14000A085 开始的 db 0E9h,41h,58h插入的垃圾数据,和之前的 db 0E8h,41h,5Bh 一样:

  • 这些字节会破坏指令流解析,必须和前面的跳转花指令一起批量 NOP 掉
  • 否则 IDA 会把这些垃圾数据误解析为指令,导致后续 jnb loc_14000A2E8 等代码解析错位。
处理建议
  1. NOP 跳转块:从 0x14000A06Fmov r8, ...)到 0x14000A082jmp r8)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A0850x14000A087db ... 垃圾数据全部 NOP 掉。
  3. 还原代码:处理后 IDA 会正确识别下方的 jnb loc_14000A2E8jmp loc_14000A094 等真实指令。
下一段
image-20260319093704734

计算目标地址:

0x189E67159−0x49E85D05=0x189E67159+0xFFFFFFFFFB61A2F5B=0x14000A0B4

jmp r9 最终跳转到 0x14000A0B4,也就是下方 C8 0F 83 ... 指令的起始位置。

处理建议
  1. NOP 跳转块:从 0x14000A09Dmov r9, ...)到 0x14000A0B0jmp r9)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A0B30x14000A11Fdb ...dq ... 垃圾数据全部 NOP 掉。
  3. 还原代码:处理后 IDA 会正确识别下方的真实指令流。
下一段
image-20260319093949164

计算目标地址:

0x109122C9D+0x36EE74B1=0x14000A14E

jmp rcx 最终跳转到 0x14000A14E,也就是下方 test eax, eax 指令的起始位置

下方 0x14000A14D 开始的 db 0E8h,59h,48h插入的垃圾数据

下一段
image-20260319094353213

计算目标地址:

0xC85BAA45+0x77A4F73F=0x14000A184

jmp rdx 最终跳转到 0x14000A184,也就是下方 not rcx 指令的起始位置。

下方 0x14000A183 开始的 db 0E9h,5Ah,48h,87h,0CEh插入的垃圾数据

下一段

image-20260319094453622

lea     r8, cs:173BC73E1h
lea     r8, [r8-33BBD233h]
jmp     r8

计算目标地址:

0x173BC73E1−0x33BBD233=0x14000A1AE

jmp r8 最终跳转到 0x14000A1AE,也就是下方 xchg rdx, rsi 指令的起始位置。

处理

NOP 跳转块:从 0x14000A19Apush r8)到 0x14000A1AAjmp r8)全部 NOP 掉(前面的 mov r8d, r15dnot rsi 是真实业务逻辑,不要 NOP)。

NOP 垃圾数据:从 0x14000A1AD0x14000A1AFdb ... 垃圾数据全部 NOP 掉

下一段
image-20260319094728748
lea     r9, cs:16207C72Ch
lea     r9, [r9-22072556h]
jmp     r9

计算目标地址:

0x16207C72C−0x22072556=0x14000A1D6

jmp r9 最终跳转到 0x14000A1D6,也就是下方 mov rbx, rcx 指令的起始位置。

下方 0x14000A1D5 开始的 db 0E8h,41h,59h插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A1C2push r9)到 0x14000A1D2jmp r9)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A1D50x14000A1D7db ... 垃圾数据全部 NOP 掉。
下一段
image-20260319095426933
mov     r10, 11A17ED71h
pushfq
add     r10, 25E8B49Eh
popfq
jmp     r10

计算目标地址:

0x11A17ED71+0x25E8B49E=0x14000A20F

jmp r10 最终跳转到 0x14000A20F,也就是下方 pop rdx 指令的起始位置。

下方 0x14000A20E 开始的 dw 41E8h(即 db 0E8h, 0x41h)是插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A1F6push r10)到 0x14000A20Bjmp r10)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A20E0x14000A20Fdw ... 垃圾数据全部 NOP 掉。
下一段
image-20260319095618705
push    r11
lea     r11, cs:byte_14000A23B  ; 加载垃圾数据标签地址
lea     r11, [r11+1]           ; 地址 +1,计算目标
jmp     r11                     ; 跳转

byte_14000A23B 的地址是 0x14000A23B[r11+1] 相当于直接跳转到了 0x14000A23C

0x14000A23B 处的 db 0E9h, 41h, 5Bh, 4Dh, 87h典型的伪指令垃圾

0xE9jmp 指令的操作码。如果不 NOP,IDA 会把 0xE9 误识别为一个跳转指令,把后面的 4 个字节当作偏移。

处理

NOP 范围

  • 第一部分0x14000A228 ~ 0x14000A238push r11jmp r11)。
  • 第二部分0x14000A23B ~ 0x14000A23Fbyte_14000A23B 及其后面的垃圾字节,直到 0xA250 之前)。
下一段
image-20260319100223240

计算目标地址:

0x11D28C2E9+0x22D7DF7E=0x14000A267

jmp rax 最终跳转到 0x14000A267,也就是下方 not rcx 指令的起始位置。

下方 0x14000A266 开始的 dw 58E9h(即 db 0xE9h, 0x58h)是插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A250push rax)到 0x14000A264jmp rax)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A2660x14000A267dw ... 垃圾数据全部 NOP 掉。
下一段
image-20260319102820368

典型的两次 lea 计算地址 + 跳转花指令

计算目标地址:

0x186D3B244−0x46D30FB0=0x14000A294

jmp rcx 最终跳转到 0x14000A294,也就是下方 and al, 58h 指令的起始位置。

下方 0x14000A293 开始的 db 0E8h,59h,48h,8Bh,5Ch插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A282push rcx)到 0x14000A291jmp rcx)全部 NOP 掉(前面的 not edi 是真实业务逻辑,不要 NOP)。
  2. NOP 垃圾数据:从 0x14000A2930x14000A297db ... 垃圾数据全部 NOP 掉。
下一段
image-20260319102932362

计算目标地址:

0x176E74261−0x36E69FA5=0x14000A2BC

jmp rdx 最终跳转到 0x14000A2BC,也就是下方 not rcx 指令的起始位置。

下方 0x14000A2BB 开始的 db 0E8h,5Ah,48h,87h,0CBh插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A2AApush rdx)到 0x14000A2B9jmp rdx)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A2BB0x14000A2BFdb ... 垃圾数据全部 NOP 掉。
下一段

这段是典型的两次 lea 计算地址 + 跳转花指令

image-20260319103057948

计算目标地址:

0x0CD5F4469+0x72A15E77=0x14000A2E0

jmp r8 最终跳转到 0x14000A2E0,也就是下方 pop r8 指令的起始位置。

下方 0x14000A2DF 开始的 db 0E9h插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A2CCpush r8)到 0x14000A2DCjmp r8)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A2DF0x14000A2DFdb ... 垃圾数据全部 NOP 掉。
下一段
image-20260319103246048

计算目标地址:

0x0E7550831+0x58AB9AD2=0x14000A303

jmp r9 最终跳转到 0x14000A303,也就是下方 and al, 60h 指令的起始位置。

下方 0x14000A302 开始的 dw 41E9h, 4859h, 748Bh(即 db 0xE9h, 0x41h, 0x59h, 0x48h, 0x8Bh, 0x74h)是插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A2EFpush r9)到 0x14000A2FFjmp r9)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A3020x14000A307dw ... 垃圾数据全部 NOP 掉
下一段
image-20260319103355968

计算目标地址:

0x14000A320+0xB=0x14000A32B

jmp r10 最终跳转到 0x14000A32B,也就是下方 and al, 8 指令的起始位置。

下方 0x14000A32A 开始的 dw 41E8h, 485Ah, 648Dh(即 db 0xE8h, 0x41h, 0x5Ah, 0x48h, 0x8Dh, 0x64h)是插入的垃圾数据

处理建议
  1. NOP 跳转块:从 0x14000A317push r10)到 0x14000A327jmp r10)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A32A0x14000A32Fdw ... 垃圾数据全部 NOP 掉。
下一段
image-20260319103452365
下一段
image-20260319103534284
处理建议
  1. NOP 跳转块:从 0x14000A36Epush rax)到 0x14000A382jmp rax)全部 NOP 掉。
  2. NOP 垃圾数据:从 0x14000A3840x14000A387dd ... 垃圾数据全部 NOP 掉。

什么时候用 u+c?什么时候用 NOP

1. 用 u+c 的场景(仅修正解析,不改字节)
  • 例子:IDA 把正常的 jmp r10 误解析成 db 0FFh, 0E2h
  • 核心:字节本身是合法指令,只是 IDA 解析边界错了,u+c 让 IDA 重新识别即可,无需改字节。
2. 用 NOP 的场景(替换垃圾字节,消除影响)
  • 例子:插入的 db 0E8h,41h,5Bhdw 41E9h 等垃圾数据;
  • 核心:字节本身是非法 / 无意义的,会破坏指令流或导致运行崩溃,必须替换成 NOP 空指令。
操作方式 本质作用 适用场景
u+c 修正 IDA 的指令解析边界,让 IDA 重新识别指令(仅改 “解析逻辑”,不改字节) 花指令是 “指令被误解析成数据” 或 “数据被误解析成指令”,但字节本身不影响运行
直接 NOP 替换原始字节为 0x90(空指令),彻底消除垃圾字节的影响(改 “原始字节”) 花指令是 “插入的垃圾数据字节”,会破坏指令流 / 导致运行崩溃
posted @ 2026-03-26 09:59  L1dk  阅读(4)  评论(0)    收藏  举报