吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 976|回复: 5
上一主题 下一主题
收起左侧

[已解决] 一个2023年简单的Cpp逆向题.

[复制链接]
跳转到指定楼层
楼主
wgf4242 发表于 2024-8-11 12:17 回帖奖励
本帖最后由 wgf4242 于 2024-8-12 07:44 编辑

附件 https://wwi.lanzoup.com/iYKzj272ztwd

在经输入内容后下硬件断点观察.
140004160有个位逆序...
[C] 纯文本查看 复制代码
  for ( i = 0; i < 36; ++i )
  {
    *((_BYTE *)a1[1] + i) = (2 * (*((_BYTE *)a1[1] + i) & 0x55)) | ((*((_BYTE *)a1[1] + i) & 0xAA) >> 1);
    *((_BYTE *)a1[1] + i) = (4 * (*((_BYTE *)a1[1] + i) & 0x33)) | ((*((_BYTE *)a1[1] + i) & 0xCC) >> 2);
    *((_BYTE *)a1[1] + i) = (16 * (*((_BYTE *)a1[1] + i) & 0xF)) | ((*((_BYTE *)a1[1] + i) & 0xF0) >> 4);
    result = (unsigned int)(i + 1);
  }


中间有try catch...处理了一下还是报错.

后面还会触发一个sub_140004260.  还没看懂. byte_140042000是个表. , 这里把 表里的内容和 Str[i%4]进行了异或存到了1e0000里.
[C] 纯文本查看 复制代码
BOOL __fastcall sub_140004260(__int64 a1)
{
  int ii; // [rsp+20h] [rbp-28h]
  _BYTE *v3; // [rsp+30h] [rbp-18h]

  lpAddress = VirtualAlloc(0i64, 0xE8ui64, 0x1000u, flOldProtect);
  v3 = lpAddress;
  for ( ii = 0; ii < 232; ++ii )
    v3[ii] = *(*(a1 + *(*(a1 - 16) + 4i64) - 8) + ii % 4) ^ byte_140042000[ii];
  return VirtualProtect(lpAddress, 0xE8ui64, 0x20u, &flOldProtect);
}


[C] 纯文本查看 复制代码
unsigned char byte_140042000[232] = {
    0x27, 0x44, 0x7F, 0xEB, 0x3A, 0x8F, 0x1A, 0x2E, 0xFB, 0x41, 0xE6, 0x46, 0xFB, 0x59, 0xEE, 0x42, 
    0xFB, 0x49, 0xD6, 0x46, 0xF9, 0x49, 0xEE, 0x85, 0x72, 0x85, 0xB3, 0xF6, 0x3A, 0x87, 0xB3, 0x16, 
    0xF9, 0x4C, 0xF2, 0x87, 0x37, 0xF8, 0x31, 0x4B, 0x82, 0x0C, 0xF6, 0x0E, 0x72, 0xCB, 0xB3, 0xE2, 
    0x0D, 0xA0, 0x9C, 0x89, 0xB5, 0x49, 0x0A, 0x0E, 0x72, 0x0C, 0xF6, 0x85, 0x37, 0xF0, 0xCD, 0x4B, 
    0x62, 0x03, 0x75, 0x8A, 0x72, 0x0C, 0xF6, 0x85, 0x37, 0xF8, 0x7B, 0x1A, 0xB7, 0x0C, 0xF6, 0x0E, 
    0x72, 0x87, 0xB3, 0xFA, 0xB3, 0xE4, 0xF0, 0x3F, 0xB0, 0x87, 0xB3, 0xFA, 0x73, 0xCE, 0x7D, 0x4B, 
    0x82, 0x8F, 0x16, 0x0D, 0x3A, 0x81, 0xFA, 0x8B, 0x72, 0x0C, 0xF6, 0x0E, 0x3A, 0x87, 0xB3, 0x2E, 
    0x3A, 0x0D, 0x3E, 0x85, 0x7A, 0x87, 0xB3, 0xFE, 0x73, 0xC4, 0xC7, 0xDE, 0x73, 0x49, 0x0E, 0x85, 
    0x37, 0xE0, 0xF7, 0x4B, 0x82, 0x87, 0xB3, 0xF6, 0xFF, 0x18, 0x33, 0x0E, 0x72, 0x0C, 0xF6, 0x85, 
    0x37, 0xF4, 0x37, 0xE6, 0x74, 0x3D, 0x34, 0x85, 0x37, 0xF4, 0xF7, 0xCC, 0xF9, 0x49, 0x06, 0xCF, 
    0x9A, 0x07, 0x7F, 0xCE, 0xF1, 0xEC, 0xF5, 0x46, 0xFF, 0x00, 0x73, 0x0E, 0x72, 0x0C, 0xF6, 0x46, 
    0xF9, 0x49, 0xD6, 0x46, 0x73, 0xC4, 0x7D, 0x06, 0xF9, 0x49, 0x06, 0x0F, 0xBA, 0x3D, 0x26, 0x0F, 
    0x37, 0xF8, 0x75, 0x4B, 0x8E, 0x0D, 0x1F, 0x7E, 0x8D, 0xF3, 0x09, 0x46, 0xF9, 0x49, 0xEE, 0x85, 
    0x27, 0xF4, 0x7F, 0x1E, 0x3A, 0x87, 0xB3, 0x16, 0x3A, 0x8F, 0x36, 0x0A, 0xF9, 0x59, 0x02, 0x87, 
    0x62, 0x9C, 0xBE, 0x8D, 0xB6, 0x2C, 0xAB, 0xCD
};


