首页
社区
课程
招聘
[原创] 默默无闻·恶意代码分析Lab5
2021-4-5 06:44 5515

[原创] 默默无闻·恶意代码分析Lab5

2021-4-5 06:44
5515

系列往期:

[原创]默默无闻·恶意代码分析Lab1


工具说明

参照书籍:《恶意代码分析实战》;

文件来源:官网随书文件、或者附件中(文件密码:apebro);;

使用工具:WinHex、CFF、StudyPE+、Exeinfo PE、Resource Hacker、Depends Walker、OD、IDA、MSDN;

Lab5-1

只用IDA Pro分析在文件Lab05-01.dll中发现的恶意代码。这个实验的目标是给你的一个用IDA Pro动手的经验。如果你已经用IDA Pro工作过,你可以选择忽略这些问题,而将精力集中在逆向工程恶意代码上。

问题

  • 拿到文件先查壳:

image-20210404235334914

 

很显然,没有壳;

 

再审题,基本上都是IDA Pro的操作,看来是一次复习IDA Pro使用了。

1、DLLMain的地址是什么?

  • 打开IDA导入文件,选择Graph View视图(其实进去就是):

image-20210404235828438

  • 选择函数右击选择Text View视图,看向左边的地址列;

image-20210405000039790

 

获得DLLMain的位置0x1000D02E;

 

这里有一个知识点:就是这里的地址是理论上的VA,注意是理论上的,因为如果理论位置被加载了,就需要让系统重新选择一个位置,然后重定位。所以有效的地址应该是换算成RVA

2、使用Imports窗口并浏览到gethostbyname,导入函数定位到什么地址?

  • 选择imports窗口,使用ALT+T快捷键搜索函数名字符串;

image-20210405000944550

 

获得定位0x100163CC;

3、有多少函数调用了gethostbyname

  • 双击0x100163CC地址,跳转到Text View

image-20210405001431726

  • 点击函数名,嗯x键,跳出xrefs窗口;

image-20210405002313071

 

共18次引用,Type处p代表被调用,r代表[…]“读取”式引用,所以有9个交叉引用对gethostbyname()进行调用;

4、将精力集中在位于0x10001757处的对gethostbyname的调用,你能找出哪个DNS请求将被触发吗?

  • IDA View-A窗口,使用g键,跳转到0x10001757处;

image-20210405003439929

 

这里是一个call操作,所以向上找参数;

1
2
3
mov eax , off_10019040    ;这里是一个数据地址头
add eax , ODh            ;ODh大小
push eax                ;把eax压栈

所以先要找到数据地址头;

  • 双击off_10019040,打开数据段看数据;

image-20210405005742483

 

将鼠标放在黄色字符串上,就能看到完整字符串;或者打开Strings窗口,检索红色框起来的字符串;

 

image-20210405005925461

 

这就找到了网址,所以会对该网址发起DNS请求,获得其IP地址;

5、IDA Pro识别了在0x10001656处的子过程中的多少个局部变量?

  • IDA View-A窗口,使用g键,跳转到0x10001656处;

image-20210405011521090

 

这个就很显然了,偏移为负的是局部变量

 

IDA很智能的识别出了23个局部变量;

6、IDA Pro识别了在0x10001656处的子过程中的多少个参数?

  • IDA View-A窗口,使用g键,跳转到0x10001656处;

image-20210405011611041

 

偏移为正的是参数

 

IDA很智能的识别出了1个参数;

7、使用Strings窗口,来在反汇编中定位字符串\cmd.exe /c。它位于哪?

  • 打开Strings窗口,快捷键ALT+T搜索字符串;

image-20210405012018575

 

找到字符串地址0x10095B34;

8、在引用cmd.exe /c的代码所在的区域发生了什么?

  • 转到IDA View-A窗口,快捷键g跳转到0x10095B34,点击地址,摁快捷键x

image-20210405012327733

 

双击Address地址,跳转到IDA View-A代码;

 

image-20210405012632751

 

这是个push操作;

9、在同样的区域,在0x100101c8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)

  • 找到全局变量,查找xrefs引用,发现总共三次引用,只有一次有move操作;

image-20210405014455621

  • 双击Address地址,跳转查看对应位置代码;

image-20210405014710391

  • 找到对应代码,往上查看,紧接着的是一个函数调用;
  • eax就是上一个函数的返回值,那就双击sub_10003695,查看对应代码;

image-20210405015226786

  • 经过MSDN查看;
