Algorithm, Secret key and Protocol

来源:互联网 发布:mac系统不见了 编辑:程序博客网 时间:2024/04/28 02:14

最近在对基于区块链构建的信任社会(未来社会形态)非常感兴趣,区块技术去中心化的特性,让没有金融机构成为了可能(包括央行,以及各种商业银行)。

除了在数字货币领域大放异彩外,在包括供应链,网络购物,公平合约等方面的应用也非常广泛。其中智能合约的特性十分的吸引我。

不过我今天并不想讨论区块技术,因为区块技术建立在密码学的技术之上,通过最近两周对密码学以及数论基础的研究,分享一些知识,主要关于如何攻击一个宣称安全的密码系统。

在密码学中,算法,秘钥以及协议构成了一个完整的密码系统。

算法是密码系统的基础,它是将一组或多组输入信息转化成一组或多组输出信息的数学方法,算法的输出会被秘钥影响,秘钥不同,输出也不同。因此,加密同样的信息,A用秘钥Ka加密与B用秘钥Kb加密输出的信息是完全不同的。

秘钥是密码系统的核心,奥秘在于算法存在一个最优解(最优解有很多种解释,例如超递增序列,因式分解两素数之和等),这个秘密就是秘钥。如果一个人想要解密别人的密文,在不知道秘钥的情况下,他只能尝试解数学难题,这对于普通人拥有的资源来说,直到宇宙毁灭也无法得出答案。

有了算法和秘钥,没有协议就不是一个密码系统。协议是指由多方参与的,确定要去完成一些事情。如果没有多方参与,或者没有去完成一些目标就不叫协议。协议有很多种,最明显比如秘钥交换协议(例如A和B要在一个加密的通道中传递信息,在这之前A和B要协商一个秘钥并交换,使得A可以加密消息发送给B并确保B可以解密信息)。

在一个安全的密码系统中,必须是算法,秘钥,协议都安全,如果其中一个是不安全的,那么整个系统就是不安全的。

大多数流行的公开算法,已经被广泛的证明是安全的,它们经历过很多理论数学家的分析。秘钥的安全性由秘钥长度,以及你是否妥当的保管你的秘钥决定。如果你做的不错,那么秘钥也是安全的。

协议却不是如此了,协议很脆弱。举个例子,A将重要的信息加密发送给B,只有B有秘钥可以解密这些信息,C在网路上监听到了A发送给B的秘密,但是C没有秘钥无法解密信息,怎么办。上面说过,秘钥是这个数学难题的最优解,如果没有秘钥,C就只能尝试解数学难题,恐怕C这一生都无法解开,但是C的老板要求他必须在72小时内解密信息,不然C所在的公司就要遭受重大损失。于是C决定铤而走险,他绑架了B,并对B进行严刑拷打,B扛不住,交出了他的秘钥,C拿着B的秘钥解密了秘密,并干掉了B。

上面的例子说明了协议在整个密码系统中是一个易攻击的薄弱环节,这样的攻击类型很多(贿赂,美色诱惑,威胁等)。

我们来尝试把这种攻击带入到软件破解上来,同样的结论也是攻击协议,而非攻击算法和秘钥。同样来举个例子,软件D采用RSA公开秘钥算法来对用户的信息以及软件产品序列号摘要进行加密,并发送给软件D公司的注册服务器,软件公司D的计算机收到用户A发送给它的加密信息,用自己的私钥解密并把用户信息与数据库中的信息进行对比,如果序列号不在D公司的数据库中说明这个产品的不是D公司销售的产品,如果序列号在D公司的数据库中,并且A用户信息不在数据库中,那么A是第一次注册,将A的信息与序列号绑定在一起保存在数据库中,此序列号将不能再给其他人使用。如果序列号和A都在数据库中,并且序列号与A绑定,那么D公司用私钥加密一段许可信息发送给A,A每次使用D软件的时候验证这段信息,如果是D公司发送的许可信息允许使用,如果不是不允许登陆。

看起来很不错,RSA是安全的公开密钥算法,并且秘钥被D公司保存起来,除非你能贿赂D公司的员工获得秘钥,或者攻破D公司的网路,否则秘钥也是安全的。因此攻击算法和秘钥是不现实的。那么,我们可以从协议的角度入手,看看有没有弱点。

先简单说一下RSA算法:

  1. 选择两个大素数 p 和 q,假设 p=3,q=11
  2. 计算 n = p × q = 3 × 11 = 33
  3. 计算 ∅(n) = (p – 1) × (q – 1) = 2 × 10 = 20
  4. 选择 e 且 1 < e < ∅(n) , e 与 n 互为素数. 假设 e = 7
  5. 计算 d,使 (d × e) % ∅(n) = 1. 假设 d = 3,且 d = (3 × 7) % 20 = 1,满足条件
  6. 公开 e, n => (7, 33)
  7. 保密 d => 3
  8. 加密公式 C = Me % n
  9. 解密公式 M = Cd % n

用上面的公式加密,假如消息是2,C = 27 % 33 = 29,密文就是29。解密的话就是M = 293 % 33 = 2。

回到刚才的软件破解问题上,软件产品中有e和n,如果能够分解n我们就能够构造d,也就知道了D公司的秘钥,由于分解n是一个数学难题,我们不知道最优解很难解出答案。我们采用另一种方法,破坏协议,首先我们自己选择2个大素数,构造另一个n,并分别求出对应的e和d。然后我们架设一个服务器,用来模仿D公司的验证服务器。

我们侵入D公司的软件,用我们自己的n和e替换D公司的n和e,这时软件D会用我们的公开秘钥加密用户信息以及序列号,这段信息是无法送给D公司验证的,因为D公司没有我们的保密秘钥,无法解密信息,因此这样还无法完成软件的验证。

下一步,我们使软件D将加密好的信息发送给我们刚才架设的模仿D公司的服务器,这台机器拥有我们的保密秘钥,它成功的解密信息,并直接构造一个验证通过的数据块,并用保密秘钥加密发送给D软件。D软件用我们的公开秘钥解密信息,验证一切正常,并将这段信息保存到计算机中。每次D软件启动时,重新检查是否存在验证通过信息块,并用我们设给它的公开秘钥解密,解密成功后通过验证,启动D软件。只要通过一次验证,D软件就无需再次与D公司服务器进行交互,因此完成了破解。