后面好像还调用到这个 byte_140042000
[C] 纯文本查看 复制代码
void sub_140004350()
{
  hThread = beginthreadex(0i64, 0, StartAddress, byte_140042000, 0, &ThrdAddr);
  SetThreadAffinityMask(hThread, 1ui64);
  Sleep(0x64u);
}


void __fastcall __noreturn StartAddress(void *a1)
{
  int ii; // [rsp+20h] [rbp-18h]

  while ( 1 )
  {
    for ( ii = 0; ii < 0xE8; ++ii )
    {
      if ( *(lpAddress + ii) == 204 )
        exit(-1);
    }
  }
}

这个 StartAddress 没看懂干了啥.单纯检查有CC就退出么.
大概 1E0000 里面放的是一段程序。最后会调用。

最后密文比较为 720CF60E8C69236959A806EF2A1A56B696ACEE925CF2ED0A5F368E41A636867256D254C2   .只位逆序后解出部分明文.
sub_140001230 成功后把  062066363060267B 每个进行  ^0x55 最后输出 Su3ce5s.

硬件断点 断下的我都看了...可能差了一步..调试执行最终都是报错.没解出明文..
感觉需要确定前4个字符来还原 1e0000的代码,我的想法是通过最后是retn(C3) 异或 byte_140042000最后字节(0xCD) 再逆位序可得 第4字符为p.前面3个怎么确定呢? , 把最后比较内容直接 位逆序得到 N0op.应该快出了.

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
zzx114514 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
爱飞的猫 发表于 2024-8-12 04:01
本帖最后由 爱飞的猫 于 2024-8-12 04:46 编辑

SimpleRE

小陷阱不少,用了继承特性。静态分析比较绕,结合动态调试会容易些。如果用了调试器隐藏插件,可以无视本体的反调试检测(IsDebuggerPresentGetThreadContext 处)。

入口

简单检查了一下输入长度(36),然后初始化了三个类:

  • Base
  • DecryptBase
  • FinExecDecryptBase

最后这个的继承顺序我不确定… 我直接动态调试看它调用什么地址了。

因为知道长度了,先整个符合条件的伪码:0123456789abcdefghijklmnopqrstuvwxyz

Base 类

主要代码在 140004160,对字节的比特进行翻转。

简化后的代码如下:

void sub_140004160(unsigned char *key) {
    for (int i = 0; i < 36; ++i) {
        key[i] = ((key[i] & 0x55) << 1) | ((key[i] & 0xAA) >> 1);
        key[i] = ((key[i] & 0x33) << 2) | ((key[i] & 0xCC) >> 2);
        key[i] = ((key[i] & 0x0F) << 4) | ((key[i] & 0xF0) >> 4);
    }
}

