25.Android Studio下Ndk开发(参数加密解决方案)

网络请求通过http传递到后台,如果不对数据做加密处理的话,很容易会被抓包,此时,app就是很不安全的,被截取到接口地址和参数后容易被攻击。今天我要分享的就是如何提高网络接口安全性的解决方案。

之前做的项目是采取直接在java层对参数进行加密,加密方式也有很多,RSA加密,MD5加密,AES加密,DES加密,Base64加密等等,具体介绍可以参考这里 Android中的加密方法(http://www.cnblogs.com/whoislcj/p/5470095.html),这种方式在一定程度上可以提高数据的安全性,但是深入来看,我们的加密方式对外暴露出来,当app被反编译时,对方可以拿到我们的代码,可以看到我们加密的方式,这样一来,会更加容易让对方找到破解密文的方法,因为在目前所有加密方式中,既具备实用性又具备绝对安全性的方法是不存在的。所以我们是否可以做到加密方式也对外不可见呢,或者如果不能做到绝对不可见,是否可以大大的提高对方破解密文的难度。这就是今天要做的,通过jni将加密方法打包到so库中,防止被放编译,算是在这些加密算法的上面加一层壳,这里以md5加密为例。

so库破解的难度之大,远远超过破解混淆后的apk,所以jni是解决安全性隐患的一个切入点。

创建CMakeLists文件,配置相关的内容

#参数加密
cmake_minimum_required(VERSION 3.4.1)

find_library( log-lib
              log )

add_library( encrypt
             SHARED
             src/main/cpp/encrypt.cpp src/main/cpp/md5.cpp)

# 将预构建库与本地库相连
target_link_libraries( encrypt
                       ${log-lib} )

EncryptUtils

package com.app.rzm.utils;

import android.content.Context;

/**
 * ndk实现参数加密
 */
public class EncryptUtils {

    static {
        System.loadLibrary("encrypt");
    }

    public static String encrypt(Context context, String param){
        checkSignature(context);
        return encryptNative(context,param);
    }

    /**
     * 对一个字符串进行加密
     * @param context
     * @param param
     * @return
     */
    private static native String encryptNative(Context context, String param);

    /**
     * 校验app签名
     * @param context
     */
    private static native void checkSignature(Context context);
}

调用方式

public class TestParamsEncryptActivity extends AppCompatActivity {

    private TextView mText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_params_encrypt);
        mText = (TextView) findViewById(R.id.text);

        //拿到签名
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signatures = packageInfo.signatures;
            LogUtils.d("signature:"+signatures[0].toCharsString());
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        //将网络请求中的http参数拼接成这样的字符串username=renzhenming&password=123456
        //然后将这个参数字符串进行加密
        String params = EncryptUtils.encrypt(this,"username=renzhenming&password=123456");

        //作为参数给到服务器,服务器也生成同样的密文,然后将加密的字符串进行比较
        mText.setText(params);

    }
}

接下来是关键代码,在c++中实现md5加密, * 加密解密的过程:
md5.h

#ifndef MD5_H
#define MD5_H

typedef struct
{
    unsigned int count[2];
    unsigned int state[4];
    unsigned char buffer[64];
}MD5_CTX;


#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n)))

#define FF(a,b,c,d,x,s,ac) { \
    a += F(b, c, d) + x + ac; \  
    a = ROTATE_LEFT(a, s); \  
    a += b; \  
    }

#define GG(a,b,c,d,x,s,ac) { \
        a += G(b, c, d) + x + ac; \  
        a = ROTATE_LEFT(a, s); \  
        a += b; \  
    }

#define HH(a,b,c,d,x,s,ac) { \
        a += H(b, c, d) + x + ac; \  
        a = ROTATE_LEFT(a, s); \  
        a += b; \  
    }
#define II(a,b,c,d,x,s,ac) { \
        a += I(b, c, d) + x + ac; \  
        a = ROTATE_LEFT(a, s); \  
        a += b; \  
    }


void MD5Init(MD5_CTX *context);
void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen);
void MD5Final(MD5_CTX *context, unsigned char digest[16]);
void MD5Transform(unsigned int state[4], unsigned char block[64]);
void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len);
void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len);

