Synalyze It!的實(shí)現(xiàn)

1晤碘、起因

從今年1月1日開始涨醋,打算選擇一個(gè)自己一直想做的韭山,又不那么容易完成的事情郁季,于是想到了以前多次動(dòng)手,但是從未完成的軟件“任意文件格式的文件結(jié)構(gòu)分析工具”钱磅。

2009年:

  • 計(jì)劃解析swf文件內(nèi)的圖片梦裂,音頻資源,手工分析了20%左右续搀,沒有時(shí)間繼續(xù)分析
  • 分析了不少音視頻格式與它的封裝 (RTMP, mp3, mp4...)
  • FFmpeg中有個(gè)缺陷塞琼,aiff音頻文件的總時(shí)長沒有寫入文件,任何播放器都無法拖動(dòng)進(jìn)度條禁舷,需要在生成的文件中打補(bǔ)丁寫入總時(shí)長
  • 看到CSDN上有人做過類似的軟件彪杉,能分析固定大小毅往,順序的的結(jié)構(gòu)體,功能相對單一派近,還不能滿足需求

2014年(約)

  • iOS系統(tǒng)解析m4a文件攀唯,有時(shí)不能及時(shí)獲得總時(shí)長與seek點(diǎn),需要手工分析渴丸。

2016年

  • 搜索到了一款軟件 Synalyze It!侯嘀,這就是我想要的完美軟件,憑著愛心我購買了谱轨,這個(gè)軟件的核心是利用固定的分析引擎戒幔,把語法文件(.grammar)被分析文件(.mp3, .mp4)*粘合在一起,生成一個(gè)新的文件結(jié)構(gòu)樹土童,每一個(gè)數(shù)的枝葉對應(yīng)到被分析文件的具體某個(gè)字節(jié)诗茎,并標(biāo)明含義,該軟件特別之處包含:
  1. 支持Python和lua腳本献汗,方便在復(fù)雜的條件下進(jìn)行具體分析敢订,比如zip文件需要先分析文件末尾,PE文件包含結(jié)構(gòu)體循環(huán)嵌套罢吃。
  2. 部分屬性楚午,比如結(jié)構(gòu)體長度,循環(huán)次數(shù)支持表達(dá)式尿招,這就為大部分通用容器類文件提供了很好的支持矾柜,因?yàn)榻Y(jié)構(gòu)體的大小依賴于結(jié)構(gòu)體內(nèi)第一個(gè)或者第二個(gè)元素的值。
  3. 支持結(jié)構(gòu)體派生泊业,減少了重復(fù)勞動(dòng)把沼,比如PNG文件的結(jié)構(gòu),都是存在基類吁伺,根據(jù)基類中的第一個(gè)字段來決定具體是用哪一個(gè)類
  4. 支持分析某一個(gè)bit的值

2017年

  • 多次想動(dòng)手寫饮睬,一共寫不超過100句話,總是放棄了篮奄,突破不了自己的思維局限捆愁,也沒有時(shí)間
  • 因?yàn)槭謾C(jī)需要支持wma,分析了一遍wma文件格式窟却,分析過程也花了不少時(shí)間

2019年

  • 分析pdf文件

2021年

  • 計(jì)劃做一個(gè)和Synalyze It!一模一樣的軟件昼丑,不能馬馬虎虎的做,做之前就打算再做好之后要回憶做的過程中的思考夸赫,也能預(yù)測到做完之后就不想回憶了菩帝,這是很矛盾的。
  • 不要自己創(chuàng)新,完全抄襲呼奢,我不可能做的比它更好

2宜雀、現(xiàn)狀

經(jīng)過一個(gè)來月的努力,每一次技術(shù)的選擇都很精心準(zhǔn)備握础,已經(jīng)完美支持Synalyze It!軟件中核心功能辐董,不過還是控制臺的,沒有GUI禀综,如下的截圖简烘。

解析jpg文件

3、過程

3.1 技術(shù)選型