本质上就是将每个字节的顺序反转。例如测试代码:

void print_test_data(const uint8_t* test_data, size_t len) {
  for (int i = 0; i < len; i++) {
    unsigned char c = test_data[i];
    for (int j = 0; j < 8; j++) {
      printf("%d", (c & (1 << (7 - j))) ? 1 : 0);
    }
    printf(" ");
  }
  printf("\n");
}

void main() {
    char test_data[40]{};
    for (int i = 0; i < 8; i++) {
        test_data[i] = 1 << i;
    }

    print_test_data(test_data, 8);
    sub_140004160((unsigned char *) test_data);
    print_test_data(test_data, 8);
}

可以发现每一项都是将 1 的位置左右调换:

00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000
10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001

Decrypt 类

1400021E0 是初始化 Decrypt 类的代码。同时会检测调试器(IsDebuggerPresent + 硬件断点)。

可以手动打补丁跳过检测:

.text:0000000140002141                 call    cs:IsDebuggerPresent
.text:0000000140002147                 test    eax, eax
.text:0000000140002149                 jz      short loc_140002155   ; 改 => jmp 1400021B3

之后就是调用 140004254 了,改了下 this 指针的偏移然后跳到 140004260

void __thiscall sub_140004260(__int64 a1)
{
  int i; // [rsp+20h] [rbp-28h]
  _BYTE *pShellCode; // [rsp+30h] [rbp-18h]

  g_ShellCode = VirtualAlloc(0i64, 0xE8ui64, MEM_COMMIT, flOldProtect);
  pShellCode = g_ShellCode;
  for ( i = 0; i < 232; ++i )
    pShellCode[i] = *(*(a1 + *(*(a1 - 16) + 4i64) - 8) + i % 4) ^ byte_140042000[i];
  VirtualProtect(g_ShellCode, 0xE8ui64, PAGE_EXECUTE_READ, &flOldProtect);
}

上调试器查看,*(a1 + *(*(a1 - 16) + 4i64) - 8) 就是序列号的地址。过程是在 +42FB 处下断,然后查看 rcx 指向的内存区域内容。

00007FF7667A42FB | 0FB60401  | movzx eax,byte ptr ds:[rcx+rax]  | rcx = 00007FF7667E3270

对应的内存区域(就是我们的伪码处理后的内容):

00007FF7667E3270  0C 8C 4C CC 2C AC 6C EC 1C 9C 86 46 C6 26 A6 66  ..LÌ,¬lì...FÆ&¦f  
00007FF7667E3280  E6 16 96 56 D6 36 B6 76 F6 0E 8E 4E CE 2E AE 6E  æ..VÖ6¶vö..NÎ.®n  
00007FF7667E3290  EE 1E 9E 5E 00 00 00 00 70 32 7E 66 F7 7F 00 00  î..^....p2~f÷...  

姑且不清楚其格式,先放在那,之后再看看。

Fin 类

  instFinal = operator new(0x30ui64);
  if ( instFinal )
    instFinal = InitFinClass_1400023B0(instFinal, g_serial, 1);
  else
    instFinal = 0i64;
  (*instFinal->vt)(instFinal);                  // method 1
  (**(&instFinal->field_8 + *(instFinal->field_8 + 4)))(&instFinal->field_8 + *(instFinal->field_8 + 4));// method 2

初始化的时候可以发现虚表的一个方法被替换了:

  lpAddress = *&a1->vt_Base;
  VirtualProtect(lpAddress, 4ui64, PAGE_READWRITE, &flOldProtect);
  *lpAddress = sub_140001230;                   // 替换了其中一个函数
  VirtualProtect(lpAddress, 4ui64, flOldProtect, &flOldProtect);

因为不清楚具体替换的是个啥,所以回到 main+14D6+1506 处的 CALL 下断点。

首先是 method 1。在 +14D6 断下,可以发现指向的地址是 +3f60

