Skip to content

mal快速入门

Intro

IoM 基于lua5.1以及gopher-lua实现了一套复杂但强大的插件系统 mals

  • 一组类似AggressiveScript的简化api
  • 一组全量功能的原始grpc api
  • 一系列方便的内置lua库

lua 是一门非常简单的脚本语言, 阅读几个示例后读者应该就可以直接上手使用, 几乎不需要看lua的相关文档, 如果你准备从零开始编写插件, 推荐使用vscode+sumneko插件

基础使用

Hello World

编写hello.lua

print("hello world") -- 在client的标准输出中打印hello world

broadcast("hello world") -- 在所有的client中打印hello world

notify("hello world") -- 在所有client中打印, 并且如果配置了第三方通知接口(飞书,tg,微信) 则同时会在向第三方发送通知

为了让这个插件能被正确加载, 我们还需要编写 mal.yaml 让IoM可以识别.

name: hello
type: lua
author: M09Ic
version: v0.0.1
entry: hello.lua

将这两个文件都放在插件的默认保存目录下即可

windows: %USERPROFILE%/.config/malice/mals/hello/ linux: ~/.config/malice/mals/hello

mal load hello

分发插件

社区仓库 中的插件都通过zip包分发.

也可以将刚刚的hello.luamal.yaml 打包成zip, 然后命令安装

mal install hello.zip

注册命令

通过command函数, 可以将lua的函数注册到IoM client的命令中。

如下将举一些常见的例子:

基本语法

command(name, function, help, ttp)
  • name: 命令名称,支持多级命令(用:分隔)
  • function: 执行的lua函数
  • help: 命令帮助信息
  • ttp: MITRE ATT&CK TTP编号

详细文档请见:https://wiki.chainreactors.red/IoM/manual/mal/builtin/#command

基础用法

简单命令注册

-- print hello world
local function hello()
    print("hello world")
end

command("hello", hello, "print hello world", "T1000")
在客户端执行hello命令时,将调用hello函数并输出"hello world"。

多级命令注册

在复杂场景中, 例如mimikatz 拥有多个常见用法 logonpasswords tickets 。 我们需要其注册为mimikatz的子命令。

local function logonpasswords()
    ...
end

local function tickets()
    ...
end

command("mimikatz:logonpasswords", logonpasswords, "logonpasswords", "T1000")
command("mimikatz:tickets", logonpasswords, "logonpasswords", "T1000")

: 表示子命令的分隔符, 可以添加任意层级的子命令

command("mimikatz:common:hello", hello, "print hello world", "T1000")

参数处理

在命令行程序中,通常将命令行的输入分为:

  • args, hello arg1 arg2,
  • flags, POSIX中分为 long|short flag . hello -shello --long
  • cmdline, 表示完整的命令行字符串

在IoM中还有一类特殊的用法。 我们可以直接传入cobra(一个被广泛使用的命令行库)的cmd 对象对象。 用来手动解析flag和arg。

通过这些方式,IoM mal提供了接近golang原生的命令行控制能力, 包括注册、解析甚至自动补全的能力。

args

local function print_args(args)
    print(args[1])
    print(args[2])
end

command("hello", print_args, "print args", "T1000")
执行hello arg1 arg2时,将调用print_args函数,输出arg1和arg2。注意Lua数组索引从1开始。

arg_<number>

local function print_args(arg_1, arg_2, arg_3)
    print(arg_1)
    print(arg_2)
    print(arg_3)
end

command("hello", print_args, "print args", "T1000")
执行hello arg1 arg2 arg3时,将调用print_args函数,分别输出三个参数。此方式适用于参数数量固定且较少的场景。

flags

local function print_flags(cmd)
    local name = cmd:Flags():GetString("name")
    print(name)
end

local cmd = command("hello", print_flags, "print flags", "T1000")
cmd:Flags():String("name", "", "the name to print")
执行hello --name flag1时,将调用print_flags函数,输出flag1。此方式适用于参数较多或需要精确控制的场景。

flag_<name>自动注册

local function print_flags(flag_name)
    local name = flag_name
    print(name)
end

