微信保存并發(fā)送指定語(yǔ)音

最終目標(biāo):

1作彤、保存發(fā)送的微信語(yǔ)音

2、發(fā)送指定的微信語(yǔ)音

思路:

因?yàn)橐猦ook的是微信語(yǔ)音功能,所以首先要知道微信語(yǔ)音功能的流程瑞妇。
查閱資料以及利用Xposed的hook功能,最終知道微信的語(yǔ)音功能是通過(guò)AudioRecord來(lái)實(shí)現(xiàn)的梭冠,一般的流程是這樣的:

// 1辕狰、初始化audioRecord 對(duì)象
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recBufSize);

// 2、調(diào)用audioRecord的start方法
audioRecord.startRecording();

// 3控漠、讀取audioRecord的錄音數(shù)據(jù)蔓倍,這些操作跟文件的io操作類似
byte data[] = new byte[recBufSize];
String filename = getTempFilename();
String filename = getTempFilename();
FileOutputStream os = null;
try {
     os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
     e.printStackTrace();
}
int read = 0;
if(null != os){
     while(isRecording){
           read = audioRecord.read(data, 0, recBufSize);
           if(AudioRecord.ERROR_INVALID_OPERATION != read){
           try {
                os.write(data);
           } catch (IOException e) {
                e.printStackTrace();
           }
     }
}

try {
    os.close();
} catch (IOException e) {
    e.printStackTrace();
}

// 4、將AudioRecord錄制到的pcm文件轉(zhuǎn)為amr
copyWaveFile(String inFilename,String outFilename);

// 5盐捷、停止錄制
private void stopRecord(){
     
    if(null != audioRecord){
       isRecording = false;
       audioRecord.stop();
       audioRecord.release();
       audioRecord = null;
       recordingThread = null;
    }
    copyWaveFile(getTempFilename(),getFilename());
    deleteTempFile();
}   

這就是一個(gè)AudioRecord的工作流程偶翅,這里由于我們可以想要保存文件,所以第四步的壓縮為amr文件可以省略碉渡,直接操作pcm文件即可聚谁。

  • 現(xiàn)在就有兩個(gè)問(wèn)題了:

  • 1、如何保存錄制的語(yǔ)音滞诺?
  • 2形导、如何替換發(fā)送指定的語(yǔ)音环疼?

首先,第二個(gè)問(wèn)題是建立在第一個(gè)問(wèn)題所保存的語(yǔ)音的基礎(chǔ)上的朵耕。

現(xiàn)在來(lái)看第一個(gè)問(wèn)題炫隶,因?yàn)槭且4嫖⑿潘l(fā)送的語(yǔ)音,所以我們利用Xposed來(lái)hook在微信調(diào)用AudioRecord的后阎曹,在其after回調(diào)里面進(jìn)行相應(yīng)的保存操作即可伪阶。

這里先hook了AudioRecord的read方法。

// hook  read 方法处嫌,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)需要在before操作望门,當(dāng)錄入自己的語(yǔ)音文件時(shí)需要在after操作
 XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "read", byte[].class, int.class,
                        int.class, new ReadMethodHook());

