SpoofWebGL分析

背景

由于很多headless瀏覽器的webgl信息比較明顯正卧,如果源站嘗試采集webgl參數(shù)會(huì)暴露自動(dòng)化工具的特征春宣,所以黑產(chǎn)攻擊中需要去欺騙webgl的信息上報(bào)既峡。

目前在github上可以找到一個(gè)spoof webgl的項(xiàng)目预茄,star數(shù)并不多隔显,但其思路應(yīng)該是比較主流的hook webgl相關(guān)接口的方式崎溃。本文主要對該工具的使用和源碼進(jìn)行分析蜻直。

這份代碼并不完美,甚至能找到幾處bug,但不妨礙我們學(xué)習(xí)其思想概而;github地址:https://github.com/siejqa/spoofHeadless

背景知識(shí)簡單介紹

Webgl和參數(shù)采集

簡單來說webgl就是瀏覽器給前端js代碼調(diào)用的渲染繪圖API呼巷,該API可以在在html canvas元素中使用,可以調(diào)用到硬件進(jìn)行加速赎瑰,所以webgl的參數(shù)通常與硬件強(qiáng)相關(guān)王悍。更具體的介紹和教程可以參考:https://www.w3cschool.cn/webgl/i4gf1oh1.html

具體采集webgl的參數(shù)時(shí),需要首先先獲取canvas下的webgl Context餐曼,使用getContext接口压储。而采集具體參數(shù)是使用getParameter函數(shù)完成,getParameter接受一個(gè)整數(shù)源譬,每個(gè)整數(shù)對應(yīng)一個(gè)屬性;以獲取GPU型號(hào)為例:

// 獲取webgl context
var gl = document.createElement("canvas").getContext("webgl")
// 采集GPU render:編號(hào)為37446
gl.getExtension("WEBGL_debug_renderer_info")["UNMASKED_RENDERER_WEBGL"]
gl.getParameter(37446)

完整的getParameter常量表可以參考:https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants

Webdriver

webdriver本質(zhì)上是瀏覽器根據(jù)w3c實(shí)現(xiàn)的一套操作瀏覽器的接口踩娘,而每個(gè)瀏覽器都有一個(gè)特定的 WebDriver 實(shí)現(xiàn)刮刑,如chrome webdriver:https://chromedriver.chromium.org/downloads

而目前比較廣義的定義(或者說黑產(chǎn)使用的方式)翘紊,通常是指puppeteer/selenium這類中捆,集成了多種瀏覽器泄伪,并提供高級(jí)api供上層應(yīng)用調(diào)用的自動(dòng)化工具津函;可以直接使用python(selenium)和nodejs(puppeteer)來編寫腳本魂那,完成webdriver的控制涯雅,從而完成瀏覽器上的自動(dòng)化操作划乖。相關(guān)資料可以自行搜索學(xué)習(xí)仰美。

SpoofWebGL使用方法

此處介紹如何在selenium使用SpoofWebGL工具,當(dāng)然該工具簡單改造后可以在所有的webdriver上使用。

  • 將項(xiàng)目clone下來之后壤圃,使用可以看到src文件夾下有兩個(gè)文件效床,其中manifest.json是extension的配置文件,injected是源碼卤妒。
image
  • 之后用zip命令將src文件夾打包:zip -rj extension.zip src/
  • 將zip后綴名改成.crx(chrome extension的后綴名) :mv extension.zip extension.crx
  • 編寫webdriver腳本如下(注意要先安裝好selenium和chrome webdriver)士复,去觀察我們的webgl參數(shù)讀取情況(注意原項(xiàng)目中使用的是firefox的webdriver,所以腳本要做修改):
from selenium import webdriver

opt = webdriver.ChromeOptions()

extension_path = './extension.crx'
opt.add_extension(extension_path)

driver = webdriver.Chrome(options=opt)
# Check what data is spoofed
driver.get('https://browserleaks.com/webgl')

image

