app加固原理(一)

前言

apk正常打包后可以通過 反編譯工具使用 得到源碼米同,那這么長時間的辛苦不就白費了嗎鸯乃,這就引出一個問題了:怎么保證不讓別人不容易拿到源碼呢罕邀?

當(dāng)然得是通過加固啦,使用第三方的加固工具 (結(jié)尾給大家)蛉顽,但是作為一名熱愛學(xué)習(xí)的程序員,當(dāng)然得明白其中的原理才好先较。

app加固原理

加固原理.png
  1. 制作一個殼程序 (功能:解密和加載dex文件)
  2. 使用加密工具對原apk的dex文件進(jìn)行加密
  3. 最后重新打包携冤、對齊悼粮、簽名

實現(xiàn)

1. 制作殼程序

  • 制作殼程序,殼程序包含兩功能解密dex文件和加載dex文件曾棕,先說加載dex扣猫,

  • 解密dex文件:解壓apk包得到dex文件,然后把加密過的dex文件進(jìn)行解密

  • 那系統(tǒng)又是怎么加載dex文件呢翘地?

Android源碼目錄\libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

這里調(diào)用父類的構(gòu)造方法

Android源碼目錄\libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java

看下面這個方法

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //這里傳個名字 和 集合申尤, 就是說把某個類進(jìn)行加載 
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
}
Android源碼目錄\libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
public Class findClass(String name, List<Throwable> suppressed) {
       //通過遍歷dexElements去加載
       for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
      }
        return null;
}

從這個方法中看到,dex是通過遍歷dexElements去加載的衙耕,可以通過反射dexElements拿到已經(jīng)加載的dex文件昧穿,那我們看dexElements的初始化

  //dexElements 初始化
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
                                            suppressedExceptions);


 private static Element[] makePathElements(List<File> files, File optimizedDirectory,
                                              List<IOException> suppressedExceptions) {
............................                                              
}

那我們通過反射調(diào)用這個方法把解密后的dex文件通過makePathElements方法反射 加載進(jìn)來,再和原來的dex合并橙喘,那這個app就能運(yùn)行了时鸵。

3. 重新打包、對齊厅瞎、簽名

  1. 重新打包
    把殼程序的dex文件和加密后的文件進(jìn)行打包
  2. 對齊

zipalign -v -p 4 input_unsigned.apk output_unsigned.apk

  1. 簽名

apksigner sign --ks jks文件地址 --ks-key-alias 別名 --ks-pass pass:jsk密碼 --key-pass pass:別名密碼 --out out.apk in.apk

代碼實現(xiàn)

殼程序

  • 加密算法
    public class AES {
    
      //16字節(jié)
      public static final String DEFAULT_PWD = "abcdefghijklmnop";
      //填充方式
      private static final String algorithmStr = "AES/ECB/PKCS5Padding";
      private static Cipher encryptCipher;
      private static Cipher decryptCipher;
    
      public static void init(String password) {
          try {
              // 生成一個實現(xiàn)指定轉(zhuǎn)換的 Cipher 對象饰潜。
              encryptCipher = Cipher.getInstance(algorithmStr);
              decryptCipher = Cipher.getInstance(algorithmStr);// algorithmStr
              byte[] keyStr = password.getBytes();
              SecretKeySpec key = new SecretKeySpec(keyStr, "AES");
              encryptCipher.init(Cipher.ENCRYPT_MODE, key);
              decryptCipher.init(Cipher.DECRYPT_MODE, key);
          } catch (NoSuchAlgorithmException e) {
              e.printStackTrace();
          } catch (NoSuchPaddingException e) {
              e.printStackTrace();
          } catch (InvalidKeyException e) {
              e.printStackTrace();
          }
      }
    
      public static byte[] encrypt(byte[] content) {
          try {
              byte[] result = encryptCipher.doFinal(content);
              return result;
          } catch (IllegalBlockSizeException e) {
              e.printStackTrace();
          } catch (BadPaddingException e) {
              e.printStackTrace();
          }
          return null;
      }
    
      public static byte[] decrypt(byte[] content) {
          try {
              byte[] result = decryptCipher.doFinal(content);
              return result;
          } catch (IllegalBlockSizeException e) {
              e.printStackTrace();
          } catch (BadPaddingException e) {
              e.printStackTrace();
          }
          return null;
        }
    }
    
  • 解壓和壓縮