然后在它的afterHookedMethod中保存pcm文件到我們指定的目錄下。因?yàn)檫@個(gè)方法是在一個(gè)while循環(huán)中調(diào)用的锰霜,所以我們通過(guò)一個(gè)map來(lái)維護(hù)每個(gè)AudioRecord的FileOutputStream數(shù)據(jù)流筹误。

        // 錄入自己的語(yǔ)音文件時(shí)
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            try {
                 ....
            
                    FileOutputStream fileOutputStream ;
                    // 拿到當(dāng)前的AudioRecord對(duì)象
                    AudioRecord record = (AudioRecord) param.thisObject;

                    byte[] buffer = (byte[]) param.args[0];
                    Integer integer = (Integer) param.args[1];
                    int offset = integer.intValue();
                    Integer integer2 = (Integer) param.args[2];
                    int size = integer2.intValue();

                    // 創(chuàng)建輸出的臨時(shí)文件流
                    if (mFosMap.get(record) == null) {
                        execCommand("chmod 777 /data/local/tmp",true);
                        String pcmFileName = "myPcmFile";
                        mNum++;
                        pcmFileName = pcmFileName + mNum;
                        File file = new File("/data/local/tmp/" + pcmFileName + ".pcm");
                        File fileParent = file.getParentFile();
                        if (!fileParent.exists()) {
                            fileParent.mkdirs();
                        }
                        file.createNewFile();
                        Log.i(TAG, "pcmFileName: " + pcmFileName);
                        fileOutputStream = new FileOutputStream("/data/local/tmp/" + pcmFileName + ".pcm");
                        mFosMap.put(record, fileOutputStream);
                        mPcmFileMap.put(record, pcmFileName);
                    } else {
                        fileOutputStream = mFosMap.get(record);
                    }

                    // 獲取當(dāng)前AudioRecord的read方法的返回值
                    int read = (int) param.getResult();

                    // read方法還在不斷的執(zhí)行中
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        // 新建一個(gè)byte[] ,用于拿到微信的buffer數(shù)據(jù)
                        byte[] bytes = new byte[read];
                        // 將微信的buffer數(shù)據(jù)賦予到自己的byte[]中
                        for (int i = 0; i < bytes.length; i++) {
                            bytes[i] = buffer[i + offset];
                        }

                        // 將byte[] 寫到臨時(shí)的語(yǔ)音文件中,這樣既可拿到當(dāng)前語(yǔ)音輸入的內(nèi)容
                        fileOutputStream.write(bytes);
                    }


                }
            } catch (Exception e) {
                Log.i(TAG, "afterHookedMethod Exception: " + e.getMessage());
                e.printStackTrace();
            }
        }

接著就是在Stop方法中做文件的關(guān)閉以及map資源釋放等處理了癣缅。

// hook  stop 方法厨剪,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)需要在before操作,當(dāng)錄入自己的語(yǔ)音文件時(shí)需要在after操作
 XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "stop",new StopMethodHook() );

 private class StopMethodHook extends XC_MethodHook{
        // 錄入自己的語(yǔ)音文件
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 1) {
                    AudioRecord record = (AudioRecord) param.thisObject;
                    if (mFosMap.get(record) != null) {
                        // 關(guān)閉文件輸出流友存,清理map
                        FileOutputStream fos = mFosMap.get(record);
                        fos.close();
                        mFosMap.remove(record);

                        // 修改指定輸出文件的父目錄權(quán)限祷膳。
                        File file = new File(mRecordPcmFileName);
                        String parentPath = file.getParent();
                        execCommand("chmod 777 "+parentPath,true);

                        // 覆蓋拷貝到指定的文件目錄
                        String pcmFileName = mPcmFileMap.get(record);
                        execCommand("chmod 777 /data/local/tmp/" + pcmFileName + ".pcm", true);
                        execCommand("\\cp /data/local/tmp/" + pcmFileName + ".pcm  " + mRecordPcmFileName, true);
                        Log.i(TAG, "命令 : \\cp /data/local/tmp/" + pcmFileName + ".pcm  " + mRecordPcmFileName);
                        execCommand("chmod 777 " + mRecordPcmFileName, true);
                        // 刪除臨時(shí)文件
                        execCommand("rm /data/local/tmp/" + pcmFileName + ".pcm", true);

                        // 清理map
                        mPcmFileMap.remove(record);
                    }
                }
            } catch (Exception e) {
                Log.i(TAG, "AudioRecord  # stop 里的 afterHookedMethod出錯(cuò) : " + e.getMessage());
            }
        }
    }

到此我們就是實(shí)現(xiàn)了將發(fā)送的微信語(yǔ)音保存到本地的功能了,第一個(gè)問(wèn)題也就解決了屡立。