1
2
3
4
5
// GetVersionEx函数获取当前运行的操作系统版本的扩展信息。
BOOL GetVersionEx(
  LPOSVERSIONINFO lpVersionInformation   // pointer to version
                                         // information structure
);
  • 通过这段函数的反汇编;

image-20210405020752653

 

得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool sub_10003695(){
    typedef struct {    // 通过MSDN类型溯源获得结构体完整类型;
        DWORD dwOSVersionInfoSize;
        DWORD dwMajorVersion;        // 函数没使用
        DWORD dwMinorVersion;        // 函数没使用
        DWORD dwBuildNumber;        // 函数没使用
        DWORD dwPlatformId;
        CHAR   szCSDVersion[ 128 ];   // Maintenance string for PSS usage
    }VersionInformation;
 
    VersionInformation.dwOSVersionInfoSize = 0x94h;
    GetVersionExA(&VersionInformation);
 
    return VersionInformation.dwPlatformId == 2;
}

由代码可知:dwPlatformId与数字2进行比较,如果相等,al置1,也就是返回true

 

由此可以简单的判断当前操作系统的版本;

 

只需要查到GetVersionExA()函数的参数类型,然后一直向上找定义,就能找到结构体类型;

10、在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memcmp来比较字符串的比较。如果对robotwork的字符串比较是成功的(当memcmp返回0),会发生什么?

  • g跳转到0x1000ff58,初看有点蒙,哪里来的memcmp();

image-20210405024950679

  • 一直往下翻;

image-20210405035201538

 

终于找到了,用Graph View看更壮观,真的好多memcmp

  • 然后开始找robotwork,终在0x1001044C处找到了;

image-20210405035937249

 

指令是个push,大概率是一个传参操作,继续向下找call

 

注意看call下面那句,明显是平栈操作,很显然memcmp使用的是__cdecl 调用方式;

  • 当然了,我们得先复习一下memcmp的功能吧;
1
2
3
4
5
6
7
8
9
10
11
// 比较两个缓冲区中的字符。
 
int memcmp( const void *buf1, const void *buf2, size_t count );
 
// Return Value
 
// 返回值指示缓冲区之间的关系。
// 返回buf1和buf2的第一个计数字节的值关系
//  < 0 buf1 小于 buf2
//  = 0 buf1 等于 buf2
//  > 0 buf1 大于 buf2

根据题意,memcmp返回0

  • 再复习一下test指令;

    • TEST 指令在两个操作数的对应位之间进行 AND 操作,并根据运算结果设置符号标志位、零标志位和奇偶标志位。
    • TEST 指令总是先清除溢出和进位标志位;
  • 所以test eax,eax等于and eax , eax也就是and 0 , 0,结果为0,ZF位置1

  • 如果ZF位为1jnz指令则不跳转;

  • 顺势执行,调用sub_100052A2函数;

image-20210405043341855

  • 通过MSDN查询,RegOpenKeyExA()函数;
1
2
3
4
5
6
7
8
9
10
11
12
// RegOpenKeyEx函数打开指定的注册表键。
 
LONG RegOpenKeyEx(
  HKEY hKey,         // handle to open key
  LPCTSTR lpSubKey,  // address of name of subkey to open
  DWORD ulOptions,   // reserved
  REGSAM samDesired, // security access mask
  PHKEY phkResult    // address of handle to open key
);
// 返回值
// 如果函数成功执行,返回值为ERROR_SUCCESS。
// 如果函数失败,返回值为WINERROR.H中定义的非零错误代码。您可以使用带有 FORMAT_MESSAGE_FROM_SYSTEM标志的FormatMessage函数来获取错误的通用描述。
  • 如果正确返回,那就应该进入0x10005309处函数;

image-20210405051018253

  • 说明它查询了路径为HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WorkTimeWorkTimes的注册表项;

  • 有意思的是看sub_100038EE处的函数;

image-20210405051611968

 

这是一个隐藏的Socket函数,所以应该是先查询注册表值,然后再发送一个网络信息;

11、PSLIST导出函数做了什么?

  • 打开Exports界面;

image-20210405052019165

  • 双击;

image-20210405052154539

 

最近的跳转是关联sub_100036C3函数的返回值;

  • 进函数瞅瞅;

image-20210405052339901

 

这函数很眼熟,是获取操作系统版本的;

 

至于25是操作系统版本号;

 

答案给出的是Windows Vista/7或是Windows XP/2003/2000;

  • 返回0x10007025处的函数查看;