主要語言:
常用的可以發(fā)布到Store的語言包含C/C++定枷,C#孤澎,OC,Swift依鸥,因?yàn)镃/C++太啰嗦亥至,OC和Swift在Windows平臺上沒有優(yōu)秀的IDE支持,所以選擇C#語言贱迟,C#語言聽說也可以編譯成Apple Store的包,自己曾經(jīng)試過的確可以開發(fā)Mac App應(yīng)用絮供。
腳本語言:
程序中有三個(gè)地方用到了可能是腳本的地方

  1. 結(jié)構(gòu)體或者結(jié)構(gòu)體內(nèi)成員的屬性衣吠,比如占用字節(jié)長度,重復(fù)次數(shù)壤靶,可能是表達(dá)式比如缚俏,根據(jù)Synalyze It!的文檔,沒有說明用的是什么語法贮乳,只是說明支持的表達(dá)式很多忧换,但都是一句話表達(dá)式
// 各種表達(dá)式
used_bytes_count + 5
ceil(sqr(used_bytes_count)) + offset
  1. 結(jié)構(gòu)體內(nèi)可能包含一個(gè)script代碼,該script只解析該結(jié)構(gòu)體內(nèi)的一個(gè)內(nèi)容向拆,不解析結(jié)構(gòu)體外的內(nèi)容亚茬,比如把剛解析出來的unix time轉(zhuǎn)換成 UTC 字符串作為結(jié)果顯示到結(jié)果樹中, 這一段代碼能用到的函數(shù)比較多浓恳,都在幫助文檔中說明刹缝,能用到的全局變量并沒有說明,需要閱讀代碼颈将,比如:
# 復(fù)雜的script代碼
endFound = False
theOffset = currentOffset
byteView = currentMapper.getCurrentByteView()
results = currentMapper.getCurrentResults()
grammar = currentMapper.getCurrentGrammar()

while not endFound:
    theByte = byteView.readByte(theOffset);
    if (theByte == 0xff):
        theSecondByte = byteView.readByte(theOffset + 1);
        if ((theSecondByte > 0) and (theSecondByte < 0xFF)):
            endFound = True
            theValue = Value();
            theValue.setString("EOF");
            struct = grammar.getStructureByName("ImageBytes")
            element = struct.getElementByName("ImageBytes")
            length = theOffset - currentOffset
            currentMapper.mapElementWithSize(element, length);

    theOffset = theOffset + 1;

該代碼中currentOffset的含義需要多閱讀積分代碼確定

  1. 作為文件級別通用的script代碼, 這類script有固定的函數(shù)申明梢夯,用于處理指定區(qū)間內(nèi)的數(shù)據(jù)
# 解析指定區(qū)間內(nèi)的數(shù)據(jù)
from datetime import datetime, timedelta

def parseByteRange(element, byteView, bitPos, bitLength, results):

    timeStamp = byteView.readUnsignedInt(bitPos/8, 4, ENDIAN_LITTLE)

    value = Value()

    if (timeStamp != 0):
        dt = datetime.fromtimestamp(timeStamp)
        dtAdjusted = dt - timedelta(hours=1)
        dateString = dtAdjusted.strftime("%Y-%m-%d %H:%M:%S")
        value.setString(dateString)
    else:
        value.setString("<not set>")

    results.addElement(element, 4, 0, value)

    return 4

