首页
社区
课程
招聘
[原创]H&NCTF RE 部分题解
发表于: 2024-5-16 19:31 12703

[原创]H&NCTF RE 部分题解

2024-5-16 19:31
12703

打了一下这个比赛,发现有两道比较有意思的题目,和传统的re题不太一样,特此记录一下

本质上就是一个恶意程序,运行时在别的进程注入shellcode,flag就藏在shellcode里面

首先用分析工具发现存在enigma virtual box,相当于把多个可执行文件或者链接库给一块打包了,用解包工具尝试解包发现失败,那就只能先硬着分析

静态分析发现有很多內存访问异常点,均出自函数调用上,可能是程序重定位时产生的错误,没办法只能动态调试了。

在main函数的初始地址处下断点,发现函数名正常恢复了:

题目附件里没有给出mydll1.dll文件,侧面也验证了这个链接库被一起打包进了这个exe里面。

这个函数的逻辑就是在c盘的用户目录下的视频文件夹中创建了一个和源程序一模一样的副本并启动它,具体的逻辑如下:

一开始我在虚拟机里面跑这个程序,发现调用完后v5一直小于5,这里面v47存储的是system_info结构体的内容,我怀疑v48和虚拟机配置有关,因为sub_140001070函数就是结束函数了(后面会分析),并没有flag的逻辑,最后考虑在实体机里面跑一下,发现实体机里面v48的值等于16

这部分代码就是在枚举当前计算机运行的进程,看有没有上面6个进程存在,有的话就进入结束函数,这部分功能实际上就是在检测VMWare tools和virtualbox Guests Additions的运行情况,如果在运行该进程就会退出

程序加载的模块链表(LDR)本质上是一个双向链表,因此一直索引下一个结点一定会定位到链表头,这里的v17[6].Flink索引到的是模块名,每一个字符占两个字节,另外一个补0,如下图:

微信截图_20240516163821

最后检测到的程序名的形式如上图,根据v22的值就是程序名生成的对应hash值的结果,最后可以确定是kernel32

基本方式也是调用kernel32库里面的函数,检测杀毒软件的原理和检测虚拟机服务的原理一致,也是打进程快照然后再遍历。

主逻辑在sub_140001320函数中,基本思路是将shellcode自解密然后注入另外一个进程中。

这里待注入的进程名是exp10rer.exe,但是我看这个程序本身并没有启动过名为exp10rer.exe的进程,这个地方最开始以为自己哪个地方的代码分析出错了,只能先尝试分析一下shellcode本身

当程序执行到上面最后两行代码位置时即是打印最后生成好的shellcode,首先使用idapython去dump这段內存,然后需要手写一个加载器去模拟执行它。

对应加载器的代码如下(dump.bin是对应的shellcode)

其实在shellcode末尾就能看到flag,但是还是想继续分析一下

这段shellcode本身的逻辑就是不断更新r10寄存器的值,然后去各个模块中找对应的函数名算哈希,如果找到了某个函数的hash值和r10的值相等就调用对应的函数,在这期间shellcode还会设置好rcx,rdx,r8,r9等寄存器的值作为参数。

计算hash的汇编代码如下:

主体逻辑就是小写变大写,其余不变,然后累计循环右移13位做加法,

下面给出每一次调用的函数名和作用:

​ 加载wininet.dll

​ 调用InternetOpenA函数

​ 调用InternetConnectA,这个函数的参数包含了待连接的服务器ip或者域名,由第二次参数给出,而第二个参数rdx刚好指向shellcode的內存,我们去对应地址看看,则发现了flag。

安卓逆向,程序解包后没有lib库,考虑代码逻辑仅在java层

作者实现了一个类似于购买商品的客户端,要先注册账号,才能进一步去购买flag,但是好像没有充值或者赚钱的逻辑,也没有flag的标价。

浏览了一下shopactivity的逻辑,发现两处比较关键的代码

构造的数据包中有用户名,签名和钱,服务器响应中有钱款不够,代码被修改等字样,考虑到客户端有计算程序hash值的步骤,会在发送的数据包中体现,因此如果服务端比对程序hash值改变说明有人恶意修改了代码,会直接返回错误,这说明我们不能通过修改程序逻辑的方式增加钱款数量

既然本地逻辑不能修改,那就修改客户端发送的数据包,先抓包获得发送钱款为0时的数据包为底板,再用python写脚本

构造脚本如下:

发送过去后会有两种回显

一个是Not enough money!!,另一个是Did you cheat with this much money?!,然后出题人给了钱款的上限,其实也可以专门对金额写一个二分,但是没必要,可以手动试出来,最后金额设定为114514.00时返回flag

这题没有过多的钱款验证逻辑,其实还可以对username,money一起做一个hash,然后写入数据包,服务端可以采取相同的逻辑进行运算,然后对比hash结果,这样构造数据包时不能只修改钱款,也要还原正常哈希的逻辑补充hash值,会更像逆向题一些。