image-20210405061917138

  • 继续进loc_1000704E函数;
  • loc_1000704E函数;

image-20210405053146610

  • 这里看loc_100066DFsub_100038BB;

image-20210405062057623

 

image-20210405053644298

  • 这是loc_100066DF;

image-20210405062354634

 

依旧是到了sub_100038BB;

  • 这是sub_100038BB;

image-20210405053551125

 

综上所述,这两条代码路径都使用CreateToolHelp32Snapshot函数,从字符串和API来看,是获取进程列表,通过send将进程列表通过socket发送出去;

12、使用图模式来绘制处对sub_10004E79的交叉引用图。当进入这个函数时,哪个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?

  • 在函数窗口搜索字符串sub_10004E79,双击进函数;
  • 右击进入Graph View视图;
  • 通过View -> Graphs -> User Xrefs Chart步骤,获得交叉引用图;

image-20210405063330860

  • 通过下行功能函数名称,可以命名为:send_languageID或者sendLanguageID

13、DLLMain直接调用了多少个Windows API?多少个在深度为2时被调用?

  • 先通过g键跳到0x1000D02E的DLLMain处;

  • 右击进入Graph View视图;

  • 通过View -> Graphs -> User Xrefs Chart步骤,获得交叉引用图;

image-20210405064038052

 

将红标处设置为2

  • 因为太大,只能截取部分图;

image-20210405064258528

14、在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码向后看,如果这段代码执行,这个程序会睡眠多久?

  • 先通过g键跳到0x10001358处;

  • 找到Sleep函数;

image-20210405064507452

  • MSDN找函数申明;
1
2
3
4
// Sleep函数在指定的时间间隔内挂起当前线程的执行。
VOID Sleep(
  DWORD dwMilliseconds   // sleep time in milliseconds
);
  • 由函数申明可知,只有一个参数,查看call上方的eax的值;
  • off_10019020=[This is CTI]30;
  • eax=“[T”
  • eax+=0x0Dh == “30”
  • atoi(eax)==(int)30
  • imul eax, 3E8h== eax = 30*1000=30000;
  • Sleep()参数的单位是毫秒;
  • 30000毫秒=30秒
  • 所以睡眠30秒;

15、在0x10001701处是一个对socket的调用。它的3个参数是什么?

  • 直接查MSDN,查看参数类型;
1
2
3
4
5
6
7
// Windows Sockets套接字函数创建绑定到特定服务提供程序的套接字。
 
SOCKET socket (
  int af,      
  int type,    
  int protocol 
);

16、使用MSDN页面的socket和IDA Pro中的命名符号常量,你能使参数更加有意义吗?在你应用了修改以后,参数是什么?

  • 先通过g键跳到0x10001701处;

image-20210405070724604

 

选中数字,右击选择Use Symbolic Constant,出现对话框选择OK就好了;

 

image-20210405071009941

 

这就成功了;

  • 从参数类型来看,基于IPv4的TCP链接;

17、搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware检测。这在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?

  • 有两种方式:

    • Search->Textin;
    • Search->Sequence of Bytes,搜ED;

    选择Find All Occurrences,可以列出所有匹配;

    image-20210405072059097

    选中0x564D5868hr键可以看出字符串等于VMXh

    image-20210405072826713

    看书后答案,这里涉及了反虚拟机技巧,17章的时候会有更详细的介绍;

18、将你的光标跳转到0x1001D988处,你发现了什么?

  • 先通过g键跳到0x1001D988处;

  • 猜测是一端硬编码;

19、如果你安装了IDA Python插件(包括IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的PDA Pro Python脚本,(确定光标是在0x1001D988处。)在你运行这个脚本后发生了什么?

  • 根据题意,选择菜单File -> Script File,选择随书文件中的Python脚本运行;

  • 可惜我的版本并不支持python脚本,掠过掠过;

20、将光标放在同一位置,你如何将这个数据转成一个单一的ASCII字符串?

  • 可惜我的版本并不支持python脚本,掠过掠过;

21、使用一个文本编辑器打开这个脚本。它是如何工作的?

  • 可惜我的版本并不支持python脚本,掠过掠过;

总结

看似东西不多,做起来也快,就是写笔记写帖子总是很慢很慢;

 

坚持吧,坚持总会有结果的;

 

共勉!


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-4-8 14:55 被平头猿小哥编辑 ,原因: 上传附件
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回