對于2,3的script代碼晴圾,已知的語法有python和lua颂砸,分別有110多個(gè)和40多個(gè),那么現(xiàn)在要選擇什么作為腳本語言呢?有幾種選擇

  • C# 非常容易和主要語言C#集成人乓,可惜Store版本中不能把C#作為腳本語言梗醇,因?yàn)椴恢С謩?dòng)態(tài)生成可執(zhí)行代碼
  • Javascript 已知的實(shí)現(xiàn)JINT庫,這個(gè)庫的代碼寫的非常漂亮撒蟀,沒有任何使用特殊權(quán)限的地方叙谨,所以不會和Store的限制相互沖突,也很方便自定義新的函數(shù)來滿足2中提到的表達(dá)式保屯,唯一的缺陷是需要把現(xiàn)有的150個(gè)腳本手工寫成Javascript的手负,寫代碼不會花費(fèi)很長時(shí)間,問題在于很多文件格式?jīng)]有現(xiàn)成的姑尺,難以把握質(zhì)量
  • Python C#語言庫中成熟的Python庫是IronPython竟终,需要測試是否和Store限制沖突,因?yàn)槲覀冎繮ython中有很多Process切蟋,Event相關(guān)的內(nèi)容统捶,肯定是無法提交到Store的,于是找到了IronPython源代碼柄粹,嘗試去掉那些功能喘鸟,這里花了不少時(shí)間,具體過程如下
  1. 現(xiàn)在源代碼編譯驻右,編譯不過什黑,不像Javascript的JINT庫,沒有其他依賴堪夭,IronPython依賴Microsoft DLR庫愕把,這個(gè)DIR庫目前只給IronPython和IronLua用,還不清楚DLR庫離開了這兩個(gè)實(shí)現(xiàn)要怎么用
  2. 測試是否可以自定義模塊森爽,自定義函數(shù)
  3. 編譯通過后恨豁,找到啟用新進(jìn)程部分,發(fā)現(xiàn)屏蔽方式是依靠條件編譯爬迟,找到了條件編譯文件是在.sln目錄下的一個(gè)固定文件名叫做Directory.Build.props橘蜜,找這個(gè)文件花了一些時(shí)間,因?yàn)檫@個(gè)文件不在解決方案目錄樹中雕旨,無法在IDE中搜索到扮匠,說明了一切隱含的東西,都會讓人迷糊凡涩,比如C++的那么多奇怪的隱藏行為
  4. 不斷的調(diào)整條件編譯和編譯Store包棒搜,用測試程序自檢
  • Lua 前期并沒有打算實(shí)現(xiàn)Lua腳本部分,后來也是懶得寫40多個(gè)腳本活箕,Lua的C#實(shí)現(xiàn)比較好找便斥,為了方便查找Exception,也是找了源碼來參與編譯佛吓。

所以腳本最后的選擇是Python+Lua

3.2 主要難點(diǎn)

3.2.1 結(jié)構(gòu)體的派生

問題:
已知兩個(gè)結(jié)構(gòu)體parent和child,child從parent派生闺鲸,parent定義了確定的元素和順序a,b,c, child定義可能是以下情況

  1. 只包含parent中的元素,元素的順序保持不變
  2. 只包含parent中的元素埃叭,元素的順序和parent中不同[難]
  3. 不包含parent中任何元素
  4. 即包含parent中的元素摸恍,又包含新元素,而且parent中元素在先定義赤屋,且順序保持不變
  5. 即包含parent中的元素立镶,又包含新元素,而且parent中元素在先定義类早,順序發(fā)生變化[難]
  6. 即包含parent中的元素媚媒,又包含新元素,而且parent中元素定義位置不確定涩僻,順序也不確定[難]

想起6那種復(fù)雜的場景缭召,雖然人工可以簡單的感覺到答案,但是代碼實(shí)現(xiàn)優(yōu)雅逆日,不容易想到嵌巷,于是先寫好了測試用例