#endif

md5.cpp

#include "md5.h"
#include "string"
unsigned char PADDING[] = {
        0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

//在逆向代码的时候,需要关注下面的特征值
void MD5Init(MD5_CTX *context)
{
    context->count[0] = 0;
    context->count[1] = 0;
    context->state[0] = 0x67452301;
    context->state[1] = 0xEFCDAB89;
    context->state[2] = 0x98BADCFE;
    context->state[3] = 0x10325476;
}

void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen)
{
    unsigned int i = 0, index = 0, partlen = 0;
    index = (context->count[0] >> 3) & 0x3F;
    partlen = 64 - index;
    context->count[0] += inputlen << 3;
    if (context->count[0] < (inputlen << 3))
        context->count[1]++;
    context->count[1] += inputlen >> 29;

    if (inputlen >= partlen)
    {
        memcpy(&context->buffer[index], input, partlen);
        MD5Transform(context->state, context->buffer);
        for (i = partlen; i + 64 <= inputlen; i += 64)
            MD5Transform(context->state, &input[i]);
        index = 0;
    }
    else
    {
        i = 0;
    }
    memcpy(&context->buffer[index], &input[i], inputlen - i);
}

void MD5Final(MD5_CTX *context, unsigned char digest[16])
{
    unsigned int index = 0, padlen = 0;
    unsigned char bits[8];
    index = (context->count[0] >> 3) & 0x3F;
    padlen = (index < 56) ? (56 - index) : (120 - index);
    MD5Encode(bits, context->count, 8);
    MD5Update(context, PADDING, padlen);
    MD5Update(context, bits, 8);
    MD5Encode(digest, context->state, 16);
}

void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len)
{
    unsigned int i = 0, j = 0;
    while (j < len)
    {
        output[j] = input[i] & 0xFF;
        output[j + 1] = (input[i] >> 8) & 0xFF;
        output[j + 2] = (input[i] >> 16) & 0xFF;
        output[j + 3] = (input[i] >> 24) & 0xFF;
        i++;
        j += 4;
    }
}

void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len)
{
    unsigned int i = 0, j = 0;
    while (j < len)
    {
        output[i] = (input[j]) |
                    (input[j + 1] << 8) |
                    (input[j + 2] << 16) |
                    (input[j + 3] << 24);
        i++;
        j += 4;
    }
}