public class Zip {

    private static void deleteFile(File file){
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File f: files) {
                deleteFile(f);
            }
        }else{
            file.delete();
        }
    }

    /**
     * 解壓zip文件至dir目錄
     * @param zip
     * @param dir
     */
    public static void unZip(File zip, File dir) {
        try {
            deleteFile(dir);
            ZipFile zipFile = new ZipFile(zip);
            //zip文件中每一個條目
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            //遍歷
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                //zip中 文件/目錄名
                String name = zipEntry.getName();
                //原來的簽名文件 不需要了
                if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || name
                        .equals("META-INF/MANIFEST.MF")) {
                    continue;
                }
                //空目錄不管
                if (!zipEntry.isDirectory()) {
                    File file = new File(dir, name);
                    //創(chuàng)建目錄
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                    //寫文件
                    FileOutputStream fos = new FileOutputStream(file);
                    InputStream is = zipFile.getInputStream(zipEntry);
                    byte[] buffer = new byte[2048];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        fos.write(buffer, 0, len);
                    }
                    is.close();
                    fos.close();
                }
            }
            zipFile.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 壓縮目錄為zip
     * @param dir 待壓縮目錄
     * @param zip 輸出的zip文件
     * @throws Exception
     */
    public static void zip(File dir, File zip) throws Exception {
        zip.delete();
        // 對輸出文件做CRC32校驗
        CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(
                zip), new CRC32());
        ZipOutputStream zos = new ZipOutputStream(cos);
        //壓縮
        compress(dir, zos, "");
        zos.flush();
        zos.close();
    }

    /**
     * 添加目錄/文件 至zip中
     * @param srcFile 需要添加的目錄/文件
     * @param zos   zip輸出流
     * @param basePath  遞歸子目錄時的完整目錄 如 lib/x86
     * @throws Exception
     */
    private static void compress(File srcFile, ZipOutputStream zos,
                                 String basePath) throws Exception {
        if (srcFile.isDirectory()) {
            File[] files = srcFile.listFiles();
            for (File file : files) {
                // zip 遞歸添加目錄中的文件
                compress(file, zos, basePath + srcFile.getName() + "/");
            }
        } else {
            compressFile(srcFile, zos, basePath);
        }
    }

    private static void compressFile(File file, ZipOutputStream zos, String dir)
            throws Exception {
        // temp/lib/x86/libdn_ssl.so
        String fullName = dir + file.getName();
        // 需要去掉temp
        String[] fileNames = fullName.split("/");
        //正確的文件目錄名 (去掉了temp)
        StringBuffer sb = new StringBuffer();
        if (fileNames.length > 1){
            for (int i = 1;i<fileNames.length;++i){
                sb.append("/");
                sb.append(fileNames[i]);
            }
        }else{
            sb.append("/");
        }
        //添加一個zip條目
        ZipEntry entry = new ZipEntry(sb.substring(1));
        zos.putNextEntry(entry);
        //讀取條目輸出到zip中
        FileInputStream fis = new FileInputStream(file);
        int len;
        byte data[] = new byte[2048];
        while ((len = fis.read(data, 0, 2048)) != -1) {
            zos.write(data, 0, len);
        }
        fis.close();
        zos.closeEntry();
    }

}
  • 工具類


public class Utils {

    /**
     * 讀取文件
     *
     * @param file
     * @return
     * @throws Exception
     */
    public static byte[] getBytes(File file) throws Exception {
        RandomAccessFile r = new RandomAccessFile(file, "r");
        byte[] buffer = new byte[(int) r.length()];
        r.readFully(buffer);
        r.close();
        return buffer;
    }