// 派生關(guān)系的測試用例
public static void TestJoinKeys()
{
    List<(string, string, string)> data = new List<(string, string, string)>()
    {
        ("","",""),
        ("1","","1"),
        ("1,2,3","","1,2,3"),
        ("1,2,3","1,2,3","1,2,3"),
        ("1,2,3","1,2,3,4","1,2,3,4"),
        ("1,2,3","2","1,2,3"),
        ("1,2,3","2,3","1,2,3"),
        ("1,2,3","2,3,4","1,2,3,4"),
        ("","1,2,3","1,2,3"),
        ("1,2,3","1,3","1,2,3"),
        ("1,2,3,4,5","2,4","1,2,3,4,5"),
        ("1,2,3,4,5","1","1,2,3,4,5"),
        ("1,2,3,4,5","1,2","1,2,3,4,5"),
        ("1,2,3,4,5","5","1,2,3,4,5"),
        ("1,2,3,4,5","7,8","1,2,3,4,5,7,8"),
        ("1,2,3,4,5","1,2,7,8","1,2,3,4,5,7,8"),
        ("1,2,3,4,5","1,3,2","1,3,2"),
        ("1,2,3,4,5","3,2","1,3,2"),
        ("1,2,3,4,5,6,7,8","3,5,7","1,2,3,4,5,6,7,8"),
        ("1,2,3,4,5,6,7,8","2,4,9,10,11,12","1,2,3,4,5,6,7,8,9,10,11,12"),
    };
    foreach(var dataItem in data)
    {
        var parent = dataItem.Item1.Split(',').Where(s => s.Length > 0).ToList();
        var current = dataItem.Item2.Split(',').Where(s => s.Length > 0).ToList();
        var result = dataItem.Item3.Split(',').Where(s => s.Length > 0).ToList();
        var joined = JoinKeys(parent, current);
        Debug.Assert(joined.Count == result.Count);
        foreach(var i in Enumerable.Range(0, joined.Count))
        {
            Debug.Assert(joined[i] == result[i]);
        }
    }
}

最后總結(jié)出還算合理的規(guī)則,如下

// 派生關(guān)系的合并
while (parent.length > 0 && child.length > 0)
{
      if (parent或者child其中一個(gè)長度為0)
      {
          把另外一個(gè)全部添加到結(jié)果列表
          退出循環(huán)
      }
      if (parent.first == child.first)
      {
            則加入到結(jié)果屏富,同時(shí)parent和child都刪除第一個(gè)元素       
            繼續(xù)循環(huán)     
      }  
      else
      {
          if (child.contains(parent.first))
          {
              則說明child已經(jīng)開始自定義剩下的部分了晴竞,把child剩下部分都加入到結(jié)果列表
              退出循環(huán)
          }
          else
          {
              則說明child沒有覆蓋定義parent中該元素定義,把parent中的第一個(gè)元素加入結(jié)果列表
              繼續(xù)循環(huán)
          }
      }
}

3.2.2 結(jié)構(gòu)體占用空間的計(jì)算

結(jié)構(gòu)體占用空間的定義有三種

  1. 寫明了多少字節(jié)狠半,比如12字節(jié),或者0x22字節(jié)
  2. 沒有定義颤难,說明是根據(jù)元素占用來計(jì)算神年,比如子元素一共占用12字節(jié),說明結(jié)構(gòu)體一共占用12字節(jié)
  3. 是一個(gè)表達(dá)式行嗤,表達(dá)式可能是之前已經(jīng)計(jì)算過的其他元素的值已日,也可能是該結(jié)構(gòu)體子元素的值,也就意味著他的值需要等他的子元素解析后才知道結(jié)果栅屏。

前兩者的實(shí)現(xiàn)比較容易飘千,第3種中,首先需要分析出表達(dá)式中所有的變量栈雳,變量是否已經(jīng)定義护奈,行為有所不同,如果遇到子元素名稱和以往解析過的名稱相同哥纫,該使用哪一個(gè)值呢霉旗?因此,我解析出了Synalyze It!中所有的表達(dá)式1000多個(gè),利用簡單而實(shí)用的函數(shù)解析出所有的變量

static List<String> GetVarsFromExpression(string expression)
{
    HashSet<string> functionNames = new HashSet<string>() { "ceil", "pow", "mod", "select", "if", "abs", "prev", "this", "Math.", "this." };
    List<String> r = expression.Split(new char[] { '+', '-', '*', '/', '(', ')', ',', '^', '.' })
        .Select(item => item.Trim())
        .Distinct()
        .Where(item => item.Length > 0)
        .Where(item => functionNames.Contains(item) == false)
        .Where(item => Char.IsDigit(item[0]) == false).ToList();
    return r;
}

