译]用lua创建一个Wireshark解剖器系列3(解析负载)

在第1部分和第2部分中,我们查看了MongoDB有线协议消息的消息头。这次是解析消息内容的时候了。但是,我们实际上不会解码MongoDB返回的文档,因为这不属于本教程的范围。

解码OP_QUERY消息

OP_QUERY消息用于在数据库中查询集合中的文档。此消息的格式为:


各种领域的含义可以在specification中看到。在消息头中我们只需要处理int32s,但现在我们也有一个字符串。我们可以从解析flags字段开始:

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length = ProtoField.int32("mongodb.message_length", "messageLength", base.DEC)
request_id     = ProtoField.int32("mongodb.requestid"     , "requestID"    , base.DEC)
response_to    = ProtoField.int32("mongodb.responseto"    , "responseTo"   , base.DEC)
opcode         = ProtoField.int32("mongodb.opcode"        , "opCode"       , base.DEC)

-- Payload fields
flags          = ProtoField.int32("mongodb.flags"         , "flags"        , base.DEC)

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,    -- Header
  flags                                               -- OP_QUERY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
  length = buffer:len()
  if length == 0 then return end

  pinfo.cols.protocol = mongodb_protocol.name

  local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")

  -- Header
  subtree:add_le(message_length, buffer(0,4))
  subtree:add_le(request_id,     buffer(4,4))
  subtree:add_le(response_to,    buffer(8,4))

  local opcode_number = buffer(12,4):le_uint()
  local opcode_name = get_opcode_name(opcode_number)
  subtree:add_le(opcode,         buffer(12,4)):append_text(" (" .. opcode_name .. ")")

  -- Payload
  if opcode_name == "OP_QUERY" then
    local flags = buffer(16,4):le_uint()
    subtree:add_le(flags,      buffer(16,4))
  end
end

function get_opcode_name(opcode)
  ...
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

为了使消息的头部和实际有效负载之间的区别更加清晰,我们将使用注释来显示不同部分的起始位置。因为不同的操作码具有不同的结构,我们必须使用if语句检查我们正在解析的消息类型。我们只是在上面的代码中解析OP_QUERY消息。
该脚本现在开始变得很大,所以我将开始缩短我们之前已经看过的内容...
因此,现在,flags字段显示在子树中,用于OP_QUERY消息。与opcode类似,如果可以在值旁边的括号中描述标志值,那将是很好的。值的描述可在规范中找到。与操作码描述一样,我们创建一个查找函数来获取flag描述:

function get_flag_description(flags)
  local flags_description = "Unknown"

      if flags == 0 then flags_description = "Reserved"
  elseif flags == 1 then flags_description = "TailableCursor"
  elseif flags == 2 then flags_description = "SlaveOk.Allow"
  elseif flags == 3 then flags_description = "OplogReplay"
  elseif flags == 4 then flags_description = "NoCursorTimeout"
  elseif flags == 5 then flags_description = "AwaitData"
  elseif flags == 6 then flags_description = "Exhaust"
  elseif flags == 7 then flags_description = "Partial"
  elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

  return flags_description
end

然后更改我们将字段添加到子树的方式:

if opcode_name == "OP_QUERY" then
    local flags = buffer(16,4):le_uint()
    local flags_description = get_flag_description(flags)
    subtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")
end

对于带有OP_QUERY操作码的消息,MongoDB子树将如下所示:


flag字段不会出现在其他消息中,因为它们永远不会进入OP_QUERY if块。
下一个字段与前一个字段略有不同:我们现在必须解析除了int32之外的其他字段。在这里,它是一个字符串。字符串与其他类型不同,因为它没有固定长度。所以我们必须遍历缓冲区中的字节,直到我们到达字符串的末尾。我们如何确定字符串的结尾取决于它是什么类型的字符串。在这种情况下,它是一个cstring,这意味着字符串由NUL(字节00)终止。

-- Loop over string
local string_length

for i = 20, length - 1, 1 do
  if (buffer(i,1):le_uint() == 0) then
    string_length = i - 20
    break
  end
end

subtree:add_le(full_coll_name, buffer(20,string_length))