    /**
     * 反射獲得 指定對象(當(dāng)前-》父類-》父類...)中的 成員屬性
     *
     * @param instance
     * @param name
     * @return
     * @throws NoSuchFieldException
     */
    public static Field findField(Object instance, String name) throws NoSuchFieldException {
        Class clazz = instance.getClass();
        //反射獲得
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(name);
                //如果無法訪問 設(shè)置為可訪問
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                return field;
            } catch (NoSuchFieldException e) {
                //如果找不到往父類找
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }


    /**
     * 反射獲得 指定對象(當(dāng)前-》父類-》父類...)中的 函數(shù)
     *
     * @param instance
     * @param name
     * @param parameterTypes
     * @return
     * @throws NoSuchMethodException
     */
    public static Method findMethod(Object instance, String name, Class... parameterTypes)
            throws NoSuchMethodException {
        Class clazz = instance.getClass();
        while (clazz != null) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }
                return method;
            } catch (NoSuchMethodException e) {
                //如果找不到往父類找
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList
                (parameterTypes) + " not found in " + instance.getClass());
    }

    // 所有文件md5總和
    private static String fileSum = "";

    /**
     *
     * @param file
     * @param suffix
     * @return
     */
    public static String traverseFolder(File file, String suffix) {

        if (file == null) {
            throw new NullPointerException("遍歷路徑為空路徑或非法路徑");
        }

        if (file.exists()) { //判斷文件或目錄是否存在

            File[] files = file.listFiles();

            if (files.length == 0) { // 文件夾為空
                return null;
            } else {
                for (File f : files) { // 遍歷文件夾

                    if (f.isDirectory()) { // 判斷是否是目錄

                        if ((f.getName().endsWith(suffix))) { // 只小羊.dex 結(jié)尾的目錄 則計算該目錄下的文件的md5值

                            // 遞歸遍歷
                            traverseFolder(f, suffix);
                        }

                    } else {
                        // 得到文件的md5值
                        String string = checkMd5(f);
                        // 將每個文件的md5值相加
                        fileSum += string;
                    }
                }
            }

        } else {
            return null; // 目錄不存在
        }

        return fileSum; // 返回所有文件md5值字符串之和
    }

    /**
     * 計算文件md5值
     * 檢驗文件生成唯一的md5值 作用:檢驗文件是否已被修改
     *
     * @param file 需要檢驗的文件
     * @return 該文件的md5值
     */
    private static String checkMd5(File file) {

        // 若輸入的參數(shù)不是一個文件 則拋出異常
        if (!file.isFile()) {
            throw new NumberFormatException("參數(shù)錯誤!請輸入校準(zhǔn)文件和簸。");
        }

        // 定義相關(guān)變量
        FileInputStream fis = null;
        byte[] rb = null;
        DigestInputStream digestInputStream = null;
        try {
            fis = new FileInputStream(file);
            MessageDigest md5 = MessageDigest.getInstance("md5");
            digestInputStream = new DigestInputStream(fis, md5);
            byte[] buffer = new byte[4096];

            while (digestInputStream.read(buffer) > 0) ;

            md5 = digestInputStream.getMessageDigest();
            rb = md5.digest();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < rb.length; i++) {
            String a = Integer.toHexString(0XFF & rb[i]);
            if (a.length() < 2) {
                a = '0' + a;
            }
            sb.append(a);
        }
        return sb.toString(); //得到md5值
    }
}

  • application

public class ProxyApplication extends Application {
    //定義好解密后的文件的存放路徑
    private String app_name;
    private String app_version;