檢查每一個(gè)變量如果同時(shí)出現(xiàn)在子元素和其他結(jié)構(gòu)體的行為厌秒,具體分析后得出結(jié)論:如果變量存在于子元素读拆,則一定意味著子元素,不意味著其他結(jié)構(gòu)體中已解析變量鸵闪。

那么該如何寫代碼呢檐晕?難道每解析一個(gè)子元素就嘗試計(jì)算一次表達(dá)式,如果該有的變量還沒有定義蚌讼,解析程序會拋出異常辟灰,異常內(nèi)容并非一個(gè)結(jié)構(gòu)化數(shù)據(jù),不方便知道是不是那個(gè)變量沒有定義引起啦逆。

定義集合伞矩,集合包含了需要解析的子元素列表,當(dāng)最后一個(gè)需要解析的子元素解析了之后夏志,再計(jì)算結(jié)構(gòu)體長度乃坤,這里遇到一個(gè)另外一個(gè)問題,解析出來的長度可能小于已經(jīng)解析過的子元素需要的長度沟蔑,或者長度大于該結(jié)構(gòu)體所可能的最大空間湿诊,這時(shí),整個(gè)結(jié)構(gòu)體都是需要拋棄的瘦材。

// 檢查需要解析的子元素是否都解析好了
HashSet<string> depends = current_result.dic_attribute_depends_map[elementKey];
depends.ExceptWith(lst_just_finished_sub_element_name);
if (depends.Count == 0)
{
    long? tmp = mapContext.scriptInstance.GetScript(ScriptEnv.python).EvalExpression(s);
    Debug.Assert(tmp != null, "全局變量缺失");
    SetDefinedLength(tmp.GetValueOrDefault(0));
    continue_get = false;
    return;
}
else
{
    // 繼續(xù)等待
    return;
}

3.2.3 獲取從任意bit開始厅须,小于64bit長度的數(shù)值

因?yàn)樵氐拈L度可能只有1bit,而不是1byte食棕,所以整個(gè)程序的度量單位必須定義成bit.
如何獲取數(shù)值呢朗和?
首先找到這么多長度的bit,如果bit的長度剛好是8的倍數(shù)簿晓,還比較好處理眶拉,如果不是8的倍數(shù),需要考慮填充bit憔儿,使得取得的長度是8的整數(shù)倍忆植,這些0填在哪里呢?是填入到最前面谒臼,還是最后面朝刊,依賴于數(shù)值的Endian類型,還有一個(gè)特例蜈缤,如果總位數(shù)小于8拾氓,則總是左側(cè)補(bǔ)0, 代碼來說大概是這樣

// bit長度補(bǔ)齊為8的倍數(shù)
if (bit length不是8的倍數(shù))
{
      if (是大端 or bit length < 8)
      {
          左側(cè)補(bǔ)0  
      }
      else
      {
        右側(cè)補(bǔ)0
      }
}
根據(jù)Endian類型轉(zhuǎn)成數(shù)值

由于年事已高劫樟,很難一次性推理出是左側(cè)補(bǔ)0還是右側(cè)補(bǔ)0痪枫,答案都是實(shí)驗(yàn)出來的织堂。

3.2.4 越界保護(hù)

程序中的主要越界保護(hù)用于兩個(gè)地方

  1. 已確定占用空間的某個(gè)元素,如果想獲取它的值(數(shù)值奶陈,字符串)易阳,需要遍歷,寫for語句容易訪問到界外吃粒,比如原本只占用2個(gè)字節(jié)的元素潦俺,想獲取它的Int32值,容易多訪問兩個(gè)字節(jié)
  2. 當(dāng)結(jié)構(gòu)體大小不確定的時(shí)候徐勃,需要盡可能的給結(jié)構(gòu)體分配空間事示,這時(shí)會假定結(jié)構(gòu)體大小是Int64.Max, 如果把Int64.Max用于數(shù)值下標(biāo),會出現(xiàn)異常僻肖。
    還好強(qiáng)大的C#可以一句話解決肖爵,那就是array.skip(m).take(n), 經(jīng)過測試無論m和n怎么越界都不會出現(xiàn)異常,如果越界會盡可能的獲取臀脏,或者返回空的數(shù)組劝堪,這也是為什么我選擇C/C++的一個(gè)重要原因,需要寫太多的保護(hù)邏輯了