接著我們看第二個(gè)問(wèn)題直晨,要想替換微信所發(fā)送的語(yǔ)音,就是讓微信的語(yǔ)音發(fā)不出去膨俐,而發(fā)出去的是我們自己的語(yǔ)音勇皇,所以此時(shí)要在hook方法中的before回調(diào)里面執(zhí)行操作了。

我們需要利用Xposed來(lái)hook AudioRecord的startRecording焚刺,read敛摘,getRecordingState,stop和release方法乳愉。

                // hook  startRecording 方法兄淫,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù),將微信的流程打斷,自己維護(hù)整個(gè)發(fā)送過(guò)程
                XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "startRecording",new StartRecordingMethodHook());


                // hook  getRecordingState 方法蔓姚,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)
                XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "getRecordingState",new GetRecordingStateMethodHook());


                // hook  read 方法捕虽,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)需要在before操作,當(dāng)錄入自己的語(yǔ)音文件時(shí)需要在after操作
                XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "read", byte[].class, int.class,
                        int.class, new ReadMethodHook());


                // hook  stop 方法坡脐,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)需要在before操作泄私,當(dāng)錄入自己的語(yǔ)音文件時(shí)需要在after操作
                XposedHelpers.findAndHookMethod("android.media.AudioRecord",
                        loadPackageParam.classLoader, "stop",new StopMethodHook() );


                // hook  release 方法,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù),打斷微信
                XposedHelpers.findAndHookMethod("android.media.AudioRecord", loadPackageParam.classLoader,
                        "release", new ReleaseMethodHook());


注意看before回調(diào)的操作挖滤。

