首页
社区
课程
招聘
[原创] 默默无闻·恶意代码分析Lab9
2021-4-17 00:16 13747

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

2021-4-17 00:16
13747

系列往期:

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

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

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

[原创]默默无闻·恶意代码分析第七章

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


工具说明

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

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

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

摘要

本章节(OllyDbg)顺承第八章(动态调试),所以本章节以OllyDbg工具为主,IDA Pro工具为辅的形式进行分析;

 

由于OllyDbg显示的更紧凑,但是IDA Pro显示的更好理解;所以我们整体截图一般使用OllyDbg视图,细节截图我们一般使用IDA Pro视图;

Lab9-1

OllyDbgIDA Pro分析恶意代码文件Lab09-01.exe,回答下列问题。在第三章中,我们使用基础的静态和动态分析技术,已经对这个恶意代码做了初步分析;

Lab9-1分析

1、首先查壳;

 

image-20210411131928308

 

无壳;

 

2、拖进IDA Pro查看导入函数;

 

太多,就不截图了。但是我们可以看见很多特性函数,比如:

  • OpenSCManagerOpenServiceA等服务函数;

  • Reg开头的注册表函数;

  • GetSet开头获取权限或者信息的函数;

  • 最后是一些WSAStartupconnectsocketsend等网络传输函数;

综上判断这个程序可能出现的操作涉及注册表以及注册服务工作,目的是进行网络传输一些数据;

 

3、shift+f12快捷键查看字符串;

  • command.comcmd.exe这种敏感指令;
  • .com.bat.cmd这种有趣的文件后缀;
  • http/1.0\r\n\r\nGET这种网络关键字;
  • 一个网址http://www.practicalmalwareanalysis.com
  • %SYSTEMOOT%\\SYSTEM32\\敏感路径;

更加确认了有网络行为,并且有远程指令的嫌疑;

 

4、分析行为;

  • 使用IDA Pro查看main()入口,再使用OllyDbg跟踪;

image-20210411143623602

  • 再使用OllyDbg打开Lab09-01.exe文件;

image-20210411150828629

 

很奇怪,这里却是0x00403896开始,是不是很奇怪?这里我们看一下右下角的调用栈:

 

image-20210411150925436

 

是的,这个状态还不是真正的Lab09-01.exemain()入口,还处在内核初始化环节;

  • 所以,我们使用F8(step-over),一路跟到call 0x00102AF0指令;

image-20210411151336289

 

如果发现跟的太快跑飞了程序,我们可以使用Ctrl+F12组合键重新开始;

  • 我们使用F7(step-into)进入Lab09-01.exe

image-20210411151916433

 

这就是进入了;

 

image-20210411152437045

 

首先,我们可以看到,再0x00402AFD处是一个CMP [ebp+argc] , 1指令(关于这个指令分析看往期帖子),这个指令是判断main()函数参数是否等于1,不等于1则跳到loc_402B1D处;

  • 因为我们没有添加参数,所以进入sub_401000处;

此处OllyDbg显示的更紧凑一些;

 

image-20210411153505828

 

此处,我们可以清晰的看到,这个函数中使用了注册表相关的函数(使用MSDN查看详情);

1
2
3
RegOpenKeyExA(...);        // 打开指定的注册表键;
RegQueryValueExa(...);    // 检索与打开的注册表项关联的指定值名的类型和数据;
CloseHandle(...);        // 关闭句柄;

可以看见,在打开注册表键函数中,使用的是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft \XPS键;

 

image-20210411154528021

 

因为我电脑的注册表项不存在,所以RegOpenKeyExA返回0x2

 

image-20210411155436420

 

注意,失败并不是返回0,详情查阅MSDN,这里是使用跟OD的方式;

  • 继续往下走判断test eax,eax,很显然,并不符合;所以只能跳转loc_401066

image-20210411155849333

 

退回0x00402B08处;

 

image-20210411161227011

 

因为在返回前执行了xor eax,eax,所以eax处是0,执行test eax,eax后,执行跳转loc_402B13,执行call sub_402410处;

  • 进行跳转看细节;

image-20210411161741604

 

首先看第一个GetModuleFileNameA函数,获取当前可执行文件的路径;

 

因为是栈内空间,所以只有执行了,我们才能知道是啥,直接OD看内存;

 

使用方法是先找到ebp地址,再减去208

 

image-20210411163304074

 

要说明的是,这个程序中使用了ebp获取参数,所以我们有理由相信这是debug版编译的程序;
如果是esp存取参数,同理寻址;

 

image-20210411163516520

  • 继续向下运行,是一个GetShortPathNameA函数(更多细节查看MSDN),这里简单理解是截取路径字符串;
1
GetShortPathName(...);// 获取指定输入路径的短路径形式。
  • 继续往下是一连串的位操作,直接使用OD跳过细节,看最近跳转函数ShellExecuteA前;

image-20210411164710509

 

image-20210411164806742

 

很显然,这是一个通过构造的字符串加上cmd.exe程序删除Lab09-01.exe(也就是删除自我)的程序;

  • 下面,我们就要给Lab09-01.exe传入参数,使它能够在运行,至少有两种方式;
    • 是给定足够参数,满足0x402AFD处的检查;
    • 修改检查注册表项的代码路径;