v4 = &unk_140003450;
  v5 = 7i64;
  do
  {
    v3 += 32;
    v6 = *v4;
    v7 = v4[1];
    v4 += 8;
    *(v3 - 8) = v6;
    v8 = *(v4 - 6);
    *(v3 - 7) = v7;
    v9 = *(v4 - 5);
    *(v3 - 6) = v8;
    v10 = *(v4 - 4);
    *(v3 - 5) = v9;
    v11 = *(v4 - 3);
    *(v3 - 4) = v10;
    v12 = *(v4 - 2);
    *(v3 - 3) = v11;
    v13 = *(v4 - 1);
    *(v3 - 2) = v12;
    *(v3 - 1) = v13;
    --v5;
  }
  while ( v5 );
  v14 = *(v4 + 4);
  v15 = 6;
  v16 = -1162190778i64;
  v17 = 1i64;
  *v3 = *v4;
  v3[4] = v14;
  do
  {
    v18 = 227i64;
    v19 = &v45;
    v20 = 227;
    v21 = &v44;
    do
    {
      v22 = *v21;
      v21 -= 4;
      v19 -= 4;
      v23 = *(&v41 + (v20 + 1) % 0xBu);
      v24 = v17 ^ v18-- & 3;
      *(v19 + 1) -= ((v23 ^ v16) + (si128.m128i_i32[v24] ^ v22)) ^ (((v22 >> 6) ^ (4 * v23)) + ((16 * v22) ^ (v23 >> 3)));
      --v20;
    }
    while ( v20 );
    v25 = v42 ^ v16;
    v16 -= 1953785185i64;
    v41 -= (v25 + (si128.m128i_i32[v17] ^ v43)) ^ (((v43 >> 6) ^ (4 * v42)) + ((16 * v43) ^ (v42 >> 3)));
    v17 = (v16 >> 2) & 3;
    --v15;
  }
  while ( v15 );
  v26 = &v41;
  for ( i = 0; i < 0x393; ++i )
    sub_140001010("%c ", *v26++);
v4 = &unk_140003450;
  v5 = 7i64;
  do
  {
    v3 += 32;
    v6 = *v4;
    v7 = v4[1];
    v4 += 8;
    *(v3 - 8) = v6;
    v8 = *(v4 - 6);
    *(v3 - 7) = v7;
    v9 = *(v4 - 5);
    *(v3 - 6) = v8;
    v10 = *(v4 - 4);
    *(v3 - 5) = v9;
    v11 = *(v4 - 3);
    *(v3 - 4) = v10;
    v12 = *(v4 - 2);
    *(v3 - 3) = v11;
    v13 = *(v4 - 1);
    *(v3 - 2) = v12;
    *(v3 - 1) = v13;
    --v5;
  }
  while ( v5 );
  v14 = *(v4 + 4);
  v15 = 6;
  v16 = -1162190778i64;
  v17 = 1i64;
  *v3 = *v4;
  v3[4] = v14;
  do
  {
    v18 = 227i64;
    v19 = &v45;
    v20 = 227;
    v21 = &v44;
    do
    {
      v22 = *v21;
      v21 -= 4;
      v19 -= 4;
      v23 = *(&v41 + (v20 + 1) % 0xBu);
      v24 = v17 ^ v18-- & 3;
      *(v19 + 1) -= ((v23 ^ v16) + (si128.m128i_i32[v24] ^ v22)) ^ (((v22 >> 6) ^ (4 * v23)) + ((16 * v22) ^ (v23 >> 3)));
      --v20;
    }
    while ( v20 );
    v25 = v42 ^ v16;
    v16 -= 1953785185i64;
    v41 -= (v25 + (si128.m128i_i32[v17] ^ v43)) ^ (((v43 >> 6) ^ (4 * v42)) + ((16 * v43) ^ (v42 >> 3)));
    v17 = (v16 >> 2) & 3;
    --v15;
  }
  while ( v15 );
  v26 = &v41;
  for ( i = 0; i < 0x393; ++i )
    sub_140001010("%c ", *v26++);
#include <windows.h>
#include <stdio.h>
 
// 从文件加载shellcode的函数声明
BOOL LoadShellcodeFromFile(const char* filename, PBYTE* shellcode, DWORD* size);
 
// shellcode执行函数声明
void ExecuteShellcode(PBYTE shellcode);
 
int main() {
    PBYTE shellcode;
    DWORD size;
 
    // 从文件加载shellcode
    if (!LoadShellcodeFromFile("dump.bin", &shellcode, &size)) {
        printf("Failed to load shellcode from file.\n");
        return 1;
    }
 
    // 执行shellcode
    ExecuteShellcode(shellcode);
 
    // 清理资源
    VirtualFree(shellcode, 0, MEM_RELEASE);
 
    return 0;
}
 