我们从字符串的开头(字节20)遍历到整个消息的末尾。然后我们使用buffer(i,1):le_uint()一次读取一个字节,并检查它是否是NUL字节,表示字符串的结尾。如果是,我们将字符串的长度存储在string_length中并打破循环。
然后我们可以将该字段添加到子树中。我们还必须通过将此行添加到脚本顶部来创建该字段:

full_coll_name = ProtoField.string("mongodb.full_coll_name", "fullCollectionName", base.ASCII)

我们可以看到我们这次使用ProtoField的string函数而不是int32。我们还想用ASCII而不是十进制来表示字符串,所以我们必须使用base.ASCII。该字段还必须添加到fields表中:

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,    -- Header
  flags, full_coll_name                               -- OP_QUERY
}

我们现在在数据包详细信息窗格中有了集合名称:


其余的字段很简单。我不会详细解释它们,而是显示最终代码。名为query的字段包含BSON文档,但如前所述,解码它们超出了本文的范围。我将ProtoField.none用于该字段,这是一种可用于非结构化数据的类型。添加OP_QUERY的脚本是:

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length  = ProtoField.int32 ("mongodb.message_length"  , "messageLength"     , base.DEC)
request_id      = ProtoField.int32 ("mongodb.requestid"       , "requestID"         , base.DEC)
response_to     = ProtoField.int32 ("mongodb.responseto"      , "responseTo"        , base.DEC)
opcode          = ProtoField.int32 ("mongodb.opcode"          , "opCode"            , base.DEC)