void MD5Transform(unsigned int state[4], unsigned char block[64])
{
    unsigned int a = state[0];
    unsigned int b = state[1];
    unsigned int c = state[2];
    unsigned int d = state[3];
    unsigned int x[64];

    MD5Decode(x, block, 64);
    FF(a, b, c, d, x[0], 7, 0xd76aa478);
    FF(d, a, b, c, x[1], 12, 0xe8c7b756);
    FF(c, d, a, b, x[2], 17, 0x242070db);
    FF(b, c, d, a, x[3], 22, 0xc1bdceee);
    FF(a, b, c, d, x[4], 7, 0xf57c0faf);
    FF(d, a, b, c, x[5], 12, 0x4787c62a);
    FF(c, d, a, b, x[6], 17, 0xa8304613);
    FF(b, c, d, a, x[7], 22, 0xfd469501);
    FF(a, b, c, d, x[8], 7, 0x698098d8);
    FF(d, a, b, c, x[9], 12, 0x8b44f7af);
    FF(c, d, a, b, x[10], 17, 0xffff5bb1);
    FF(b, c, d, a, x[11], 22, 0x895cd7be);
    FF(a, b, c, d, x[12], 7, 0x6b901122);
    FF(d, a, b, c, x[13], 12, 0xfd987193);
    FF(c, d, a, b, x[14], 17, 0xa679438e);
    FF(b, c, d, a, x[15], 22, 0x49b40821);


    GG(a, b, c, d, x[1], 5, 0xf61e2562);
    GG(d, a, b, c, x[6], 9, 0xc040b340);
    GG(c, d, a, b, x[11], 14, 0x265e5a51);
    GG(b, c, d, a, x[0], 20, 0xe9b6c7aa);
    GG(a, b, c, d, x[5], 5, 0xd62f105d);
    GG(d, a, b, c, x[10], 9, 0x2441453);
    GG(c, d, a, b, x[15], 14, 0xd8a1e681);
    GG(b, c, d, a, x[4], 20, 0xe7d3fbc8);
    GG(a, b, c, d, x[9], 5, 0x21e1cde6);
    GG(d, a, b, c, x[14], 9, 0xc33707d6);
    GG(c, d, a, b, x[3], 14, 0xf4d50d87);
    GG(b, c, d, a, x[8], 20, 0x455a14ed);
    GG(a, b, c, d, x[13], 5, 0xa9e3e905);
    GG(d, a, b, c, x[2], 9, 0xfcefa3f8);
    GG(c, d, a, b, x[7], 14, 0x676f02d9);
    GG(b, c, d, a, x[12], 20, 0x8d2a4c8a);


    HH(a, b, c, d, x[5], 4, 0xfffa3942);
    HH(d, a, b, c, x[8], 11, 0x8771f681);
    HH(c, d, a, b, x[11], 16, 0x6d9d6122);
    HH(b, c, d, a, x[14], 23, 0xfde5380c);
    HH(a, b, c, d, x[1], 4, 0xa4beea44);
    HH(d, a, b, c, x[4], 11, 0x4bdecfa9);
    HH(c, d, a, b, x[7], 16, 0xf6bb4b60);
    HH(b, c, d, a, x[10], 23, 0xbebfbc70);
    HH(a, b, c, d, x[13], 4, 0x289b7ec6);
    HH(d, a, b, c, x[0], 11, 0xeaa127fa);
    HH(c, d, a, b, x[3], 16, 0xd4ef3085);
    HH(b, c, d, a, x[6], 23, 0x4881d05);
    HH(a, b, c, d, x[9], 4, 0xd9d4d039);
    HH(d, a, b, c, x[12], 11, 0xe6db99e5);
    HH(c, d, a, b, x[15], 16, 0x1fa27cf8);
    HH(b, c, d, a, x[2], 23, 0xc4ac5665);


    II(a, b, c, d, x[0], 6, 0xf4292244);
    II(d, a, b, c, x[7], 10, 0x432aff97);
    II(c, d, a, b, x[14], 15, 0xab9423a7);
    II(b, c, d, a, x[5], 21, 0xfc93a039);
    II(a, b, c, d, x[12], 6, 0x655b59c3);
    II(d, a, b, c, x[3], 10, 0x8f0ccc92);
    II(c, d, a, b, x[10], 15, 0xffeff47d);
    II(b, c, d, a, x[1], 21, 0x85845dd1);
    II(a, b, c, d, x[8], 6, 0x6fa87e4f);
    II(d, a, b, c, x[15], 10, 0xfe2ce6e0);
    II(c, d, a, b, x[6], 15, 0xa3014314);
    II(b, c, d, a, x[13], 21, 0x4e0811a1);
    II(a, b, c, d, x[4], 6, 0xf7537e82);
    II(d, a, b, c, x[11], 10, 0xbd3af235);
    II(c, d, a, b, x[2], 15, 0x2ad7d2bb);
    II(b, c, d, a, x[9], 21, 0xeb86d391);
    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;
}

客户端通过定义的规则将参数加密后,将密文和铭文参数同时传递到服务器,服务器收到参数进行解析,使用同样的加密算法将参数加密,然后对比此次得到的密文和客户端传递的密文是否相同,如果相同说明数据安全,没有被篡改,如果不同,则表示数据改变,不再发送数据到客户端

将加密方法打包到so库中的好处就是可以防止对方反编译看到我们的加密条件,如果对方不知道我们是如何加密的,也就可以在一定程度上防止数据泄漏,但是只是单纯的这样做并不能保证绝对的安全,比如,我不需要知道你是怎么加密的,只需要反编译apk后得到几个信息1.你应用的包名,2.你的so库,3.你的native方法
的完整类名和方法名(native方法不能被混淆,混淆后无法使用,所以可以得到),只要得到这三个信息,我就可以创建包名相同方法名相同的一个应用,把so放进去,然后就可以绕过密钥检查,轻松的调用你的接口了。