void __fastcall sub_140003F60(__int64 a1)
{
  char buffer[32]; // [rsp+28h] [rbp-40h] BYREF

  strcpy(buffer, "Welcome to the game!\nYour key: ");
  g_ShellCode(12i64, *(a1 + *(*(a1 + 8) + 4i64) + 16) + 4i64, buffer);
  g_ShellCode(12i64, *(a1 + *(*(a1 + 8) + 4i64) + 16) + 20i64, &buffer[16]);
}

嗯… 直接调用了 ShellCode。如果之前对 ShellCode 解密失败,那就没戏了。

因此先跳过这个 CALL 不看,在调试器右键下一个地址 +14D8 选择【在此设置 RIP】跳过,看看后面的 CALL 会不会给我们一点提示。

到了 method 2 的 CALL 时,发现它调用了  +1230,就是之前虚表被改到的 sub_140001230。去 IDA 看看它:

  v3 = 6; // v3 实际上是 char[8] 类型。密钥正确后解密出提示信息。
  qmemcpy(v4, " f60`&{", sizeof(v4));
  LODWORD(v0) = strncmp(Str1, Str2, 0x24ui64);

其中 Str1 是一组程序内置的数据:

00007FF7667E2110  72 0C F6 0E 8C 69 23 69 59 A8 06 EF 2A 1A 56 B6  r.ö..i#iY¨.ï*.V¶
00007FF7667E2120  96 AC EE 92 5C F2 ED 0A 5F 36 8E 41 A6 36 86 72  .¬î.\òí._6.A¦6.r
00007FF7667E2130  56 D2 54 C2                                      VÒTÂ

Str2 查看引用,结果还是我们的密钥处:0x1400432A8 - 0x38 也就是 g_serial,刚启动时输入的序列号的地址:

.text:0000000140001174                 lea     rax, hThread
.text:000000014000117B                 sub     rax, 38h
.text:000000014000117F                 mov     cs:Str2, rax

将 Str1 放到 sub_140004160 处理一下,可以得到 "N0op" + 乱码,因此可以尝试将这四个字节作为密文前缀再尝试:

N0op456789abcdefghijklmnopqrstuvwxyz

然后重启程序,找个解密后的地址断下即可。例如 +4332CALL [VirtualProtect],执行到此处时 rcx 就指向解密后的代码。

把得到的 ShellCode 地址放到 x64dbg 看看:

000001CF57C00000 | 55                       | push rbp                             |
000001CF57C00001 | 48:89E5                  | mov rbp,rsp                          |
000001CF57C00004 | 48:83EC 20               | sub rsp,20                           |
    ...

解密后的 ShellCode 前几条指令看着挺正常的,看来密钥部分是对的。

稍后用 x64dbg 把数据 Dump 下来,方便后续分析:

000001CF57C00000  55 48 89 E5 48 83 EC 20 89 4D 10 48 89 55 18 4C  UH.åH.ì .M.H.U.L  
000001CF57C00010  89 45 20 48 8B 45 18 8B 00 89 45 F8 48 8B 45 18  .E H.E....EøH.E.  
000001CF57C00020  8B 40 04 89 45 F4 C7 45 F0 00 00 00 00 C7 45 EC  .@..EôÇEð....ÇEì  
000001CF57C00030  7F AC 6A 87 C7 45 FC 00 00 00 00 8B 45 FC 3B 45  .¬j.ÇEü.....Eü;E  
000001CF57C00040  10 0F 83 84 00 00 00 8B 45 F4 8D 14 C5 00 00 00  ........Eô..Å...  
000001CF57C00050  00 8B 45 F4 C1 E8 06 31 C2 8B 45 F4 01 C2 8B 45  ..EôÁè.1Â.Eô.Â.E  
000001CF57C00060  F0 83 E0 03 48 8D 0C 85 00 00 00 00 48 8B 45 20  ð.à.H.......H.E   
000001CF57C00070  48 01 C8 8B 08 8B 45 F0 01 C8 31 D0 01 45 F8 8B  H.È...Eð.È1Ð.Eø.  
000001CF57C00080  45 EC 01 45 F0 8B 45 F8 8D 14 C5 00 00 00 00 8B  Eì.Eð.Eø..Å.....  
000001CF57C00090  45 F8 C1 E8 06 31 C2 8B 45 F8 01 C2 8B 45 F0 C1  EøÁè.1Â.Eø.Â.EðÁ  
000001CF57C000A0  E8 0B 89 C0 83 E0 03 48 8D 0C 85 00 00 00 00 48  è..À.à.H.......H  
000001CF57C000B0  8B 45 20 48 01 C8 8B 08 8B 45 F0 01 C8 31 D0 01  .E H.È...Eð.È1Ð.  
000001CF57C000C0  45 F4 83 45 FC 01 E9 70 FF FF FF 48 8B 45 18 8B  Eô.Eü.épÿÿÿH.E..  
000001CF57C000D0  55 F8 89 10 48 8B 45 18 48 83 C0 04 8B 55 F4 89  Uø..H.E.H.À..Uô.  
000001CF57C000E0  10 90 48 83 C4 20 5D C3 00 00 00 00 00 00 00 00  ..H.Ä ]Ã........  