    /**
     * ActivityThread創(chuàng)建Application之后調(diào)用的第一個方法
     * 可以在這個方法中進(jìn)行解密彭雾,同時把dex交給android去加載
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        //獲取用戶填入的metadata
        getMetaData();
        //得到當(dāng)前加密了的APK文件
        File apkFile = new File(getApplicationInfo().sourceDir);
        //把a(bǔ)pk解壓   app_name+"_"+app_version目錄中的內(nèi)容需要boot權(quán)限才能用
        File versionDir = getDir(app_name + "_" + app_version, MODE_PRIVATE);
        File appDir = new File(versionDir, "app");
        File dexDir = new File(appDir, "dexDir");

        //得到我們需要的加載的Dex文件
        List<File> dexFiles = new ArrayList<>();
        //進(jìn)行解密(最好做MD5文件校驗)
        if (!dexDir.exists() || dexDir.listFiles().length == 0) {
            //把a(bǔ)pk解壓到appDir
            Zip.unZip(apkFile, appDir);
            //獲取目錄下的所有的文件
            File[] files = appDir.listFiles();
            for (File file : files) {
                String name = file.getName();
                if (name.endsWith(".dex") && !TextUtils.equals(name, "classes.dex")) {

                    try {
                        AES.init(AES.DEFAULT_PWD);
                        //讀取文件內(nèi)容
                        byte[] bytes = Utils.getBytes(file);
                        //解密
                        byte[] decrypt = AES.decrypt(bytes);
                        //寫到指定的目錄
                        FileOutputStream fos = new FileOutputStream(file);
                        fos.write(decrypt);
                        fos.flush();
                        fos.close();
                        dexFiles.add(file);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        } else {
            for (File file : dexDir.listFiles()) {
                dexFiles.add(file);
            }
        }

        try {
            //2.把解密后的文件加載到系統(tǒng)
            loadDex(dexFiles, versionDir);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void loadDex(List<File> dexFiles, File versionDir) {


        try {
            //1.獲取pathlist
            Field   pathListField = Utils.findField(getClassLoader(), "pathList");
            Object  pathList = pathListField.get(getClassLoader());

            //2.獲取數(shù)組dexElements
            Field dexElementsField=Utils.findField(pathList,"dexElements");
            Object[] dexElements=(Object[])dexElementsField.get(pathList);
            //3.反射到初始化dexElements的方法
            Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);

            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);

            //合并數(shù)組
            Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);
            System.arraycopy(dexElements,0,newElements,0,dexElements.length);
            System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);

            //替換classloader中的element數(shù)組
            dexElementsField.set(pathList,newElements);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    private void getMetaData() {
        try {
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
                    getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            if (null != metaData) {
                if (metaData.containsKey("app_name")) {
                    app_name = metaData.getString("app_name");
                }
                if (metaData.containsKey("app_version")) {
                    app_version = metaData.getString("app_version");
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

加固工具

  • 加密算法、解壓和壓縮和工具類 上面的一樣比搭,這里就不貼代碼了
public class Main {

    public static void main(String[] args) throws Exception {
        /**
         * 1.制作只包含解密代碼的dex文件
         */
        File aarFile = new File("proxy_core/build/outputs/aar/proxy_core-debug.aar");
        File aarTemp = new File("proxy_tools/temp");
        Zip.unZip(aarFile,aarTemp);
        File classesJar = new File(aarTemp, "classes.jar");
        File classesDex = new File(aarTemp, "classes.dex");
//
//        //dx --dex --output out.dex in.jar
        Process process = Runtime.getRuntime().exec("cmd /c dx --dex --output " + classesDex.getAbsolutePath()
                + " " + classesJar.getAbsolutePath());
        process.waitFor();
        if (process.exitValue() != 0) {
            throw new RuntimeException("dex error");
        }

        /**
         * 2.加密APK中所有的dex文件
         */
        File apkFile=new File("app/build/outputs/apk/debug/app-debug.apk");
        File apkTemp = new File("app/build/outputs/apk/debug/temp");
        //解壓
        Zip.unZip(apkFile, apkTemp);
        //只要dex文件拿出來加密
        File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });
        //AES加密了
        AES.init(AES.DEFAULT_PWD);
        for (File dexFile : dexFiles) {
            byte[] bytes = Utils.getBytes(dexFile);
            byte[] encrypt = AES.encrypt(bytes);
            FileOutputStream fos = new FileOutputStream(new File(apkTemp,
                    "secret-" + dexFile.getName()));
            fos.write(encrypt);
            fos.flush();
            fos.close();
            dexFile.delete();
        }

        /**
         * 3.把dex放入apk解壓目錄冠跷,重新壓成apk文件
         */
        classesDex.renameTo(new File(apkTemp,"classes.dex"));
        File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk");
        Zip.zip(apkTemp,unSignedApk);