解决这个问题的方法就是在so库中加入签名验证,当调用加密方法对操作参数的时候,验证此时应用签名是否是我们本应用的,如果不是,则表示当前应用是伪应用,直接返回, 防止上边那种恶意调用接口情况的出现。对签名做校验,也就是只允许指定的应用可以使用,类似在微信支付中,也有在官方管理后台申请和配置应用的的签名和包名,否则就禁止使用,签名和包名必须得要一致。

com_app_rzm_utils_EncryptUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_app_rzm_utils_Encryptils */

#ifndef _Included_com_app_rzm_utils_EncryptUtils
#define _Included_com_app_rzm_utils_EncryptUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_app_rzm_utils_Encryptils
 * Method:    encryptNative
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative
  (JNIEnv *, jclass, jobject ,jstring);

JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature
  (JNIEnv *, jclass, jobject);

#ifdef __cplusplus
}
#endif
#endif

encrypt.cpp

#include "com_app_rzm_utils_EncryptUtils.h"
#include "md5.h"
#include <string>
#include <android/log.h>

using namespace std;

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__);

//我们加密的方式是对参数进行md5加密,在md5加密之前还有一层加密,就是对参数字符串进行改造
//在字符串前加上自定义的key值,然后去掉字符串后边两位字符串,这个规则按需定制,增加破解的难度
#define MD5_KEY "renzhenming"

//签名校验是否通过,否返回-1
static int signature_verify = -1;

//app包名
static char* PACKAGE_NAME = "com.app.rzm";

//app签名,在这里配置我们app的正式签名,在so库中,可以保证安全性
static char* APP_SIGNATURE = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137303332333033323030305a170d3437303331363033323030305a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d00308189028181008658a9a532d1c5e8c5a1a78c61535636220f73deb9b36d0912e2b6b1c50f5ed7eccb5cd8e0e4b2fd192d983fa15aeef6be5e5258e809b3fbad538fb68d1c78ebbdd89080664d707e9731c706386a45242a1e3a9e4819789bf832206a2bb7a45b663b6deb9be153ca4fe77b26ca1c43d85cbc20465cc6046f1e1dc16bbc65fe310203010001300d06092a864886f70d010105050003818100213550aa14811a7a407e7eb148b9cb50709c2c84185340b4c52f22caff07fd2e4a79e2814dc5a16fbd7a4b3ec638574eb5ee6baf7537b69aed6529a594bf556f1e0f073884739271f3e2572c4c174031b547846212643ae57bb35e8157c65b3760e37fc3c74f4e24daf3d91086d6ddd7a3f1e54b69ad235a6eb6c5524ce24800";
/**
 * @param env
 * @param jclazz
 * @param jparam
 * @return
 */
JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative
  (JNIEnv *env, jclass jclazz,jobject context,jstring jparam){

    if(signature_verify == -1){
        return env->NewStringUTF("EncryptUtils--> signature check err");
    }

    const char *param = env->GetStringUTFChars(jparam,NULL);

    //在参数头位置加上MD5_KEY,然后去掉后面两位字符串
    string signature_str(param);
    //insert(int p0, const char *s);在p0位置插入字符串s
    signature_str.insert(0,MD5_KEY);
    signature_str = signature_str.substr(0,signature_str.length()-2);


    //md5加密
    MD5_CTX *ctx = new MD5_CTX();
    MD5Init(ctx);
    MD5Update(ctx,(unsigned char *)signature_str.c_str(),signature_str.length());
    unsigned char digest[16];
    MD5Final(ctx, digest);

    int i = 0;
    char szMd5[32] = {0};
    for(i = 0;i< 16 ; i++){
        LOGI("EncryptUtils--> szMd5[%d]:%s",i,szMd5);
        //最终生成32位,不足前面补一位0
        //x 表示以十六进制形式输出 ,02 表示不足两位,前面补0输出;出过两位,不影响
        sprintf(szMd5,"%s%02x",szMd5,digest[i]);
    }

    env->ReleaseStringUTFChars(jparam,param);

    return env->NewStringUTF(szMd5);
}
JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature
  (JNIEnv *env, jclass jclazz, jobject context){
     //1.获取包名  通过Context的getPackageName方法获取

     //获取Context对象的class
     jclass context_class = env->GetObjectClass(context);
     //获取getPackageName的方法id
     jmethodID context_method_id = env->GetMethodID(context_class,"getPackageName","()Ljava/lang/String;");
     //调用getPackageName方法
     jstring package_name = (jstring)env->CallObjectMethod(context,context_method_id);
     //转换为char*
     const char *c_package_name = (char *)env->GetStringUTFChars(package_name,NULL);
     LOGI("EncryptUtils--> package name:%s\n",c_package_name);
     //2.对比包名

     if(strcmp(c_package_name,PACKAGE_NAME)){
         LOGI("EncryptUtils--> package name check err");
        return;
    }

     //3.获取签名(通过下边这种方式)
     //PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
     //Signature[] signatures = packageInfo.signatures;
    //LogUtils.d("signature:"+signatures[0].toCharsString());

    //获取Context中的getPackageManager方法id
    jmethodID get_package_manager_method_id = env->GetMethodID(context_class,"getPackageManager","()Landroid/content/pm/PackageManager;");
    //从Context中通过调用getPackageManager获取PackageManager对象
    jobject package_manager = env->CallObjectMethod(context,get_package_manager_method_id);

    //获取PackageManager对象的class
    jclass package_manager_class = env->GetObjectClass(package_manager);
    //获取PackageManager对象中的getPackageInfo方法id
    jmethodID get_package_info_method_id = env->GetMethodID(package_manager_class,"getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    //调用PackageInfo中的getPackageInfo方法获取PackageInfo对象(PackageManager.GET_SIGNATURES=64)
    jobject package_manager_info = env->CallObjectMethod(package_manager,get_package_info_method_id,package_name,64);

    //获取PackageInfo的class,获取它的属性的时候要用到
    jclass package_info_class = env->GetObjectClass(package_manager_info);
    //获取PackageInfo中的signatures属性的fieldid
    jfieldID  signatures_field_id = env->GetFieldID(package_info_class,"signatures","[Landroid/content/pm/Signature;");
    //获取PackageInfo中的signatures属性
    jobjectArray signatures_arrary = (jobjectArray) env->GetObjectField(package_manager_info, signatures_field_id);
    //获取数组中[0]位置的元素
    jobject signature = env->GetObjectArrayElement(signatures_arrary,0);

    //获取String的class
    jclass signature_class = env->GetObjectClass(signature);
    //获取String中的toCharsString方法的methodid
    jmethodID  to_chars_string_method_id = env->GetMethodID(signature_class,"toCharsString","()Ljava/lang/String;");
    //调用String的toCharsString
    jstring signature_string = (jstring) env->CallObjectMethod(signature, to_chars_string_method_id);
    //转换为char*
    const char * signature_char = env->GetStringUTFChars(signature_string,NULL);
    LOGI("EncryptUtils--> current app signature:%s\n",signature_char);
    LOGI("EncryptUtils--> real app signature:%s\n",APP_SIGNATURE);
    //4.对比签名
    if(strcmp(signature_char,APP_SIGNATURE) == 0){
        signature_verify = 1;
        LOGI("EncryptUtils--> signature_verify success:%d\n",signature_verify);
    }else{
        signature_verify = -1;
        LOGI("EncryptUtils--> signature_verify check failed:%d\n",signature_verify);
    }
}


总结一下:
我们通过使用纯c++代码实现md5加密,将加密实现方式打包成so库,提高反编译的难度,另外在md5加密之外我们还设置了另一层加密规则,对参数字符串头尾进行处理,双层加密,确保数据的安全性。在加密手段之外,再进行app包名和签名的校验,从而保证so库只能在我们自己的app中使用。三层保护,这样一来,相信即便是遇到逆向工程师,要破解我们的app也是有一定难度的。

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

推荐阅读更多精彩内容