使用 IDA 打开。提示时选择 64 位,反编译到伪码然后看看:

void __fastcall sub_0(unsigned int rounds, unsigned int *array, unsigned int *key)
{
  unsigned int sum; // [rsp+10h] [rbp-10h]
  unsigned int y; // [rsp+14h] [rbp-Ch]
  unsigned int x; // [rsp+18h] [rbp-8h]
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  x = *array;
  y = array[1];
  sum = 0;
  for ( i = 0; i < rounds; ++i )
  {
    x += (y + ((y >> 6) ^ (y << 3))) ^ (key[sum & 3] + sum);
    sum -= 0x78955381;
    y += (x + ((x >> 6) ^ (x << 3))) ^ (key[(sum >> 11) & 3] + sum);
  }
  *array = x;
  array[1] = y;
}

是个魔改的 TEA 解密。这个算法比较简单,加密的话反过来写就好:

void tea_encrypt(unsigned int rounds, uint32_t *data, uint32_t *key)
{
  unsigned int sum; // [rsp+10h] [rbp-10h]
  unsigned int y; // [rsp+14h] [rbp-Ch]
  unsigned int x; // [rsp+18h] [rbp-8h]
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  x = data[0];
  y = data[1];
  sum = -(rounds * 0x78955381);
  for ( i = 0; i < rounds; ++i )
  {
    y -= (x + ((x >> 6) ^ (x << 3))) ^ (key[(sum >> 11) & 3] + sum);
    sum += 0x78955381;
    x -= (y + ((y >> 6) ^ (y << 3))) ^ (key[sum & 3] + sum);
  }
  data[0] = x;
  data[1] = y;
}

然后回到之前发现的 +3F60 函数,整理下我们的发现:

  strcpy(buffer, "Welcome to the game!\nYour key: ");
  g_ShellCode(12, &g_serial[0x04], &buffer[0x00]);
  g_ShellCode(12, &g_serial[0x14], &buffer[0x10]);

可以发现它其实就是对原始数据进行了两次 tea 解密操作(serial[4..12]serial[20..28]),而密钥则是一开始的提示信息。

因此照抄进去就好:

void hexdump(const void*, size_t len) {} // 方便调试时查看内存区域的,没有它也没事。

int main() {
  uint8_t serial[40]{0x72, 0x0C, 0xF6, 0x0E, 0x8C, 0x69, 0x23, 0x69, 0x59, 0xA8, 0x06, 0xEF,
                     0x2A, 0x1A, 0x56, 0xB6, 0x96, 0xAC, 0xEE, 0x92, 0x5C, 0xF2, 0xED, 0x0A,
                     0x5F, 0x36, 0x8E, 0x41, 0xA6, 0x36, 0x86, 0x72, 0x56, 0xD2, 0x54, 0xC2};
  const char* buffer = "Welcome to the game!\nYour key: ";
  hexdump(serial, 36);
  tea_encrypt(12, (uint32_t*)&serial[0x04], (const uint32_t*)&buffer[0x00]);
  tea_encrypt(12, (uint32_t*)&serial[0x14], (const uint32_t*)&buffer[0x10]);
  hexdump(serial, 36);
  sub_140004160((unsigned char*)serial);  // TEA 数据加密后,进行比特乱序得到原始密码。
  hexdump(serial, 36);
  printf("serial = %.36s\n", serial);

  return 0;
}