直接修改路径很难判断是否会对后续运行造成影响,所以先使用给定参数的方式;

  • 那么问题来了,我们应该输入什么参数呢?还是得去IDA看看反汇编;

main()函数往下看,我们会看到_mbscmp函数,这是一个字符串对比函数;

 

image-20210413191910782

 

image-20210413191948569

 

image-20210413192023850

 

image-20210413192109068

 

_mbscmp函数的上面,我们可以找到固定的字符串(这还是相对简单的);

 

我们可以双击进去,就看到字符串是什么了;

 

image-20210413193008251

 

由此可见,目前找到的这个程序的参数有四个,分别是-in-re-c-cc

先分析-in
  • 我们继续从反汇编的视角来看这个程序;

此时假设已经有一个参数传进去了

 

image-20210413195751278

 

此时我们发现程序跳转到了sub_402510

 

我们同时使用OD动态跟踪;

  • Debug->Arguments,然后在出现的对话框中输入-in;

image-20210411165625310

  • 此时,我们在OD中跟到call sub_402510,暂时不知道这个函数功能;
    我们来观察一下传入的参数;
    1
    ![image-20210413201158399](https://imagesubmit001.oss-cn-beijing.aliyuncs.com/img/image-20210413201158399.png)

此时我们有两个选择,一个是直接跳过(step-out),但是令人担心的是会不会跑飞;

 

还有一个就是进去分析一波;

  • 我们可以点进去看一下,F5看一下反汇编,还是比较清晰的,我就直接截图了;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __cdecl sub_402510(int a1)
{
  int result; // eax@2
  char v2; // [sp+4h] [bp-4h]@5
  char v3; // [sp+4h] [bp-4h]@7
 
  if ( strlen((const char *)a1) == 4 )
  {
    if ( *(_BYTE *)a1 == 97 )    // a
    {
      v2 = *(_BYTE *)(a1 + 1) - *(_BYTE *)a1;    // b
      if ( v2 == 1 )
      {
        v3 = 99 * v2;
        if ( v3 == *(_BYTE *)(a1 + 2) )            //  c
          result = (char)(v3 + 1) == *(_BYTE *)(a1 + 3);    // d
        else
          result = 0;
      }
      else
      {
        result = 0;
      }
    }
    else
    {
      result = 0;
    }
  }
  else
  {
    result = 0;
  }
  return result;
}

很显然,这是一堆位操作,我们根据函数可以很简单的判断出这是一个密码验证函数:

1
2
3
4
*(_BYTE *)a1 = a;
*(_BYTE *)(a1 + 1) = b;
*(_BYTE *)(a1 + 2) = c;
*(_BYTE *)(a1 + 3) = d;

最终得到密码是abcd

 

在这里,我们选择的是直接跳过(step-out);

  • 直接看sub_402510函数返回值:

image-20210413201726796

 

很显然,因为我们传入的并不是abcd,所以返回值是0

 

image-20210413205031610

 

我们发现因为返回值eax0,所以并不跳转,而是直接运行了sub_402410(这是自我删除函数);

  • 所以为了能够通过检查,我们直接在OD中的eax寄存器处双击修改成0x00000001;

image-20210413210307871

 

然后就成功跳转0x00402B3F
这里要提一句的是,修改要在test eax,eax语句之前,因为jnz判断是根据标志寄存器

  • 在这里,我们使用OD添加参数的方式进行跳转;

image-20210414140325813

 

我们可以看到,函数的返回值eax

 

image-20210414140511119

  • 继续往下,我们看一下loc_402B3F函数;

image-20210413214448670

 

我们可以通过OD来查看传入_mbscmp函数的参数(这个是IDA经过特征数据库识别的,虽然我不清楚识别的依据,但是在OD中没有显示出函数名,我就觉得大概率还不是原型函数);

 

image-20210413214947096

 

还是正常跳过查看返回值;

 

image-20210414141027779

 

我们发现eax返回的是0x01300000,有点奇怪,但是不打紧,我们继续往下跟;

  • OD继续往下跟,跳转到了sub_4025B0(IDA)也就是0x004025B0(OD)

image-20210414144659601

 

我们可以看到,这里获取了当前文件路径,并且截取了路径名,这大概就是sub_4025B0(IDA)函数的功能;

  • 我们直接跳过,一路往下看;

image-20210414145510646

 

通过跳转,我们可以看见一个熟悉的字段lpServiceName,所以我们可以猜测sub_402600是一个服务相关的函数;

  • 我们先来看看传入sub_402600函数的参数;

image-20210414145731429

 

这里我猜测Lab09-01就是之前截取的字符串;

  • 进入sub_402600函数的细节;

image-20210414151651512

 

首先是__alloca_probe应该是一个注册内存相关的函数,我们暂时不用管;

 

sub_4025B0根据上面的经验是一个获取文件路径并且截取的函数;

 

经过跳转,跳到了loc_402632函数;

 

在这个函数中,我们发现,有%SYSTEMROOT%\system32\字符串;

  • 再往下就是一堆字符拼接操作,我们直接使用OD跳过,至0x004026CC处;

image-20210414152254898

 

我们发现OpenSCManagerA函数传入的三个参数都是立即数,根据第七章中的知识总结[[原创]默默无闻·恶意代码分析第七章 ],我们知道它返回一个服务控制管理器句柄,所有要和服务交互的代码会调用这个函数;

 

所以我们对于这种已经确定的官方API可以跳过(但是我们可能需要知道传入的参数);

  • 直接用OD单步跳过;

image-20210414153541687

 

下面是一个OpenServiceA函数,这是打开一个服务,我们需要看一下栈,看看要打开的是什么服务;

 

image-20210414154508725

 

是这个程序;

  • 依旧是OD单步跟;

我们发现,直接跳过了跳转,来到了ChangeServiceConfig函数,这个功能用于修改服务的配置参数,我们来看一眼传参;

 

image-20210414154805054

 

再往下,我们就发现跳出了sub_402600函数;

  • 继续往下跟,我们发现从jmp short loc_402BC2跳转到jmp loc_402D76,直至跳出main函数结束;

到此我们可以判断-in参数是安装服务功能
这里有一个细节,貌似我们并没有看见CreateService函数呀,怎么就创建服务了呢?
我们再返回IDA查看,在OpenService函数的打开函数下方,有一个判断:

 

image-20210414161530508

 

也就是说,如果没有打开,我们就进入loc_40277D函数进行创建服务;
那为什么我没有进入这个函数呢?主要是因为这个程序我反复的单步太多遍,导致已经有一个叫做Lab09-01的服务了;

 

image-20210414161805520

 

这也就是为啥,每次都能打开的原因了;

 

至此,对于-in参数的功能分析到此结束

再分析-re
  • 先将OD中调试->参数,修改传入参数,然后再Ctrl+F2重启服务;

image-20210414162446920

  • 单步至call 00402AF0进入主程序开始跟;

image-20210414163013852

 

依旧是一个三步走然后进行密码检验,细节看上面对-in的分析;

  • 继续往下跟;

image-20210414163415292

  • 紧接着又是字符串匹配,没啥好看的,一路往下跟;

image-20210414163651987

  • 找到了sub_402900函数,用IDA打开看细节,除了上一个看到的,又看到了DeleteService,所以我们初步判断这是一个服务删除功能;

image-20210414164021348

  • OD跟踪运行,直到jmp loc_402C4A跳到jmp loc_402D76,又是熟悉的main函数末尾;

  • 使用service.msc查看结果;

image-20210414164408548

 

在之前的位置已经看不到Lab09-01服务了;

 

到此,对于-re参数功能的分析已经结束,我们知道这是删除服务功能

分析-c参数;

在此之前,为了我们后续的分析,我们要先将服务安装上去;
切不可在退出ODIDA后用命令行安装,别忘了,它还有自我删除的行为;

  • 第一步,更改参数;

image-20210414165053917

  • 找到00402AF0使用F7步入,至sub_402510密码校验结束;

image-20210414165349133

  • 单步跳转直至__mbscmp结束

image-20210414165605599

  • 继续跟,发现跳转至loc_402C4F

image-20210414165744648

 

使用OD可以看到,这里大概就是-c参数开始的程序段了;

 

image-20210414172037151

  • 我们先通过IDA看一下这个函数的细节;

image-20210414172255661

 

在这里,我们可以看到又四个地址跳转,我们需要一个一个的来看;

  • 首先mbscmp函数我们传入的值应该两个都是-c

image-20210414172447680

 

返回值为:

 

image-20210414172513783

 

所以0x00402CD9并不跳转;

 

下一个是cmp [ebp+argc],7,这里是判断参数是否为七个,

 

image-20210414173027701

 

这里轻微的反汇编一下就是:

1
2
3
4
5
6
if(argc != 7){
    loc_402CCF();
}
else{
    sub_401070(argv[2],argv[3],argv[4],argv[5]);
}

首先,我们要过去看看loc_402CCF是什么;

 

image-20210414173151160

 

根据上面的经验,即将跳转的是sub_402410函数是自我删除函数,这显然是不正常的;

  • 所以,我们需要再去看看sub_401070函数都做了什么;

image-20210414174206797

 

貌似是有些功能,那么我们先给OD添加上足够数量的参数;

 

image-20210414182357840

 

在这里,有小伙伴问了,这-cqwerabcd这不是六个么

 

这里要说一下main()函数的参数判断了:

1
int main(int argc,const char **argv);

如果我们使用命令行输入Lab09-01.exe -c abcd
那么参数的对应值应该是argc=3,argv={"Lab09-01.exe","-c","abcd"}
你明白了吗?(<-__<-)

  • 重新跟至00402C4F处;
  • 开始单步,就可以顺利的单步下去,直到sub_401070
  • F7进入sub_401070函数;

首先是一连串的字符串拼接,直接F5看过程;

1
2
3
4
5
6
7
8
9
10
11
memset(&Data, 0, 0x1000u);
v7 = 0;
v8 = (char *)&Data;
strcpy((char *)&Data, a1);
v8 += strlen(a1) + 1;
strcpy(v8, a2);
v8 += strlen(a2) + 1;
strcpy(v8, a3);
v8 += strlen(a3) + 1;
strcpy(v8, a4);
v8 += strlen(a4) + 1;

再继续往下看RegCreateKeyExA函数,在以前的经验中,我们直到这个函数是用来创建指定的键,如果该键已经存在于注册表中,函数将打开它;

 

我们通过IDA可以看见,SOFTWARE\\Microsoft \\XPS字符串被传进参数;

 

因为我使用了52pojie官网上的OD,插件比较多,所以可以直接在栈内看到清晰的参数;

 

image-20210414183920238

  • 不跟RegCreateKeyExA函数细节,直接F8往下走;

image-20210414184201400

 

我们可以看到,这里有一个RegSetValueExA函数,用来设置注册表键值,我们需要关注一下,传入的参数是什么;

 

image-20210414184325172

 

image-20210414184354772

 

两者相差无几;

 

最后一路F8跳出sub_401070函数

 

紧接着jmp short loc_402CD4跳到jmp loc_402D76跳出程序执行;

  • 打开注册表HKEY_LOCAL_MACHINE\SOFTWARE|Microsoft\XPS可以看到:

image-20210414184942741

 

而我们输入的参数qwer就是键值的数值;

 

到此,对于-c参数功能的分析已经结束,我们知道这是设置注册表配置键

分析-cc参数;
  • 先将OD中调试->参数,修改传入参数,然后再Ctrl+F2重启服务;

image-20210414185234726

  • 找到00402AF0使用F7步入,至sub_402510密码校验结束;
  • 继续一路跟下去,跳到loc_402BC7

image-20210414185606891

  • 跳到loc_402C4F

image-20210414185805878

  • 跳到loc_402CD9

image-20210414185915956

  • 进入loc_402CD9,我们发现这里匹配的就是参数-cc了,到这里我们就要一步一步来了;

image-20210414190348043

  • 继续向下走到call 00401280,在上面的两次跳转没有跳出去,那这个应该就是功能函数了,所以进去看看;

image-20210414190817915

 

感觉貌似跟-c没有什么区别,我们使用OD单步一下看看;

  • 我们发现跳转到loc_401309函数,概览一下,貌似又是字符串拼接;

image-20210414190945319

 

快速通过后,直接看函数返回后的状态;

 

image-20210414191555911

  • 传入的参数很显然就是拼接后的字符串,我们再看offset aKSHSPSPerS ; "k:%s h:%s p:%s per:%s\n",这个很像printf()函数的格式化;
  • 我们进入sub_402E7E看看细节;

image-20210414191927449

 

果然跟我们之前看的printf函数很像;

 

我们用命令行输出一下确认看看;

 

image-20210414220344894

 

到此,对于-cc参数功能的分析已经结束,我们知道这是设置注册表配置键打印出来

对于无参分析
  • 参数只放密码;

image-20210414223746331

  • 然后我们会发现进入了sub_401000

还记得开头我们分析到了sub_401000,在其中有一个判断是否能找到注册表项;

 

image-20210415101625727

 

所以从loc_401066跳出sub_401000

  • 继续往下运行到call_402360,进入;

F5反汇编看一下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signed int sub_402360()
{
  int v1; // eax@5
  char v2; // [sp+0h] [bp-1000h]@1
  char v3; // [sp+400h] [bp-C00h]@1
  char name; // [sp+800h] [bp-800h]@1
  char v5; // [sp+C00h] [bp-400h]@1
 
  while ( 1 )
  {
    if ( sub_401280(&v3, 1024, &name, 1024, &v2, 1024, &v5) )
      return 1;
    atoi(&v2);
    if ( sub_402020(&name) )
      break;
    v1 = atoi(&v5);
    Sleep(1000 * v1);
  }
  return 1;
}

可以看见,这是一个死循环;

 

其中通过sub_401280sub_402020还有一个在反汇编中没有体现的loc_402408跳出循环;

 

此处,我们主要把前两个函数理清楚;

  • sub_401280

    • 首先第一个注意到的函数是RegOpenKeyExA它的功能是打开指定的注册表键;

      • 因为正常打开,所以返回了0
      • 我也挺奇怪的,查了MSDN后发现;
      1
      // 如果函数失败,返回值为WINERROR.H中定义的非零错误代码。

      所以返回0反而是正常的;

    • 使用OD继续往下跟就是RegQueryValueExA函数,它的功能是检索与打开的注册表项关联的指定值名的类型和数据;

      • 同样跟上面的函数一样,如果失败则返回非0错误代码;
      • 所以因为获取成功,所以返回了0
    • 再往下跳转到了loc_401309处,依旧是我们喜闻乐见的字符串操作,直接跳过看代码;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    v12 = &Data;
    strcpy(a1, (const char *)&Data);
    v12 += strlen(a1) + 1;
    strcpy(a3, (const char *)v12);
    v12 += strlen(a3) + 1;
    strcpy(a5, (const char *)v12);
    v12 += strlen(a5) + 1;
    strcpy(a7, (const char *)v12);
    v12 += strlen(a7) + 1;

    我们可以判断,就是把注册表中的键值依次取出;

    sub_402020

    • 首先遇见的是sub_401E60处函数,进去瞅瞅;

      • 跳过第一个__alloca_probe函数就看到sub_401420F7进去看一下;

        • 我们就看到一个熟悉的sub_401280函数,我们知道它是获取注册表键值的;
        • 往下跳转到loc_401468,直至跳出;
      • 跳转到loc_401E9B

      • 行至sub_401470函数,F7进去看一下;

        • 又是一个熟悉的sub_401280,,我们知道它是获取注册表键值的;
        • 往下跳转到loc_4014C0,直至跳出;
      • 跳转至loc_401EB8

      • 行至sub_401D80函数,F7进去看一下;

        • 先是_time函数获取系统时间,因为下面用到了rand函数,所以这个应该是获取随机数种子;

        • sub_4030E0函数是给0x0040C180处赋值0x6077BBD4

        • 跳转至loc_401DB1;行至sub_401D10函数,F7进去看一下;

          • 看得出是一个循环F5看一下反汇编;
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          char sub_401D10()
          {
            char v1; // [sp+0h] [bp-4h]@1
           
            do
            {
              v1 = rand();
              if ( v1 > 127 )
                v1 -= 127;
            }
            while ( (v1 < 48 || v1 > 57) && (v1 < 97 || v1 > 122) && (v1 < 65 || v1 > 90) );
            return v1;
          }

          很显然,判断条件就是随机出来的v1不是数字&&不是小写字母&&不是大写字母);

          因为是随机的,所以看看后面是干啥用的,F8直至跳出;

          • 跳转至loc_401DA8,这是一个循环;
        • 所以我们仍然使用F5看一眼反汇编;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        int __cdecl sub_401D80(char *a1)
        {
          time_t v1; // eax@1
          signed int i; // [sp+0h] [bp-8h]@1
          signed int j; // [sp+0h] [bp-8h]@4
          signed int k; // [sp+0h] [bp-8h]@7
          char *v6; // [sp+4h] [bp-4h]@1
          char *v7; // [sp+4h] [bp-4h]@4
          char *v8; // [sp+4h] [bp-4h]@7
         
          v1 = time(0);
          sub_4030E0(v1);
          v6 = a1;
          for ( i = 0; i < 4; ++i )
            *v6++ = sub_401D10();
          *v6 = 47;
          v7 = v6 + 1;
          for ( j = 0; j < 4; ++j )
            *v7++ = sub_401D10();
          *v7 = 46;
          v8 = v7 + 1;
          for ( k = 0; k < 3; ++k )
            *v8++ = sub_401D10();
          *v8 = 0;
          return 0;
        }

        这里我们发现,这就是一个无聊的延时函数,所以我们直接使用F8直至跳出;

      • 跳转至loc_401ED7

      • 行至sub_401AF0函数,F7跳入;

        • 步进到sub_401640F7跳入;

          我们发现这是一个网络连接功能函数;

          1
          2
          3
          4
          5
          6
          7
          WSAStartup(...);
          gethostbyname(...);
          WSACleanup(...);
          socket(...);
          htons(...);
          connect(...);
          closesocket(...);

          既然知道函数功能了,我们就直接F8跳出sub_401640

          这里我们要说一下的是,因为我们电脑是断网环境,所以网络连接是失败的,下一步应该是直接跳出函数了

          此处,我们假设联网成功,使用静态分析

          • 跳转loc_401B35,可以看到是一个Get头;

          • 然后协议是HTTP/1.0\r\n\r\n

          • image-20210415125247157

            然后是一个发送指令,根据上面参数的传递,是[ebp+buf],这个就是函数传入的参数,也就是我们之前获取的注册表配置信息;

            如果发送成功,返回的应该是发送的字节数,所以失败就退出,否则跳转loc_401C18

          • loc_401C18函数中,出现了recv函数,应该是从网络上获取了数据;

            image-20210415200524233

            在这里,有一个判断回收长度是否小于等于0,如果是,则跳转loc_401CAD

            话说两头,左右我们都得看一下,我们直接使用F5看机器翻译的代码;

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            do{
              v10 = recv(s, &v6, 512, 0);
              if (v10 <= 0){
                if (v10){
                  sub_401740(&s);
                  return 1;
                }
                continue;
              }
              if ((unsigned int)(v10 + v8) > *(_DWORD *)a5){
                sub_401740(&s);
                return 1;
              }
              qmemcpy(&a4[v8], &v6, v10);
              v8 += v10;
              if (strstr(a4, asc_40C068))
                break;
            } while (v10 > 0);
             
            if (sub_401740(&s)){
              result = 1;
            }
            else{
              *(_DWORD *)a5 = v8;
              result = 0;
            }

            这里我们可以看见,重点在于sub_401740函数是做什么的,找到它,进去看一下;

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            signed int __cdecl sub_401740(SOCKET *a1)
            {
              signed int result; // eax@2
             
              if ( shutdown(*a1, 1) == -1 )
              {
                closesocket(*a1);
                WSACleanup();
                result = 1;
              }
              else
              {
                closesocket(*a1);
                WSACleanup();
                result = 0;
              }
              return result;
            }

            很显然哦,shutdown函数功能是关闭功能禁止在套接字上发送或接收;

            F8跳出sub_401740

        • 行至跳转loc_401CFC跳出sub_401AF0函数;

      • 假设获得了相应字符串,跳转loc_401F10

        image-20210416132713921

        image-20210416135503642

        我们可以看见这是一个字符串匹配函数strstr,以及即将跳转的loc_401F3D函数;

        再往下,就是sub_401E60的结束了;

        纵观sub_401E60函数,我们可以得到以下这么个结构:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        int _cdecl sub_401E60(cahr *a1,signed int a2){
            signed int result;
         
            if(sub_401420(...)){    // 获取注册表键并截取
                result = 1;
            }
            else if(sub_401470(...)){    // 获取注册表键
                result = 1;
            }
            else if(sub_401D80(...)){    // 无聊的延时
                resutl = 1;
            }
            else if(sub_401AF0(...)){    // 网络Get和recv
                result = 1;
            }
            else{
                if(a = strstr(...)){    // 字符串匹配
                    if(b = strstr(...)){
                        if(b - a + 1 <= a2){
                            memcpy(a1 , ...);    // 复制
                            a1[...] = 0;
                            result = 0;
                        }
                        else{
                            result = 1;
                        }
                    }
                    else{
                        result = 1;
                    }
                }
                else{
                    result = 1;
                }
            }
            return result;
        }

        以上就是sub_401E60函数的功能,最主要的是从网络获取字符串;

    • 如果sub_401E60网络获取字符串成功,那么就进入loc_40204C;

      一路往下总揽一下,发现又是一个if - else if结构,直接使用结构代码看情况;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      if(!strncmp(...,"SLEEP",...)){
          ...;
      }
      else if(!strncmp(...,"UPLOAD",...)){
          ...;
      }
      else if(!strncmp(...,"DOWNLOAD",...)){
          ...;
      }
      else if(!strncmp(...,"CMD",...)){
          ...;
      }
      else{
          strncmp(...,"NOTHING",...);
      }

      很显然,这就是一个后门功能;