可以看到這個(gè)vendor和render已經(jīng)不太正常了;

  • 作為對比,注釋掉options直接啟動(dòng)捻脖,會(huì)顯示本機(jī)的真實(shí)GPU:
from selenium import webdriver

# opt = webdriver.ChromeOptions()
# 
# extension_path = './extension.crx'
# opt.add_extension(extension_path)

driver = webdriver.Chrome()
# Check what data is spoofed
driver.get('https://browserleaks.com/webgl')

image

注:此處是使用瀏覽器界面模式打開的抛寝,實(shí)際上如果是啟動(dòng)headless模式晶府,該renderer會(huì)和本機(jī)的有差別剂习,這也是為什么要使用spoof webgl的原因

源碼分析

總結(jié)來說拂封,該extension是將webgl相關(guān)的接口全部進(jìn)行了hook萧恕,本質(zhì)技術(shù)難度上并不大吆视,且可以很容易進(jìn)行定制化拙寡。下面開始對hook方法進(jìn)行分析

webdriver相關(guān)繞過

開始的第一部分跟webgl檢測關(guān)系不大,主要是用defineProperty方法對navigator下一些字段進(jìn)行了hook淮摔,繞過webdriver相關(guān)的一些檢測厕隧;主要是設(shè)置上瀏覽器語言,以及將Navigator.webdriver置為false:

Object.defineProperty(navigator, 'languages', {
    get: function () {
        var availableLanguages = Array('en', 'pl', 'cs', 'ru', 'fr', 'fr-fr', 'lb', 'no')
        return ['en-US', get_random_item(availableLanguages)];
    },
});
//  fake webdriver property (headless has it as true)
Object.defineProperty(navigator, 'webdriver', {
    get: () => false,
});

WebGL Hook

根據(jù)上文中webgl調(diào)用示例可知調(diào)用webgl接口采集參數(shù)主要分為三步:

  • 使用getContext獲取webgl Context
  • 使用context.getExtension獲取webgl拓展的編號(hào)
  • 使用context.getParameter獲取具體參數(shù)的值

對應(yīng)步驟我們查看該腳本的hook方法:

HTMLCanvasElement.getContext Hook

要hook該方法拴曲,我們需要先定義一個(gè)類店溢,如下:

function WebGLRenderingContext(canvas) {
    this.canvas = canvas;
    this.drawingBufferWidth = canvas.width;
    this.drawingBufferHeight = canvas.height;
};

之后將WebGLRenderingContext中的基本屬性和方法進(jìn)行初始化叁熔,即對Object.prototype.attribute進(jìn)行賦值一個(gè)空函數(shù)。注意床牧,基礎(chǔ)屬性本質(zhì)上都是一些編號(hào)荣回,如上文中的例子一樣,他是用來傳入getParameter做入?yún)⒌摹?/p>

// 原webgl Context中的基本方法集合
var functions = [
    'viewport',
    'vertexAttribPointer',
    'vertexAttrib4fv',
    'vertexAttrib4f',
    'vertexAttrib3fv',
    ...
 ]

// 原webgl Context中的基本屬性集合戈咳,這里挑選一些經(jīng)常被收集的作為例子
var enumerates = {
    ...
    'VERSION': 7938,
    ...
    'UNMASKED_VENDOR_WEBGL': 37445,
    'UNMASKED_RENDERER_WEBGL': 37446,
    ...
    'DEPTH_BITS': 3414,
    'GREEN_BITS': 3411,
    'BLUE_BITS': 3412,
    ...
    'STENCIL_BITS': 3415,
    ...
    'MAX_VERTEX_UNIFORM_VECTORS': 36347,
    'MAX_VERTEX_TEXTURE_IMAGE_UNITS': 35660,
    'MAX_VERTEX_ATTRIBS': 34921,
    'MAX_VARYING_VECTORS': 36348,
    'MAX_TEXTURE_SIZE': 3379,
    'MAX_TEXTURE_IMAGE_UNITS': 34930,
    'MAX_RENDERBUFFER_SIZE': 34024,
    'MAX_FRAGMENT_UNIFORM_VECTORS': 36349,
    'MAX_CUBE_MAP_TEXTURE_SIZE': 34076,
    'MAX_COMBINED_TEXTURE_IMAGE_UNITS': 35661,
    ...

};

