ProGuard实战

1. android使用proguard

我们在使用Android Studio创建一个Android工程的时候,Android Studio已经在build.gradle中自动配置了proguard。

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
  • minifyEnabled: 是否开启混淆,默认为false,需要混淆时要设为true。
  • proguardFiles: 它有两个参数,proguard-android.txt代表默认的android 混淆配置文件,路径为 $SDK/tools/proguard/proguard-android.txt ,该文件已经包含了基本的混淆声明。proguard-rules.pro是我们项目里的自定义的混淆配置文件,路径一般为: $project/$module/proguard-rules.pro,在这个文件里我们可以声明一些我们自定义的混淆规则。

2. android proguard 默认配置文件

我们先来讲讲android 默认的proguard配置文件:

  • proguard-android.txt,不带优化选项的配置文件。
  • proguard-android-optimize.txt,带优化选项的配置文件。
  • proguard-project.txt,全部都是注释,没有有效内容,忽略它即可。

2.1 proguard-android.txt

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

2.2 proguard-android-optimize.txt

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.

# Optimizations: If you don't want to optimize, use the
# proguard-android.txt configuration file instead of this one, which
# turns off the optimization flags.  Adding optimization introduces
# certain risks, since for example not all optimizations performed by
# ProGuard works on all versions of Dalvik.  The following flags turn
# off various optimizations known to have issues, but the list may not
# be complete or up to date. (The "arithmetic" optimization can be
# used if you are only targeting Android 2.0 or later.)  Make sure you
# test thoroughly if you go this route.
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify

# The remainder of this file is identical to the non-optimized version
# of the Proguard configuration file (except that the other file has
# flags to turn off optimization).

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

2.3 proguard-android-optimize.txt和proguard-android.txt对比

proguard-android.txt和proguard-android-optimize.txt的大部分内容是相同的,仅在优化选项上不同。下面我们就来对比一下,在各个选项上方加注释标明选项的作用。
不同的部分
proguard-android.txt:

# 不启用优化
-dontoptimize

proguard-android-optimize.txt:

# 优化选项
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 优化遍数
-optimizationpasses 5
# 允许修改类和类成员的访问修饰符
-allowaccessmodification

相同部分

# 不预检,预检主要是针对JavaME的,Java 6 以上都不需要预检。
-dontpreverify
# 指定在混淆时不生成大小写混合的类名,混合后类名为小写,主要是应对大小写不敏感的操作系统, 例如Windows
-dontusemixedcaseclassnames
# 指定不跳过非公共库的类文件
-dontskipnonpubliclibraryclasses
# 指定在处理期间写出更多信息。如果程序因异常终止,设置此选项将打印出整个堆栈,而不仅仅是异常消息。
-verbose

# 保留指定的属性
-keepattributes *Annotation*
# 保留指定的类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService


# 保留包含native方法的类及其类成员
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留View类的子类的getter和setter方法
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# 保留Activity类的子类的任意名称、参数为View的方法,一般用于保留在XML属性中使用的方法,例如,onClick方法。
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# 保留枚举的values和valueOf方法
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保留Parcelable 子类的成员CREATOR
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

#保留R类及其内部类的公共且静态的成员
-keepclassmembers class **.R$* {
    public static <fields>;
}

# 不提示support包的警告,因为support包是可以兼容的,google已经处理好了。
-dontwarn android.support.**

# 保留指定类Keep,为了方便使用Keep注解。
-keep class android.support.annotation.Keep

# 保留使用了Keep进行注解的类及其所有字段和方法
-keep @android.support.annotation.Keep class * {*;}

# 保留使用了Keep进行注解的类及其中使用了Keep进行注解的方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

# 保留使用了Keep进行注解的类及其中使用了Keep进行注解的字段
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

# 保留使用了Keep进行注解的类及其中使用了Keep进行注解的构造方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

3. Android 中比较通用的proguard配置原则

3.1 继承默认proguard配置

继承是指我们使用proguard配置文件时尽量在原有的默认配置文件的基础上去添加自定义配置选项,因为原有的默认配置已经帮我们预置了很多常用的选项。例如,保留native方法、保留枚举类,保留R类等等。需要优化的项目,复制proguard-android-optimize.txt的内容,然后在此基础上去添加自定义的配置选项。不需要优化的项目,复制proguard-android.txt的内容,在此基础上去添加自定义的配置选项。

3.2 保留反射使用到的类

《ProGuard基础》的入口点一节中提到,要保留所有反射动态创建或调用的类或类成员。保留方式有很多种,例如,可以指定保留包或者指定保留类。

-keep class com.example.** { *; }
-keep public class com.example.Example

3.3 保留可能被外部调用的类