Lab9-1分析总结

到此,应该算是大体理清楚了,看了一眼书上答案,有点迷,感觉自己还是漏了点细节;

 

还有就是关于我们查看字符串,我们可以使用xrof或者x键进行交叉引用查询,然后一路向上反向操作,这样可以有目的性的查看网络或者其它功能;

问题:

1、如何让这个恶意代码安装自身;

 

-in选项;

2、这个恶意代码的命令行选项是什么?它要求的密码是什么?

 

有四个命令选项,一个密码;密码是abcd,四个选项分别是-in-re-c-cc

3、如何利用OllyDbg永久修补这个恶意代码,使其不需要指定的命令行密码?

 

方法很多,重点是查看密码检查方式;

4、这个恶意代码基于系统的特征是什么?

 

使用了注册表项,以及安装服务;

5、这个恶意代码通过网络命令执行哪些不同操作?

 

五个SLEEPUPLOADDOWNLOADCMDNOTHING

6、这个恶意代码是否有网络特征?

 

一个网址www.practicalmalwareanalysis.comHTTP/1.0 GET请求;


Lab9-2

1、用OllyDbg分析恶意代码文件Lab09-02.exe,回答下列问题;

Lab9-2分析

1、首先查壳;

 

image-20210416145858116

 

无壳,下一步;

 

