首页
社区
课程
招聘
[原创]南极动物厂 游戏安全2024决决决赛 PC
2024-4-22 00:00 1185

[原创]南极动物厂 游戏安全2024决决决赛 PC

2024-4-22 00:00
1185


腾讯游戏安全2024决赛-PC-WriteUp

感谢宝子们对我的帮助!
特别感谢!逍哥!遥哥!呜呜呜呜!

ps:就是觉得挺有意思,稍微模仿一下.


简单来说,题目为编写程序实现注册机,以及内存扫描


在1-2-3中提到的为实现注册代码

文中提到样本需要在 "C:\card.txt" 中放置注册码

那么必然会使用读取文件API 

那么内核读文件API有那些呢?


  1. Zw/NtCreateFile

  2. Zw/NtOpenFile

  3. IoCreateFile

  4. FltCreateFileEx

包含不仅限与上列 函数.

我们HOOK监控这些函数,得知样本使用ZwCreateFile打开文件,ZwReadFile 函数读取文件


通过windbg 断在ZwReadFile 读取卡密文件时,使用命令 ba r1 读取缓冲区

一路跟踪到:

      v3 = sub_FFFFF80789969B40(v9, qword_FFFFF8078996AC90, 0i64);
      if ( v3 != -1 )
      {
        str_userName_Key_fenge_sub_FFFFF8078996A41C(v9, user_name, 0i64, v3);
        str_userName_Key_fenge_sub_FFFFF8078996A41C(v9, key_so, v3 + 1, -1i64);
        v0 = str_c_str_sub_FFFFF8078996820C((__int64)key_so);
        str_to_int_sub_FFFFF8078996A7D0(v0, (__int64)&str_g_323032, 10i64);
        if ( !*str_g_323032 )
        {
          user_name_str_len = get_str_len((__int64)user_name);
          user_name_1 = str_c_str_sub_FFFFF8078996820C((__int64)user_name);
          calc_user_name_sub_FFFFF80789969CBC(user_name_1, user_name_str_len);
        }
        sub_FFFFF80789967028((__int64)key_so);
        sub_FFFFF80789967028((__int64)user_name);
      }
}

其中这个用与解析key,他是读取key字符串转换成4字节整数,稍后他将与计算用户名函数返回值做对比

str_to_int_sub_FFFFF8078996A7D0

这个函数用于计算用户名与key的对应关系

calc_user_name_sub_FFFFF80789969CBC(user_name_1, user_name_str_len);

这个函数有简单的混淆,我们手动去除,看看真实,到底有多真.

	__int64 __fastcall sub_FFFFF8078A45C131(char* _RCX, unsigned __int64 user_name_str_len)
	{
		int i; // [rsp+4h] [rbp-24h]
		unsigned int v4; // [rsp+8h] [rbp-20h]

		v4 = 0;
		for (i = 0; i < user_name_str_len; ++i)
			v4 = 65599 * (v4 + (unsigned __int8)_RCX[i]);
		return v4;
	}

就是这么真实,朴实无华的操作.

附上出题人真实姓名图(还是怕难为我胖虎):

接下来就是第5点了.为什么没有第4点?因为我不知道他到底要干什么????

 what? 邓总:what are you talking about??

好的,扫描ShellCode,既然提到ShellCode,那么必然会触发释放ShellCode


我们Hook ExAllocatePoolWithTag 函数,找到他申请的内存

分析内存中有什么

  1. API_JMP

  2. PE

这文中我们一直在HOOK来达到监控的目的,其中我没有提一个坑,那就是API_JMP

我们通常HOOK 函数的头部,因为可以很方便的调用原始函数,以及拦截参数.

API_JMP 做了什么呢?

读取API第一指令字节码,填充头 + 跳转字节,如下

ffffb188`1a382183 681570fb4d      push    4DFB7015h
ffffb188`1a382188 c744240405f8ffff mov     dword ptr [rsp+4],0FFFFF805h
ffffb188`1a382190 c3              ret

我们观察到 他申请API_JMP 内存为0x1000 字节,调用地址是随机的,(他在强迫我们进行搜索)

哪里有压迫哪里就有反抗,举起手中的大..


每个API_JMP 都是独立的一个页面(0x1000)

我们提取特征:

68 ?? ?? ?? ?? c7 44 24 04 ?? ?? ?? ?? c3

那么接下来就要说到PE了,这个就比较无脑且特征很多,我们可随意发挥

而笔者则是直接把PE头0x1000字节作为特征码.


在内核中扫描内存,特别是x64中是比较困难的.

x64内核空间无比巨大,我们需要效率(需要快),当然这里举例的是暴力扫描

x64内核中是没有像应用层那样使用可以查询内存的API

我们需要获取获取"内核布局"


Windows内存篇Ⅱ x64内核内存布局的迁移演变

win10 1909逆向(反向计算windows内核内存布局及代码实现)


直接一手 CV 不解释.


根据拦截到的信息,我们知道他申请ShellCode内存为:NonPagedPool

我们的范围就定在 0x100000000000 大小了.

接下来只需要编写扫描函数就可以了


好的,完!


对了,编写查找函数还是有许多地方要注意的.

建议:自己编写一遍.


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回