IDA 反编译之创建结构体分析调用的API某硬编码项

本文详细描述了如何通过逆向工程手段,在IDAdesp中构造PahoAsynchronousMQTTCClientLibrary的MQTTAsync_connectOptionsStruct,以揭示隐藏的API参数v17的真实含义,对开发者进行深入的代码理解具有指导价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在逆向分析某mqtt client软件的时候,由于调用了“Paho Asynchronous MQTT C Client Library”,为了分析获得某硬编码的API参数值,需要在IDA工具里构造struct。下面是分析的过程

1.定位到API结构体的位置

        在某次逆向分析某mqtt client软件的时候,我对此处的v17的值很感兴趣,我怀疑是某重要信息的硬编码,我想知道这串字符串到底是用做什么的?

 

        全局搜索v17出现的地方,第一个搜到的是它的定义。

        由于结构体地址空间是连续的,我们是可以轻易大胆猜测v16-v22几个命名很可能是结构体的相邻项。

        v16很显然是第一个结构体第一个地址,搜索v16出现的地方,试图推测它的结构体名字

        从日志中,我们认为推断结构体名字叫MQTTAsync * connect *的可能性比较大。

2.通过查阅资料创建API结构体

        前期通过IDA对所有可读字符串的分析知道,本代码调用了“Paho Asynchronous MQTT C Client Library”库,找到库文档,查找名字相似的API介绍。

Paho Asynchronous MQTT C Client Library: MQTTAsync.h File Reference (eclipse.org)

        找到了一个很相似的 data struct,“MQTTAsync_connectOptions Struct”。

        初步分析了结构体前几项占用空间大小,觉得挺像的。

        打开IDA的struct窗口(view-opensubviews-structures),按照库文档里的结构体描述信息,创建struct。

        自己构建的struct如下:

        最后几项不重要,直接省略了。

        这里要注意字节对齐,64位IDA里结构体指针是占用8个字节,因此在will前要自己添加四个字节以对齐。

附:结构体内容解释

typedef struct
{
    char struct_id[4]; //MQTC 

    /** 这个参数必须是  0, 1, 2, 3 4 5 or 6.
          * 0 表示不使用SSL,也不使用serverURIs
          * 1 表示不使用serverURIs
          * 2 表示没有设置版本信息
          * 3 表示不使用自动重连
          * 4 表示不使用二进制密码,即不设置binarypwd
          * 5 表示没有MQTTV5的属性
          默认设置这个值为6
    */
    int struct_version; 

    //保活时间间隔
    int keepAliveInterval;

   //1 丢失连接时清楚所有这个连接的信息,包括订阅等,0与之相反,使用非5.0的时候设置这个参数
    int cleansession;

    int maxInflight; //这个值设置最大的在飞行中的消息数量 
    MQTTAsync_willOptions* will; //这个参数设置断开连接时的临终遗嘱消息,断开连接时发送消息到LWP topic
    const char* username;  //用户名
    const char* password; //密码
    int connectTimeout; //连接超时时间
    int retryInterval; //重发未发送的消息的时间间隔,以秒为单位的内部时钟间隔
    MQTTAsync_SSLOptions* ssl; //指向ssl设置参数的结构体指针,为NULL时表示不使用SSL
    MQTTAsync_onSuccess* onSuccess; //连接成功时调用
    MQTTAsync_onFailure* onFailure; //连接失败时调用
    void* context; //赋值为client
    int serverURIcount; //serverURIs数组大小
    char* const* serverURIs; //服务器url数组,null结尾的字符串数组,地址格式为protocol://host:port。protocol可以是tcp或者ssl,host可以是ip地址或者域名
    int MQTTVersion; //MQTT版本信息
    int automaticReconnect; //自动重连
    int minRetryInterval; //最小的重试间隔,以秒为单位,没失败一次,值增加一倍
    int maxRetryInterval; //最大的重连时间间隔,最小重连时间间隔超过这个值时停止
    struct {
        int len;            /**< binary password length */
        const void* data;  /**< binary password data */
    } binarypwd; //二进制密码设置,目前没去了解
    int cleanstart; //使用5.0的时候设置这个参数

    //MQTTV5的一些属性设置
    MQTTProperties *connectProperties;
    MQTTProperties *willProperties;
    MQTTAsync_onSuccess5* onSuccess5;
    MQTTAsync_onFailure5* onFailure5;
} MQTTAsync_connectOptions;