//
//
//        /**
//         * 4.對齊和簽名
//         */
//        zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
        File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");
        process = Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 " + unSignedApk.getAbsolutePath()
                + " " + alignedApk.getAbsolutePath());
        process.waitFor();
//        if(process.exitValue()!=0){
//            throw new RuntimeException("dex error");
//        }
//
//
////        apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk
////        apksigner sign  --ks jks文件地址 --ks-key-alias 別名 --ks-pass pass:jsk密碼 --key-pass pass:別名密碼 --out  out.apk in.apk
        File signedApk = new File("app/build/outputs/apk/debug/app-signed-aligned.apk");
        File jks = new File("proxy_tools/proxy2.jks");
        process = Runtime.getRuntime().exec("cmd /c apksigner sign --ks " + jks.getAbsolutePath()
                + " --ks-key-alias 123 --ks-pass pass:123456 --key-pass pass:123456 --out "
                + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath());
        process.waitFor();
//        if(process.exitValue()!=0){
//            throw new RuntimeException("dex error");
//        }
        System.out.println("執(zhí)行成功");

    }
}

GitHub代碼

第三方的加固工具

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市身诺,隨后出現(xiàn)的幾起案子蜜托,更是在濱河造成了極大的恐慌,老刑警劉巖霉赡,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橄务,死亡現(xiàn)場離奇詭異,居然都是意外死亡穴亏,警方通過查閱死者的電腦和手機(jī)蜂挪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗓化,“玉大人棠涮,你說我怎么就攤上這事〈谈玻” “怎么了严肪?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我驳糯,道長篇梭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任酝枢,我火速辦了婚禮恬偷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帘睦。我一直安慰自己袍患,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布官脓。 她就那樣靜靜地躺著协怒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卑笨。 梳的紋絲不亂的頭發(fā)上孕暇,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音赤兴,去河邊找鬼妖滔。 笑死,一個胖子當(dāng)著我的面吹牛桶良,可吹牛的內(nèi)容都是我干的座舍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼陨帆,長吁一口氣:“原來是場噩夢啊……” “哼曲秉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疲牵,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤承二,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纲爸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亥鸠,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年识啦,在試婚紗的時候發(fā)現(xiàn)自己被綠了负蚊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡颓哮,死狀恐怖家妆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冕茅,我是刑警寧澤伤极,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布腰鬼,位于F島的核電站,受9級特大地震影響塑荒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姜挺,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一齿税、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炊豪,春花似錦凌箕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缺虐,卻和暖如春芜壁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背高氮。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工慧妄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剪芍。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓塞淹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親罪裹。 傳聞我的和親對象是個殘疾皇子饱普,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 聲明:原創(chuàng)文章,轉(zhuǎn)載請備注來源:http://shuwoom.com/?p=360&preview=true 10...
    空同定翁閱讀 2,254評論 0 6
  • Base64.java public final class Base64 { static private ...
    BUG弄潮兒閱讀 782評論 0 0
  • Java字節(jié)碼詳解(二)字節(jié)碼的運(yùn)行過程 2018年10月23日 17:31:04 talex 閱讀數(shù) 677 文...
    呵呵_9e25閱讀 184評論 0 0
  • 山雞哥來啦 素萬獨家冠名哦 看到素萬小U和靠墊了嗎 29738213
    94f1d6bd2d12閱讀 198評論 0 0
  • 教育既要看過程更要看結(jié)果状共,當(dāng)溫和的過程沒有效果時套耕,我會改變策略,就像今晚口芍,我們又回到了以前那個熟悉的不能再熟悉場面...
    九五自尊閱讀 178評論 0 0