-- Payload fields
flags           = ProtoField.int32 ("mongodb.flags"           , "flags"             , base.DEC)
full_coll_name  = ProtoField.string("mongodb.full_coll_name"  , "fullCollectionName", base.ASCII)
number_to_skip  = ProtoField.int32 ("mongodb.number_to_skip"  , "numberToSkip"      , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn"    , base.DEC)
query           = ProtoField.none  ("mongodb.query"           , "query"             , base.HEX)

mongodb_protocol.fields = {
    message_length, request_id, response_to, opcode,                  -- Header
    flags, full_coll_name, number_to_skip, number_to_return, query    -- OP_QUERY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
  length = buffer:len()
  if length == 0 then return end

  pinfo.cols.protocol = mongodb_protocol.name

  local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")

  -- Header
  subtree:add_le(message_length, buffer(0,4))
  subtree:add_le(request_id,     buffer(4,4))
  subtree:add_le(response_to,    buffer(8,4))
  local opcode_number = buffer(12,4):le_uint()
  local opcode_name = get_opcode_name(opcode_number)
  subtree:add_le(opcode,         buffer(12,4)):append_text(" (" .. opcode_name .. ")")

  -- Payload
  if opcode_name == "OP_QUERY" then
    local flags_number = buffer(16,4):le_uint()
    local flags_description = get_flag_description(flags_number)
    subtree:add_le(flags,      buffer(16,4)):append_text(" (" .. flags_description .. ")")

    -- Loop over string
    local string_length
    for i = 20, length - 1, 1 do
      if (buffer(i,1):le_uint() == 0) then
        string_length = i - 20
        break
      end
    end

    subtree:add_le(full_coll_name,   buffer(20,string_length))
    subtree:add_le(number_to_skip,   buffer(20+string_length,4))
    subtree:add_le(number_to_return, buffer(24+string_length,4))
    subtree:add_le(query,            buffer(28+string_length,length-string_length-28))
  end
end

function get_opcode_name(opcode)
  local opcode_name = "Unknown"

      if opcode ==    1 then opcode_name = "OP_REPLY"
  elseif opcode == 2001 then opcode_name = "OP_UPDATE"
  elseif opcode == 2002 then opcode_name = "OP_INSERT"
  elseif opcode == 2003 then opcode_name = "RESERVED"
  elseif opcode == 2004 then opcode_name = "OP_QUERY"
  elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
  elseif opcode == 2006 then opcode_name = "OP_DELETE"
  elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
  elseif opcode == 2010 then opcode_name = "OP_COMMAND"
  elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end

  return opcode_name
end

function get_flag_description(flags)
  local flags_description = "Unknown"

      if flags == 0 then flags_description = "Reserved"
  elseif flags == 1 then flags_description = "TailableCursor"
  elseif flags == 2 then flags_description = "SlaveOk.Allow"
  elseif flags == 3 then flags_description = "OplogReplay"
  elseif flags == 4 then flags_description = "NoCursorTimeout"
  elseif flags == 5 then flags_description = "AwaitData"
  elseif flags == 6 then flags_description = "Exhaust"
  elseif flags == 7 then flags_description = "Partial"
  elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

  return flags_description
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

OP_QUERY的数据包详细信息最终如下所示:

解码OP_REPLY消息

我不会解码所有消息,但我们可以再看一遍。它有以下字段:


我们在这里有一个新类型:int64。我们不会触及文档字段,因为它变得复杂。我们制作以下字段:

response_flags =ProtoField.int32 ("mongodb.response_flags" ,"responseFlags" ,base.DEC)
cursor_id      =ProtoField.int64 ("mongodb.cursor_id"      ,"cursorId"      ,base.DEC)
starting_from  =ProtoField.int32 ("mongodb.starting_from"  ,"startingFrom"  ,base.DEC)
number_returned=ProtoField.int32 ("mongodb.number_returned","numberReturned",base.DEC)
documents      =ProtoField.none  ("mongodb.documents"      ,"documents"     ,base.HEX)

我们还必须将字段添加到fields表中:

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,                     -- Header
  flags, full_coll_name, number_to_skip, number_to_return, query,      -- OP_QUERY
  response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}

解析字段的方式如下:

if opcode_name == "OP_QUERY" then
...
elseif opcode_name == "OP_REPLY" then
  local response_flags_number = buffer(16,4):le_uint()
  local response_flags_description = get_response_flag_description(response_flags_number)

  subtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
  subtree:add_le(cursor_id,      buffer(20,8))
  subtree:add_le(starting_from,  buffer(28,4))
  subtree:add_le(number_returned,buffer(32,4))
  subtree:add_le(documents,      buffer(36,length-36))
end

这就像解析其他字段的方式一样。 cursor_id是一个int64,这意味着它长8个字节(8 * 8 = 64)。这意味着我们必须读取8个字节。响应标志的查找函数如下所示:

function get_response_flag_description(flags)
  local flags_description = "Unknown"

      if flags == 0 then flags_description = "CursorNotFound"
  elseif flags == 1 then flags_description = "QueryFailure"
  elseif flags == 2 then flags_description = "ShardConfigStale"
  elseif flags == 3 then flags_description = "AwaitCapable"
  elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end

  return flags_description
end

OP_REPLY消息最终将在数据包详细信息窗格中显示如下:


documents字段几乎未解析。它只是作为一个字符串读取。最终代码如下所示:

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length  = ProtoField.int32 ("mongodb.message_length"  , "messageLength"     , base.DEC)
request_id      = ProtoField.int32 ("mongodb.requestid"       , "requestID"         , base.DEC)
response_to     = ProtoField.int32 ("mongodb.responseto"      , "responseTo"        , base.DEC)
opcode          = ProtoField.int32 ("mongodb.opcode"          , "opCode"            , base.DEC)

-- Payload fields
flags           = ProtoField.int32 ("mongodb.flags"           , "flags"             , base.DEC)
full_coll_name  = ProtoField.string("mongodb.full_coll_name"  , "fullCollectionName", base.ASCII)
number_to_skip  = ProtoField.int32 ("mongodb.number_to_skip"  , "numberToSkip"      , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn"    , base.DEC)
query           = ProtoField.none  ("mongodb.query"           , "query"             , base.HEX)

response_flags  = ProtoField.int32 ("mongodb.response_flags"  , "responseFlags"     , base.DEC)
cursor_id       = ProtoField.int64 ("mongodb.cursor_id"       , "cursorId"          , base.DEC)
starting_from   = ProtoField.int32 ("mongodb.starting_from"   , "startingFrom"      , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned"    , base.DEC)
documents       = ProtoField.none  ("mongodb.documents"       , "documents"         , base.HEX)

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,                     -- Header
  flags, full_coll_name, number_to_skip, number_to_return, query,      -- OP_QUERY
  response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
  length = buffer:len()
  if length == 0 then return end

  pinfo.cols.protocol = mongodb_protocol.name

  local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")

  -- Header
  subtree:add_le(message_length, buffer(0,4))
  subtree:add_le(request_id,     buffer(4,4))
  subtree:add_le(response_to,    buffer(8,4))
  local opcode_number = buffer(12,4):le_uint()
  local opcode_name = get_opcode_name(opcode_number)
  subtree:add_le(opcode,         buffer(12,4)):append_text(" (" .. opcode_name .. ")")

  -- Payload
  if opcode_name == "OP_QUERY" then
    local flags_number = buffer(16,4):le_uint()
    local flags_description = get_flag_description(flags_number)
    subtree:add_le(flags,      buffer(16,4)):append_text(" (" .. flags_description .. ")")

    -- Loop over string
    local string_length

    for i = 20, length - 1, 1 do
      if (buffer(i,1):le_uint() == 0) then
        string_length = i - 20
        break
      end
    end

    subtree:add_le(full_coll_name,   buffer(20,string_length))
    subtree:add_le(number_to_skip,   buffer(20+string_length,4))
    subtree:add_le(number_to_return, buffer(24+string_length,4))
    subtree:add_le(query,            buffer(28+string_length,length-string_length-28))
  elseif opcode_name == "OP_REPLY" then
    local response_flags_number = buffer(16,4):le_uint()
    local response_flags_description = get_response_flag_description(response_flags_number)

    subtree:add_le(response_flags,   buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
    subtree:add_le(cursor_id,        buffer(20,8))
    subtree:add_le(starting_from,    buffer(28,4))
    subtree:add_le(number_returned,  buffer(32,4))
    subtree:add_le(documents,        buffer(36,length-36))
  end
end

function get_opcode_name(opcode)
  local opcode_name = "Unknown"

      if opcode ==    1 then opcode_name = "OP_REPLY"
  elseif opcode == 2001 then opcode_name = "OP_UPDATE"
  elseif opcode == 2002 then opcode_name = "OP_INSERT"
  elseif opcode == 2003 then opcode_name = "RESERVED"
  elseif opcode == 2004 then opcode_name = "OP_QUERY"
  elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
  elseif opcode == 2006 then opcode_name = "OP_DELETE"
  elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
  elseif opcode == 2010 then opcode_name = "OP_COMMAND"
  elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end

  return opcode_name
end

function get_flag_description(flags)
  local flags_description = "Unknown"

      if flags == 0 then flags_description = "Reserved"
  elseif flags == 1 then flags_description = "TailableCursor"
  elseif flags == 2 then flags_description = "SlaveOk.Allow"
  elseif flags == 3 then flags_description = "OplogReplay"
  elseif flags == 4 then flags_description = "NoCursorTimeout"
  elseif flags == 5 then flags_description = "AwaitData"
  elseif flags == 6 then flags_description = "Exhaust"
  elseif flags == 7 then flags_description = "Partial"
  elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

  return flags_description
end

function get_response_flag_description(flags)
  local flags_description = "Unknown"

      if flags == 0 then flags_description = "CursorNotFound"
  elseif flags == 1 then flags_description = "QueryFailure"
  elseif flags == 2 then flags_description = "ShardConfigStale"
  elseif flags == 3 then flags_description = "AwaitCapable"
  elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end

  return flags_description
end

local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)

脚本中缺少其余的操作码。我不会进一步详细介绍这些内容,因为它只是重复我上面所写的内容。

mongodb_protocol = Proto("MongoDB",  "MongoDB Protocol")

-- Header fields
message_length  = ProtoField.int32 ("mongodb.message_length"  , "messageLength"     , base.DEC)
request_id      = ProtoField.int32 ("mongodb.requestid"       , "requestID"         , base.DEC)
response_to     = ProtoField.int32 ("mongodb.responseto"      , "responseTo"        , base.DEC)
opcode          = ProtoField.int32 ("mongodb.opcode"          , "opCode"            , base.DEC)

-- Payload fields
flags           = ProtoField.int32 ("mongodb.flags"           , "flags"             , base.DEC)
full_coll_name  = ProtoField.string("mongodb.full_coll_name"  , "fullCollectionName", base.ASCII)
number_to_skip  = ProtoField.int32 ("mongodb.number_to_skip"  , "numberToSkip"      , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn"    , base.DEC)
query           = ProtoField.none  ("mongodb.query"           , "query"             , base.HEX)

response_flags  = ProtoField.int32 ("mongodb.response_flags"  , "responseFlags"     , base.DEC)
cursor_id       = ProtoField.int64 ("mongodb.cursor_id"       , "cursorId"          , base.DEC)
starting_from   = ProtoField.int32 ("mongodb.starting_from"   , "startingFrom"      , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned"    , base.DEC)
documents       = ProtoField.none  ("mongodb.documents"       , "documents"         , base.HEX)

mongodb_protocol.fields = {
  message_length, request_id, response_to, opcode,                     -- Header
  flags, full_coll_name, number_to_skip, number_to_return, query,      -- OP_QUERY
  response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}

function mongodb_protocol.dissector(buffer, pinfo, tree)
    length = buffer:len()
    if length == 0 then return end

    pinfo.cols.protocol = mongodb_protocol.name

    local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")

    -- Header
    subtree:add_le(message_length, buffer(0,4))
    subtree:add_le(request_id,     buffer(4,4))
    subtree:add_le(response_to,    buffer(8,4))

    local opcode_number = buffer(12,4):le_uint()
    local opcode_name = get_opcode_name(opcode_number)
    subtree:add_le(opcode,         buffer(12,4)):append_text(" (" .. opcode_name .. ")")

    -- Payload
    if opcode_name == "OP_QUERY" then
        local flags_number = buffer(16,4):le_uint()
        local flags_description = get_flag_description(flags_number)
        subtree:add_le(flags,      buffer(16,4)):append_text(" (" .. flags_description .. ")")

        -- Loop over string
        local string_length
        for i = 20, length - 1, 1 do
            if (buffer(i,1):le_uint() == 0) then
                string_length = i - 20
                break
            end
        end

        subtree:add_le(full_coll_name,   buffer(20,string_length))
        subtree:add_le(number_to_skip,   buffer(20+string_length,4))
        subtree:add_le(number_to_return, buffer(24+string_length,4))
        subtree:add_le(query,            buffer(28+string_length,length-string_length-28))
    elseif opcode_name == "OP_REPLY" then
        local response_flags_number = buffer(16,4):le_uint()
        local response_flags_description = get_response_flag_description(response_flags_number)

        subtree:add_le(response_flags,   buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
        subtree:add_le(cursor_id,        buffer(20,8))
        subtree:add_le(starting_from,    buffer(28,4))
        subtree:add_le(number_returned,  buffer(32,4))
        subtree:add_le(documents,        buffer(36,length-36))
    end
end

function get_opcode_name(opcode)
    local opcode_name = "Unknown"

        if opcode ==    1 then opcode_name = "OP_REPLY"
    elseif opcode == 2001 then opcode_name = "OP_UPDATE"
    elseif opcode == 2002 then opcode_name = "OP_INSERT"
    elseif opcode == 2003 then opcode_name = "RESERVED"
    elseif opcode == 2004 then opcode_name = "OP_QUERY"
    elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
    elseif opcode == 2006 then opcode_name = "OP_DELETE"
    elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
    elseif opcode == 2010 then opcode_name = "OP_COMMAND"
    elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end

    return opcode_name
end

function get_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "Reserved"
    elseif flags == 1 then flags_description = "TailableCursor"
    elseif flags == 2 then flags_description = "SlaveOk.Allow"
    elseif flags == 3 then flags_description = "OplogReplay"
    elseif flags == 4 then flags_description = "NoCursorTimeout"
    elseif flags == 5 then flags_description = "AwaitData"
    elseif flags == 6 then flags_description = "Exhaust"
    elseif flags == 7 then flags_description = "Partial"
    elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

function get_response_flag_description(flags)
    local flags_description = "Unknown"

        if flags == 0 then flags_description = "CursorNotFound"
    elseif flags == 1 then flags_description = "QueryFailure"
    elseif flags == 2 then flags_description = "ShardConfigStale"
    elseif flags == 3 then flags_description = "AwaitCapable"
    elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end

    return flags_description
end

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

推荐阅读更多精彩内容