这道题,我当时没有做出来,有思路,但是感觉总差一点东西。最近看到了一些帖子,是关于这个,写的很好。我也学到了很多。今天这个帖子,一方面是由于很久没有发了,另一方面也是希望可以学到东西。
——题记
a. 查壳通过工具,发现没有壳,同时也可以知道一些段的信息。这里不多做赘述。
b. 运行观察直接正常运行程序,发现该程序有一下的特点。第一,标题是XDDDDDDDD;第二,有一个画布,上面有一个箭头,感觉是一种提示;第三,鼠标光标消失,并且屏幕随鼠标移动。
c. 发现线索在上面的基础上,认为可以有一下的线索。第一,搜索字符串,“XDDDDDDDD”;第二,搜索读取桌面鼠标的IPA,WindowsFromPoint;第三,可能存在ASLR。- 验证以上猜想,打开OD,ALT+T,搜索字符串,果然结果如下:
这里很显然,这个函数,需要压入5 个参数,大胆认为,这里是构造窗口的。- 搜索WindowsFromPoint,得到的结果如下:
然后这里的上下文还有很多其他的API及作用,也一并将它们贴出来,分享一下。windowFromPoint: 获取鼠标位置的窗口句柄
GetImeHotKey: 重映射鼠标按键;
SetImeHotkey: 同上,相互呼应
CreateEmptyCursorObject:创建空的光标
DestroyCursor: 摧毁光标
GetControlBrush:控制
GetDC: 从一个设备上下文(DC)中提取一个句柄
GetThreadState:创建一个线程
GetIconSize: 得到图标的大小
- 载入OD,动态调试一下,发现每次的基地址都不一样,所以很显然,这里存在ASLR。需要我们使用PE工具,来关闭ASLR。对于什么是ALSR,文章的最后面,我会回答大家。
2. 关闭ASLR,分析主函数通过ALSR disabler来关闭ALSR。
然后重新载入IDA,通过动态调试,可以定位到winmain(这个地方,如果没有看到,那就就需要载入后,逐步单步跟踪,那麽一定会看到的。)F5得到如下的伪代码:int __stdcall sub_406360(HMODULE hModule, int a2, int a3, int a4) //_stdcall,约定函数
{
char *v4; // esi@1
DWORD v5; // ebx@1
HANDLE v6; // eax@1
void *v7; // edi@1
void *v8; // ebx@3
DWORD v9; // edi@3
HANDLE v10; // eax@3
void *v11; // esi@3
LONG lDistanceToMove; // [sp+Ch] [bp-110h]@1
void *lpBuffer; // [sp+10h] [bp-10Ch]@1
LPVOID lpBuffera; // [sp+10h] [bp-10Ch]@3 //缓冲区
DWORD NumberOfBytesRead; // [sp+14h] [bp-108h]@2
CHAR Filename; // [sp+18h] [bp-104h]@1 //文件名
v4 = (char *)hModule + *(_DWORD *)((char *)hModule + *((_DWORD *)hModule + 15) + 84); //这里的hModule,栈指针,所以这里是开辟空间
sub_4245A0(&Filename, 0, 256); //这里应该是定义这个文件的文件名。
GetModuleFileNameA(hModule, &Filename, 0x100u); //获取文件路径
lDistanceToMove = *((_DWORD *)v4 - 2); //移动距离
sub_407380(*((_DWORD *)v4 - 3));
lpBuffer = (void *)dword_465FE4; //缓冲区的指针
v5 = dword_465FE8 - dword_465FE4;
v6 = CreateFileA(&Filename, 0x80000000, 1u, 0, 3u, 0x80u, 0); //创建文件,以及相关的格式
v7 = v6;
if ( v6 != (HANDLE)-1 ) //这里是为了防止文件为空或者有问题
{
SetFilePointer(v6, lDistanceToMove, 0, 0); // 这里姑且认为是重新调整文件的指针
NumberOfBytesRead = 0;
ReadFile(v7, lpBuffer, v5, &NumberOfBytesRead, 0); //读取缓冲区的内容V5字节,到文件中,并从0开始存放
CloseHandle(v7);
}
lpBuffera = (LPVOID)*((_DWORD *)v4 - 4); //下面的同上,是为了创建另一个文件。
sub_407380(*((_DWORD *)v4 - 5));
v8 = ::lpBuffer;
v9 = dword_465FF8 - (_DWORD)::lpBuffer;
v10 = CreateFileA(&Filename, 0x80000000, 1u, 0, 3u, 0x80u, 0);
v11 = v10;
if ( v10 != (HANDLE)-1 )
{
SetFilePointer(v10, (LONG)lpBuffera, 0, 0);
NumberOfBytesRead = 0;
ReadFile(v11, v8, v9, &NumberOfBytesRead, 0);
CloseHandle(v11);
}
return sub_4064D0();
}
为了更加清晰,这里采用OD,来动态调试一下,ctrl+G,搜索GetModuleFileNameA,然后F2,下断点。然后运行。
然后通过OD插件:用来将内存窗口中按照指定字节数选中数据并将数据保存到硬盘,点击可以下载。题目需要我们找到flag,所以我们需要把这些图的内存找到。注意到这一部分,很特殊,这里存储的是浮点数,如下图;
很显然,这里存储的就是这些图的内存。这个时候,寄存器的值如下:
选择相应的内存格式,发现这一部分,存储的都是坐标。
这个时候,就需要使用上面的插件,将这里的内存数据给dump下来,然后将它们使用代码,转换成数字。(下面,是我借鉴别人的代码)#include <iostream>
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
using namespace std;
#pragma warning(disable:4996)
#define FILEFATH "F:\\腾讯游戏安全\\dump.dat"
typedef struct
{
float x;
float y;
float z;
}st;
int main()
{
FILE* fp = fopen(FILEFATH, "r");
fseek(fp, 0, SEEK_END);
DWORD file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
freopen("d:\\testout.txt", "w", stdout);
printf("list1 = [");
for (int i = 0; i < 1019; i++)
{
st t;
fread(&t, sizeof(st), 1, fp);
printf(",[%f,%f,%f]", t.x, t.y, t.z);
}
cout << "]";
return 0;
}
然后通过python来显示,画图。import numpy as np
import matplotlib.pyplot as plt
list1 = [37.000000, 9.000000, -6.500000], [38.000000, 9.000000, -4.100000], [39.000000, 9.000000, -5.900000],
[40.000000, 9.000000, -4.600000], [41.000000, 9.000000, -7.000000], [73.000000, 9.000000, -5.600000],
[74.000000, 9.000000, -4.200000], [75.000000, 9.000000, -6.400000], [76.000000, 9.000000, -5.600000],
[77.000000, 9.000000, -4.000000], [36.000000, 10.000000, -4.300000], [37.000000, 10.000000, -6.400000],
[38.000000, 10.000000, -4.700000],
...(省略大部分数据)
]
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 设置标题
ax1.set_title('ANSWER')
# 设置X轴标签
plt.xlabel('X')
# 设置Y轴标签
plt.ylabel('Y')
# 画散点图
for elist in list1:
ax1.scatter(elist[0], elist[1], c='r', marker='.')
# 设置图标
plt.legend('x1')
# 显示所画的图
plt.show()
最后就得到了flag
3. 另类解法,修改参数这里采用的是CE。这里我们需要使用到ALT +TAB来切换界面。因为直接拖入,无法载入,但是我们可以打开这个目标文件,然后在CE上挂起。- 采用FSP游戏的寻找基址的方法,选择未知数值,然后回到游戏,再选择首次扫描,变动数值,然后再回到游戏。这样的方法,抓取游戏的数值。
- 但是这里的地址,还是太多,需要我们不断过滤。最后剩余4个地址,
- 然后不断修改视角,下内存访问断点。这里比较耗时,我还赶着做事情,没有找到满意的,所以我就借鉴一张别人的图。
- 然后回到视图,就可以看到了flag。
4. 总结- 什么是ASLR?它是一种安全策略,中文就是地址随机,主要是为了防止缓冲区溢出问题。ASLR包括随机排列程序的关键数据区域的位置,包括可执行的部分、堆、栈及共享库的位置。那么我们怎样来判断,一个exe文件是ASLR,还是重定位了?可重定位模块(exe或dll)不一定需要启用ASLR,但是启用了ASLR的模块需要可重定位。不可重定位的模块将在其文件头的"特征"字段中设置IMAGE_FILE_RELOCS_STRIPPED(0x0001)位标志.可重定位模块将清除此位,并且还将包含带有重定位的节(如.reloc).您可以使用PEView或dumpbin /headers your_module.exe(或dll)之类的软件检查该标志启用了ASLR的模块将可重定位(未设置重定位剥离标志),并且还将在可选标头的DllCharacteristics字段中设置IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(0x0040)标志.不论其名称是什么,DllCharacteristics都用于EXE和DLL.
所以我们可以检查这个标志。
- 其实在分析程序的时候,可以看到一个_security_cookie,并且每一次循环,都要对这个进行一次验证。其实这里是一种GS,当我们编译程序的时候,可以在属性里面设置。这是GS,原理就是调用函数初始化一个栈帧之后将一个随机数放入栈当中,并且在“.data“节区保存一个副本。每次在执行返回地址得指令之前都需要验证一下随机值。如果发生变化,则认为产生溢出。最初的时候,我没有做出来,也是一直在钻这个GS的牛角尖,结果没有做出来。
- 看了网上的评论,说这道题与18年的很像,可惜了,我没有看,也是自己对这方面的解题做的太少。当然了看到网上很多这样的解题方法,觉得自己也是豁然开朗,学到了很多。
附件中,是关于关闭ALSR的工具,请君取用。
|