// 將原本的函數(shù)全部替換成空函數(shù)
functions.forEach(function (func) {
    WebGLRenderingContext.prototype[func] = function () {
        return {};
    };
});

Object.keys(enumerates).forEach(function (key) {
    WebGLRenderingContext.prototype[key] = enumerates[key];
});

實(shí)際上原腳本之后馬上對context.getExtension完成了賦值心软,那此處其實(shí)順序不影響執(zhí)行結(jié)果,所以我們留在下一節(jié)描述著蛙。

進(jìn)入hook的代碼删铃,實(shí)際上document.createElement("canvas").getContext("webgl")調(diào)用到的是HTMLCanvasElement.getContext方法,所以對該方法進(jìn)行Hook:

try {
    const getContext = HTMLCanvasElement.prototype.getContext; 
    // 利用重定義HTMLCanvasElement.prototype.getContext完成Hook册踩,是常見的hook方法
    HTMLCanvasElement.prototype.getContext = function () {
        // 獲取第一個(gè)入?yún)⒂窘悖ǔ?webgl"效拭,'webgl-experimental'等
        var name = arguments[0];
        console.log("HTMLCanvasElement app requested extension: " + name);
        console.log(JSON.stringify(arguments, null, 4));
        if (name == 'webgl' || name == 'webgl-experimental' || name == 'experimental-webgl' || name == 'moz-webgl') {
            // 最終返回了上文中自定義的類WebGLRenderingContext暂吉,完成hook
            var y = new WebGLRenderingContext(this);
            console.log("WEBGL " + y);
            console.log(JSON.stringify(y, null, 4));
            return y;
        }
        // 其他的webgl類型不支持胖秒,返回原始數(shù)據(jù)
        if (name == 'webgl2' || name == 'experimental-webgl2' || name == 'fake-webgl') {
            console.log("WEBGL2")
            return null;
        }
        var ext = getContext.apply(this, arguments);
        console.log("HTMLCanvasElement extension " + name + " " + (ext ? "found" : "not found"));
        console.log(ext);
        return ext;
    }
} catch (e) { }

context.getExtension定義

實(shí)際上很簡單,只需要get對應(yīng)屬性時(shí)返回指定編號(hào)即可慕的,此處以上文中的"WEBGL_debug_renderer_info"為例子:

var extensions = {
    // ratified
    ...
    'WEBGL_debug_renderer_info': {
        'UNMASKED_VENDOR_WEBGL': 37445,
        'UNMASKED_RENDERER_WEBGL': 37446
    },
    ...
}

WebGLRenderingContext.prototype.getExtension = function (ext) {
    console.log("WebGLRenderingContext.getExtension" + ext);
    return extensions[ext];
};

注意此處有一些特例是"WEBGL_lose_context"和

"WEBGL_draw_buffers", 他們的屬性內(nèi)部包含方法阎肝,需要定義一下:

function loseContext () {
}
function restoreContext () {
}
function drawBuffersWEBGL () {
}

var extensions = {
    // ratified
    ...
    'WEBGL_lose_context': {
        loseContext,
        restoreContext
    },
    ...

    'WEBGL_draw_buffers': {
        'MAX_DRAW_BUFFERS_WEBGL': 34852,
        'MAX_COLOR_ATTACHMENTS_WEBGL': 36063,
        ...
        drawBuffersWEBGL
    },
}

context.getParameter 定義,完成取值的Hook

代碼可以拆解如下:

  • 定義部分肮街,拿到getParameter的參數(shù):