local cmd = command("hello", print_flags, "print flags", "T1000")
使用flag_<name>格式的参数会自动注册为标志参数。执行hello --name flag1时,将调用print_flags函数,输出flag1。此方式代码更简洁,适用于参数较少的场景。

cmdline

local function print_cmdline(cmdline)
    print(cmdline)
end

command("hello", print_cmdline, "print cmdline", "T1000")
cmdline参数会将所有命令行参数以空格分隔拼接为字符串。执行hello arg1 arg2 arg3时,将输出arg1 arg2 arg3。

cmd

辅助函数

当然你也可以让这个命令更加丰富。

  • help, help("hello", "a description for this command") , 添加long helper
  • example, example("hello", "a example for this command") 添加命令行exmaple
  • opsec, opsec("hello", 9.8) , 添加OPSEC 评分

添加compleler 自动补全, 我们提供了多组场景的自动补全参数

示例:

local rem_socks_cmd = command("rem_community:socks5", run_socks5, "serving socks5 with rem", "T1090")

bind_args_completer(rem_socks_cmd, { rem_completer() })

标准&内置库

mal package

目前mal已经实现了数百个api, 为了更好的管理与分类这些api. 我们将这些api分到三个package中.

  • builtin, 直接在当前上下文可用, mal相关核心api
  • rpc, grpc相关api的lua实现
  • beacon, 对CobaltStrike的兼容层api. 实现了大量与aggressive scirpt中b开头函数等价的api

lua 标准库

mal lua中同样支持lua标准库, 相关文档可以查阅 lua5.1文档. 关于异步/并发的文档可以查阅 https://github.com/yuin/gopher-luahttp://godoc.org/github.com/yuin/gopher-lua

  • package
  • table
  • io
  • os
  • string
  • math
  • debug
  • channel
  • coroutine

lua 拓展库

为了更方便的使用,我们已经导入了许多常用工具包。

当前添加的拓展库:

你可以通过 require 引入对应的依赖,每个仓库的README中都有较为详细的使用说明

local crypto = require("crypto")

高级用法

beacon package

beacon package 是否按照CobaltStrike的aggressive script的api签名封装的一套接口, 用来提供类似aggressive script 编写的体验。

local beacon = require("beacon")

beacon.bexecute(active(), "whoami")

目前支持的所有aggressive script 风格的api文档请见: https://chainreactors.github.io/wiki/IoM/manual/mal/beacon/

new protobuf message

在builtin和beacon 包中, 绝大多数api都是高度封装的, 并不需要过多关注底层的GRPC接口调用。

但如果要调用 rpc 包中的接口,就需要手动创建对应的protobuf message.

mals已经将所有的protobuf message都注册到了lua中, 并添加了必要的接口.

local bin = ExecuteBinary.New()
bin.Name = "execute_assembly"
bin.Args = {"whoami"}
bin.Bin = read_resource("example.exe")

local bin = ExecuteBinary.New({
    Name = "execute_assembly",
    Bin = read_resource("example.exe"),
    Type = "example_type",
    Args = {"whoami"}
})

动态new protobuf message

mals添加了ProtobufMessage 作为通用的反射获取package name的

local msg = ProtobufMessage.New("modulepb.ExecuteBinary", {
    Name = "execute_assembly",
    Bin = read_resource("example.exe"),
    Type = "example_type",
    Args = {"whoami"}
})

调用rpc命令

function load_rem()
     local rpc = require("rpc")

     local task = rpc.LoadRem(active():Context(), ProtobufMessage.New("modulepb.Request", {
         Name = "load_rem",
         Bin = read_resource("chainreactors/rem.dll"),
     }))
     wait(task)
end

注册为库

mals允许用户自行实现的插件作为类库,成为其他库的依赖。

首先, 需要将lib设置为ture

name: community-lib
type: lua
author: M09Ic
version: v0.0.1
entry: main.lua
lib: true
depend_module: 

你的lib需参考如下写法

-- community-lib/main.lua

local lib = {}

function lib.demo()
    print("this is a lib demo")
end

return lib

当你需要在其他插件中使用这个库时, 需要通过require等调用

-- community-other/main.lua

local clib = require("common-lib")
clib.demo()