// 越界的簡單處理揉稚,一定不會越界
byte[] bytes = byteView.Take(8).ToArray();
if (endianType == ENDIAN_TYPE.ENDIAN_LITTLE)
{
    Array.Reverse(bytes);
}
Int64 v = 0;
foreach (byte b in bytes.Take(8))
{
    Int64 t = b;
    v = v * 256 + t;
}
return v;

3.2.5 Script的集成

script的集成主要準(zhǔn)備好script環(huán)境秒啦,把script可能要使用的變量準(zhǔn)備好,這需要多看script代碼才知道要在程序中定義哪些變量搀玖,答案是

  1. 所有已經(jīng)解析過的number類型字段的值需要定義到script環(huán)境中
  2. 需要定義變量currentOffset余境,表明當(dāng)前分析到哪個(gè)字節(jié)了
    這兩種的定義方法如下
// 為腳本定義變量
IScript script = mapContext.scriptInstance.GetScript(this.scriptLanguage.ToLower() == "python" 
                                                        ? ScriptEnv.python 
                                                        : ScriptEnv.lua);
script.SetValue("currentOffset", currentOffset);
  1. 需要定義一些全局變量,比如ENDIAN_LITTLE灌诅,有兩種定義辦法芳来,一種是定義ENDIAN_LITTLE為string類型,一種是定義為枚舉類型猜拾,如果定義成枚舉類型绣张,script中需要增加枚舉類型前綴變成ENDIAN_TYPE.ENDIAN_LITTLE,為了不修改script关带,定義方法如下
//  在IronPython.Modules工程內(nèi)添加代碼文件,如下
namespace IronPython.Modules 
{
    public partial class file_structure_plugin
    {   
        public static readonly string ENDIAN_LITTLE = "ENDIAN_LITTLE";
        public static void logMessage(String module, int messageID, string severity, String message)
        {
            // TODO
        }
        ...
    }
}

通過上面的代碼可以看到沼撕,我們也為Python腳本添加了全局函數(shù)logMessage

lua腳本中添加ENDIAN_LITTLE的方式暫時(shí)沒有實(shí)現(xiàn)宋雏,等需要的時(shí)候再實(shí)現(xiàn)。

3.2.6 調(diào)試

整個(gè)調(diào)試過程务豺,都是先運(yùn)行一遍磨总,看結(jié)果和Synalyze It!從哪個(gè)地方開始不一樣的,不一樣的地方笼沥,增加條件斷點(diǎn)蚪燕,因?yàn)檎麄€(gè)引擎的核心地方可能就是1-2個(gè)循環(huán)娶牌,檢查的地方就是那1-2個(gè)函數(shù)對于結(jié)構(gòu)體復(fù)雜的文件,那簡直就是圓環(huán)套圓環(huán)馆纳,頭很暈诗良,,只能用結(jié)構(gòu)體id或者文件偏移當(dāng)做斷點(diǎn)的條件鲁驶,一旦有修改鉴裹,則把之前已經(jīng)測試過的文件重新運(yùn)行一遍,對關(guān)鍵地方進(jìn)行重新檢查钥弯。