// 當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)需要在before操作崩溪,當(dāng)錄入自己的語(yǔ)音文件時(shí)需要在after操作
    private class ReadMethodHook extends XC_MethodHook{
        // 發(fā)送指定語(yǔ)音
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();

                if (mMode == 0) {
                    AudioRecord record = (AudioRecord) param.thisObject;
                    byte[] buffer = (byte[]) param.args[0];
                    int off = (int) param.args[1];
                    int size = (int) param.args[2];

                    FileInputStream fis;

                    // 指定發(fā)送的語(yǔ)音文件
                    if (mFisMap.get(record)==null) {
                        fis = new FileInputStream(mSendPcmFileName);
                        mFisMap.put(record,fis);
                    }else {
                        fis = mFisMap.get(record);
                    }

                    // 創(chuàng)建byte[]數(shù)據(jù),用來(lái)替換微信的buffer
                    int min = Math.min(buffer.length - off, size);
                    byte[] bytes = new byte[min];
                    // 將指定的語(yǔ)音文件讀取到微信的語(yǔ)音文件斩松,實(shí)現(xiàn)替換發(fā)送指定語(yǔ)音
                    int res = fis.read(bytes);

                    if (res == -1) {
                        param.setResult(0);
                    } else {
                        for (int i = 0; i < bytes.length; i++) {
                            buffer[off + i] = bytes[i];
                        }
                        param.setResult(res);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 錄入自己的語(yǔ)音文件時(shí)
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 1) {
                    FileOutputStream fileOutputStream ;
                    // 拿到當(dāng)前的AudioRecord對(duì)象
                    AudioRecord record = (AudioRecord) param.thisObject;

                    byte[] buffer = (byte[]) param.args[0];
                    Integer integer = (Integer) param.args[1];
                    int offset = integer.intValue();
                    Integer integer2 = (Integer) param.args[2];
                    int size = integer2.intValue();

                    // 創(chuàng)建輸出的臨時(shí)文件流
                    if (mFosMap.get(record) == null) {
                        execCommand("chmod 777 /data/local/tmp",true);
                        String pcmFileName = "myPcmFile";
                        mNum++;
                        pcmFileName = pcmFileName + mNum;
                        File file = new File("/data/local/tmp/" + pcmFileName + ".pcm");
                        File fileParent = file.getParentFile();
                        if (!fileParent.exists()) {
                            fileParent.mkdirs();
                        }
                        file.createNewFile();
                        Log.i(TAG, "pcmFileName: " + pcmFileName);
                        fileOutputStream = new FileOutputStream("/data/local/tmp/" + pcmFileName + ".pcm");
                        mFosMap.put(record, fileOutputStream);
                        mPcmFileMap.put(record, pcmFileName);
                    } else {
                        fileOutputStream = mFosMap.get(record);
                    }

                    // 獲取當(dāng)前AudioRecord的read方法的返回值
                    int read = (int) param.getResult();

                    // read方法還在不斷的執(zhí)行中
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        // 新建一個(gè)byte[] 伶唯,用于拿到微信的buffer數(shù)據(jù)
                        byte[] bytes = new byte[read];
                        // 將微信的buffer數(shù)據(jù)賦予到自己的byte[]中
                        for (int i = 0; i < bytes.length; i++) {
                            bytes[i] = buffer[i + offset];
                        }

                        // 將byte[] 寫到臨時(shí)的語(yǔ)音文件中,這樣既可拿到當(dāng)前語(yǔ)音輸入的內(nèi)容
                        fileOutputStream.write(bytes);
                    }


                }
            } catch (Exception e) {
                Log.i(TAG, "afterHookedMethod Exception: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    private class StopMethodHook extends XC_MethodHook{

        // 發(fā)送指定語(yǔ)音
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 0) {
                    AudioRecord record = (AudioRecord) param.thisObject;
                    // 關(guān)閉自己的文件輸入,清理map
                    if (mFisMap.get(record) != null) {
                        FileInputStream fis = mFisMap.get(record);
                        fis.close();
                        mFisMap.remove(record);
                    }

                    // 將錄音狀態(tài)設(shè)置為stopped
                    int flag = -1;
                    if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_STOPPED) {
                        flag = AudioRecord.RECORDSTATE_STOPPED;
                        mRecordingFlagMap.put(record, flag);
                    }
                    //  打斷微信惧盹,完成發(fā)送指定的語(yǔ)音文件
                    Object o = new Object();
                    param.setResult(o);
                }
            } catch (Exception e) {
                Log.i(TAG, "AudioRecord  # stop 里的 beforeHookedMethod出錯(cuò) : " + e.getMessage());
            }
        }

        // 錄入自己的語(yǔ)音文件
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 1) {
                    AudioRecord record = (AudioRecord) param.thisObject;
                    if (mFosMap.get(record) != null) {
                        // 關(guān)閉文件輸出流乳幸,清理map
                        FileOutputStream fos = mFosMap.get(record);
                        fos.close();
                        mFosMap.remove(record);

                        // 修改指定輸出文件的父目錄權(quán)限。
                        File file = new File(mRecordPcmFileName);
                        String parentPath = file.getParent();
                        execCommand("chmod 777 "+parentPath,true);

                        // 覆蓋拷貝到指定的文件目錄
                        String pcmFileName = mPcmFileMap.get(record);
                        execCommand("chmod 777 /data/local/tmp/" + pcmFileName + ".pcm", true);
                        execCommand("\\cp /data/local/tmp/" + pcmFileName + ".pcm  " + mRecordPcmFileName, true);
                        Log.i(TAG, "命令 : \\cp /data/local/tmp/" + pcmFileName + ".pcm  " + mRecordPcmFileName);
                        execCommand("chmod 777 " + mRecordPcmFileName, true);
                        // 刪除臨時(shí)文件
                        execCommand("rm /data/local/tmp/" + pcmFileName + ".pcm", true);

                        // 清理map
                        mPcmFileMap.remove(record);
                    }
                }
            } catch (Exception e) {
                Log.i(TAG, "AudioRecord  # stop 里的 afterHookedMethod出錯(cuò) : " + e.getMessage());
            }
        }
    }


    // StartRecordingMethodHook類钧椰,當(dāng)發(fā)送指定語(yǔ)音時(shí)需要hook這個(gè)函數(shù)
    private class StartRecordingMethodHook extends XC_MethodHook{

        // 將recordingState置為RECORDSTATE_RECORDING粹断,打斷微信的發(fā)送過(guò)程,為了發(fā)送自己指定文件嫡霞。
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 0) {
                    // 修改要發(fā)送的語(yǔ)音文件的權(quán)限
                    File file = new File(mSendPcmFileName);
                    String parentName = file.getParent();
                    Log.i(TAG, "parentName: " + parentName);

                    //  創(chuàng)建并修改要發(fā)送的語(yǔ)音文件的父目錄
                    File fileParent = file.getParentFile();
                    if (!fileParent.exists()) {
                        fileParent.mkdirs();
                    }
                    execCommand("chmod 777 " + parentName, true);
                    file.createNewFile();

                    execCommand("chmod 777 " + mSendPcmFileName, true);

                    AudioRecord record = (AudioRecord) param.thisObject;
                    int flag = -1;

                    // 將錄音狀態(tài)置為RECORDSTATE_RECORDING狀態(tài)
                    if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_RECORDING) {
                        flag = AudioRecord.RECORDSTATE_RECORDING;
                        mRecordingFlagMap.put(record, flag);
                    }

                    // 打斷微信的錄音過(guò)程
                    Object o = new Object();
                    param.setResult(o);

                }
            } catch (Exception e) {
                Log.i(TAG, "AudioRecord #  startRecording  beforeHookedMethod  出錯(cuò)");
                Log.i(TAG, "出錯(cuò)原因 —— " + e.getMessage());
            }
        }
    }


    // getRecordingState瓶埋,獲取我們?cè)趕tartRecording和Stop中維護(hù)的state的值
    private class GetRecordingStateMethodHook extends  XC_MethodHook{
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 0) {

                    // 獲取我們?cè)趕tartRecording和Stop中維護(hù)的state的值
                    AudioRecord record = (AudioRecord) param.thisObject;
                    int res = mRecordingFlagMap.get(record) == null ? AudioRecord.RECORDSTATE_STOPPED : mRecordingFlagMap.get(record);
                    // 將返回值給微信
                    param.setResult(res);
                    // 清理mRecordFlagMap
                    mRecordingFlagMap.remove(record);

                }
            } catch (Exception e) {
                Log.i(TAG, "AudioRecord # getRecordingState beforeHookedMethod 出錯(cuò): " + e.getMessage());
            }
        }
    }

    // release方法,打斷微信的
    private class ReleaseMethodHook extends XC_MethodHook{
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                getCurrentModeAndPath();
                if (mMode == 0) {
                    Log.i(TAG, "AudioRecord # release beforeHookedMethod ");
                    Object o = new Object();
                    param.setResult(o);
                }
            }catch (Exception e){
                Log.i(TAG, "AudioRecord # release beforeHookedMethod 出錯(cuò): " + e.getMessage());
            }

        }
    }

