首页
论坛
课程
招聘
[原创]2021腾讯PC客户端初赛题解(对大佬解题手法的学习)
2021-4-14 16:24 5673

[原创]2021腾讯PC客户端初赛题解(对大佬解题手法的学习)

2021-4-14 16:24
5673

这道题,我当时没有做出来,有思路,但是感觉总差一点东西。最近看到了一些帖子,是关于这个,写的很好。我也学到了很多。今天这个帖子,一方面是由于很久没有发了,另一方面也是希望可以学到东西。


——题记


  1. 前期的准备

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的工具,请君取用。




[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~

上传的附件:
收藏
点赞2
打赏
分享
最新回复 (2)
雪    币: 10115
活跃值: 活跃值 (12668)
能力值: ( LV12,RANK:385 )
在线值:
发帖
回帖
粉丝
SSH山水画 活跃值 3 2021-4-14 17:25
2
0

奥利给干了兄弟们!

最后于 2021-4-14 17:31 被SSH山水画编辑 ,原因:
雪    币: 371
活跃值: 活跃值 (651)
能力值: ( LV3,RANK:32 )
在线值:
发帖
回帖
粉丝
Licae0 活跃值 2021-4-15 22:43
3
0
学到了
游客
登录 | 注册 方可回帖
返回