// 很多測試用例
static void Main(string[] args)
{
    ElementStructure.TestJoinKeys();        // 結(jié)構(gòu)體派生

    test_bit_padding();                     // bit位數(shù)不是8倍數(shù)時(shí)候的填充            
    test_all_element_express_is_number();   // 所有表達(dá)式是否能被識別
    test_all_script_content();              // 所有的腳本的類型
    test_offset_elements();                 // Offset類型的元素
    testIronPython();                       // IronPython是否支持import語法

    test_parse_one_file("jpeg.grammar",         "1.jpg");    
    test_parse_one_file("pe.grammar",           "1.dll");   // 復(fù)雜的結(jié)構(gòu)體
    test_parse_one_file("gzip.grammar",         "1.gz");    // custom script
    test_parse_one_file("png.grammar",          "1.png");   // "must_match == true"
    test_parse_one_file("wav.grammar",          "1.wav");   
    test_parse_one_file("mp3.grammar",          "1.mp3");   // python script
    test_parse_one_file("qt.grammar",           "1.mp4");   // 復(fù)雜的結(jié)構(gòu)體
    test_parse_one_file("flac.grammar",         "1.flac");  // 解析bit
    test_parse_one_file("zip.grammar",          "1.zip");   // lua script
    test_parse_one_file("test_padding.grammar", "1.flac");  // alignment
    test_parse_one_file("bitmap.grammar",       "1.bmp");   // offset
    

    Debug.WriteLine("Hello World!");
}

4 總結(jié)

  • 要想做的心里踏實(shí)径荔,把事情調(diào)查清楚,的確需要花費(fèi)很多時(shí)間
  • 測試用例還是需要寫脆霎,多多的寫
  • 對于可能發(fā)生的总处,但是不應(yīng)該發(fā)生的,多多寫斷言
  • 今年心愿已了睛蛛,明年還有機(jī)會的話再寫另外一個(gè)心愿

5 擔(dān)憂

版權(quán)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹦马,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子玖院,更是在濱河造成了極大的恐慌菠红,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难菌,死亡現(xiàn)場離奇詭異试溯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)郊酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門遇绞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人燎窘,你說我怎么就攤上這事摹闽。” “怎么了褐健?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵付鹿,是天一觀的道長。 經(jīng)常有香客問我蚜迅,道長舵匾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任谁不,我火速辦了婚禮坐梯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刹帕。我一直安慰自己吵血,他們只是感情好谎替,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹋辅,像睡著了一般钱贯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晕翠,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天喷舀,我揣著相機(jī)與錄音,去河邊找鬼淋肾。 笑死硫麻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的樊卓。 我是一名探鬼主播拿愧,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碌尔!你這毒婦竟也來了浇辜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唾戚,失蹤者是張志新(化名)和其女友劉穎柳洋,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叹坦,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熊镣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了募书。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绪囱。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖莹捡,靈堂內(nèi)的尸體忽然破棺而出鬼吵,到底是詐尸還是另有隱情,我是刑警寧澤篮赢,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布齿椅,位于F島的核電站,受9級特大地震影響启泣,放射性物質(zhì)發(fā)生泄漏媒咳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一种远、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顽耳,春花似錦坠敷、人聲如沸妙同。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粥帚。三九已至,卻和暖如春限次,著一層夾襖步出監(jiān)牢的瞬間芒涡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工卖漫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留费尽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓羊始,卻偏偏與公主長得像旱幼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子突委,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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

  • cordova/channel 從factory轉(zhuǎn)變成exports 我們從cordova 的factory 中轉(zhuǎn)...
    充滿活力的早晨閱讀 597評論 0 0
  • IT軟件開發(fā)常用英語詞匯 Aabstract抽象的 abstract base class抽象基類 abstrac...
    豌豆小公主small閱讀 937評論 0 0
  • 組件(Component)是 Vue.js 最核心的功能柏卤,也是整個(gè)框架設(shè)計(jì)最精彩的地方,當(dāng)然也是最難掌握的匀油。本章將...
    遼A丶孫悟空閱讀 556評論 1 13
  • 久違的晴天缘缚,家長會。 家長大會開好到教室時(shí)敌蚜,離放學(xué)已經(jīng)沒多少時(shí)間了桥滨。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評論 16 22
  • 今天感恩節(jié)哎钝侠,感謝一直在我身邊的親朋好友该园。感恩相遇!感恩不離不棄帅韧。 中午開了第一次的黨會里初,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,564評論 0 11