[原创]恶意样本简单分析
发表于:
2021-3-12 22:33
12562
这是一篇针对初学小白的文章, 本人也正在学习样本分析,本篇文章主要记录分析过程,思想方法,如有不对不足,欢迎指出。 目录 1. 情报收集 1 1.1 哈希值 1 1.2 查壳 2 1.1 导入表 2 1.2 资源表 3 1.3 监控软件 4 1.4 上传网站 4 1.5 Process Explorer监控 6 1.6 Process Monitor监控 8 1.7 测试环境及工具 8 1.8 分析目标 8 2 具体行为分析 8 2.1 主要行为 8 2.2 恶意程序对用户造成的危害 8 2.2恶意代码分析-主程序 9 2.3 恶意代码分析-资源文件 46
1. 情报收集 1.1 哈希值 1.2 查壳 1.1 导入表 使用了createprocessA,getthreadcontext,setthreadcontext来创建新的进程,并修改进程中线程的执行上下文。使用Readprocessmemory,writeprocessmemory对进程内存空间读写,使用findresource,lockresource和sizeofresource来对资源进行操作。Virtualalloc申请内存空间 1.2 资源表 1.3 监控软件 1.4 上传网站 有使用ntunmapviewofsection 函数,常见的恶意手法会以挂起模式创建一个新进程通过调用CreateProcess并将流程创建标志设置为CREATE_SUSPENDED(0x00000004)完成。新进程的主线程被创建为挂起状态,直到ResumeThread函数被调用才会运行。接下来,恶意软件需要用恶意的有效载荷来替换合法文件的内容。这可以通过调用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存。这两个API释放目标的内存。内存被取消映射后,通过执行VirtualAllocEx为恶意软件分配新内存,并使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间。恶意软件调用SetThreadContext将entrypoint指向已编写的新代码段。 1.5 Process Explorer监控 存在一个孤儿进程svchost 文件映像与内存映像不一致 内存映像存在大量键盘符号 以及一个奇怪的log文件 1.6 Process Monitor监控 查看进程树 lab03-02.exe启动了一个svchost进程 有大量写log的操作 1.7 测试环境及工具 测试环境为winXP-32虚拟机 分析攻击:IDA、OD、PEID等 1.8 分析目标 详细分析病毒的操作原理,给出修复建议 2 具体行为分析 2.1 主要行为 1.这个程序秘密的启动了svchost孤儿进程 2.这个程序通过将资源数据替换到svchost进程中 3.资源数据需要XOR解码 2.2 恶意程序对用户造成的危害 键盘数据记录 2.2恶意代码分析-主程序 004014E9地址处比较传入的参数是否是2个,如果不是则跳转至地址00401573 传入3E8H(10进制1000ms)参数进入sleep函数休眠1秒钟停止。 004014FA地址处传入0 到getmodulehandleA函数即获取当前exe进程的句柄。 传入lpbuffer:12FB7C usize:400 字符串:\svchost.exe 3个参数到函数sub_40149D中
参数lpbuffer:12FB7C与 usize:400先传入寄存器中 然后再被压入栈做为getsystemdirectoryA函数的参数,函数执行完后eax中保存了系统目录路径的长度为13换算成10进制为19,lpbuffer:12FB7C缓冲区存着系统目录路径 将lpbuffer:12FB7C先传入edx寄存器中然后压入栈做为_strlen函数的参数,lpbuffer保存着系统目录路径字符串,计算出仍为13返回值保存在eax 平衡堆栈,然后将usize:400传入eax寄存器中 然后减去eax中保存的系统目录的长度,将减去后的值压入栈中即3ED,同时将最开始压入栈中的字符串:\svchost.exe存入edx寄存器中然后压入栈,将lpbuffer:12FB7C传入eax中然后压入栈,由于传入的参数仍为lpbuff再次计算出仍为13返回值保存在eax中 平衡堆栈 将lpbuff的地址存入eax中然后偏移eax的长度即eax当前指向字符串的结尾,将ecx压入栈中,调用strncat函数 当前函数的3个参数分别为ecx(12fb8f)即为dest目标数组,405030(字符串:\svchost.exe)即为 src要追加的字符串,3ED即为要追加的最大字符数。 可以看到函数执行后字符串进行了拼接 由于上面执行strncat传入了3个参数这里add esp,0ch 来平衡堆栈 平衡堆栈 同时将最开始通过getmodulehandleA函数获得的当前exe的句柄从堆栈中传入ecx中 将exc压入堆栈做为sub_40132c函数的参数 提升栈底栈顶 清空栈顶往下的5个空间的值,比较参数是否为0由于我们进入前传入了句柄做为参数所以跳转实现 压入3个参数做为findresourceA函数的参数,函数的目前是查找当前进程中是否有对应的资源名为LOCALZATION type为unicode 查看资源确实是有的,返回了资源的句柄为00406048存在eax寄存器中 将返回的句柄存入栈中 同时比较是否为0 来验证是否找到该资源,将资源句柄传入ecx寄存器中然后压入栈中,将进程句柄传入edx中 然后压入栈中,压入2个值作为loadresource函数的参数 函数执行完成后返回的资源第一个字节的指针句柄存放在eax中我们在内存中查看该指向的值发现与工具中查看的资源一致 这里同样继续将eax的值存入堆栈中与0进行比较来验证是否有找到资源,同时再讲该字节的指针句柄存放在eax中然后压入堆栈,做为lockresource的参数 函数执行完有返回值到eax中说明资源可用 这里同样继续将返回值存入栈中然后与0比较来检测是否有可用的返回值,然后将存入栈中的数据再存入ecx中压入栈中,同时再次将栈中进程的句柄存入edx中压入栈 做为sizaofresource的2个参数 计算出资源的大小为16进制6000存放在eax中 同样这里有做比较来验证是否获取到可用的值,然后压入了4个参数其中之前计算的资源的大小6000也做为参数 传入virtualalloc函数中 函数执行完成后 返回了分配区域的基地址为003B0000存入eax中,即从当前进程中地址003B0000处开始分配了6000大小的空间 同样先将返回值与0进行比较来验证是否内存分配成功,然后将资源大小6000,资源的第一个字节指针句柄00406084,分配区域的基地址003B0000,依次传入寄存器中然后压入栈,3个值做为memcpy函数的参数。即将资源中6000h的数据复制到分配的内存中 执行完成后003B0000空间被赋值 平衡堆栈 将003B0000存入ecx中,清空edx寄存器,将ecx地址中的第一个字节0C存入dl中即edx的低4位,将0C与4D比较是否相等如果相等则继续将003B0000中的第二个字节1B存入cl中与5A比较是否相等如果相等则跳转到40142d,其实这里的4D与5A是003B0000中的值与41异或后得到的值这里由于第一个字节就不相等就跳转到loc_40141B处 这个代码块的目的其实就是将003B0000中的数据都与41进行异或,异或完成后再出来比较自然就能相等进行跳转了,继续分析这个进行异或的代码块 由于003B0000的第一个字节0C没能匹配上4D所以跳转至40141B,这里压入了3个值分别为0x41,资源大小60000,分配内存的基地址003B0000 这里先将ecx(003B0000)压入栈中,然后将之前栈中存放003B0000的地址中的值置为0,跳转到地址401016处,将eax置为0然后与6000比较,如果不是6000那么就跳转到40101E处,将003B0000存入edx中,同时将003b0000加上栈中ebp-4处的值此时为0000000,把edx003B0000地址中存的第一个第一个字节0C放到al中即eax的低4位,然后将al即0C与ebp+10的值即最开始存的41即‘A‘进行异或,异或后的值4D存入al中,然后将栈中ebp+8地址处的值003B0000存入ecx中,将ecx与ebp-4处的值此时为0000000相加,然后将异或完成的al的值4D存入ecx的地址中,最后跳转指40100D处 我们将查看这个函数的伪代码,这里其实是将003B0000处的6000h个字节与41进行异或 完成后跳出循环 a2即为6000,a1为地址003B0000 ,a3为41 平衡堆栈,上个异或函数的返回值是资源的大小6000存在eax中,这里继续将eax的值存入堆栈中然后与0比较是否有资源,如果有资源则将资源的第一个字节的指针句柄00406048压入栈中,作为freeresource的参数,释放加载的资源 Eax为0释放成功 至此sub_40132c执行完毕返回值为分配内存区域的基地址003B0000存于eax中,然后平衡堆栈,将eax压入栈中与0比较看是否执行成功,继续将栈中的003B0000存入edx中,将edx的值压入栈中,将之前存入栈中的拼接好的目录c:\windows\system32\svchost.exe的地址存入eax中,然后将eax压入栈中,做为函数sub_4010EA的两个参数 将参数中的003B0000存入eax中,再将eax存入局部变量中,然后将局部变量中的值003B0000又存入eax中,清空edx的值,将ecx地址中的值即003B0000中的值取2个字节4D5A放入dx中,然后将dx与0x4D5A比较看是否相等 相等则不跳转。 接着继续局部变量中的值003B0000存入eax中,然后将参数中的值003B0000存入ecx中,将eax地址偏移3C位置处的地址传入ecx中,将ecx的值传入栈中,然后又将栈中的数据转移到edx中,比较003B00E0地址的4个字节的值与0X4550比较 ,其实这两步仍然在验证该内存异或后是否为PE文件 接下来传入了3个参数中到memset函数中分别为44h,0,地址12DB0C,目的是将地址12DB0C处的44h字节的数据置为0 可以看到函数执行完后地址的44h大小的数据都为0 同上 这里又传入3个参数将12FB50地址处10h大小的数据置为0 平衡堆栈 然后将开始置为0的2块内存空间地址传入edx,eax中压入栈,同时又压入8个参数,其中有一个是之前c:\windows\system32\svchost.exe文件路径的地址,共10个参数传入createprocessA函数 这里是是用挂起的状态创建 后面需要resumethread后才能运行, 传入的第一个参数12FB50是一个processinformation结构体里面保存了创建进程的句柄34主线程句柄44后面会用到 接下来又传入4个参数来给分配内存空间由于传入的address值为0所以分配的区域由系统决定,分配2CC大小,我这边分配的地址是3E0000 将eax的值传入栈中 然后栈又传给edx中,将0x10007存入edx地址的前4个字节,即003E0000地址的前4个字节的数据。然后继续将栈中的该地址传入eax中 然后再压入栈,将栈中的局部变量44放入ecx中然后压入栈,作为getthreadcontext的两个参数 线程上下文获取成功 清空栈中连续3块地址的数据压入函数readprocessmemory所需的参数,eax+0A4h是context._ebx再加8指向的是进程的基地址7FFDE008,这里是context结构体的偏移 压入两个字符串到栈中其中ntdll.dll字符串做为函数getmodulehandA的参数 获取该模块的句柄 然后将获得的模块句柄压入栈中和之前压入的字符串ntunmapviewofsection一起做为参数传入getprocaddress,即获取该dll中该函数的地址 将eax中的函数地址传入栈中ebp-64,比较ebp-64是否有值,将栈中之前缓冲区的地址的值(存放着readprocessmemory函数读取到的值)存入eax中 将eax入栈,将挂起的进程句柄地址传入ecx中 eax入栈 ZwUnmapViewOfSection函数来取消映射目标进程的内存 传入了5个参数 其中有挂起进程的句柄00000034,由于通过ZwUnmapViewOfSection函数取消了目标进程的内存映射,这时我们为这个挂起的进程申请分配模块的基地址提供给挂起的进程使用分配的空间为7000。这里的ecx+34是PE头的偏移34指向的imagebase进程基地址,ecx+50是pe头偏移50指向的是imagesize 执行成功,我们通过zwunmapviewofsection函数进程卸载内存镜像后得到了一个“干净“的空间,然后我们通过virtualallocEx往该进程填其他程序的内存镜像。
传入5个参数到wirteprocessmemory函数中,通过句柄00000034找到目标进程由于目标进程的基地址就是我们要填充的进程基地址即为恶意程序的基地址0x400000,填充的数据为003B0000里面的数据,填充1000h大小,这里的ecx+54h是指pe头偏移54指向的是sizeofheaders 将ebp-70地址的值置为0,然后将eax寄存器置为1同时传入栈中做为变量,将PE头的地址003B00E0传入eac中同时通过偏移+0x6找到节的数量这个PE文件有3个节区,然后将值3赋给dx中,dx与ebp-70的值比较做为循环的条件 +3c指向的是PE标志位再+f8指向的image_section_header区段名称的地址,由于image_section_heade的大小为40个字节即28h,imul edx,28h就是在读取这个每个区段的数据结构 开始通过writeprocessmemory函数向挂起的进程写入1000h大小接下来继续把.Text的内容写入该进程中。 循环遍历剩下2个节.rdata,.data 修改该进程的基地址为400000 设置该挂起进程的线程上下文 减少挂起计数恢复线程进程运行,返回值是1. 有使用ntunmapviewofsection 函数,常见的恶意手法会以挂起模式创建一个新进程通过调用CreateProcess并将流程创建标志设置为CREATE_SUSPENDED(0x00000004)完成。新进程的主线程被创建为挂起状态,直到ResumeThread函数被调用才会运行。接下来,恶意软件需要用恶意的有效载荷来替换合法文件的内容。这可以通过调用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存。这两个API释放目标的内存。内存被取消映射后,通过执行VirtualAllocEx为恶意软件分配新内存,并使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间。恶意软件调用SetThreadContext将entrypoint指向已编写的新代码段。 将存文件路径的内存数据置为0 释放开始分配的内存空间 2.3 恶意代码分析-资源文件 提取资源文件 用winhex异或解密出资源中的PE文件 用PEid查看未加壳,导入表 分配一个新的控制台 在所有窗口中查找类名称为ConsoleWindowsClass的窗口 函数返回值存到eax中然后放到ebp+hwnd局部变量里面,然后比较返回值是否为空为空就跳转到00401035处,不为0继续执行00401029处 将找到的窗口通过showwindow函数设为隐藏的窗口 将405350 大小为400h的数据全置为1 获取当前进程的句柄 安装监视低级键盘输入事件的钩子程序,钩子过程为fn指针 获取当前线程的任何窗口可用的消息 一直获取消息,获取成功后调用unhookwindowshoookex来取消挂钩 钩子过程分析: 传入了3个参数 先比较code参数是否有值,然后比较wParam参数是否是104或者是100,执行4010A1处的函数,函数的参数为lparam 1个参数3个局部变量,创建一个文件名为practicalmalwareanalysis.log的普通属性文件保存一直打开的状态并且开启请求读写的权限 设置文件移动的指针来控制 获取当前前景窗口的指针句柄 将当前窗口标题栏的文本复制到缓冲区中 返回复制的字符串长度
比较两处字符串是否相等 相等返回0 进行 向创建的文件中写入\r\n[window: 计算缓冲区字符串的长度 缓冲区的数据写入\r\n[window:的后面 继续在后面写入]\r\n字符串 将缓冲区字符串复制到405350处 根据输入的字符进行比较判断来输入对应的数据到文件中最后关闭文件句柄 将挂钩信息传递到当前挂钩链中的下一个挂钩过程
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)