這里就是自己維護(hù)整個(gè)AudioRecord的操作流程诊沪,主要的操作就是hook了read方法里面的before回調(diào)养筒,利用FileInputStream將數(shù)據(jù)寫入到byte[ ]中,然后將這個(gè)byte數(shù)組讀到微信中端姚,接著通過(guò)setResult就是修改了微信的語(yǔ)音數(shù)據(jù)

// 創(chuàng)建byte[]數(shù)據(jù)晕粪,用來(lái)替換微信的buffer
int min = Math.min(buffer.length - off, size);
byte[] bytes = new byte[min];
// 將指定的語(yǔ)音文件讀取到微信的語(yǔ)音文件,實(shí)現(xiàn)替換發(fā)送指定語(yǔ)音
int res = fis.read(bytes);
     if (res == -1) {
           param.setResult(0);
     } else {
           for (int i = 0; i < bytes.length; i++) {
                 buffer[off + i] = bytes[i];
           }
           param.setResult(res);
     }

最后在其他的方法的before回調(diào)中維護(hù)好這個(gè)過(guò)程即可渐裸,到這里我們就解決了第二個(gè)問(wèn)題了巫湘。

當(dāng)然,因?yàn)殇浿聘l(fā)送是兩個(gè)不同的操作昏鹃,所以這里通過(guò)設(shè)置一個(gè)mode來(lái)維護(hù)切換操作尚氛,再書寫接入文檔,說(shuō)明這個(gè)模塊的使用方法盆顾。