2、检查导入表;

 

首先导入动态库有两个WS2_32KERNEL32,可以看出主要还是网络操作,和一些敏感系统函数;

1
2
3
4
5
6
7
8
connect();    // WS2_32 网络连接;
gethostbyname();    // WS2_32 从主机数据库中检索与主机名对应的主机信息;
WaitForSingleObject();    // KERNEL32 多线程函数;
CreateProcessA();    // KERNEL32 创建进程;
loadLibrary();    // KERNEL32 动态库加载;
TerminateProcess();    // KERNEL32 终止所有线程;
GetCurrentProcess();    // KERNEL32 获取当前进程句柄;
WriteFile();    // KERNEL32 写操作;

3、检查字符串

 

image-20210416151705308

 

貌似暂时没看到什么有意思的敏感字符串;

 

4、分析行为;

  • 先用IDA打开,找到main()函数地址是0x00401128,用OD打开程序,找到相应点,设置软中断,一路跳到相应位置,开始分析;

image-20210416152121654

 

image-20210416152135398

  • 首先看到一连串的立即数赋值,这个就很奇怪了,怀疑是硬编码,我们可以用OD单步,然后找到内存位置,查看是什么;

    image-20210416154342040

    这是两个字符串,一个是1qaz2wsx3edc、一个是ocl.exe

  • 再往下是一个又是一个字符串;

    image-20210416154813536

    • 使用IDA查看;

    image-20210416154743146

    在这里,我们看见一个比较有意思的事情,这里是一个ComandLine[4]的字符串数组,指向的的是另外一个字符串组;

    而且我们看见了cmd这个敏感命令

  • 继续OD往下单步到GetModuleFileName函数;

    image-20210416155843047

    这是传参,直接跳过;

    • 往下看是两个__strrchr__strcmp两个函数;
    1
    2
    strrchr();    // 查找一个字符串在另一个字符串中 末次 出现的位置,并返回从字符串中的这个                //位置起,一直到字符串结束的所有字符;
    strcmp();    // 字符串匹配,没啥好说的;

    按照这样的逻辑,可以得到反汇编框架:

    1
    2
    3
    4
    5
    6
    7
    8
    char *strexe = "olc.exe"
    GetModuleFileNameA(...);
    char *strchr(...);
    if(!strcmp(strexe,strchr)){
        while(1){
            ...
        }
    }