再次说明,算法,秘钥,协议是一个密码系统的组成部分,任何一个方面不安全,整个系统就是不安全的。当下,很多厂商对算法和秘钥很关注,在算法的选择与秘钥的长度上下足了功夫。但他们的系统并不像他们想象的那么安全,因为他们忽视了协议。

好了,今天就聊到这,欢迎@我的email与我交流。

TedZhang2891@gmail.com

Posted onAugust 25, 2016CategoriesCryptographyTagsCryptographyLeave a commenton Algorithm, Secret key and Protocol

Ransomware Locky Analysis

 ImageDiffLocky的变种非常的多,这个样本来自下面的Url,是最新的一种变种。 

这是程序在刚开始执行时与释放了Image并替换了之后的对比,很明显发生了进程替换,因此进行分析之前有必要把它内部释放出来的image提取出来,分析这个image才能搞清楚它是如何做加密的。

 

 Locky存在一个未知的壳,IDA并不能检测出这个壳,因为它的导入表等信息并没有被破坏。它在运行起来后
,执行相当大量的垃圾代码干扰调试,依据是在调试的过程中它进行相当多的寄存器操作但6个通用寄存器的值始终是0。

这这些垃圾代码中隐藏着它获取Kernel32.dll的Addr的逻辑,它会获取Kernel32的BaseAddress并通过偏移计算出API的地址。