這個(gè)項(xiàng)目默認(rèn)可以錄制跟替換5條語(yǔ)音數(shù)據(jù)怠褐,你也可以修改實(shí)現(xiàn)你想要的數(shù)目等畏梆。

最后您宪,附上github地址
https://github.com/carrys17/HookWxYYDemo

嚴(yán)重說(shuō)明:本文的目的只有一個(gè)就是學(xué)習(xí)逆向分析技巧,如果有人利用本文技術(shù)進(jìn)行非法操作帶來(lái)的后果都是操作者自己承擔(dān)奠涌,和本文以及本文作者沒(méi)有任何關(guān)系

2018/6/25補(bǔ)充

今天發(fā)現(xiàn)在模擬器上發(fā)送指定語(yǔ)音失敗宪巨,經(jīng)過(guò)測(cè)試后發(fā)現(xiàn)需要hook AudioRecord的構(gòu)造函數(shù),該構(gòu)造函數(shù)有5個(gè)參數(shù)溜畅,其第五個(gè)參數(shù)bufferSizeInBytes的值在模擬器和真機(jī)上有區(qū)別捏卓,所以將模擬器的值hook后修改為真機(jī)上的值即可。代碼的修改即在Module里面增加對(duì)構(gòu)造函數(shù)的hook

 // --- 構(gòu)造方法int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
                //            int bufferSizeInBytes
                XposedHelpers.findAndHookConstructor("android.media.AudioRecord", loadPackageParam.classLoader,
                        int.class, int.class, int.class, int.class, int.class, new XC_MethodHook() {
                            @Override
                            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                                Log.i(TAG, "AudioRecord # 構(gòu)造方法beforeHookedMethod: ");
                                //  修改
                                param.args[0] = 1;
                                param.args[1] = 16000;
                                param.args[2] = 2;
                                param.args[3] = 2;
                                param.args[4] = 12800;
                                
                            }
                            
                        });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市怠晴,隨后出現(xiàn)的幾起案子遥金,更是在濱河造成了極大的恐慌,老刑警劉巖蒜田,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稿械,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡冲粤,警方通過(guò)查閱死者的電腦和手機(jī)美莫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梯捕,“玉大人厢呵,你說(shuō)我怎么就攤上這事】耍” “怎么了襟铭?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)短曾。 經(jīng)常有香客問(wèn)我蝌矛,道長(zhǎng),這世上最難降的妖魔是什么错英? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任入撒,我火速辦了婚禮,結(jié)果婚禮上椭岩,老公的妹妹穿的比我還像新娘茅逮。我一直安慰自己,他們只是感情好判哥,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布献雅。 她就那樣靜靜地躺著,像睡著了一般塌计。 火紅的嫁衣襯著肌膚如雪挺身。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天锌仅,我揣著相機(jī)與錄音章钾,去河邊找鬼。 笑死热芹,一個(gè)胖子當(dāng)著我的面吹牛贱傀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伊脓,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼府寒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起株搔,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剖淀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纤房,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祷蝌,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年帆卓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巨朦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剑令,死狀恐怖糊啡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吁津,我是刑警寧澤棚蓄,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站碍脏,受9級(jí)特大地震影響梭依,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜典尾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一役拴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钾埂,春花似錦河闰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至髓考,卻和暖如春部念,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氨菇。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工儡炼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人门驾。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓射赛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親奶是。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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