目前知道的是通过文件路径获取文件名,如果是ocl.exe,则进入while(1)的死循环

  • 而我们因为断网问题,所以需要在检查跳转处修改判断值;

    image-20210416165352218

    image-20210416165441137

    修改了strcmp的返回值eax,就可以进入loc_0040124C

    image-20210416165602552

  • 下面就是依次的调用WSAStartupWSASocket,一路往下;

  • 继续往下,又发现一个返回值检查,一个一个的改感觉有点麻烦,先整理一下静态反汇编框架;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    while(1){
        if(WSAStartup(...))
            return 1;
        if(WSASocketA(...) == -1)
            break;
        char *name=(char *)sub_401089(...);
        if(gethostbyname(name)){    // 从主机数据库中检索与主机名对应的主机信息。
            ...
            data[] = htons(0x270);    // 0x270 是端口号:9999
            ...
            if(connect(...) == -1){
                closesocket(...);
                WSACleanup();
                Sleep(0x7530);    // 30
            }
            else{
                sub_401000(...);
                closesocket(...);
                WSACleanup();
                Sleep(0x7530);    // 30
            }
        }
        else{
            closesocket(...);
            WSACleanup();
            Sleep(0x7530);    // 30
        }
    }

    这样一看就很清晰了,我们只需要把重心放在sub_401089获取了什么字符串,和如果网络链接成功sub_401000做了什么;

  • 进入sub_401089是一堆位操作,功力不够,逆这个有点浪费时间,我直接用OD看结果了;

    image-20210416185842968

    所以在内存区找到[ebp-0x108]的位置;

    image-20210416190153044

    获得了解密字符串www.practicalmalwareanalysis.com

  • 再进入sub_401000看看到底做了啥;

    还是老样子,先看函数框架;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int _cdecl sub_401000(int a1, int a2, int a3, int a4 ,void *a5){
        memset(&struct1, 0 , 0x44);
        ...
        memset(&struct2, 0 ,0x10);
        ...
        CreateProcessA(.., ComLine,..,&struct1,&struct2);    // ComLine是cmd
        WaitForSingleObject(...);
        return 0;
    }

    由此可见,两个memset是设置两个结构体清零,IDA中有识别的命名,这个有提示意义;

    CreateProcessA运行之前对结构体进行了赋值;