// 从文件加载shellcode到内存的函数实现
BOOL LoadShellcodeFromFile(const char* filename, PBYTE* shellcode, DWORD* size) {
    HANDLE file;
    DWORD bytesRead;
 
    // 打开文件
    file = CreateFileA(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
 
    // 获取文件大小
    *size = GetFileSize(file, NULL);
    if (*size == INVALID_FILE_SIZE) {
        CloseHandle(file);
        return FALSE;
    }
 
    // 分配内存
    *shellcode = (PBYTE)VirtualAlloc(NULL, *size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (*shellcode == NULL) {
        CloseHandle(file);
        return FALSE;
    }
 
    // 读取文件内容到内存
    if (!ReadFile(file, *shellcode, *size, &bytesRead, NULL) || bytesRead != *size) {
        VirtualFree(*shellcode, 0, MEM_RELEASE);
        CloseHandle(file);
        return FALSE;
    }
 
    // 关闭文件
    CloseHandle(file);
 
    return TRUE;
}
 
// 执行shellcode的函数实现
void ExecuteShellcode(PBYTE shellcode) {
    // 将shellcode地址转换为函数指针
    void (*func)() = (void(*)())shellcode;
 
    // 调用函数指针,执行shellcode
    func();
}
#include <windows.h>
#include <stdio.h>
 
// 从文件加载shellcode的函数声明
BOOL LoadShellcodeFromFile(const char* filename, PBYTE* shellcode, DWORD* size);
 
// shellcode执行函数声明
void ExecuteShellcode(PBYTE shellcode);
 
int main() {
    PBYTE shellcode;
    DWORD size;
 
    // 从文件加载shellcode
    if (!LoadShellcodeFromFile("dump.bin", &shellcode, &size)) {
        printf("Failed to load shellcode from file.\n");
        return 1;
    }
 
    // 执行shellcode
    ExecuteShellcode(shellcode);
 
    // 清理资源
    VirtualFree(shellcode, 0, MEM_RELEASE);
 
    return 0;
}
 
// 从文件加载shellcode到内存的函数实现
BOOL LoadShellcodeFromFile(const char* filename, PBYTE* shellcode, DWORD* size) {
    HANDLE file;
    DWORD bytesRead;
 
    // 打开文件
    file = CreateFileA(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
 
    // 获取文件大小
    *size = GetFileSize(file, NULL);
    if (*size == INVALID_FILE_SIZE) {
        CloseHandle(file);
        return FALSE;
    }
 
    // 分配内存
    *shellcode = (PBYTE)VirtualAlloc(NULL, *size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (*shellcode == NULL) {
        CloseHandle(file);
        return FALSE;
    }
 
    // 读取文件内容到内存
    if (!ReadFile(file, *shellcode, *size, &bytesRead, NULL) || bytesRead != *size) {

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-5-16 22:29 被ccccl1180编辑 ,原因:
上传的附件:
收藏
免费 3
支持
分享
最新回复 (8)
雪    币: 29182
活跃值: (63621)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2
附件上传一份论坛,谢谢!
2024-5-16 20:06
0
雪    币: 1457
活跃值: (2058)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
3
Ezshopping出题人路过啦,其实你说的那个一起哈希的方法在真实的业务中是这样的,但是我在出题的时候为了让做题人是以二分的方式拿到真是钱款的,所以我服务端必须要知道钱的具体数额,如果只是哈希的话,就只能知道钱对不对了,而不是下发多了少了。这样就变成了只能爆破的烂题了
2024-5-18 23:14
0
雪    币: 3692
活跃值: (1675)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
为什么不能修改hash程序的部分,直接返回静态原hash呢
2024-5-20 07:39
0
雪    币: 229
活跃值: (385)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
Shangwendada Ezshopping出题人路过啦,其实你说的那个一起哈希的方法在真实的业务中是这样的,但是我在出题的时候为了让做题人是以二分的方式拿到真是钱款的,所以我服务端必须要知道钱的具体数额,如果只是哈希的话, ...
服务器的代码能上传一份到github上吗?想复现一下!
2024-6-5 17:00
0
雪    币: 1457
活跃值: (2058)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6
snowYa 服务器的代码能上传一份到github上吗?想复现一下!
在H&NCTF的群里是有发布喔
2024-6-6 16:50
0
雪    币: 229
活跃值: (385)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
Shangwendada 在H&NCTF的群里是有发布喔
没有在群里,github可以发一份吗?
2024-6-6 18:24
0
雪    币: 1457
活跃值: (2058)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
snowYa 没有在群里,github可以发一份吗?
https://github.com/SHangwendada/Ezshopping
2024-6-6 19:22
0
雪    币: 229
活跃值: (385)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
Shangwendada https://github.com/SHangwendada/Ezshopping
感谢Da师傅!
2024-6-6 19:48
0
游客
登录 | 注册 方可回帖
返回
//