TinyShell后门通信模型剖析
通过对TinyShell后门的外联通信函数进行剖析,梳理其通信过程如下:
- 调用gettimeofday函数及getpid函数获取当前时间tv及进程pid,将tv和pid作为SHA1算法的输入,生成得到20字节的IV1数据
- 调用gettimeofday函数及pid++获取tv及pid,将tv和pid作为SHA1算法的输入,生成得到20字节的IV2数据
- 使用socket套接字发送40字节的IV1+IV2数据
- 提取内置的key字符串信息(tsh.h文件中的secret数据)
- 初始化发送数据及接收数据的session key信息
- 将key字符串信息及IV1数据作SHA1运算,取16字节作为发送数据时AES算法的AES key
- 将key字符串信息及IV2数据作SHA1运算,取16字节作为接收数据时AES算法的AES key
- 取IV1数据的前16字节作为第一次发送数据时的AES iv
- 取IV2数据的前16字节作为第一次接收数据时的AES iv
- 控制端与被控端相互发送内置的challenge数据(
\x58\x90\xAE\x86\xF1\xB9\x1C\xF6\x29\x83\x95\x71\x1D\xDE\x58\x0D
),用于校验通信是否建立成功 - 发送数据时,每16字节进行一次AES运算,前16字节的加密结果将作为第二段数据加密的IV值
- 通信数据结构
- 2字节数据:后续实际载荷长度
- 实际载荷数据
实际通信数据包截图如下:
实际通信数据包案例剖析:
#************第一个通信数据包 tsh -> tshd b1180d0d0b5c3cd49366af469c313586e29bd96111bae610609d82c733f8b10767e4b0627570ce5f b1180d0d0b5c3cd49366af469c313586e29bd961 #IV1 11bae610609d82c733f8b10767e4b0627570ce5f #IV2 b1180d0d0b5c3cd49366af469c313586 #初始send_aes_iv 11bae610609d82c733f8b10767e4b062 #初始recv_aes_iv #************计算send_aes_key #74696E797368656C6C对应内置的key字符串信息tinyshell 74696E797368656C6C + b1180d0d0b5c3cd49366af469c313586e29bd961 a13e91013d7f4a3ef3a149467af680262c45b141 #SHA1运算结果 a13e91013d7f4a3ef3a149467af68026 #send_aes_key #************计算recv_aes_key 74696E797368656C6C + 11bae610609d82c733f8b10767e4b0627570ce5f 0813d1deba152c1d2c6dc774293c18ff86f3239b #SHA1运算结果 0813d1deba152c1d2c6dc774293c18ff #recv_aes_key #************第二个通信数据包 tsh -> tshd 4ff6ac2d77894dbe5355e773c0a6216c8b6fba90ac890c125424c9f3bce4c021cbd0eefd4ea658a45ecfcd49a4efa95177da55e8 00105890ae86f1b91cf6298395711dde580d #AES解密数据 0010 #载荷长度 5890ae86f1b91cf6298395711dde580d #内置的challenge校验数据 #************第三个通信数据包 tshd -> tsh 16516b60fd4b37c540ec7ad3902830c8332393f5a22187147c7cc72e60943c0e36b8a4851cdae3b5058b56af7eed56533743930c 00105890ae86f1b91cf6298395711dde580d #AES解密数据 0010 #载荷长度 5890ae86f1b91cf6298395711dde580d #内置的challenge校验数据
模拟构建通信解密程序
为了更便利的对TinyShell后门的通信数据进行解密,笔者尝试编写了一个解密程序,可对TinyShell后门通信数据进行批量解密,解密效果如下:
自动化脚本输入文件格式如下:(备注:直接从wireshark导出”C Arrays“数据即可)
代码实现
代码结构截图如下
- main.go
-
package main import ( "awesomeProject5/common" "encoding/hex" "fmt" "io/ioutil" "strings" ) func main() { key := "tinyshell" // 读取文件的所有内容 content, err := ioutil.ReadFile("C:\\Users\\admin\\Desktop\\1.txt") if err != nil { fmt.Println("Error reading file:", err) return } data := string(content) data = strings.ReplaceAll(data, "\n0x", "0x") datas := strings.Split(data, "\n") lable := strings.Split(datas[0], "_")[0] //fmt.Println(lable) firstdata := strings.ReplaceAll(strings.Split(strings.Split(datas[0], " };")[0], "*/0x")[1], ", 0x", "") firstdata_hex, _ := hex.DecodeString(firstdata) if len(firstdata_hex) == 40 { send_aes_key := []byte{} recv_aes_key := []byte{} send_iv := []byte{} recv_iv := []byte{} send_aes_iv := []byte{} recv_aes_iv := []byte{} send_iv = append(send_iv, firstdata_hex[:20]...) recv_iv = append(recv_iv, firstdata_hex[20:]...) send_aes_iv = append(send_aes_iv, send_iv[:16]...) recv_aes_iv = append(recv_aes_iv, recv_iv[:16]...) send_aes_key = append(send_aes_key, common.CalculateSHA1(append([]byte(key), send_iv...))[:16]...) recv_aes_key = append(recv_aes_key, common.CalculateSHA1(append([]byte(key), recv_iv...))[:16]...) fmt.Println("send_aes_key:", hex.EncodeToString(send_aes_key)) fmt.Println("recv_aes_key:", hex.EncodeToString(recv_aes_key)) fmt.Println("send_aes_iv:", hex.EncodeToString(send_aes_iv)) fmt.Println("recv_aes_iv:", hex.EncodeToString(recv_aes_iv)) decryptedText := []byte{} for _, str := range datas[1:] { if str == "" { break } buf := strings.ReplaceAll(strings.Split(strings.Split(str, " };")[0], "*/0x")[1], ", 0x", "") hex_buf, _ := hex.DecodeString(buf) if strings.HasPrefix(str, lable) { fmt.Println("************send************") fmt.Println("Raw Data:", hex.EncodeToString(hex_buf)) common.Decrypt_msg(hex_buf, send_aes_key, &send_aes_iv) } else if strings.HasPrefix(str, "char peer") { fmt.Println("************recv************") fmt.Println("Raw Data:", hex.EncodeToString(hex_buf)) common.Decrypt_msg(hex_buf, recv_aes_key, &recv_aes_iv) fmt.Println(hex.EncodeToString(decryptedText)) fmt.Println(string(decryptedText)) } } } }
- common.go
package common import ( "crypto/aes" "crypto/cipher" "crypto/sha1" "encoding/hex" "fmt" ) func Decrypt_msg(ciphertext []byte, aeskey []byte, aes_iv *[]byte) { total_len := 0 blk_len := 0 for { //前两字节为buffer长度 tmp := []byte{} tmp = append(tmp, ciphertext[total_len:total_len+16]...) output, _ := DecryptAES(ciphertext[total_len:total_len+16], aeskey, *aes_iv) *aes_iv = tmp plaintext_len := (int(output[0]) << 8) + int(output[1]) if (plaintext_len+2)%16 > 0 { blk_len = ((plaintext_len+2)/16+1)*16 + 20 total_len = total_len + blk_len } else { blk_len = ((plaintext_len+2)/16)*16 + 20 total_len = total_len + blk_len } if blk_len > 0x24 { aa := decrypt_pel_msg(ciphertext[total_len-blk_len+16:total_len-20], aeskey, aes_iv) output = append(output, aa...) buffer := []byte{} buffer = append(buffer, output[2:plaintext_len+2]...) fmt.Println("hex:", hex.EncodeToString(buffer)) fmt.Println("string:", string(buffer)) } else { buffer := []byte{} buffer = append(buffer, output[2:plaintext_len+2]...) fmt.Println("hex:", hex.EncodeToString(buffer)) fmt.Println("string:", string(buffer)) } if len(ciphertext) == total_len { return } } } func decrypt_pel_msg(ciphertext []byte, aeskey []byte, aes_iv *[]byte) (plaintext []byte) { if len(ciphertext)%16 == 0 { blocks := len(ciphertext) / 16 buffer := []byte{} for i := 0; i < blocks; i++ { tmp := []byte{} tmp = append(tmp, ciphertext[16*i:16*(i+1)]...) output, _ := DecryptAES(ciphertext[16*i:16*(i+1)], aeskey, *aes_iv) *aes_iv = tmp buffer = append(buffer, output...) } plaintext = append(plaintext, buffer...) } return } func DecryptAES(ciphertext, key, iv []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(ciphertext) < aes.BlockSize { return nil, fmt.Errorf("ciphertext too short") } if len(ciphertext)%aes.BlockSize != 0 { return nil, fmt.Errorf("ciphertext is not a multiple of the block size") } mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(ciphertext, ciphertext) return ciphertext, nil } func CalculateSHA1(input []byte) []byte { hasher := sha1.New() hasher.Write(input) hashBytes := hasher.Sum(nil) return hashBytes }