Lab9-2分析总结

这个程序相较于第一个程序还是非常简单的。用书上答案来讲,这个程序创建了多个反向shell

 

还有关于一些关于CreateProcess中参数的细节,在此就不再赘述,因为病毒分析对于程序功能更加关注;

问题:

1、在二进制文件中,你看到的静态字符串是什么?

 

找到的cmd字符串;

2、当你运行这个二进制文件时,会发生什么?

 

死循环,网络连接成功后,多线程创建反向shell;

3、怎样让恶意代码的攻击负荷(payload)获得运行?

 

将程序命名为ocl.exe

4、在地址0x00401133处发生了什么?

 

有两个字符串,一个用于最后解析出网址,一个用于解析出ocl.exe,使程序实现运行;

5、传递给子例程(函数)0x00401089的参数是什么?

 

image-20210416202542745

 

选中一路网上找,可以发现,分别是1qaz2wsx3edc和一个数据缓冲区;

6、恶意代码使用的域名是什么?

 

practicalmalwareanalysis.com

7、恶意代码使用什么编码函数来混淆域名?

 

使用字符串异或加密;

8、恶意代码在0x0040106E处调用CreateProcessA函数的意义是什么?

 

通过绑定一个套接字与命令行shell来创建反向shell;


Lab9-3