try {
    const getParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function () {
        var name = arguments[0];
        console.log("WebGLRenderingContext - getParameter: " + name);
        ...
} catch (a) { }

  • Hook UNMASKED_VENDOR_WEBGL 和UNMASKED_RENDERER_WEBGL 參數(shù)风题,從一個(gè)備選列表中隨機(jī)返回一個(gè)vendor/renderer,可以很好的防止收集信息結(jié)果過度集中嫉父,也可以很方便的進(jìn)行拓展:
function get_random_item(list) {
    return list[Math.floor((Math.random() * list.length))];
}

WebGLRenderingContext.prototype.getParameter = function () {
    ...
    // UNMASKED_VENDOR_WEBGL
    if (name == 37445) {
        var options = ['Intel Open Source Technology Center', 'X.Org', 'Vendor Google Inc.'];
        return get_random_item(options);
    } else if (name == 37446) {
        // UNMASKED_RENDERER_WEBGL
        var options = ['Mesa DRI Intel(R) Ivybridge Mobile', 'AMD KAVERI (DRM 2.43.0 / 4.4.0-119-generic, LLVM 5.0.0)', 'Renderer Google SwiftShader', 'AMD ARUBA (DRM 2.43.0 / 4.4.0-119-generic, LLVM 5.0.0)', 'Mesa DRI Intel(R) HD Graphics 630 (Kaby Lake GT2)', 'Gallium 0.4 on AMD KAVERI (DRM 2.43.0 / 4.4.0-83-generic, LLVM 3.8.0)'];
        return get_random_item(options);
    }
    ...
}

  • Hook 一些基礎(chǔ)屬性, 如RENDERER / VENDOR / SHADING_LANGUAGE_VERSION /

VERSION

WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 7937 || name == 7936) {
        // RENDERER // VENDOR
        return 'Mozilla';
    } else if (name == 35724) {
        // SHADING_LANGUAGE_VERSION
        return 'WebGL GLSL ES 1.0';
    } else if (name == 7937 || name == 7938) {
        // VERSION
        return 'WebGL 1.0';
    }
    ...
}

  • Hook ALIASED_LINE_WIDTH_RANGE / ALIASED_POINT_SIZE_RANGE, 會(huì)返回一個(gè)float array沛硅,size為2;這里代碼有點(diǎn)小問題绕辖,不影響功能摇肌,name == 7937是VERSION,不過在上面已經(jīng)判斷過了仪际,不會(huì)進(jìn)到這個(gè)分支:
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 7937 || name == 33901 || name == 33902) {
        // ALIASED_LINE_WIDTH_RANGE // ALIASED_POINT_SIZE_RANGE
        var option = new Float32Array([1, 8192]);
        return option;
    }
    ...
}

  • 針對一些webgl位寬信息進(jìn)行Hook围小,返回隨機(jī)值[2, 4, 8, 16]中1個(gè),具體參數(shù)見注釋:
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 3413 || name == 3412 || name == 3411 || name == 3410 || name == 34852) {
        // ALPHA_BITS // BLUE_BITS // GREEN_BITS // RED_BITS // MAX_DRAW_BUFFERS_WEBGL
        return get_random_item([2, 4, 8, 16]);
    }
    ...
}

  • 針對一些位寬信息進(jìn)行Hook树碱,返回固定值肯适,參數(shù)見注釋
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 3415) 
        // STENCIL_BITS
        return 0;
    } else if (name == 3414) {
        // DEPTH_BITS
        return 24;
    }
    ...
}

  • 接下來是該腳本bug的地方,Hook出現(xiàn)問題成榜,如果使用該腳本不加修改框舔,很容易通過此bug識(shí)別;原因主要在于以下hook的三個(gè)參數(shù)值理論上是返回一個(gè)整數(shù)赎婚,但不知為何作者這里使用了get_random_items, 但沒有給第二個(gè)參數(shù)雨饺,所以n會(huì)為undefined,導(dǎo)致固定返回一個(gè)Array:undefined惑淳;修復(fù)也很簡單额港,換成get_random_item即可。源代碼如下:
function get_random_items(list, n) {
    var result = new Array(n),
        len = list.length,
        taken = new Array(len);
    if (n > len)
        n = len
    while (n--) {
        var x = Math.floor(Math.random() * len);
        result[n] = list[x in taken ? taken[x] : x];
        // 比較巧妙的取隨機(jī)多個(gè)值的方式歧焦,留一個(gè)array標(biāo)記如果下次再取到其下標(biāo)會(huì)從目前未取成的最后一個(gè)元素
        taken[x] = --len in taken ? taken[len] : len;
    }
    return result;
}

WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 34047 || name == 34921) {
        // MAX_TEXTURE_MAX_ANISOTROPY_EXT // MAX_VERTEX_ATTRIBS
        return get_random_items([2, 4, 8, 16]);
    } else if (name == 35661) {
        // MAX_COMBINED_TEXTURE_IMAGE_UNITS
        return get_random_items([128, 192, 256]);
    }
    ...
}

  • 對一些其他的MAX相關(guān)屬性進(jìn)行Hook移斩,返回隨機(jī)值,具體屬性見注釋
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    } else if (name == 34076 || name == 34024 || name == 3379) {
        // MAX_CUBE_MAP_TEXTURE_SIZE // MAX_RENDERBUFFER_SIZE
        return get_random_item([16384, 32768]) ;
    } else if (name == 36349 || name == 36347) {
        // MAX_FRAGMENT_UNIFORM_VECTORS // MAX_VERTEX_UNIFORM_VECTORS
        return get_random_item([4096, 8192]);
    } else if (name == 34930 || name == 36348 || name == 35660) {
        // MAX_TEXTURE_IMAGE_UNITS // MAX_VARYING_VECTORS // MAX_VERTEX_TEXTURE_IMAGE_UNITS
        return get_random_item([16, 32, 64]);
    }
    ...
}

  • 對MAX_VIEWPORT_DIMS進(jìn)行Hook绢馍,會(huì)返回一個(gè)長度為2且兩個(gè)值相等的Int32Array向瓷,同樣此處隨機(jī)取值:
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else if (name == 3386) {
        // MAX_VIEWPORT_DIMS
        var value = get_random_item([8192, 16384, 32768])
        var options = new Int32Array([value, value]);
        return options;
    }
    ...
}

  • 最后,剩下的參數(shù)統(tǒng)一隨機(jī)從[0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]隨機(jī)取值返回(此處還有個(gè)冗余分支STENCIL_BITS舰涌,上面已經(jīng)判斷過了猖任,屬于冗余代碼)
WebGLRenderingContext.prototype.getParameter = function () {
    ...
    else {
        console.log("Retuning random value for: " + name);
        return get_random_item([0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]);
    }
    ...
}

  • 最后的迷惑操作:理論上此處已經(jīng)涵蓋了所有的case返回,但是最后還多了個(gè)跑不到的分支:
WebGLRenderingContext.prototype.getParameter = function () {
    ...
     var ext = getParameter.apply(this, arguments);
     console.log("WebGLRenderingContext extension " + name + " " + (ext ? "found" : "not found"));
     console.log(JSON.stringify(ext, null, 4));

     return ext;
}

說實(shí)話我猜測此處他是想模擬一些參數(shù)瓷耙,他們在getParameter之前必須先調(diào)用getExtension方法后才可以獲取朱躺,但是此處加在最后屬實(shí)看不懂刁赖,個(gè)人理解應(yīng)該放在這個(gè)大if...else...前面;有時(shí)間我可以好好修復(fù)一下這個(gè)項(xiàng)目????

其他的一些被Hook的方法

  • getSupportedExtension:比較簡單长搀,隨機(jī)從extensions中間選擇隨機(jī)個(gè)keys并返回宇弛,出現(xiàn)異常則將所有的keys都返回。
