wgf4242 发表于 2024-8-11 12:17

一个2023年简单的Cpp逆向题.

本帖最后由 wgf4242 于 2024-8-12 07:44 编辑

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

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

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

后面还会触发一个sub_140004260.还没看懂. byte_140042000是个表. , 这里把 表里的内容和 Str进行了异或存到了1e0000里.
BOOL __fastcall sub_140004260(__int64 a1)
{
int ii; //
_BYTE *v3; //

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

unsigned char byte_140042000 = {
    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
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; //

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.应该快出了.

爱飞的猫 发表于 2024-8-12 04:01

本帖最后由 爱飞的猫 于 2024-8-12 04:46 编辑

# SimpleRE

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

## 入口

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

- `Base`
- `Decrypt` ← `Base`
- `Fin` ← `Exec` ← `Decrypt` ← `Base`

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

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

## Base 类

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

简化后的代码如下:

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

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

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

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

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

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

```text
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`:

```cpp
void __thiscall sub_140004260(__int64 a1)
{
int i; //
_BYTE *pShellCode; //

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

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

```asm
00007FF7667A42FB | 0FB60401| movzx eax,byte ptr ds:| rcx = 00007FF7667E3270
```

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

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

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

## Fin 类

```cpp
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
```

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

```cpp
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`:

```cpp
void __fastcall sub_140003F60(__int64 a1)
{
char buffer; // 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);
}
```

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

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

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

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

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

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

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

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

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

```c
N0op456789abcdefghijklmnopqrstuvwxyz
```

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

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

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

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

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

```
000001CF57C0000055 48 89 E5 48 83 EC 20 89 4D 10 48 89 55 18 4CUH.åH.ì .M.H.U.L
000001CF57C0001089 45 20 48 8B 45 18 8B 00 89 45 F8 48 8B 45 18.E H.E....EøH.E.
000001CF57C000208B 40 04 89 45 F4 C7 45 F0 00 00 00 00 C7 45 EC.@..EôÇEð....ÇEì
000001CF57C000307F AC 6A 87 C7 45 FC 00 00 00 00 8B 45 FC 3B 45.¬j.ÇEü.....Eü;E
000001CF57C0004010 0F 83 84 00 00 00 8B 45 F4 8D 14 C5 00 00 00........Eô..Å...
000001CF57C0005000 8B 45 F4 C1 E8 06 31 C2 8B 45 F4 01 C2 8B 45..EôÁè.1Â.Eô.Â.E
000001CF57C00060F0 83 E0 03 48 8D 0C 85 00 00 00 00 48 8B 45 20ð.à.H.......H.E   
000001CF57C0007048 01 C8 8B 08 8B 45 F0 01 C8 31 D0 01 45 F8 8BH.È...Eð.È1Ð.Eø.
000001CF57C0008045 EC 01 45 F0 8B 45 F8 8D 14 C5 00 00 00 00 8BEì.Eð.Eø..Å.....
000001CF57C0009045 F8 C1 E8 06 31 C2 8B 45 F8 01 C2 8B 45 F0 C1EøÁè.1Â.Eø.Â.EðÁ
000001CF57C000A0E8 0B 89 C0 83 E0 03 48 8D 0C 85 00 00 00 00 48è..À.à.H.......H
000001CF57C000B08B 45 20 48 01 C8 8B 08 8B 45 F0 01 C8 31 D0 01.E H.È...Eð.È1Ð.
000001CF57C000C045 F4 83 45 FC 01 E9 70 FF FF FF 48 8B 45 18 8BEô.Eü.épÿÿÿH.E..
000001CF57C000D055 F8 89 10 48 8B 45 18 48 83 C0 04 8B 55 F4 89Uø..H.E.H.À..Uô.
000001CF57C000E010 90 48 83 C4 20 5D C3 00 00 00 00 00 00 00 00..H.Ä ]Ã........
```

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

```c
void __fastcall sub_0(unsigned int rounds, unsigned int *array, unsigned int *key)
{
unsigned int sum; //
unsigned int y; //
unsigned int x; //
unsigned int i; //

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

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

```c
void tea_encrypt(unsigned int rounds, uint32_t *data, uint32_t *key)
{
unsigned int sum; //
unsigned int y; //
unsigned int x; //
unsigned int i; //

x = data;
y = data;
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);
}
data = x;
data = y;
}
```

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

```c
strcpy(buffer, "Welcome to the game!\nYour key: ");
g_ShellCode(12, &g_serial, &buffer);
g_ShellCode(12, &g_serial, &buffer);
```

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

因此照抄进去就好:

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

int main() {
uint8_t serial{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, (const uint32_t*)&buffer);
tea_encrypt(12, (uint32_t*)&serial, (const uint32_t*)&buffer);
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](http://blog.leanote.com/post/xp0int/2022-%E5%B9%BF%E4%B8%9C%E7%9C%81%E5%A4%A7%E5%AD%A6%E7%94%9F%E7%BD%91%E7%BB%9C%E6%94%BB%E9%98%B2%E5%A4%A7%E8%B5%9B%E9%83%A8%E5%88%86),得到 CTF 名 `2022 广东省大学生网络攻防大赛` 以及项目名 `simple_re`。

这两个关键字组合检索,能找到[其它人的 WP](https://www.52pojie.cn/thread-1643560-1-1.html) 和[原作者放的源码](https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1643560&pid=44425804)。

fhsuh7 发表于 2024-8-11 13:11

是不是有一行错了

qq465881818 发表于 2024-8-11 14:07

本帖最后由 qq465881818 于 2024-8-11 14:19 编辑

#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 = 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;
}

qq465881818 发表于 2024-8-11 14:19

本帖最后由 qq465881818 于 2024-8-11 14:21 编辑

#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 + 4); // 假设 ptr_to_first_struct 是一个指向另一个结构的指针,该结构的前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()

qq465881818 发表于 2024-8-11 14:26

// 如果 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 + 4); // 假设 ptr_to_struct 是一个指向另一个结构的指针,该结构的前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;
页: [1]
查看完整版本: 一个2023年简单的Cpp逆向题.