使用OllyDbgIDA Pro分析恶意代码文件Lab09-03.exe。这个恶意代码加载3个自带的DLL(DLL1.dllDLL2.dllDLL3.dll),它们在编译时请求相同的内存加载位置。因此,在OllyDbg中对照IDA Pro浏览这些DLL可以发现,相同代码可能会出现在不同的内存位置。这个实验的目的是让你在使用OllyDbg看代码时可以轻松的在IDA Pro里找到它对应的位置;

Lab9-2分析

因为题意说明,这并不是重在分析的题目,所以根据问题走;

 

问题:

1、Lab09-03.exe导入了哪些DLL?

 

使用OD打开Lab09-03.exe,点击View->memory查看;

 

或者直接使用Dependency Walker

 

导入表包括了DLL2DLL1netapi32kernel32

 

对照答案后,我发现还有动态加载的dll

  • 然后我有点迷,把Lab09-03.exe拖进CFF,并没有看看见延迟加载表有所表现;

    image-20210416205924406

  • 然后拖进IDAloadLibrary函数;

    在导入表中找到函数,双击后找到定义,摁x就可以看到交叉引用;

    image-20210416210328406

    image-20210416210404552

这就找到了标准答案了;

2、DLL1.dllDLL2.dllDLL3.dll要求的基地址是多少?

 