// extensions的keys可以參見getExtension部分
const getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions;
WebGLRenderingContext.prototype.getSupportedExtensions = function () {
    try {
        console.log("WebGLRenderingContext.getSupportedExtensions")
        var availableExtensions = Object.keys(extensions);
        console.log(availableExtensions);
        var itemsToGet = Math.floor(Math.random() * (availableExtensions.length - 6) + 5);
        console.log(itemsToGet);
        var selectedExtensions = get_random_items(availableExtensions, itemsToGet);
        console.log(selectedExtensions);
        return selectedExtensions;
    } catch (a) {
        console.log(a)
        return Object.keys(extensions);
    }
}

  • 針對一些headless瀏覽器有可能會(huì)出現(xiàn)canvas的一些屬性異常(broken會(huì)為0)源请,如canvas的width和height枪芒,以及offset,進(jìn)行Hook谁尸,還是使用defineProperty重寫get方法對屬性進(jìn)行hook:
// in case of broken image return random height/width
var size = 0;
['height', 'width'].forEach(property => {
    const imageDescriptor = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, property);
    Object.defineProperty(HTMLImageElement.prototype, property, {
        imageDescriptor,
        get: function () {
            // 如果canvas破損舅踪,則返回隨機(jī)size
            if (this.complete && this.naturalHeight == 0) {
                if (!size) {
                    // 返回隨機(jī)的長/寬
                    size = Math.floor(Math.random() * (30 - 10 + 1)) + 10;
                }
                return size;
            }
            // 未破損則返回正常size
            return imageDescriptor.get.apply(this);
        },
    });
}); 

// hairline feature (headless can't render it normally)
const imageDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight');
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
    ...imageDescriptor,
    get: function () {
        if (this.id == 'modernizr') {
            return 1;
        }
        return imageDescriptor.get.apply(this);
    },
});

插件執(zhí)行

方法比較簡單,將整個(gè)大函數(shù)作為字符串良蛮,最后在html document中新建一個(gè)script tag硫朦,script.textContent賦值為字符串即可:

var scriptCode = '(' + function () {
    ...
    function WebGLRenderingContext(canvas) {
        ...
    };
    ...
    WebGLRenderingContext.prototype.getExtension = function (ext) {
        ...
    };
    ...
    WebGLRenderingContext.prototype.getParameter = function () {
        ...
    }
    ...
} + ')();'; // 轉(zhuǎn)成字符串,可直接執(zhí)行

// 新建script節(jié)點(diǎn)插入document中背镇,即自動(dòng)執(zhí)行
var script = document.createElement('script');
script.textContent = scriptCode;
(document.head || document.documentElement).appendChild(script);
// 最后move掉代碼即可
script.remove();

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咬展,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瞒斩,更是在濱河造成了極大的恐慌破婆,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胸囱,死亡現(xiàn)場離奇詭異祷舀,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)烹笔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門裳扯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谤职,你說我怎么就攤上這事饰豺。” “怎么了允蜈?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵冤吨,是天一觀的道長。 經(jīng)常有香客問我饶套,道長漩蟆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任妓蛮,我火速辦了婚禮怠李,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己捺癞,他們只是感情好夷蚊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翘簇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儿倒。 梳的紋絲不亂的頭發(fā)上版保,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音夫否,去河邊找鬼彻犁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凰慈,可吹牛的內(nèi)容都是我干的汞幢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼微谓,長吁一口氣:“原來是場噩夢啊……” “哼森篷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豺型,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤仲智,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后姻氨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钓辆,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年肴焊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了前联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娶眷,死狀恐怖似嗤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情届宠,我是刑警寧澤双谆,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站席揽,受9級(jí)特大地震影響顽馋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜幌羞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一寸谜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧属桦,春花似錦熊痴、人聲如沸他爸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诊笤。三九已至,卻和暖如春巾陕,著一層夾襖步出監(jiān)牢的瞬間讨跟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工鄙煤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晾匠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓梯刚,卻偏偏與公主長得像凉馆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子亡资,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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