它会调用VirtualAlloc分配一块可写可执行的内存块,大小是61BB,可执行意味着会释放代码到这块内存区中,应该着重分析。(0x404587

TextSegment中存在一部分未被IDA识别的func,位置在(0x402940)。这部分代码的作用是将一些数据(在TextSegment中)释放到刚才通过VirtualAlloc分配的内存中去。

GenerateShellCodes(PVOID lpShellCodes, BYTE* pDataInTextSegment, DWORD dwLen, DWORD dwHEX);

fillShellCodes_40298D

其中bl寄存器用来作为临时存储一个字节的寄存器,ebx寄存器用作计数器,它从函数参数中获得Data的Length,然后递减,如果等于0就跳出循环,esi是指向VirtualAlloc返回的内存地址。处理完毕后,会在指定的位置写入一堆数据,这些数据还需要进行异或处理才能变成真正可以被执行的代码。

以下是没有被处理的数据,只是单纯的填充了缓冲区。

001D0000

它的解这部分Code的算法如下:

xorShellCodes_4029A2

其中dwHEX是这个处理函数的第四个参数。解完了以后的数据如下:

001D0000_2

下面已经完成了部分ShellCodes的释放,接下来将跳转到ShellCode去执行代码。

loc_404CB8

这段ShellCode的目的释放一个被压缩的PE文件,并加载这个PE文件,整个过程没有释放任何文件出来,无法被监控软件发现,释放PE文件使用了非常精巧的方式加载到当前进程中,并完成了进程替换。

被释放的ShellCode本身还有很多代码没有释放出来,在ShellCode的Offset=247处有一个func用于释放ShellCode中的代码(释放不太严谨,应该是通过一些异或操作把原来不是代码的数据转换成代码)。

loc_1D0266

这个操作会将ShellCode头部的一些代码释放出来。在后面经过调试发现,这个func会被反复的多次调用,用于释放存在ShellCode中的数据(转换成可以被执行的CPU代码)。因此这里应该是释放ShellCode自身代码的逻辑。

sub_1D1020用来获得Kernel32的BaseAddr:

loc_1D08EC

loc_1D0B58GetModuleHandle

 

 

再被释放的ShellCode+1020处有一个Func(SC_GetModuleAddr)它用来根据传入的HardCode得到对应的Module的基地址,例如Kernel32.dll Advapi32.dll等。

LPVOID SC_GetModuleAddr(DWORD dwHEX);

对应的在ShellCode+1122处有一个Func(SC_GetFuncAddr)它用来根据传入的HardCode得到对应的func的基地址,例如Kernel32的GetProcAddress等。

PFUNC SC_GetFuncAddr(DWORD dwHEX, LPVOID lpModuleBaseAddr);

如下图:loc_1D03E9

ShellCode会调用这段代码GetProcAddress,并传入Module的基地址以及要获取的函数名。这里函数的名字被嵌入到了ShellCode中,被解释称代码了,需要重新解析成字符串。loc_1D047C注册服务,隐藏自己的行为。

后面的逻辑大致符合如下描述:

  1. 首先通过压入一些特定的HEX值获取GetProcAddr的地址。
  2. 然后传入要用到的API所在的dll的基地址以及在ShellCode中隐藏的函数名。
  3. 使用获得的API,不缓存,下次再用还要走这个逻辑。

例如:用同样的方法获得了EnumServicesStatusExA的函数地址。001D0518

这里调用GlobalAlloc(ShellCode+001D0686)在堆上分配内存分配的内存用0初始化,大小是0x4214。(可能是一个结构)001D05E6因为被释放掉了。001D07A2

这里又使用API分配了一块内存,大小是1799C。页属性是可读写不可执行。001D2207这个函数的参数很值得注意,居然是当前进程的基地址。001D22C40012FCA0

这里从当前的进程中释放了一堆压缩数据到刚才分配的内存中,然后又继续调用VirtualAlloc分配了一块大小1AE00的内存,页属性依然是可读写不可以执行,这块内存用于解压缩刚才释放的压缩数据。001D30E4

程序接下来调用了RtlDecompressBuffer这个Undocument的API。用于释放压缩的数据到指定的Buffer中。释放出来的文件是一个PE Image。(ShellCode+318E)00200000

把这个PE dump出来之后可以从导入表中发现一堆Crypt相关的API,它就是真正加密用户数据的PE。00412000

到这里后面的分析都与Ransomware没有关系了,只要能拿到内部的加密数据的文件进行分析就可以了。下面是这段ShellCode作为加载器还有一些什么具体行为的分析。001D2928

这里释放了压缩数据所占用的内存。001D23F1001D24DB001D25BB

这里打开了通过获取当前壳进程的ImagePath,然后调用CreateFile打开文件并计算文件的Size。loc_1D264B

计算出来的文件大小是31000,然后调用VirtualAlloc分配相同大小的空间。下面是VirtualAlloc的参数。可读可写不可以执行。0012ECA4

 

然后调用ReadFile从当前Image的文件中读取数据,一次读取完毕。0012ECA0001D277D

然后关闭文件。001D2828

现在内存中有2份PE文件了,一份原始的壳的Image,还有一个被脱壳后真正做加密工作的Image。

00570000上面是原始壳的PE数据

00200000上面是被脱壳后的PE数据

判断它最后会还原回去,不然没有必要保存一份在内存中。这样可以不用释放文件,监控软件监控不到,非常精明的设计。001D1700

这里调用VirtualProtect来对0x400000位置的当前Image设置可写权限,要开始覆盖当前的数据了。0012FCA0

开始将Image的内容清空:001D17C6

00400000_1

然后从debug021:001D17E9开始的一大段代码用于填充新数据到当前的Image中去。好大的一段代码,因为内存中的PEOffset与加载到内存后的Offset不一样,所以需要小心的分段加载PE数据,这里的代码类似一个进程加载器。00400000_2

写完后的数据已经与上面释放出来的PE一样了(对照0x200000地址数据),这里发生了进程替换,它肯定还要找到新的PEEntryPoint开始新的调用。

001D34C2

调用RtlZeroMemory把释放出来的在内存中的做加密的PEImage擦除掉。

00200000_1

然后调用VirtualFree释放掉内存(清理现场)。

 

现在调用VirtualProtect把在0x400000位置的新的PEImage页属性设为只读。012FCB0

PEHeader,第一个页面。继续设置代码段为可执行可读。

012FCAC

到这里,新的PE已经加载完毕了,应该可以准备执行了。001D377A

这里会把一些API用到的都取出来保存在栈上,后面用。有一些,不过我最关注的是CreateThread,然后它会清理ShellCode,清理犯罪现场。通过使用下面的API调用。清理的过程分几个阶段,头部的ShellCode还保留,只是把除了头部以外的都清理成0.77475C90

0012FCDC

可以看到从F6之后都是0了。001D0000

然后调用VirtualFree把这整段Code删除掉。到这里ShellCode的使命已经完成,简单来说它的目的就是将内部的一个PE释放出来,并替换当前进程,并把当前进程的数据缓存到内存中。

然后,程序会执行CreateThread,执行新的PE代码。7682375D

405152就是新的进程的EntryPoint,可以通过对提取的PEImage的分析佐证。00405152

至此,脱壳完成并且成功的分析到了这个壳的运作机制。

flow

Continue reading Ransomware Locky Analysis

Posted onAugust 7, 2016CategoriesTech BlogTagsAssembly, C++, ReversingLeave a commenton Ransomware Locky Analysis

Ransomware Cerber Analysis

Cerber是一个可执行程序,它的感染后行为没有CryptXXX这么隐蔽,可以说分析它的行为并不困难,但是它对内部数据的保护比CryptXXX做的好。例如我可以写一个简单反向算法就可以将CryptXXX中的所有加密数据提取出来,但是对于Cerber它显然在对内部数据的保护上下足了功夫,但这并没有阻挡我提取出它所有的内部数据。下面会详细的分析它如何保护内部数据的逻辑。

还需要了解的是,CerberC语言实现的,并且它并没有使用微软的运行库,换句话说它没有采用微软的编译器,很有可能是Intel的编译器或者是Cross-Compilergcc,我不确定。能够肯定的是2点,没有采用微软编译器,没有使用C++标准库。

2016-7-8

更新一下,我原本通过分析CryptXXX,感觉CryptXXX的逻辑设计的非常巧妙,它的感染方式被分散到很多个Export函数中。现在通过最新对Cerber的分析,发现Cerber也是设计的非常巧妙,它去做一些具体的操作时会调用其它命令行进程隐蔽自己。可以看出作者对windows系统的理解程度非常之深,应该是个高级玩家。

CryptXXX有各种设计精美的Blackmail,它们是HtmlBmpTxt,有2web服务器节点,与web的交互非常频繁,相互做backup。维护这些都需要人员和精力,它很像是一个团队在做。

Cerber更像是一个人在做,它的勒索页面很简单也不美观,它也没有与web交互,应该是没有精力维护web站点,或者更好的隐藏自己。但作者应该是个windows资深玩家。

为什么这么讲,因为我发现cerber会给自己提权,也会去check UAC当前的状态,还用了很多windows的内置变量(%xxxx%)这种形式的,我感觉作者以前就是做病毒的。

Global Data Structure

NamedImageOffsetSizeDescriptiong_lpModuleFullPath0x41B7604Cerber所在的当前目录加文件名g_lpModulePath0x41B4404Cerber所在的目录g_pAddrContainMeta0x41B43C0x124一个数据结构包含Meta信息g_hEvent0x41B4384全局Event,手动Set初始无信号g_szCerCoProtMutex0x41B6484全局Mutex的nameg_hHeap0x419A504全局对象,私有堆句柄g_dwCurrentPID0x419A544当前进程IDg_dwImmMap0x419A3C0x10一组常量,用于计算字符串g_bEncryptDone0x41BF614加密是否完成标志g_hCryptProv0x41A1684cryptographic service providerg_szModuleFileName0x41B9704当前进程的文件名g_tedGlobalMeta0x41A0F8 全局Meta数据结构g_bMultiThread0x41A12C4全局标志,是否多线程加密g_dwMaxBlockSize0x41A14C4最大块的大小g_dwMaxBlocks0x41A12D4最大块数量g_dqMinFileSize0x41A1388最小文件大小g_dwRsaKeySize0x41A1544RsaKey的大小g_Cerber_key_place0x41BEB84 g_pJsonObject0x41A1344全局json对象g_pfnIsWow64Process0x41BFFC APIg_InitEnvpPrograms0x41BFF84判断以下4个全局变量是否初始化g_envpPCPFile0x419AA44%CommonProgramFiles%g_envpPCPW64320x419AA84%CommonProgramW6432%g_envpProgramFile0x419AAC4%ProgramFiles%g_envpProgramW64320x419AB04%ProgramW6432%g_dwStopReason0x41952C4程序不执行的原因g_bStartNoParam0x41BF604标志是否没有命令行参数g_lpNewExtension0x41BC984加密后新的扩展名g_szWatchdogMutex0x41BB784Watchdog的mutexg_byteDebug0x41A12E4全局的调试标记,打开可以在DbgView中收到调试信息g_byteNetWork0x41A12F4设置为1加密网络文件g_AvgReadBytes0x41A1504平均每个块的Bytes数量

Meta数据机构

包含一个初始化的临界区变量,一个当前进程PID,当前进程的TID。

Export Function

funcNameImageOffsetDescriptionStart0x406BEDEP 没有什么特别,PE的主入口RSA KeyLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2a3R5NXFocUV5ZFI5MDc2RmV2cAowdU1QN0laTm1zMUFBN0dQUVVUaE1XYllpRVlJaEJLY1QwL253WXJCcTBPZ3Y3OUsxdHRhMDRFSFRyWGdjQXAvCk9KZ0JoejlONThhZXdkNHlaQm0yY29lYURHdmNHUkFjOWU3Mk9iRlEvVE1FL0lvN0xaNXFYRFd6RGFmSThMQTgKSlFtU3owTCsvRytMUFRXZzdrUE9wSlQ3V1NrUmI5VDh3NVFnWlJKdXZ2aEVySE04M2tPM0VMVEgrU29FSTUzcAo0RU5Wd2ZOTkVwT3BucE9PU0tRb2J0SXc1NkNzUUZyaGFjMHNRbE9qZWsvbXVWbHV4amlFbWMwZnN6azJXTFNuCnFyeWlNeXphSTVEV0JEallLWEExdHAyaC95Z2JrWWRGWVJiQUVxd3RMeFQyd01mV1BRSTVPa2hUYTl0WnFEMEgKblFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==

解密Cerber的内部数据非常麻烦,它用了很多层技术防止提取出它的内部数据,这些数据虽然可以在调试时动态的跑出来但是对于静态分析代码效率太低下。因此我实现了这个解密算法。

The assembly list was implemented by me decryptThe file be generated automatically GenerateCodes

下面是解密后与解密前的数据,解密之前的数据我并没有 dump出来,因为我累计在这部分已经花去了超过24个小时(包括编写汇编算法,抽取脚本,分析等),我不想再花更多的时间完善它(后来强迫症犯了,提供了一个diff版方便看代码)。

EncryptDecrypt encrypt decrypt

通过分析decrypt.txt文件,可以发现cerber隐藏的很多细节,包括我并没有发现的细节。因此解密内部数据这项工作应该更早的展开。

通过阅读解密后的数据,可以了解它使用了那些技术。挑选一些主要的写在下面。

select * from %s难道有内部数据库,这是不可能的,有可能是应用了WMI。FirewallProduct这个好像不言自明AntiVirusProductcerber_startup_fake_serviceFake?wireshark.exe看起来会反调试反监控,但是事实上好像没有workdumpcap.exeollydbg.exeSOFTWARE\Oracle\VirtualBox看起来是虚拟机检查,不过貌似也没有workVBoxMouse.sysVMWARESOFTWARE\VMware, Inc.\VMware ToolsCERBER_WATCHDOG_PROTECTION_MUTEX这些串放到这里让人一看就知道他的行为,如果我写我会Debug和Release分别放置不同的值CERBER_CORE_PROTECTION_MUTEXCERBER_STATISTICS_PROTECTION_MUTEXbcdedit.exe

/set {default} recoveryenabled no

这个… 很贱Software\Microsoft\Windows\CurrentVersion\Run看起来要自启动AutoRun   

这是一个Json格式文件,被解密后释放出来。是一个配置文件

The file type of JSON which Extracted from decrypt config

这个Json后面会释放出3个勒索文件出来

 help_cerber注意,这个包没有病毒,但是在Windows10上会被Windows Defender警告,因为它检测到模式匹配

Cerber

主程序行为详细分析。

先看一下下面链接的代码:

start

Cerber程序被执行后,会先解密一个内部数据并计算一个四字节的值存放到全局变量中。然后通过当前进程的Pid创建一个全局唯一的Mutex命名字符串并用它构造命名的Mutex对象(MSCTF.Shared.MUTEX.575906ab)。

紧接着将当前全路径提取出来,同时将文件名去掉只保留路径,并把这两个值保存到全局变量中去。

接着,开始尝试启动新的cerber进程。这个过程比较隐晦,它先枚举系统所有的窗口站,并查找有没有交互式窗口,如果找到了,将这个交互式窗口链接到当前进程,然后注册并启动一个Windows Service,这个服务只做一件事情,就是不停的尝试再启动一个Cerber进程。如果启动服务没有成功,那么就直接创建一个Cerber进程,这个过程也很麻烦,它会先获取当前活动的SessionID,并进而获得用户的Token,用用户的身份去创建一个新的Cerber,无论两种方法只要创建成功,那就不会返回,进程退出。

start

TED_CreateCerberAgain

CB_CreateThreadOnSCM

TR_CreateProcessUtillSucess

TED_CreateProcess

wrap_CreateProcessAsUser

这是程序的Self-Spawn流程。

 

 

 

解密内部数据流程

在进入真正搞破坏之前,程序还有些准备工作要做,其中就包括从自身的资源文件中提取一个Json文件出来,并用一个JsonParser生成一个Json对象出来。这里有可能使用了第三方的Json解析库,我不确定是Jsonc还是Jansson。因为我已经把Json内部的一些方法分析出来了,才判断出那是在构造一个Json对象,因此如果早一点介入调查第三方库会效率更高一些。

Ok,回到初始化JsonObject的部分,首先程序会把自己Image中的资源段文件读取出来,然后通过自定义的算法计算出Json文件的串并Hold在内存中。下面我来分析一下这个过程。

首先,它会用一个神奇的算法解密出制定的字符串,这个神奇的算法被普遍应用于解密字符串在这个程序中(我已经实现了这个算法,1000行汇编代码)。

MakeRealString

前面我说过cerber对内部数据的保护比CryptXXX做的更好就是由于它的解密内部数据的算法需要固定3个值才能解密出对应的数据,而CryptXXX的算法是固定,只要有输入就可以解密内部数据。

PCTSTR __cdecl TED_MakeRealTString(BYTE *byteUnknown, int nResSize, DWORD dwHex, char bUseWStr);

TED_MakeRealTString

上面是解密内部数据的func原型,其中需要固定的数据有保存在rdata中的字节,字节长度,一组解密用的key(4字节16进制数据)。

这个解密函数非常复杂,首先如果是第一次调用需要要进行一下初始化,工作包括将内部数据的链表头指向空,初始化meta信息,meta信息中包括了临界区变量,进程PID,TID。

然后,根据传入的rdata中的字节和字节长度计算一个CRC,然后遍历链表,如果匹配到了相同的CRC,就把当前链表中的item取出来,返回保存在item中WStr或者Str,这个是根据上面原型的第四个参数指定的。

如果通过CRC没有找到数据,那说明数据还没有被缓存,执行计算过程。首先为链表分配一个新的节点,并把CRC放到新的item里面,然后开始执行计算。

计算的过程也很复杂,首先在栈上构造一个ASCII码表,然后把输入的字节解释称0-255的整数数组,并用一个会自动循环并且最大值不会超过输入字节长度的索引值不停的循环的取出输入数据中的值并当成整形与ASCII码表中整数计算出一个可变的数值,这个数值会与ASCII码表中的数据交换,经过这样循环的处理,会因为输入的不同得出不同的结果。(复杂代码用语言描述太难了,还是看代码吧)

TED_CalculateSpecialString

然后,它用被交换,或者说是扰乱过的ASCII码表与输入的加密后的数据做异或操作(一个复杂的异或操作,加减偏移什么的),计算出Real的数据出来。

我这里讲的是它解密出了一块Json的数据,下一步还要解析这个Json数据把它转换成内部的数据结构。

链表在这里的作用是缓存内部数据。

构造Json Object流程

在生成Json对象之前首先要了解Json的基本数据类型,json共有5种基本数据类型,它们是逻辑值,字符串,数值,数组和对象。其它信息如果想了解就去google一下吧。

程序首先构造了一个json对象:

TED_GetJsonMem

然后把这个对象和json串一起送入Parser中,Parser根据输入的Json会去填充这个JsonObject。这像是某一种第三方Json Parse库,因此我没有必要继续分析它的作用。看一下就知道是在解析Json。

JSON_Parse

Json

看的出来,这个JSON_Parse被多个函数调用,但我们这里只需要关注Start这条线就可以了。

因为他上面调用它的生成Json对象的方法是一个通用方法,程序的其它位置会调用它来生成其它的Json对象。

 

 

TED_GenerateJsonObject

TODO:JsonStructure

JsonObject被初始化完毕后,在程序中提取配置信息等操作就由相应的操作函数来完成。这个对象是在内存中分配出来的,因此在全局数据区是发现不到它的。这个对象会再接下来的程序生命期中被多次使用到。

配置全局变量

PrepareEncrypt

接下来程序需要初始化一些全局数据成员,通过从Json中提取出key对应的value。这些全局变量是加密用户数据所需要指定的配置信息,它们包括:多线程加密标志,最大块尺寸,最大块数量,最小文件尺寸,RSAkey的size。

 

接下来,程序要进行一些加密前的检查工作,它先要去检查一下指定的注册表位置,第一次执行时这个位置是空的,因此这段code会直接返回,还不会开始加密用户的数据。

“Printers\Defaults\{89873163-8DC1-7563-E594-6622BDBB1978}”这个注册表位置会被检查,如果不是第一次运行,在这个位置会有几堆数据,这些数据包含是一个被序列化的Json对象,一组RsaKey,那么程序的就会解析这个Json对象,解析后的Json对象指针会被放到g_pJsonObject(看最开头有列表)这个位置,并且程序会提取出RasKey并计算一个hash并放到全局变量中去,完成这些工作后程序返回。

上面这个过程就是加快操作的功能,如果从注册表中提取不到Rsa信息,那么就从全局的json对象中构造,首先从json对象中提取一个用来加密的Key,这个key实际上可以从资源文件中提取出来(最上面有列出)。然后把这些信息写到注册表中,让下一次操作可以从注册表中提取数据。整个的流程如下:

TED_GetCerberKeyFromRegister

完整的加密配置准备工作的流程如下:

TED_PrepareEncrypt

CoreProtectionMutex

接下来程序初始化了一个全局mutex,这个mutex被Cerber当作Core Protection Mutex,然后创建了一个。

然后还会创建一个全局的Event。

GlobalMeta

然后程序开始分配一些内存,为创建全局的数据结构,这个结构包含一个meta信息,一组句柄,还有标记句柄数据中保存了多少句柄的数值。Meta信息是另一个数据结构,包含当前进程Pid,线程的tid,以及一个临界区对象。

struct tedStruData {

int dwCount;

tedGlobalMeta meta;

HANDLE* pObjHandle[64];

};

紧接着,他会创建一个线程,并把这条线程加入到全局数据结构的句柄数组中去。这条线程启动了之后会创建一个隐藏的窗口,这个窗口的窗口过程接收END_SESSION消息,当收到这条消息后,会对全局Event设置信号,这个事件是为了保证系统中其它的Cerber进程能够有效的退出。

CB_WindowProc

接下来,程序处理一下命令行参数,因为程序是命令行应用,第一次执行时不带参数,当启动后,会释放程序内的加密数据,这些数据包含了很多命令行参数。当cerber spawn一堆cerber的时候它们都会带一些特殊的参数进来,在这里,程序的行为将发生变化。它的核心逻辑就在这里了,分析这部分代码还是很有收获的,提权功能,关闭UAC功能,反AV产品功能,反虚拟机功能,判断是不是Admin功能,在64位操作系统上禁用文件系统重定向等在这里被大量使用,也导致了程序在不同的平台上行为不一样,学习到很多病毒的做法。也正是因为分析了这部分代码,我才感觉作者在windows领域浸淫已久,很牛逼。这些技术CryptXXX并没有出现。

 TED_ProcessCommand

ProcessCommand里面的内容非常多,一一展开很费心力,我们先跳过这里,看看后面做了什么,最后在回过头看这部分。

2016-7-12 back to here

这个Func被重命名为TED_ProcessCommand,因为程序会根据不同的执行流选择做具体的操作,而具体的操作都是通过创建自身的一个副本,并提供一些不同的参数从而实现了同一个程序的不同行为,因此代码中需要有一个解析并处理Command Argv的地方,这个地方就是ProcessCommand函数。

这个函数被调用后,首先尝试启动调试权限,然后会判断当前的启动路径是不是一个特殊的路径(C:\Users\Administrator\AppData\Roaming\{64E3EBE0-DAE1-314A-731D-B7440DC4E389})在我机器上如上述路径,如果不是,就把程序拷贝到这里面去,并创建一个Process再次执行,当前Process退出。

如果是的话,接下来会去判断传入的参数数量,进入不同的if块中,这里有3种选择,没有参数,有2个参数,以及超过3个参数的条件。

没有参数的情况是直接执行本程序,这时候他会去简单当前是不是Admin的权限,如果是它尝试用管理员的身份重新启动cerber并带上-eval {dwCurrentPid}参数,但是它在这么做之前会先检查一下UAC的状态,如果UAC没有工作,那么它会这么做。如果UAC正在工作中,它会用cmd去启动当前的Process的镜像文件。{cmd /d /c start c:\tmp\cerber.exe –eval pid}. 这个原因导致调试困难。

如果程序启动后被传入了2个参数,这里有好几个可选项(eval,shadow,watchdog,stat,antiav)。如果shadow,那么将删除卷快照,并将启动恢复设置为no。

TED_PC_shadow

如果参数是watchdog,程序先初始化watchdog的专用mutex,先启动一个watchdog创建线程,创建watchdog process,然后这个线程会每隔1秒监控watchdog process是否始终活动,如果超过4秒watchdog process仍然活跃,那么久杀了这个process再创建一个,这么做是不是为了防止watchdog process被轻易的发现?

TED_PC_watchdog

TR_Watchdog

如果不是第一次运行,那么watchdog可能已经被创建了,程序打开watchdog的全局mutex,如果发现mutex的句柄不存在,说明程序可能出现了问题,例如被调试等,那么程序会继续检查Core Protection Mutex在不在,如果也不存在,那么杀死所有在系统中的cerber进程,隐蔽它的行为。

如果参数是antiav,它会检查是不是具有管理员权限,然后执行AntiAVProduct函数,内部逻辑异常复杂,涉及到权限,SID,Boot等设置,放代码读下不解释了。

TED_AnitAv

如果参数是stat,那么它开始执行真正的加密工作,在开始加密之前,需要进行一些准备工作。首先把json中的encrypt对象中的数据全部load到内存中,包括一组扩展名列表,然后打开注册表Printers\Defaults\{89873163-8DC1-7563-E594-6622BDBB1978},在这个位置把RsaKey取出来,如果取不到,它会先从内存中把Key给弄进去,下次就会取到Key了。

然后,进入Encrypt函数,开始执行真正的加密工作。这里有个debug标志,如果把这个标志打开,会把一些调试信息写到文件中,可能得到一些有效的信息。如果json文件中指定多线程加密,那么就会根据指定的数量创建多条线程进行加密。

TED_Encrypt

TR_DoEncrypt

TED_EncryptData主要加密函数,入参是路径+文件名,加密的核心算法就在这个func里面

在加密之前还有一写逻辑用来枚举所有驱动器,网络驱动器的,把需要加密的磁盘递归的加入一个链表中(这个链表数据结构很复杂,我很想弄出来,不过由于太耗费时间放弃了)。

TED_EnumDevice 枚举所有可以访问并有写权限的驱动器

TED_RecursionFolder 递归的将所有文件加入链表

退出部分

经过上面的逻辑后,程序会抛出很多的Process,函数返回后用户的数据也已经被加密了,剩下的部分是准备推出部分,这部分不是特别重要,但是它却展示了程序内部数据结构的构成,以及如何管理大量的句柄。

这次先放代码,看后分析

TED_PrepareLeave 主调

TED_WaitAllObject 具体

主调函数先进入临界区,然后调用waitAllObject去等待所有的句柄相应。句柄中大量的process和thread。WaitAllObject函数已经被我分析好了,这部分代码很有意思,理解它需要有一些领域知识,如果没有写过高并发服务器的话,估计很难看得懂这部分代码(指未翻译之前的代码)。

首先有一个背景知识,就是一个WaitForMutliObject句柄组上限是64个,超过这个数量就没法管理了(不信可以去google)。那么如果需要在wait中管理超过64个句柄怎么办,有一个办法,类似虚拟地址映射(页表,页目录),可以创建一些管理线程,这些线程每一个管理另外64个句柄,这样可以无限放大(二级,三级这样)。有了这些知识再看代码就清晰了,可以读一下我翻译过的代码。

继续往下,都是一些释放操作没什么好说的。最后如果加密完成,它要通知其他系统中的cerber进程退出,这些进程可能是上面wait不到的。反正要退出了,这里也比较暴力,枚举所有cerber然后调用Terminate。后面还有一个force的做backup,是通过cmd.exe调用taskkill来杀。最后在自杀。放一个小片段吧。

SR_start

 

Posted onJuly 15, 2016CategoriesTech BlogTagsmalware virus reversingLeave a commenton Ransomware Cerber Analysis

Ransomware CryptXXX Analysis

Global Data Structure

NamedImageOffsetDescriptionpConfig0x4259A0全局配置信息,窗口句柄,全局标志位,当前进程是否是Svchost本身等。szMutexCryptpConfig+0x100全局互斥对象的名称,szModuleFullPathpConfig+0x121Crypt.dll的当前完整路径 (C:\Tmp\Crypt_Patched.dll)dwOSVerpConfig+0x26B操作系统的主版本号和次要版本号szModuleDirectorypConfig+0x663Crypt.dll的当前所在目录dwPlatformpConfig+0x26C64 or 32 代表不同的平台dwWebServerIDpConfig+0x26A0x13524C90 or 0x40BBAA5D pConfig+0x563  pConfig+0x8A5 szWebserverIppConfig+0x26A“144.76.82.19” or “93.170.187.64”lpPubKeyString0x4272B8 .BSS运行时释放到这里dwRandomSeed0x423008 .data随机数种子,每次运行都不一样

Export Function

funcNameImageOffsetDescriptionDllEntryPoint0x422028这个入口会根据当前执行的进程名称来判断是第一次被调用(rundll32.exe),还是之后的伪装调用(svchost.exe)。如果是第一次调用,做一些初始化的工作,将rundll.exe从系统目录拷贝到当前module目录中并重命名为svchost.exe,然后用伪装的svchost加载crypt.dll并调用MS111。这回导致新进程再去load crypt.dll进而反复启动新的进程,这是系统中出现一大堆svchost.exe的原因。

0x41FFFF位置打patch 90 81 C4 28 00 00 00让它无法启动好多的进程方便调试。只要不是svchost启动的,暂时就不会干不好的事情。

MS1110x421E1C首先判断一下是否已经在入口中执行了伪装svchost加载模块的流程,如果没有创建一个新的进程调用MS112。如果执行了,先等6分钟,然后启动2条线程TR_ProcessWindow和TR_PerformCheck,然后进入消息循环等待进程退出的消息。MS112(Encrypt)0x4213C0首先从全局信息中取出互斥对象的名称,然后打开这个互斥对象。根据后面的分析这个互斥对象应该是每个Process都是唯一的,没有确认。成功创建了Mutex后先组装一个神秘的字符串没有调,然后根据一个标志来决定是否创建一个Process去执行MS113. 然后检查全局数据结构,取出一个特殊文件的路径,判断这个文件存在不存在。MS113(FillConfig)0x421384这个函数只做一件主要的事情,就是去webserver上面下载一个危险的dll。

https://www.proofpoint.com/tw/threat-insight/post/cryptxxx-ransomware-learns-samba-other-new-tricks-with-version3100

MS1140x421E0CSet一个全局标志后,调用MS112. MS112会根据是否设置了这个全局标志来决定是否调用MS113.MS115(Deamon)0x421254每隔200ms检查一下系统中是否存在WerFault.exe(CrashReport),如果存在就杀掉这个进程。同时检查当前进程的父进程是否已经退出了,如果退出了就用系统中保存的伪装的svchost加载crypt.dll并调用MS112,然后退出。

 

RSA Key—–BEGIN CERTIFICATE—–

BgIAAACkAABSU0ExAAQAAAEAAQBfFTOUbZiP6u9PppNyTSXM+Y5W9pEcKe68HJYq

dLYpXL+XCzXTUgsSRJ1iNmXqrhUEqz3hOi93Bw53U28gvnJTHRboA32xzli688MQ

eJz7kis1d2G+o8bz+VHO/7qsX+jlBLkP86a6+MYvvhZW+Z0HcsZbMjn6/yCgbhF8

BhpvuQ==

—–END CERTIFICATE—

 

写了个程序把image中的所有加密字符串解密出来。

EncryptDecrypt EncryptStringDecryptString

 

这是一个html,被解密后释放出来。是一个勒索页面

The blackmail html which Extracted from decrypt blackmail

blackmail

DllEntryPoint

 DllEntryPoint

MS111 主要逻辑

MS111

TR_ProcessWindow:遍历所有的隐藏窗口,并且不属于系统,并查找窗口所属的进程名,如果match到了一个特殊的进程名,就给他发一个消息让他恢复出来。

TR_ProcessWindow

TR_PerformCheck:创建一个Process开始调用MS112

TR_PerformCheck

如果不是Svchost.exe创建新的Process开始调用MS112,进程退出。

执行的Commandline如下:

C:\Users\ADMINI~1\Desktop\_00310~1\svchost.exe C:\Users\ADMINI~1\Desktop\_00310~1\_00310000_pe.dll, MS112

MS112 主要逻辑

MS112

根据最新的分析,这个Func是用来加密用户文件,并显示勒索信息的。

首先从全局信息中取出互斥对象的名称,然后打开这个互斥对象。根据后面的分析这个互斥对象应该是每个Process都是唯一的,没有确认。成功创建了Mutex后先组装一个神秘的字符串,根据是否已经获得了全局数据结构来决定是否创建一个Process去执行MS113,MS113会去webserver请求全局数据结构.

然后检查全局数据结构,取出一个特殊文件的路径,判断这个文件存在不存在。

如果存在,就把文件中的内容读取到一个Buffer中,这个Buffer是有结构的,它的0x20C偏移是0x3E8这是一个Marker。

如果这个文件不存在,那么请求webserver给它发送一个这份文件内容的数据。它会初始化一个请求数据结构,其中前4个字节是请求的command,最后四个字节是marker。中间的内容根据全局数据结构填充。

请求的数据使用Https安全协议加密,它有两个webserver(”144.76.82.19″ or “93.170.187.64”),当一个请求失败时会尝试第二个webserver。

首先发送校验包,用于服务器确认通信合规。先发送4个字节包含通信包长度的头包,然后继续发送包含实际验证数据的包。

TED_SendAuthenticationPackage

实际上在发送这两个包之前还发送了一个52字节的包,

发送完验证包后,开始接受返回数据。协议相同,先接受4个字节的包,确认要返回的实际数据的长度。然后分配一个带结构的Buffer,用来接收返回的数据,如果返回的数据太多,就一次只接收8k的数据。接收成功后把这些数据压缩保存到Buffer中。

接收的第一个包是返回的校验包,与先前发送的校验包大小一致。校验包的前4个字节是命令,第4-8个字节大概是一个唯一码,并把这个唯一码写到全局数据结构中去。然后开始接收真正的数据。并把265个字节写到全局数据结构中。

TED_RequestForSpecialInfo

然后,接着从全局结构中取回Buffer构造一个新的数据结构,数据结构的前265字节是刚才请求回来的数据(这个数据在后面分析是一个PublicKey),接着4个字节是一个flag,最后四个字节是0x3E8。中间是0。数据结构的大小是0x210。通过从全局数据结构中取一个字符串,并使用上面的Buffer创建这个特殊文件。

SR_WriteSpecialFile

接下来开始解密很多的本地加密字符串,然后组装在一起。后面分析到这实际上是在组装一个勒索的html文件。用浏览器加载这个html。

中间会删除一些文件,同时创建一个Process并向这个Process注入一段代码执行写文件的操作。

TED_CreateAWriteFileProcessThenExit

最后它会创建一个Windows窗口,这个窗口的尺寸等同于客户的屏幕大小,并将信息(大概是勒索信息)打印到窗口上。

这部分会根据是否有全局的窗口句柄来判断是否将信息打印到用户屏幕上去,或者是写入到一个Bmp文件中去。

SR_PrintScreenAndSave2File

之后会等待WM_QUIT消息,随后释放Mutex退出进程。这个Mutex应该是为了防止重入,进而在用户屏幕上绘制太多的信息。

0x41A77C应该是打印在用户屏幕上面的勒索信息,这个function涉及了大量的加密字符串的解密工作。

MS113主要逻辑

这个导出函数的主要逻辑是去Webserver上面下载数据并生成一个邪恶的dll。它首先会去尝试连接”144.76.82.19″的443端口SSL,如果这个server访问失败还有一个后备server地址是 “93.170.187.64”。

连接并校验成功后会先发送一个请求文件大小的包,根据请求到的文件大小创建一个缓冲区,并接收数据,完毕后会把这个数据写成一个image文件存放到本地磁盘上。

TED_DownloadStillerx_dll

下面是卡巴斯基对这个dll的分析介绍

StillerX

In order to further monetize the infections, CryptXXX downloads a DLL which acts as a credential stealing module. Internally referenced as “stiller.dll”, “stillerx.dll” and “stillerzzz.dll”, this DLL works as a plugin, but can also be used as a standalone stealer. The stealer, like the ransomware, is written in Delphi, and uses the object-oriented capabilities offered by the language. Its relatively large size on disk (around 1.2mb) is due to the static linking of several third party libraries such as DCPcrypt used for retrieving and decrypting locally stored credentials.

https://www.proofpoint.com/tw/threat-insight/post/cryptxxx-ransomware-learns-samba-other-new-tricks-with-version3100

我尝试下载这个dll但是失败了,我不知道是不是webserver端的程序修改了还是什么其他原因。当我请求文件大小的时候webserver返回了一个比代码中size还要大的尺寸,以至于程序总是没有进行实际的请求工作就退出了。

我Patch了个size,提供了一个更大的size,第一次请求实际的数据时能够得到数据,但是第二次继续请求的时候没有任何数据到达。因此实际获取的数据长度与请求得到的文件长度不同,程序没有继续构建这个dll出来。Webserver逻辑变更也是有可能的,所以我无法释放出这个文件加以分析。

MS115主要逻辑

这个导出函数的工作就是monitor WerFault.exe,不停的杀这个process。然后判断它的父进程是否已经退出了,如果退出的话创建一个Process用伪装的svchost去loadCrypt然后调用MS112。看起来是一个守护进程。

MS115_TerminateWerfault

加密用户数据的逻辑

这个逻辑主要在MS112中,从下面这个图可以看出来它会用随机算法build一个随机串出来,这个随机串用来给文件加密,这样它就不需要调用其它加密的API,保证了它的加密速度。Build这个随机串在生成勒索信息和真正加密用户文件中都有用到。当然它还会每隔8191个字节加密前64个字节用非对称RC4加密。

MS114

加密的逻辑是先将文件读到一个缓冲区中,然后进入一个循环中,循环每次增加8255个字节。在这个循环中拷贝缓冲区中的前64个字节到一个临时buffer中,然后用非对称的PublicKey进行RSA加密,然后将这个buffer拷贝到另外一个缓冲区中,由于加密后数据会变大,因此拷贝到另外的缓冲区中选择的size的大小是128字节。接下来的8191字节采用一个数学算法,利用随机生成的44字节的串对这部分数据进行加密,速度很快。完成后将这8191个字节拷贝到另外的缓冲区中。8191+64=8255

如果这一切完成了,它会写个0x3E8到文件末尾。还会把使用到的随机串用PublicKey加密了。

这块逻辑比较复杂,如果要准确的分析还需要动态调试验证一下,这里我还没有去做。把代码贴出来,大家自己感受吧。

TED_EncryptUserFile

TED_EncryptBytesByRC4每隔8255加密64字节头,强加密

TED_EncryptBytesByAlgorithm  剩下的8191采用随机串用数学算法加密,速度快,没有看到AES对称加密算法,和AESkey。

 

Random随机数生成一个64位的key的算法分析

这个算法的逻辑简单说就是,用两个Random函数生成2个随机数,一个是0-3用于选择使用哪一个字符数据,另一个0-44用户定位数组中哪一个字符。这样就组装除了一个64位的Key字符数组。放代码看的清楚。

TED_BuildKeyWithRandom

种子算法:

SR_System__Randomize

伪随机算法:

SR_Random

Other

 

由于恶意软件是delphi写的,因此了解一下delphi使用的字符串的结构对分析代码有些帮助。

AnsiString的内部结构

ansiString

被感染后会将rundll32.exe从windows目录中拷贝过来并改名为svchost,然后都用svchost启动,起到伪装迷惑的作用。

然后创建一个Process,进程的Commandline如下:

C:\Users\ADMINI~1\Desktop\_00310~1\svchost.exe C:\Users\ADMINI~1\Desktop\_00310~1\_00310000_pe.dll, MS111

 

这个过程是在Dll load的时候做的,所以它只要一load就会创建一个Process再去load自己,这样就会存在很多个Process在系统中。为了调试方便,可以打patch,让它不要去创建进程。

 svchost

由于会写一个bmp文件出来,这个bmp文件的内容也是勒索信息,被用于屏幕保护。所以需要知道bmp的结构才能分析对应的代码。

bmp

原文地址: http://ec2-52-196-167-189.ap-northeast-1.compute.amazonaws.com/wordpress/index.php/category/tech-blog/
0 0