查看基地址可以直接拖WinHex,这对于PE结构不熟悉的小伙伴不太友好,所以我是直接拖CFF的;

 

image-20210416210653545

 

image-20210416210800279

 

image-20210416210851900

3、当使用OllyDbg调试Lab09-03.exe时,为DLL1.dllDLL2.dllDLL3.dll分配的基地址是什么?

 

这个在OD中,View->Memory,这里有一个问题是要先单步到加载DLL3.dll

 

image-20210416211113211

 

image-20210416211316561

4、当Lab09-03.exe调用DLL1.dll中的一个导入函数时,这个导入函数都做了些什么?

 

image-20210416211552618

 

使用IDA打开DLL1.dll,找到Exports窗口,找到DLL1Print,双击找到函数;

 

image-20210416212033278

 

至于sub_10001038,经过验证是一个打印字符串函数;

 

综上所述,是一个打印函数;

5、当Lab09-03.exe调用WriteFile的函数时,它写入的文件名是什么?

 

image-20210416213125399

 

所以我们还是得先去看看DLL2ReturnJ返回了什么;

 

image-20210416213240603

 

双击进去;

 

image-20210416213326284

 

我们会发现这是一个未定义数据空间,我们摁x查看交叉引用;

 

image-20210416213628915

 

只有这个是赋值操作,双击进去;

 

image-20210416213756686

 

找到了,所以返回的文件名是temp.text

6、当Lab09-03.exe使用NetScheduleJobAdd创建一个job时,从哪里获取第二个参数的数据?

 

首先在Lab09-03.exe找到NetScheduleJobAdd

 

image-20210416214208954

 

往上找,谁交叉引用了[ebp+Buffer]了;

 

image-20210416215637406

 

DLL3.dll找到DLLGetStructure瞅一瞅;

 

image-20210416215856263

 

查看交叉引用;

 

image-20210416215953774

 

点进去查看;

 

image-20210416220103554

 

所以可以看见,ptr [eax], 36EE80h(实际汇编是不允许内存直接存取立即数的,这里只是为了清晰直观),意思是在以buff位置的值为地址存放0x36EE80偏移 ,并不是就是存放0x36EE80

 

这里要说一下的是,因为DLL3.dll加载时如果发生了重定位,这个偏移是会动的;

7、在运行或调试Lab09-03.exe时,你会看到Lab09-03.exe打印处三块神秘数据。DLL1.dll、的神秘数据,DLL2.dll的神秘数据,、DLL3.dll的神秘数据分别是什么?

 

都是以下的步骤:
1、先把dll拖进IDA
2、导出函数(Exports)找到打印函数;
3、双击进去,找到字符串;
4、摁x找到赋值语句,向上找赋值源;

 

直接给出答案:

  • DLL1.dll
    image-20210416234333801

    所以是当前进程标识;

  • DLL2.dll

    image-20210416234515724

    是文件句柄;

  • DLL3.dll

    找这个的时候遇到点以外,没有找到本来想找的mov,但是转念一想,函数可以,所以找了push,找到了;

    image-20210416235309090

    MultiByteToWideChar的功能是将一个字符串映射为宽字符(Unicode)字符串;

    根据MSDN,可以看见最后是把ping www.malwareanalysisbook.com的位置赋给了WideCharStr

8、如何将DLL2.dll加载到IDA Pro中,使得它与OllyDbg使用的加载地址匹配?

 

不匹配,但是可以手动调整加载到IDA Pro中的地址;

Lab9-3分析总结

这个题目相对简单,需要注意的就是关于PE中的重定位问题;

 

至于找东西,有两种思路:

 

​ 1、分析就是自上向下看过程;
​ 2、做题就是自下往上找调用,找变动;


总结

这一篇写了四五天了,期间是各种突发情况,眼看月底近了,真的是心急如焚,希望能抓紧写完,抓住银四的尾巴找到工作吧;

 

这三题,第一题写到很细,分析的有点因小失大,拘泥于字眼之间了;

 

最后用两句诗句与君共勉吧;

 

行路难,行路难,多歧路,今安在!
长风破浪会有时,直挂云帆济沧海。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-4-17 00:18 被平头猿小哥编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (4)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
0r@ngex 2021-11-17 10:58
2
0
师傅你好,我想问下在找filename的时候,是怎么寻址的呀,我还不熟悉od,怎么也无法根据ebp寻址。
雪    币: 2770
活跃值: (2863)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
平头猿小哥 2021-11-27 15:42
3
0
0r@ngex 师傅你好,我想问下在找filename的时候,是怎么寻址的呀,我还不熟悉od,怎么也无法根据ebp寻址。
filename么?下文件路径一般都是拼接而成的,文件名会是通过加密,不太好直接搜索到,我们就需要在跟程序的时候,注意内存空间的值或者注意EAX或者其它寄存器,使用OD的时候会看到字符串显示;具体的可以找两个小病毒跟一下就明白了;
雪    币: 396
活跃值: (1354)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4Chan 2021-11-27 20:38
4
0
平头师傅能给个联系方式吗?
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5yes 2023-2-9 11:18
5
0
lab-9-2最后,在内存窗口跟[ebp-0x108] 是怎么跟的,
游客
登录 | 注册 方可回帖
返回