为了对外的接口在proguard处理后,外部还能正常调用,对外接口是一定需要保留的。常见的如四大组件,自定义的Application等等。

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.backup.BackupAgentHelper

3.4 保留实体类及可序列化类

实体类和序列化类之所以会放到一起,是因为实际项目很多情况下实体类本身也是可序列化类。实体类在一般需要保留Getter和Setter、isxxx方法。可序列化类在android中Parcelable用得多一些,Serializable用得少一些。默认的配置文件中已经有保留Parcelable的选项了,这里使用通配符扩展一下。为了兼容一些第三方的java库,最好把保留Serializable的选项也加上。

# 保留实体类
-keep public class com.example.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}

# 保留Parcelable序列化类
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# 保留Serializable序列化类
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

3.5 保留继承自View的自定义控件

同样的,默认的配置文件中也已经有保留View子类的选项了,这里扩展一下。

-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

3.6 保留WebView和JavaScript交互类

如果我们项目中使用带有JavaScript的WebView,则需要保留JavaScript交互类,同时我们可能还需要保留WebViewClient的子类。

# fqcn.of.javascript.interface.for.webview 是the fully qualified class name to the JavaScript interface
# class的缩写,即表示JavaScript交互类的完整包名.类名。
# 有些文章可能将保留JavaScript交互类单独列为一节,其实是没有必要的,因为通配符 * 可以匹配任意字段和方法。
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
    public void *(android.webkit.WebView, java.lang.String);
}

3.7 保留第三方库

proguard使用手册上说:使用proguard处理库时,至少应保留Exceptions,InnerClasses和Signature属性。一般还应该保留SourceFile和LineNumberTable属性,以产生有用的堆栈信息,即出了异常能保留有效的文件名和行号。
但实际项目中第三方库最好全部保留。如果是开源的第三方库,根本没有混淆的必要;如果是第三方的SDK,SDK厂商给你之前肯定也混淆过了。大部分情况下,我们可以直接使用dontwarn和keep保留即可,但是SDK厂商一般也会在SDK的API文档中提供自己的混淆策略,具体以SDK厂商提供的混淆策略为准。例如:

# 支付宝
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.**  { *; }

# rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
 long producerIndex;
 long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

3.8 保留源文件名和源代码行号

为了更好的排查问题,我们可能希望proguard处理后的代码运行时产生的日志中能保留有源代码的文件名和行号。在选择保留源文件名和源代码行号时,一般有两种:

  • -keepattributes SourceFile,LineNumberTable,抛出异常时在堆栈信息中保留源文件名称及源代码行号
  • -renamesourcefileattribute SourceFile,抛出异常时在堆栈信息中不保留源文件名,但保留源代码行号

一般情况下,这两个选项至少要二选一。两者的区别,会在接下来的第4节中介绍。

3.9 删除无效代码

assumenosideeffects是一个优化选项,它可以用来删除没有任何影响的方法,比如没有返回值的且不影响执行结果的方法、有返回值但未使用其返回值且不影响执行结果的方法。例如,我们可以用来优化无用的Log:

-assumenosideeffects class android.util.Log {   
    public static *** d(...);       
    public static *** e(...);    
    public static *** i(...);    
    public static *** v(...);   
    public static *** w(...);     
}

注意
assumenosideeffects是一个很有争议的选项,assumenosideeffects全局生效,被很多人拿来删除无效日志。但实际上它很容易破坏原有代码,它无法判断这个日志的打印对于项目是否真的无用。有可能某条日志对于我们分析问题是非常关键的,但它只能一刀切的删除,因此建议尽量少用assumenosideeffects。最好的做法是保持一个良好的编码习惯,冗余代码是在编码的时候就把它删掉。

3.10 开发和生产环境配置一致

我们的项目如果确定了要使用proguard处理,最好在项目一开始就进行处理。并且建议debug版本和release版本使用同样的proguard配置,保持开发和生产环境一致。这样由proguard引起的问题,我们可以在项目的开发阶段就能够及时发现和处理,不会因为开发和生产环境的不同,而导致出了问题找不到原因。如果确实有需要,也可在自己调试时禁用proguard来验证相关问题。

4. Proguard 混淆后的堆栈信息还原

4.1 retrace

retrace是proguard的辅助工具,可以用来还原堆栈。当混淆后的程序出现异常时,生成的堆栈通常是没有什么有效信息的。 源代码中的类名和方法名已由无意义的短字符串替换,源文件名和行号完全丢失。 调试问题时非常不方便。因此,我们就需要用到retrace。retrace可以读取经过混淆的堆栈,并将其还原为没有混淆的样子。 还原基于proguard在混淆过程中产生的映射文件。 映射文件将原始类名和类成员名链接到它们的混淆名。以下是retrace的流程:


proguard的retrace流程.png

使用retrace,在命令行输入:
java -jar retrace.jar [options...] mapping_file [stacktrace_file]

参数及选项说明:
参数

  • mapping_file,映射文件。
  • stacktrace_file,包含堆栈的文件, 如果未指定,则从标准输入读取堆栈。

选项

  • -verbose,打印出更多有用的堆栈信息,这些信息不仅包括方法名称,还包括方法返回类型和参数。
  • -regex Regular_expression,用于解析堆栈中的行的正则表达式。默认值适用于大多数JVM生成的堆栈信息

4.2 proguardgui 的使用

$SDK/tools/proguard/bin/proguardgui.bat是一个GUI工具,它集成了proguard.bat和retrace.bat,在可视化界面中提供了处理过程的各个步骤的配置项,比在命令行使用更加方便。我们打开proguardgui,点击Retrace选项卡,它的界面如图:


proguardgui.png
  • Mapping file:它是一个映射文件,记录源代码到混淆后的代码之间的映射关系,一般位于:$project\app\build\outputs\mapping\[debug|release]\mapping.txt。注意。只有启用proguard才会生成,即build.gradle中的minifyEnabled为true才会生成。
  • Obfuscated stack trace:混淆后的代码运行出现的异常的堆栈信息,可以直接从错误或异常日志中拷贝进来,也可以点击右下角的Load stack trace按钮选择文件。
  • De-obfuscated stack trace:还原后的堆栈信息,可以对应源代码的具体文件及行号。
  • Load stack trace:选择记录了堆栈信息的日志文件。
  • Retrace:指定好mapping文件和日志之后,点击Retrace就可以进行还原。

注意
启用proguard处理代码后,每发一个版本,我们都需要保留好mapping文件,以便将来进行日志的堆栈还原,准确定位出错位置。

为了介绍retrace的使用,我们先制造一个异常:
MainActivity.java:

package com.example.qiuxintai.myapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Test.divide();
    }
}

Test.java

package com.example.qiuxintai.myapp;

public class Test {
    public static int divide() {
        return 1 / 0;
    }
}

错误日志堆栈:

2020-09-01 19:08:17.929 32700-32700/com.example.qiuxintai.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.qiuxintai.myapp, PID: 32700
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.qiuxintai.myapp/com.example.qiuxintai.myapp.MainActivity}: java.lang.ArithmeticException: divide by zero
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2944)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3079)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1836)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6702)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.a.a(Unknown Source:1)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(Unknown Source:9)
        at android.app.Activity.performCreate(Activity.java:8604)
        at android.app.Activity.performCreate(Activity.java:8595)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2924)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3079) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1836) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6702) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)

由于日志太长了,下面只截取跟我们的代码直接相关的部分。
retrace后:

     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.Test.divide(Unknown Source:1)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(Unknown Source:9)

直接retrace后,括号部分的文件名还是Unknown Source,依然没有准确的源代码行号。这就涉及到要保留行号了。为了保留行号,我们可以在proguard-rules.pro中使用两个选项:

  • -renamesourcefileattribute SourceFile
  • -keepattributes SourceFile,LineNumberTable

接下来我们看看两者的区别。

4.3 -renamesourcefileattribute SourceFile,混淆文件名但保留行号

混淆后的日志包含源代码准确的行号,但文件名和方法名是未知的:

     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.a.a(Unknown Source:5)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(Unknown Source:12)

retrace后,括号内的文件名还是UnKnown Source,但是前面已经有类名、方法名,再加上源代码准确的行号,我们已经可以准确的定位问题了:

     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.Test.divide(Unknown Source:5)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(Unknown Source:12)

4.4 -keepattributes SourceFile,LineNumberTable,保留文件名和行号

混淆后的日志就包含源代码的文件名和行号,如果不介意方法名被混淆了,甚至都不需要retrace了:

Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.a.a(Test.java:5)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(MainActivity.java:12)

retrace后,源代码的文件名、方法名、行号都被还原出来了:

Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.qiuxintai.myapp.Test.divide(Test.java:5)
        at com.example.qiuxintai.myapp.MainActivity.onCreate(MainActivity.java:12)

通过上面的对比,我们可以很清楚的看出区别了。一般我们建议选择-renamesourcefileattribute SourceFile,因为它仅有行号但不会暴露源文件名,保密性更强,更难被反编译破解。但是实际上所有人的时间都是宝贵的,你写的代码可能并不值得别人花时间去反编译破解。因此,使用哪一种并不是真的那么重要,除非你真的需要。

5. 参考

本文参考了以下两篇文章:
ProGuard详解
Android混淆代码错误堆栈还原
感谢两位原作者的辛勤付出。

欢迎交流、点赞、转载,码字不易,转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352