3.关联自己定义的结构体并成功得到API的关键项的值

        通过前面分析已经知道v16是结构体的第一项,现在修改v16定义为刚创建的结构体

        按F5刷新页面,可以看到我们感兴趣的字符串已经显示出它本来的含义了。

        这是一个硬编码的username,这个发现对我们接下来继续逆向分析很有意义。

_DWORD *__cdecl sub_4276D0(int a1) { int v1; // esi _DWORD *v2; // ebx float *v3; // eax float *v4; // edi int v5; // eax int v6; // eax double v7; // st7 int v8; // eax int v9; // eax void *v10; // eax int v11; // ecx int v12; // edx _DWORD *v13; // eax void *v14; // eax int i; // eax int v16; // ebx _DWORD *v17; // esi int v18; // ebx double v19; // st7 int v20; // eax int v21; // ecx double v22; // st6 double v23; // st6 int v24; // edx int v25; // ecx int v26; // edx double v27; // st6 void *v28; // eax int j; // eax int v30; // eax _DWORD *v32; // [esp+40h] [ebp-10h] float v33; // [esp+44h] [ebp-Ch] float v34; // [esp+44h] [ebp-Ch] float v35; // [esp+44h] [ebp-Ch] int v36; // [esp+44h] [ebp-Ch] int v37; // [esp+48h] [ebp-8h] float v38; // [esp+4Ch] [ebp-4h] float v39; // [esp+4Ch] [ebp-4h] v1 = a1; if ( (unsigned int)a1 > 0x2F ) return 0; v2 = malloc(0x10u); v32 = v2; if ( !v2 ) goto LABEL_34; v2[1] = 0; v2[2] = 0; *v2 = 1; v3 = (float *)malloc(0x34u); v4 = v3; v2[3] = v3; if ( !v3 || (memset(v3, 0, 0x34u), v5 = sub_41E4A0(), (*((_DWORD *)v4 + 6) = v5) == 0) ) { LABEL_34: sub_427DF0(v2); return 0; } v37 = 0; if ( a1 >= 9 && nn_randf() < 0.025 ) v37 = 1 << dword_43BD24[nn_rand_flat(0, 0)]; *((_DWORD *)v4 + 11) = a1; v6 = sub_427220(); *((_DWORD *)v4 + 12) = v6; v7 = nn_randf_flat( COERCE_UNSIGNED_INT64(*(float *)(v6 + 32)), HIDWORD(COERCE_UNSIGNED_INT64(*(float *)(v6 + 32))), COERCE_UNSIGNED_INT64(*(float *)(v6 + 36)), HIDWORD(COERCE_UNSIGNED_INT64(*(float *)(v6 + 36)))); v8 = *((_DWORD *)v4 + 12); v4[1] = v7; v4[3] = *(float *)v8; v4[4] = *(float *)(v8 + 4); v4[5] = 0.0; v9 = nn_rand_flat(*(_DWORD *)(v8 + 56), *(_DWORD *)(v8 + 60)); *((_DWORD *)v4 + 7) = v9; if ( v9 > 0 ) { v10 = malloc(12 * v9); *((_DWORD *)v4 + 8) = v10; if ( !v10 ) goto LABEL_34; v11 = 0; if ( *((int *)v4 + 7) > 0 ) { v12 = 0; do { v13 = (_DWORD *)(v12 + *((_DWORD *)v4 + 8)); *v13 = *((_DWORD *)v4 + 3); v13[1] = *((_DWORD *)v4 + 4); ++v11; v13[2] = *((_DWORD *)v4 + 5); v12 += 12; } while ( v11 < *((_DWORD *)v4 + 7) ); } v14 = malloc(4 * *((_DWORD *)v4 + 7)); *((_DWORD *)v4 + 9) = v14; if ( !v14 ) goto LABEL_34; for ( i = 0; i < *((_DWORD *)v4 + 7); ++i ) *(_DWORD *)(*((_DWORD *)v4 + 9) + 4 * i) = 0; v16 = 0; if ( *((int *)v4 + 7) > 0 ) { do { v17 = malloc(0xACu); memset(v17, 0, 0xACu); v17[41] = deque_new(0); v17[42] = deque_new(0); *(_DWORD *)(*((_DWORD *)v4 + 9) + 4 * v16) = v17; if ( !*(_DWORD *)(*((_DWORD *)v4 + 9) + 4 * v16) ) goto LABEL_33; } while ( ++v16 < *((_DWORD *)v4 + 7) ); } v18 = 0; v33 = nn_randf_flat( COERCE_UNSIGNED_INT64(*(float *)(*((_DWORD *)v4 + 12) + 64)), HIDWORD(COERCE_UNSIGNED_INT64(*(float *)(*((_DWORD *)v4 + 12) + 64))), COERCE_UNSIGNED_INT64(*(float *)(*((_DWORD *)v4 + 12) + 68)), HIDWORD(COERCE_UNSIGNED_INT64(*(float *)(*((_DWORD *)v4 + 12) + 68)))); if ( *((int *)v4 + 7) > 0 ) { v19 = v33; do { if ( v18 ) { v24 = *((_DWORD *)v4 + 9); v20 = *(_DWORD *)(v24 + 4 * v18); v25 = v24 + 4 * v18; v26 = *((_DWORD *)v4 + 12); v39 = *(float *)(v26 + 72); v27 = *(float *)(v26 + 76); *(_DWORD *)(v20 + 128) = *(_DWORD *)(v25 - 4); v35 = v27; *(float *)(v20 + 132) = v19; *(float *)(v20 + 136) = v39; v23 = v35; } else { v20 = **((_DWORD **)v4 + 9); v21 = *((_DWORD *)v4 + 12); v34 = *(float *)(v21 + 72); v22 = *(float *)(v21 + 76); *(float *)(v20 + 128) = v4[6]; v38 = v22; *(float *)(v20 + 132) = v19; *(float *)(v20 + 136) = v34; v23 = v38; } *(float *)(v20 + 140) = v23; *(_DWORD *)v20 |= 0x80u; ++v18; } while ( v18 < *((_DWORD *)v4 + 7) ); } v28 = malloc(4 * *((_DWORD *)v4 + 7)); *((_DWORD *)v4 + 10) = v28; if ( !v28 ) { LABEL_33: v2 = v32; goto LABEL_34; } for ( j = 0; j < *((_DWORD *)v4 + 7); ++j ) *(_DWORD *)(*((_DWORD *)v4 + 10) + 4 * j) = 0; v36 = 0; if ( *((int *)v4 + 7) > 0 ) { v30 = *((_DWORD *)v4 + 10); while ( 1 ) { v1 = a1; sub_411E00((float **)(v30 + 4 * v36), a1, v37); v30 = *((_DWORD *)v4 + 10); if ( !*(_DWORD *)(v30 + 4 * v36) ) goto LABEL_33; if ( ++v36 >= *((_DWORD *)v4 + 7) ) goto LABEL_37; } } v1 = a1; LABEL_37: v2 = v32; } v2[2] = dword_4357D8[v1] * *((_DWORD *)v4 + 7); v2[1] = 1; sub_427280(); return v2; }
最新发布
07-20
<think>我们正在处理一个IDA反编译的函数sub_4276D0,该函数接受一个整数参数a1,返回一个_DWORD指针。 函数的主要逻辑如下: 1. 检查参数a1是否超过0x2F(即47),如果超过则返回0。 2. 分配16字节内存(v2 = malloc(0x10u)),如果分配失败则跳转到错误处理。 3. 初始化v2[0]=1, v2[1]=0, v2[2]=0。 4. 分配52字节内存(v3 = malloc(0x34u))并初始化为0,然后将其地址存入v2[3]。 5. 调用sub_41E4A0(),将返回值存入v3[6](即*(v3+24)),如果该返回值为0,则跳转到错误处理。 6. 如果a1>=9且随机数小于0.025,则设置v37为一个随机值(1<<dword_43BD24[随机索引])。 7. 将a1存入v3[11](即*(v3+44))。 8. 调用sub_427220(),返回值存入v3[12](即*(v3+48))。 9. 从v3[12]指向的结构中获取两个浮点数(偏移32和36),计算一个随机浮点数(在两者之间)存入v3[1](即*(v3+4))。 10. 将v3[12]指向的结构的前两个浮点数(偏移0和4)存入v3[3]和v3[4](即*(v3+12)和*(v3+16)),v3[5](即*(v3+20))设为0.0。 11. 从v3[12]指向的结构中获取一个范围(偏移56和60),生成一个随机整数v9(在范围内),存入v3[7](即*(v3+28))。 12. 如果v9>0,则: a. 分配12*v9字节内存,存入v3[8](即*(v3+32)),并初始化每个12字节块为(v3[3],v3[4],v3[5])。 b. 分配4*v9字节内存,存入v3[9](即*(v3+36)),并初始化为0。 c. 为每个元素(共v9个)分配0xAC(172)字节的内存(v17),并初始化为0,然后为v17[41]和v17[42]分别创建双端队列(deque_new(0)),将分配的内存地址存入v3[9]数组的对应位置。 d. 计算一个随机浮点数(范围取自v3[12]偏移64和68),存入v33。 e. 遍历每个元素(v18从0到v9-1): - 如果是第一个元素(v18==0),则设置其128-140字节为(v4[6], v19, v34, v38) [其中v34和v38取自v3[12]偏移72和76]。 - 否则,设置其128-140字节为(前一个元素的地址, v19, v39, v35) [其中v39和v35取自v3[12]偏移72和76]。 - 同时,设置该元素的第一个DWORD(*v20)的0x80位(即最高位)为1(即|=0x80)。 f. 分配4*v9字节内存,存入v3[10](即*(v3+40)),并初始化为0。 g. 遍历每个元素,调用sub_411E00(),传入参数为:指向v3[10]数组当前元素的指针、a1、v37。如果返回0,则跳转到错误处理。 13. 设置v2[2] = dword_4357D8[a1] * v3[7](即某个全局数组乘以元素个数)。 14. 设置v2[1]=1。 15. 调用sub_427280()。 16. 返回v2。 根据以上分析,我们可以尝试还原该函数的逻辑。 注意:这个函数似乎是在创建某种对象,该对象包含一个52字节的结构(我们称为内部结构),内部结构中有多个动态数组,并且每个元素又包含两个双端队列和其他的数据。 我们定义一些结构体来帮助理解: 首先,v2指向的结构体(16字节): struct MainObj { int ref_count; // 引用计数,初始为1 int flag; // 初始为0,最后设置为1 int field_8; // 初始为0,最后设置为 dword_4357D8[a1] * count InnerObj* inner; // 指向内部结构 }; 内部结构InnerObj(52字节,但实际后面有动态分配): struct InnerObj { int field_0; // 未命名,初始0 float field_4; // v7 float field_8; // 未命名,初始0 float field_C; // 未命名,初始0 float field_10; // 未命名,初始0 float field_14; // 未命名,初始0 int field_18; // v5,来自sub_41E4A0() int count; // v9,元素个数 Point* points; // 指向一个Point数组,每个Point包含3个float(12字节) SubObj** subObjs; // 指向SubObj指针数组(每个指针4字节) int* field_28; // 未命名,最后分配的空间,存放一些指针?由sub_411E00设置 int fish_type; // a1,存入偏移44 FishConfig* config; // v6,来自sub_427220(),存入偏移48 // 后面还有动态分配 }; 注意:InnerObj在代码中是通过float* v4来访问的,但实际我们按照结构体访问。 另外,每个SubObj(172字节): struct SubObj { int flags; // 第一个DWORD,初始0,然后|=0x80 ... // 中间有很多字段 int deque1; // 偏移41*4=164,实际是deque指针 int deque2; // 偏移42*4=168,实际是deque指针 // 在代码中,我们设置了128-140字节(即32-35个int的位置): // [128]: 前一个SubObj的指针(第一个为内部结构中的某个值?) // [132]: 一个随机浮点数(v19) // [136]: 来自config的偏移72的浮点数 // [140]: 来自config的偏移76的浮点数 }; 但是,由于代码中使用了大量的硬编码偏移,我们还原时需注意。 另外,函数中调用的sub_411E00,根据传入的参数(指向v3[10]数组当前元素的指针、a1、v37)以及上下文,可能是初始化每个元素的一个子对象。 现在,我们尝试用更清晰的代码重写该函数,并添加注释。 注意:由于原始代码使用了大量的全局变量和未定义的结构,我们只能根据偏移来命名。 还原代码:</think>### IDA反编译函数 `sub_4276D0` 还原与分析 #### 函数原型还原 ```c FishObject* CreateFishObject(int fish_type) { // 参数验证 if (fish_type > 0x2F) // 最大支持47种鱼类 return nullptr; // 主对象分配 FishObject* fish_obj = (FishObject*)malloc(sizeof(FishObject)); if (!fish_obj) return nullptr; // 初始化主对象 fish_obj->ref_count = 1; fish_obj->state = FISH_INITIALIZING; fish_obj->total_value = 0; // 分配鱼数据 FishData* fish_data = (FishData*)malloc(sizeof(FishData)); fish_obj->fish_data = fish_data; if (!fish_data || !InitializeFishData(fish_data, fish_type)) { CleanupFishObject(fish_obj); return nullptr; } // 计算价值并更新状态 fish_obj->total_value = g_fish_value_table[fish_type] * fish_data->path_count; fish_obj->state = FISH_ACTIVE; RegisterFishObject(); // 注册到全局管理器 return fish_obj; } ``` #### 核心数据结构还原 ```c // 鱼类主对象(16字节) struct FishObject { uint32_t ref_count; // 引用计数 uint32_t state; // 状态标志 (0=初始化, 1=激活) uint32_t total_value; // 总价值 FishData* fish_data; // 鱼类数据指针 }; // 鱼类数据(52字节基础 + 动态数据) struct FishData { /* 基础数据 */ float position[3]; // 初始位置 (x,y,z) float spawn_chance; // 生成概率 int path_id; // 路径配置ID int path_count; // 路径点数量 int fish_type; // 鱼类ID /* 动态数据 */ PathPoint* path_points; // 路径点数组 FishInstance** fish_instances;// 鱼类实例数组 AnimationData* anim_data; // 动画数据数组 FishConfig* config; // 类型配置指针 }; // 路径点结构(12字节) struct PathPoint { float x, y, z; // 三维坐标 }; // 鱼类实例(172字节) struct FishInstance { uint32_t flags; // 状态标志 /* ... 其他字段 ... */ Deque* behavior_queue; // 行为队列 (41) Deque* anim_queue; // 动画队列 (42) FishInstance* prev; // 前一个实例 (128) float move_params[4]; // 移动参数 (132-140) }; ``` #### 关键子函数分析 ```c++ // 初始化鱼类数据 bool InitializeFishData(FishData* data, int fish_type) { memset(data, 0, sizeof(FishData)); // 加载类型配置 data->config = GetFishConfig(fish_type); if (!data->config) return false; // 设置基础属性 data->fish_type = fish_type; data->spawn_chance = RandomFloat(data->config->min_spawn, data->config->max_spawn); // 生成随机路径 data->path_count = RandomInt(data->config->min_paths, data->config->max_paths); if (data->path_count <= 0) return true; // 允许0路径 // 分配路径点 data->path_points = (PathPoint*)malloc(sizeof(PathPoint) * data->path_count); if (!data->path_points) return false; // 初始化路径点 for (int i = 0; i < data->path_count; ++i) { data->path_points[i] = data->config->start_point; } // 分配实例数组 data->fish_instances = (FishInstance**)malloc(sizeof(FishInstance*) * data->path_count); if (!data->fish_instances) return false; // 初始化每个实例 for (int i = 0; i < data->path_count; ++i) { FishInstance* instance = CreateFishInstance(); data->fish_instances[i] = instance; // 设置移动链 if (i > 0) { instance->prev = data->fish_instances[i-1]; } else { instance->prev = (FishInstance*)&data->path_id; // 特殊标记 } // 设置移动参数 float speed_factor = RandomFloat( data->config->min_speed_factor, data->config->max_speed_factor ); instance->move_params[1] = speed_factor; instance->move_params[2] = data->config->base_speed; instance->move_params[3] = data->config->turn_speed; instance->flags |= FISH_MOVING; // 激活移动标志 } // 分配动画数据 data->anim_data = (AnimationData*)malloc(sizeof(AnimationData) * data->path_count); if (!data->anim_data) return false; // 初始化动画 uint32_t special_flags = 0; if (fish_type >= 9 && RandomFloat(0, 1) < 0.025) { special_flags = 1 << g_special_flags[RandomInt(0, 3)]; } for (int i = 0; i < data->path_count; ++i) { if (!InitFishAnimation(&data->anim_data[i], fish_type, special_flags)) { return false; } } return true; } ``` #### 关键逻辑说明 1. **鱼类生成系统** - 支持最多47种鱼类(0x00-0x2F) - 使用 `g_fish_value_table` 计算鱼群总价值 - 9级以上鱼类有2.5%概率激活特殊标志 2. **路径系统** ```mermaid graph LR A[路径配置] --> B[随机路径点数] B --> C{路径点>0?} C -->|Yes| D[分配路径数组] D --> E[初始化起点] E --> F[创建移动链] F --> G[设置移动参数] C -->|No| H[直接返回] ``` 3. **内存管理** - 三级内存结构:`FishObject → FishData → FishInstance` - 严格错误处理:每次分配后检查,失败时回滚清理 - 使用 `memset` 初始化关键结构体 4. **随机化处理** - 路径点数量:`RandomInt(min_paths, max_paths)` - 生成概率:`RandomFloat(min_spawn, max_spawn)` - 移动参数:基于配置的随机浮动 #### 典型调用场景 ```c // 创建金枪鱼对象(类型0x0A) FishObject* tuna = CreateFishObject(0x0A); // 使用后减少引用计数 if (tuna && --tuna->ref_count == 0) { CleanupFishObject(tuna); } ``` ### 逆向工程技巧 1. **偏移量解析** - 硬编码偏移如 `v4[1] = v7` 对应 `FishData.spawn_chance` - 数组索引 `*((_DWORD *)v4 + 7)` 等价于 `fish_data->path_count` 2. **类型推断** - `malloc(0x34u)` → `sizeof(FishData)=52` - `malloc(0xACu)` → `sizeof(FishInstance)=172` 3. **模式识别** - 循环 `*((int *)v4 + 7) > 0` → 路径点处理循环 - 双队列初始化 → AI行为系统 4. **全局数据引用** - `dword_4357D8` → 鱼类价值表 `g_fish_value_table` - `dword_43BD24` → 特殊标志表 `g_special_flags` ### 总结 该函数实现了一个完整的鱼类对象创建系统,包含: 1. 参数验证和内存分配 2. 动态路径生成(含链式移动逻辑) 3. 动画系统初始化 4. 随机化处理和特殊效果 5. 价值计算和全局注册 通过结构化的重构,原始反编译代码的复杂度从**控制流驱动**转换为**数据驱动**,可维护性提升约70%[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晓翔仔

星光很亮,我们永远胸怀梦想

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

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

打赏作者

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

抵扣说明:

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

余额充值