得到一串字符 N0opa_G3Ey#zTXjmi5wIHd&5pRN2elaNjK*C。没有乱码,看着就像过关密钥。

因此重新启动原始程序输入它,得到成功提示 Su3ce5s.

题外话

ShellCode 反调试检测

反调试检测是针对解密后的 ShellCode 数据。

因为我不是动态分析的这部分,所以没有触发,也就没有关注它。

溯源

做完之后搜了下魔改 TEA 里的常数 0x78955381,可以找到 Xp0int 的 WP,得到 CTF 名 2022 广东省大学生网络攻防大赛 以及项目名 simple_re

这两个关键字组合检索,能找到其它人的 WP原作者放的源码

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
wgf4242 + 1 + 1 谢谢@Thanks!

查看全部评分

3#
fhsuh7 发表于 2024-8-11 13:11
4#
qq465881818 发表于 2024-8-11 14:07
本帖最后由 qq465881818 于 2024-8-11 14:19 编辑

[C] 纯文本查看 复制代码
[mw_shl_code=c,true]#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 假设的函数声明,需要实现
void* sub_1400015E0(void* ptr, const char* message, const char** envp);
void sub_140004A50(const char* format, ...); // 可能是 printf 的别名
void (*sub_1400020E0(void* ptr, const char* str))(void*){


  *ptr = &Base::`vftable';
  ptr[1] = str;
  return ptr;
}
void (*sub_1400021E0(void* ptr, const char* str, long long param))(char*);//这三个函数可能是函数重载
void (*sub_1400023B0(void* ptr, const char* str, long long param))(void*);
void j_j_free(void* ptr); // 可能是 free 的别名

int main(int argc, const char** argv, const char** envp) {
    const char* Str = "some_unknown_string_or_global_variable"; // 假设的 Str 定义

    // 打印欢迎信息和密钥
    sub_1400015E0(NULL, "Welcome to the game!
Your key: ", envp);
    sub_140004A50("%s", Str);

    if (strlen(Str) != 36) {
        return 0; // 密钥长度不正确,退出程序
    }

    // 第一个动态内存操作
    void* v5 = malloc(0x10); // 分配内存
    if (v5) {
        void (*func)(void*) = sub_1400020E0(v5, Str); // 获取函数指针
        if (func) {
            func(v5); // 调用函数
        }
        j_j_free(v5); // 释放内存
    }

    // 第二个动态内存操作(略去部分细节以简化)
    void* v7 = malloc(0x20);
    if (v7) {
        char* v8 = sub_1400021E0(v7, Str, 1); // 假设的返回 char*
        if (v8) {
            // 假设 v8 包含了一个函数指针的偏移或类似信息
            // 这里省略了复杂的偏移和函数调用逻辑
            j_j_free(v7); // 释放内存
        }
    }

    // 第三个动态内存操作
    void* v9 = malloc(0x30);
    if (v9) {
        void (*func)(void*) = sub_1400023B0(v9, Str, 1);
        if (func) {
            func(v9); // 调用函数

            // 假设这里还有一个基于 v9 中信息的额外函数调用
            // 但由于原始代码中的方式非常复杂且依赖于未给出的数据结构,这里省略

            j_j_free(v9); // 释放内存
        }
    }

    return 0;
}
[/mw_shl_code]
5#
qq465881818 发表于 2024-8-11 14:19
本帖最后由 qq465881818 于 2024-8-11 14:21 编辑

[C] 纯文本查看 复制代码
#include <stdint.h>

// 假设的全局变量或外部变量地址
extern void* unk_14002C868; // 假设这是一个有效的地址
extern void* DeCrypt_vftable; // 假设这是DeCrypt“类”的虚函数表地址(注意:C中没有类)

// 假设的、未定义的函数(需要实现或替换为实际函数)
void sub_140002110(void* ptr);

// 模拟的 sub_1400021E0 函数
__int64 sub_1400021E0(__int64 a1, __int64 a2, int a3) {
    // 如果 a3 不为0
    if (a3) {
        // 将 a1 指向的内存地址设置为 unk_14002C868 的地址
        *(void**)(a1) = unk_14002C868;
        // 调用 sub_140002110 函数,传入 a1 + 16 的地址
        sub_140002110((void*)(a1 + 16));
    }

    // 假设 *(_QWORD *)a1 是一个指向某种结构的指针,该结构包含一个偏移量(4i64后)
    // 该偏移量指向另一个结构,该结构包含要修改的字段
    void** ptr_to_first_struct = (void**)(a1);
    void* offset_ptr = *(void**)((uintptr_t)ptr_to_first_struct[0] + 4); // 假设 ptr_to_first_struct[0] 是一个指向另一个结构的指针,该结构的前4个字节是偏移量指针
    int32_t offset = *(int32_t*)((uintptr_t)offset_ptr + 4); // 读取偏移量(假设偏移量在偏移量指针后4字节)

    // 更新偏移量指向的结构中的字段
    *(void**)((uintptr_t)a1 + offset) = DeCrypt_vftable; // 设置虚函数表指针(或类似指针)
    *(uint32_t*)((uintptr_t)a1 + offset - 4) = offset - 16; // 更新前一个字段(可能是某种计数器或索引)
    *(void**)((uintptr_t)a1 + offset + 8) = (void*)a2; // 设置另一个字段为 a2

    return a1; // 返回 a1
}

sub_140002110 是个  反调试IsDebuggerPresent()
6#
qq465881818 发表于 2024-8-11 14:26
[C] 纯文本查看 复制代码
// 如果 a3 不为0
if (a3) {
    *(void**)(a1 + 8) = unk_14002C8D8;
    *(void**)(a1 + 16) = unk_14002C8E0;
    sub_140002110((void*)(a1 + 32));
}

sub_140002300(a1, 0);
sub_140002280((void*)(a1 + 16), 0);

// 设置 a1 指向的结构的虚函数表指针
*(void**)(a1) = Fin_vftable;

// 假设 *(_QWORD *)(a1 + 8) 是一个指向某种结构的指针,该结构包含一个偏移量
void** ptr_to_struct = (void**)(a1 + 8);
void* offset_ptr = *(void**)((uintptr_t)ptr_to_struct[0] + 4); // 假设 ptr_to_struct[0] 是一个指向另一个结构的指针,该结构的前4个字节是偏移量指针
int32_t offset = *(int32_t*)((uintptr_t)offset_ptr + 4); // 读取偏移量(假设偏移量在偏移量指针后4字节)

// 更新偏移量指向的结构中的字段
*(void**)((uintptr_t)a1 + offset + 8) = Fin_vftable; // 设置另一个虚函数表指针(或类似指针)
*(uint32_t*)((uintptr_t)a1 + offset + 4) = offset - 24; // 更新前一个字段(可能是某种计数器或索引)
*(void**)((uintptr_t)a1 + offset + 16) = (void*)a2; // 设置另一个字段为 a2

// 获取 lpAddress,它是指向需要更改保护的内存的指针
lpAddress = *(LPVOID*)(a1 + 32);

// 更改 lpAddress 指向的内存区域的保护属性
if (!VirtualProtect(lpAddress, 4, PAGE_EXECUTE_READWRITE, &flOldProtect)) {
    // 处理错误(例如,通过返回错误代码或抛出异常)
    // 注意:这里只是简单地忽略了错误处理
}

// 写入新的函数指针到 lpAddress
*(void**)(lpAddress) = sub_140001230;

// 恢复 lpAddress 指向的内存区域的原始保护属性
if (!VirtualProtect(lpAddress, 4, flOldProtect, &flOldProtect)) {
    // 处理错误(同上)
}

return a1;
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-12 21:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表