打开程序,发现程序开启了ASLR。于是先使用cffexplorer将ASLR关闭,便于之后的调试。
发现程序在绘制一次图案后便不再绘制了。怀疑代码只会运行一次。将程序拖入IDA进行分析。
打开IDA,首先进入winmain,看到消息的处理逻辑中。对于没有消息时,会调用 sub_1400015E0
函数。
将该函数重命名为 dosomething
,进入函数中进行分析。如果当前是第一次进入,则使用 ZwAllocateVirtualMemory
函数申请一块RWX属性的内存,大小为0x2BF9字节。由于页对齐,操作系统会申请一块3个页面大小的内存。
跟进.data这块数据,发现其是一段代码。因此将这块数据命名为shellcodefunc
。可以看到之后对这块shellcode进行了相应参数的填充。
在完成了对shellcode的初始化之后,如果shellcode的内存还存在,则会对shellcode进行调用。这里可以发现,使用了 GetTickCount
函数。如果运行时间超过4000毫秒,就使用ZwFreeVirtualMemory 释放内存。
因此这里直接将140001265位置的jbe改为jmp,实现可以一直显示图像的功能,方便调试。
考虑到在将shellcode复制到申请的内存中之后程序还对其中一些内容进行了修改,因此直接对源程序进行分析不太合适。这里首先对
处下断,得到申请的shellcode的地址,然后待其填充完毕后再将已经填充了相应参数的shellcode从内存中dump下来进行分析。dump下来之后可以看到这是一块0x3000大小的内存。
将dump下来的内存拖入ida进行分析,可以看到其中有三个函数,分别在+0、+420、+650的位置。
而在这三个函数后面,跟随了大段数据。全部dump下来可以看到数据格式如下
每一个数据单元是4个字节,其中存储了一个数字,大多为1~7,但也有一些例外。
在对shellcode的三个函数进行分析之后,发现sub_420中有一个switch-case结构,根据存储的数据的不同进行不同的处理。
可以看到,如果当前数据为5或者6的话,会调用sub_0函数。而调用时的这个参数非常像一个RGBA的数据。
调用时如果数据为5,则使用ebx中的值作为第五个参数,如果为6,则使用edi中的值作为第五个参数。这里跟到前面,把给edi赋值的代码进行patch,将其颜色替换为 ffffff00
,重新运行程序,可以看到绘制出来的方块颜色与旗子的方块颜色相同。
由此可以推断,当数据为5时,绘制的是旗子的黄色方块,数据为6时,绘制的是右边的的蓝色方块。但不知为何数据为5时黄色的方块没有被正常绘制出来。
实在太菜了,用了很笨的办法来达到目的,我猜正解肯定是修改数据来让方块正确绘制,不过我逆向能力还是不行,逆不出来。。
对switch-case中的算法进行了一段时间的分析,还是没有找到能构造出成功绘制旗子的方法。于是对shellcode中的其他代码进行了探索。由于绘制时要调用sub_0,因此对sub_0中各个call进行了分析,发现在shellcode+31c这个call之前进行对[rcx+xxx]的填充时,内存中的数据很像坐标。
每一次对这里的调用都会让内存中的对应区域多出一块28*4字节大小的数据。数据的格式很有规律,像是x,y,z+rgba的结构。在对其进行进一步的分析之后发现其结构如下
可以看出,a1,a2,a3,a4有点像是正方形的四个顶点。对其中数据进行进一步分析可以证实这一推断。
最后分析得出该结构对应的四个点如下
a1,a2,a3,a4分别为左上、右上、左下、右下四个点。
在对5和6的处理代码单步跟踪计数之后发现,每次绘制时,case 5会进入11次,case 6会进入31次。而且绘制时的顺序是先绘制11个5,然后再绘制31个6。经过对正确图像的查看,可以确定这两个就是点的坐标。因为答案中旗子使用了11个方块,右边的图案使用了31个方块。将其数据dump下来,发现绘制5时的坐标非常奇怪,从-290~100的数据都存在。而绘制6时的坐标则分布非常有规律,坐标点均在0~1之间。使用CE进行进一步分析,发现屏幕中心点为0,向两边延伸,坐标的范围在 -1~1
之间。显然,绘制5时的坐标有误,使其无法被正确绘制。
在完成以上分析后,我想到了可以在这里进行hook,让5的数据被正确绘制。这里使用了注入dll的方式。在dll中对 shellcode+0x30e
的位置挂上inlinehook。挂inlinehook的方法如下
首先在 shellcode+30e
的位置进行inlinehook,使用ff25跳转到纯汇编函数 AsmHookHandler
中
在 AsmHookHandler
中进行对寄存器的保存,然后跳转到 HookHandler
函数中。在从 HookHandler
中返回后再从栈中恢复寄存器,并执行hook点中原来的指令,然后跳转回到原函数的下一条需要执行的指令。
在HookHandler函数中,首先对最初的11个黄色方块进行绘制。由之前的dump可知在系统缩放为 125%
的情况下右边图案最高点的y坐标为 0.882840
。经过实验,旗子最左边的x坐标应为 -0.9305
。一个方块的宽为 0.064935
,高为 0.118343
。左右相邻两个格子的间隔为 0.012987
,上下相邻的两个格子间隔为 0.023669
。经过对旗子的观察可知,旗杆高度为6个格子,在第四个格子高度向右再画三个格子,然后再斜着画上两个格子即可。
为了适配不同的系统缩放,在100%、125%、150%、175%缩放下分别对旗杆左上角位置进行定位。并根据系统缩放调整绘制的格子以及间隔的大小。代码如下,其中 getScreenZoom
为封装的获取系统缩放的函数。
综上, HookHandler
中的代码如下。
最后通过cffexplorer对去掉ASLR、关闭 GetTickCount
检测后的程序进行导入表的注入。让其加载时同时加载 dll3.dll
模块。最终效果如下。
代码已经开源到https://github.com/smallzhong/gslab-2022-competition仓库中,在release https://github.com/smallzhong/gslab-2022-competition/files/8508875/exe%2Bdll.zip中可以下载经过patch的exe和用来注入的dll。
.text:
000000014000119F
call memcpy
.text:
000000014000119F
call memcpy
typedef struct _v3
{
float
x, y, z;
}v3,
*
pv3;
typedef struct _v4
{
DWORD r, g, b, a;
}v4,
*
pv4;
typedef struct _Info
{
v3 a1;
v4 b1;
v3 a2;
v4 b2;
v3 a3;
v4 b3;
v3 a4;
v4 b4;
} Info,
*
PInfo;
typedef struct _v3
{
float
x, y, z;
}v3,
*
pv3;
typedef struct _v4
{
DWORD r, g, b, a;
}v4,
*
pv4;
typedef struct _Info
{
v3 a1;
v4 b1;
v3 a2;
v4 b2;
v3 a3;
v4 b3;
v3 a4;
v4 b4;
} Info,
*
PInfo;
PUCHAR t
=
(PUCHAR)((
*
saddr)
+
0x30e
);
g_origin
=
(ULONG64)((
*
saddr)
+
0x31c
);
/
/
需要跳回的地方
char bufcode[]
=
{
0xFF
,
0x25
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
};
*
(PULONG64)&bufcode[
6
]
=
(ULONG64)AsmHookHandler;
DWORD old_protect
=
0
;
VirtualProtect(t,
0x1000
, PAGE_EXECUTE_READWRITE, &old_protect);
A(
"改变内存属性成功"
);
A(
"AsmHookHandler = %llx\r\n"
, AsmHookHandler);
memcpy(t, bufcode, sizeof bufcode);
A(
"memcpy完成"
);
PUCHAR t
=
(PUCHAR)((
*
saddr)
+
0x30e
);
g_origin
=
(ULONG64)((
*
saddr)
+
0x31c
);
/
/
需要跳回的地方
char bufcode[]
=
{
0xFF
,
0x25
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
};
*
(PULONG64)&bufcode[
6
]
=
(ULONG64)AsmHookHandler;
DWORD old_protect
=
0
;
VirtualProtect(t,
0x1000
, PAGE_EXECUTE_READWRITE, &old_protect);
A(
"改变内存属性成功"
);
A(
"AsmHookHandler = %llx\r\n"
, AsmHookHandler);
memcpy(t, bufcode, sizeof bufcode);
A(
"memcpy完成"
);
AsmHookHandler proc
push r15;
push r14;
push r13;
push r12;
push r11;
push r10;
push r9;
push r8;
push rdi;
push rsi;
push rbp;
push rsp;
push rbx;
push rdx;
push rcx;
push rax;
mov rcx,rsp;
sub rsp,
0100h
call HookHandler
add rsp,
0100h
;
pop rax;
pop rcx;
pop rdx;
pop rbx;
pop rsp;
pop rbp;
pop rsi;
pop rdi;
pop r8;
pop r9;
pop r10;
pop r11;
pop r12;
pop r13;
pop r14;
pop r15;
;原来的操作
mov rcx, r14
mov rax, [r14]
mov rdx, [rsp
+
304
]
jmp g_origin ;跳回到需要跳到的位置
AsmHookHandler endp
AsmHookHandler proc
push r15;
push r14;
push r13;
push r12;
push r11;
push r10;
push r9;
push r8;
push rdi;
push rsi;
push rbp;
push rsp;
push rbx;
push rdx;
push rcx;
push rax;
mov rcx,rsp;
sub rsp,
0100h
call HookHandler
add rsp,
0100h
;
pop rax;
pop rcx;
pop rdx;
pop rbx;
pop rsp;
pop rbp;
pop rsi;
pop rdi;
pop r8;
pop r9;
pop r10;
pop r11;
pop r12;
pop r13;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-5-1 20:48
被kanxue编辑
,原因: