iOS界的毒瘤-MethodSwizzling

原文地址

為什么有這篇博文

不知道何時(shí)開始iOS面試開始流行起來詢問什么是 Runtime逃沿,于是 iOSer 一聽 Runtime 總是就提起 MethodSwizzling岳枷,開口閉口就是黑科技爪瓜。但其實(shí)如果讀者留意過C語言的 Hook 原理其實(shí)會發(fā)現(xiàn)所謂的鉤子都是框架或者語言的設(shè)計(jì)者預(yù)留給我們的工具施戴,而不是什么黑科技,MethodSwizzling 其實(shí)只是一個簡單而有趣的機(jī)制罷了煎源。然而就是這樣的機(jī)制羹铅,在日常中卻總能成為萬能藥一般的被肆無忌憚的使用。

很多 iOS 項(xiàng)目初期架構(gòu)設(shè)計(jì)的不夠健壯坐漏,后期可擴(kuò)展性差薄疚。于是 iOSer 想起了 MethodSwizzling 這個武器,將項(xiàng)目中一個正常的方法 hook 的滿天飛赊琳,導(dǎo)致項(xiàng)目的質(zhì)量變得難以?控制街夭。曾經(jīng)我也愛在項(xiàng)目中濫用 MethodSwizzling,但在踩到坑之前總是不能意識到這種糟糕的做法會讓項(xiàng)目陷入怎樣的險(xiǎn)境躏筏。于是我才明白學(xué)習(xí)某個機(jī)制要去深入的理解機(jī)制的設(shè)計(jì)板丽,而不是跟風(fēng)濫用,帶來糟糕的后果趁尼。最后就有了這篇文章埃碱。

Hook的對象

在 iOS 平臺常見的 hook 的對象一般有兩種:

  1. C/C++ functions
  2. Objective-C method

?對于 C/C+ +的 hook 常見的方式可以使用 facebook 的 fishhook 框架,具體原理可以參考深入理解Mac OS X & iOS 操作系統(tǒng) 這本書酥泞。
對于 Objective-C Methods 可能大家更熟悉一點(diǎn)砚殿,本文也只討論這個。

最常見的hook代碼

相信很多人使用過 JRSwizzle 這個庫婶博,或者是看過 http://nshipster.cn/method-swizzling/ 的博文瓮具。
上述的代碼簡化如下。


+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {

    Method origMethod = class_getInstanceMethod(self, origSel_);
    if (!origMethod) {
        SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
        return NO;
    }

    Method altMethod = class_getInstanceMethod(self, altSel_);
    if (!altMethod) {
        SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
        return NO;
    }

    class_addMethod(self,
                    origSel_,
                    class_getMethodImplementation(self, origSel_),
                    method_getTypeEncoding(origMethod));

    class_addMethod(self,
                    altSel_,
                    class_getMethodImplementation(self, altSel_),
                    method_getTypeEncoding(altMethod));

    method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
    return YES;

在?Swizzling情況極為普通的情況下上述代碼不會出現(xiàn)問題凡人,但是場景復(fù)雜之后上面的代碼會有很多安全隱患名党。

MethodSwizzling泛濫下的隱患

Github有一個?很健壯的庫 RSSwizzle(這也是本文推薦Swizzling的最終方式) 指出了上面代碼帶來的風(fēng)險(xiǎn)點(diǎn)。

  1. 只在 +load 中執(zhí)行 swizzling 才是安全的挠轴。

  2. 被 hook 的方法必須是當(dāng)前類自身的方法传睹,如果把繼承來的 IMP copy 到自身上面會存在問題。父類的方法應(yīng)該在調(diào)用的時(shí)候使用岸晦,而不是 swizzling 的時(shí)候 copy 到子類欧啤。

  3. 被 Swizzled 的方法如果依賴與 cmd ,hook 之后 cmd 發(fā)送了變化启上,就會有問題(一般你 hook 的是系統(tǒng)類邢隧,也不知道系統(tǒng)用沒用 cmd 這個參數(shù))。

  4. 命名如果沖突導(dǎo)致之前 hook 的失效 或者是循環(huán)調(diào)用冈在。

上述問題中第一條和第四條說的是通常的 MethodSwizzling 是在分類里面實(shí)現(xiàn)的, 而分類的 Method 是被Runtime 加載的時(shí)候追加到類的 MethodList 倒慧,如果不是在 +load 是執(zhí)行的 Swizzling 一旦出現(xiàn)重名,那么 SEL 和 IMP 不匹配致 hook 的結(jié)果是循環(huán)調(diào)用包券。

第三條是一個不容易被發(fā)現(xiàn)的問題纫谅。
我們都知道 Objective-C Method 都會有兩個隱含的參數(shù) ?self, cmd,有的時(shí)候開發(fā)者在使用關(guān)聯(lián)屬性的適合可能懶得聲明 (void *) 的 key溅固,直接使用 cmd 變量 objc_setAssociatedObject(self, _cmd, xx, 0); 這會導(dǎo)致對當(dāng)前IMP對 cmd 的依賴付秕。

一旦此方法被 Swizzling,那么方法的 cmd 勢必會發(fā)生變化侍郭,出現(xiàn)了 bug 之后想必你一定找不到询吴,等你找到之后心里一定會問候那位 Swizzling 你的方法的開發(fā)者祖宗十八代安好的,再者如果你 Swizzling 的是系統(tǒng)的方法恰好系統(tǒng)的方法內(nèi)部用到了 cmd ..._(此處后背驚起一陣?yán)浜梗?/p>

Copy父類的方法帶來的問題

上面的第二條才是我們最容易遇見的場景亮元,并且是99%的開發(fā)者都不會注意到的問題汰寓。下面我們來做個試驗(yàn)


@implementation Person

- (void)sayHello {
    NSLog(@"person say hello");
}

@end

@interface Student : Person

@end

@implementation Student (swizzle)

+ (void)load {
    [self jr_swizzleMethod:@selector(s_sayHello) withMethod:@selector(sayHello) error:nil];
}

- (void)s_sayHello {
    [self s_sayHello];

    NSLog(@"Student + swizzle say hello");
}

@end

@implementation Person (swizzle)

+ (void)load {
    [self jr_swizzleMethod:@selector(p_sayHello) withMethod:@selector(sayHello) error:nil];
}

- (void)p_sayHello {
    [self p_sayHello];
    
    NSLog(@"Person + swizzle say hello");
}

@end

上面的代碼中有一個 Person 類實(shí)現(xiàn)了 sayHello 方法,有一個 Student 繼承自 Person苹粟, 有一個Student 分類 Swizzling 了原來的? sayHello, 還有一個 Person 的分類也 Swizzling 了原來的 sayhello 方法有滑。

當(dāng)我們生成一個 Student 類的實(shí)例并且調(diào)用 sayHello 方法,我們期望的輸出如下:

"person say hello"
"Person + swizzle say hello"
"Student + swizzle say hello"

但是輸出有可能是這樣的:

"person say hello"
"Student + swizzle say hello"

出現(xiàn)這樣的場景是由于在 build Phasescompile Source 順序子類分類在父類分類之前嵌削。

我們都知道在 Objective-C 的世界里父類的 +load 早于子類毛好,但是并沒有?限制父類的分類加載?會早于子類的分類的加載,實(shí)際上這取決于編譯的順序苛秕。最終會按照編譯的順序合并進(jìn) Mach-O ?的固定 section 內(nèi)肌访。

下面會分析下為什么代碼會出現(xiàn)這樣的場景。

最開始的時(shí)候父類擁有自己的 sayHello 方法艇劫,子類擁有分類添加的 s_sayHello 方法并且在 s_sayHello 方法內(nèi)部調(diào)用了 sel 為 s_sayHello 方法吼驶。

但是子類的分類在使用上面提到的 MethodSwizzling 的方法會導(dǎo)致?如下圖的變化

由于調(diào)用了 class_addMethod 方法會導(dǎo)致重新生成一份新的Method添加到 Student 類上面 但是 sel 并沒有發(fā)生變化,IMP 還是指向父類唯一的那個 IMP。
之后交換了子類兩個方法的 IMP 指針蟹演。于是方法引用變成了如下結(jié)構(gòu)风钻。
其中虛線指出的是方法的調(diào)用路徑。

單純在 Swizzling 一次的時(shí)候并沒有什么問題酒请,但是我們并不能保證同事出于某種不可告人的目的的又去 Swizzling 了父類骡技,或者是我們引入的第三庫做了這樣的操作。

于是我們在 Person 的分類里面 Swizzling 的時(shí)候會導(dǎo)致方法結(jié)構(gòu)發(fā)生如下變化羞反。

我們的代碼調(diào)用路徑就會是下圖這樣布朦,相信你已經(jīng)明白了前面的代碼執(zhí)行結(jié)果中為什么父類在子類之后 Swizzling 其實(shí)并沒有對子類 hook 到。

這只是其中一種很常見的場景昼窗,造成的影響也只是 Hook 不到父類的派生類而已是趴,?也不會造成一些嚴(yán)重的 Crash 等明顯現(xiàn)象,所以大部分開發(fā)者對此種行為是毫不知情的澄惊。

對于這種 Swizzling 方式的不確定性有一篇博文分析的更為全面玉令天下的博客Objective-C Method Swizzling

換個姿勢來Swizzling

前面提到 RSSwizzle 是另外一種更加健壯的Swizzling方式右遭。

這里使用到了如下代碼

   RSSwizzleInstanceMethod([Student class],
                            @selector(sayHello),
                            RSSWReturnType(void),
                            RSSWArguments(),
                            RSSWReplacement(
                                            {
                                                // Calling original implementation.
                                                RSSWCallOriginal();
                                                // Returning modified return value.
                                                NSLog(@"Student + swizzle say hello sencod time");
                                            }), 0, NULL);

    RSSwizzleInstanceMethod([Person class],
                            @selector(sayHello),
                            RSSWReturnType(void),
                            RSSWArguments(),
                            RSSWReplacement(
                                            {
                                                // Calling original implementation.
                                                RSSWCallOriginal();
                                                // Returning modified return value.
                                                NSLog(@"Person + swizzle say hello");
                                            }), 0, NULL);

由于 RS 的方式需要提供一種 Swizzling 任何類型的簽名的 SEL,所以 RS 使用的是宏作為代碼包裝的入口缤削,并且由開發(fā)者自行保證方法的參數(shù)個數(shù)和參數(shù)類型的正確性窘哈,所以使用起來也較為晦澀。 可能這也是他為什么這么優(yōu)秀但是 star 很少的原因吧 :(亭敢。

我們將宏展開


    RSSwizzleImpFactoryBlock newImp = ^id(RSSwizzleInfo *swizzleInfo) {
        void (*originalImplementation_)(__attribute__((objc_ownership(none))) id, SEL);
        SEL selector_ = @selector(sayHello);
        return ^void (__attribute__((objc_ownership(none))) id self) {
            IMP xx = method_getImplementation(class_getInstanceMethod([Student class], selector_));
            IMP xx1 = method_getImplementation(class_getInstanceMethod(class_getSuperclass([Student class]) , selector_));
            IMP oriiMP = (IMP)[swizzleInfo getOriginalImplementation];
                ((__typeof(originalImplementation_))[swizzleInfo getOriginalImplementation])(self, selector_);
            //只有這一行是我們的核心邏輯
            NSLog(@"Student + swizzle say hello");
            
        };
        
    };
    [RSSwizzle swizzleInstanceMethod:@selector(sayHello)
                             inClass:[[Student class] class]
                       newImpFactory:newImp
                                mode:0 key:((void*)0)];;

RSSwizzle核心代碼其實(shí)只有一個函數(shù)



static void swizzle(Class classToSwizzle,
                    SEL selector,
                    RSSwizzleImpFactoryBlock factoryBlock)
{
    Method method = class_getInstanceMethod(classToSwizzle, selector);

    __block IMP originalIMP = NULL;


    RSSWizzleImpProvider originalImpProvider = ^IMP{

        IMP imp = originalIMP;
        
        if (NULL == imp){

            Class superclass = class_getSuperclass(classToSwizzle);
            imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
        }
        return imp;
    };
    
    RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
    swizzleInfo.selector = selector;
    swizzleInfo.impProviderBlock = originalImpProvider;

    id newIMPBlock = factoryBlock(swizzleInfo);
    
    const char *methodType = method_getTypeEncoding(method);
    
    IMP newIMP = imp_implementationWithBlock(newIMPBlock);

    originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
}

上述代碼已經(jīng)刪除無關(guān)的加鎖滚婉,防御邏輯,簡化理解帅刀。

我們可以看到 RS 的代碼其實(shí)是構(gòu)造了一個 Block 里面裝著我們需要的執(zhí)行的代碼让腹。

然后再把我們的名字叫 originalImpProviderBloc 當(dāng)做參數(shù)傳遞到我們的block里面,這里面包含了對將要被 Swizzling 的原始 IMP 的調(diào)用扣溺。

需要注意的是使用 class_replaceMethod 的時(shí)候如果一個方法來自父類骇窍,那么就給子類 add 一個方法, 并且把這個 NewIMP 設(shè)置給他锥余,然后返回的結(jié)果是NULL腹纳。

originalImpProviderBloc 里面我們注意到如果 imp 是 NULL的時(shí)候,是動態(tài)的拿到父類的 Method 然后去執(zhí)行驱犹。

我們還用圖來分析代碼嘲恍。

最開始 Swizzling 第一次的時(shí)候,由于子類不存在 sayHello 方法雄驹,再添加方法的時(shí)候由于返回的原始 IMP 是 NULL佃牛,所以對父類的調(diào)用是動態(tài)獲取的,而不是通過之前的 sel 指針去調(diào)用医舆。

如果我們再次對 Student Hook俘侠,由于 Student 已經(jīng)有 sayHello 方法象缀,這次 replace 會返回原來 IMP 的指針, 然后新的 IMP 會執(zhí)被填充到 Method 的指針指向爷速。

由此可見我們的方法引用是一個鏈表形狀的央星。

同理我們在 hook 父類的時(shí)候 父類的方法引用也是一個鏈表樣式的。

相信到了這里你已經(jīng)理解 RS 來 Swizzling 方式是:

如果是父類的方法那么就動態(tài)查找遍希,如果是自身的方法就構(gòu)造方法引用鏈等曼。來保證多次 Swizzling 的穩(wěn)定性里烦,并且不會和別人的 Swizzling 沖突凿蒜。

而且 RS 的實(shí)現(xiàn)由于不是分類的方法也不用約束開發(fā)者必須在 +load 方法調(diào)用才能保證安全,并且cmd 也不會發(fā)生變化胁黑。

其他Hook方式

其實(shí)著名的 Hook 庫還有一個叫 Aspect 他利用的方法是把所有的方法調(diào)用指向 _objc_msgForward 然后自行實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的步驟废封,在里面自行處理參數(shù)列表和返回值,通過 NSInvocation 去動態(tài)調(diào)用丧蘸。

國內(nèi)知名的熱修復(fù)庫 JSPatch 就是借鑒這種方式來實(shí)現(xiàn)熱修復(fù)的漂洋。

但是上面的庫要求必須是最后執(zhí)行的確保 Hook 的成功。 而且他不兼容其他 Hook 方式力喷,所以技術(shù)選型的時(shí)候要深思熟慮刽漂。

?什么時(shí)候需要Swizzling

我記得第一次學(xué)習(xí) AO P概念的時(shí)候是當(dāng)初在學(xué)習(xí) javaWeb 的時(shí)候 Serverlet 里面的 FilterChain,開發(fā)者可以實(shí)現(xiàn)各種各種的過濾器然后在過濾器中插入log弟孟, 統(tǒng)計(jì)贝咙, 緩存等無關(guān)主業(yè)務(wù)邏輯的功能行性代碼, 著名的框架 Struts2 就是這樣實(shí)現(xiàn)的拂募。

iOS 中由于 Swizzling 的 API 的簡單易用性導(dǎo)致開發(fā)者肆意濫用庭猩,影響了項(xiàng)目的穩(wěn)定性。
當(dāng)我們想要 Swizzling 的時(shí)候應(yīng)該思考下我們能不能利用良好的代碼和架構(gòu)設(shè)計(jì)來實(shí)現(xiàn)陈症,或者是深入語言的特性來實(shí)現(xiàn)蔼水。

一個利用語言特性的例子

我們都知道在iOS8下的?操作系統(tǒng)中通知中心會持有一個 __unsafe_unretained 的觀察者指針。如果?觀察者在 ?dealloc 的時(shí)候忘記從通知中心中移除录肯,之后如果觸發(fā)相關(guān)的通知就會造成 Crash趴腋。

我在設(shè)計(jì)防 Crash 工具 XXShield 的時(shí)候最初是 Hook NSObjec 的 dealloc 方法,在里面做相應(yīng)的移除觀察者操作论咏。后來一位真大佬提出這是一個非常不明智的操作于样,因?yàn)?dealloc 會影響全局的實(shí)例的釋放,開發(fā)者并不能保證代碼質(zhì)量非常有保障潘靖,一旦出現(xiàn)問題將會引起整個 APP 運(yùn)行期間大面積崩潰或異常行為穿剖。

下面我們先來看下 ObjCRuntime 源碼關(guān)于一個對象釋放時(shí)要做的事情,代碼約在objc-runtime-new.mm第6240行卦溢。


/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}


/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

上面的邏輯中明確了寫明了一個對象在釋放的時(shí)候初了調(diào)用 dealloc 方法糊余,還需要斷開實(shí)例上綁定的觀察對象秀又, 那么我們可以在添加觀察者的時(shí)候給觀察者動態(tài)的綁定一個關(guān)聯(lián)對象,然后關(guān)聯(lián)對象可以反向持有觀察者,然后在關(guān)聯(lián)對象釋放的時(shí)候去移除觀察者贬芥,由于不能造成循環(huán)引用所以只能選擇 __weak 或者 __unsafe_unretained 的指針吐辙, 實(shí)驗(yàn)得知 __weak 的指針在 dealloc 之前就已經(jīng)被清空, 所以我們只能使用 __unsafe_unretained 指針蘸劈。


@interface XXObserverRemover : NSObject {
    __strong NSMutableArray *_centers;
    __unsafe_unretained id _obs;
}
@end
@implementation XXObserverRemover

- (instancetype)initWithObserver:(id)obs {
    if (self = [super init]) {
        _obs = obs;
        _centers = @[].mutableCopy;
    }
    return self;
}

- (void)addCenter:(NSNotificationCenter*)center {
    if (center) {
        [_centers addObject:center];
    }
}

- (void)dealloc {
    @autoreleasepool {
        for (NSNotificationCenter *center in _centers) {
            [center removeObserver:_obs];
        }
    }
}

@end

void addCenterForObserver(NSNotificationCenter *center ,id obs) {
    XXObserverRemover *remover = nil;
    static char removerKey;
    @autoreleasepool {
        remover = objc_getAssociatedObject(obs, &removerKey);
        if (!remover) {
            remover = [[XXObserverRemover alloc] initWithObserver:obs];
            objc_setAssociatedObject(obs, &removerKey, remover, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [remover addCenter:center];
    }
    
}
void autoHook() {
    RSSwizzleInstanceMethod([NSNotificationCenter class], @selector(addObserver:selector:name:object:),
                            RSSWReturnType(void), RSSWArguments(id obs,SEL cmd,NSString *name,id obj),
                            RSSWReplacement({
        RSSWCallOriginal(obs,cmd,name,obj);
        addCenterForObserver(self, obs);
    }), 0, NULL);
    
}

需要注意的是在添加關(guān)聯(lián)者的時(shí)候一定要將代碼包含在一個自定義的 AutoreleasePool 內(nèi)昏苏。

我們都知道在 Objective-C 的世界里一個對象如果是 Autorelease 的 那么這個對象在當(dāng)前方法棧結(jié)束后才會延時(shí)釋放,在 ARC 環(huán)境下?威沫,一般一個 Autorelease 的對象會被放在一個系統(tǒng)提供的 AutoreleasePool 里面贤惯,然后AutoReleasePool drain 的時(shí)候再去釋放內(nèi)部持有的對象,通常情況下命令行程序是沒有問題的棒掠,但是在iOS的環(huán)境中 AutoReleasePool是在 Runloop 控制下在空閑時(shí)間進(jìn)行釋放的孵构,這樣可以提升用戶體驗(yàn),避免造成卡頓烟很,但是在我們這種場景中會有問題颈墅,我們嚴(yán)格依賴了觀察者?調(diào)用 dealloc 的時(shí)候關(guān)聯(lián)對象也會去 dealloc,如果系統(tǒng)的 AutoReleasePool 出現(xiàn)了延時(shí)釋放雾袱,會導(dǎo)致當(dāng)前對象被回收之后 過段時(shí)間關(guān)聯(lián)對象才會釋放恤筛,這時(shí)候前文使用的 __unsafe_unretained 訪問的?就是非法地址。

我們在添加關(guān)聯(lián)對象的時(shí)候添加一個自定義的 AutoreleasePool 保證了對關(guān)聯(lián)對象引用的單一性芹橡,保證了我們依賴的釋放順序是正確的毒坛。從而正確的移除觀察者。

參考

  1. JRSwizzle
  2. RSSwizzle
  3. Aspect
  4. 玉令天下的博客Objective-C Method Swizzling
  5. 示例代碼

友情感謝

最后感謝 騎神 大佬修改我那蹩腳的文字描述僻族。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粘驰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子述么,更是在濱河造成了極大的恐慌蝌数,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件度秘,死亡現(xiàn)場離奇詭異顶伞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)剑梳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門唆貌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垢乙,你說我怎么就攤上這事锨咙。” “怎么了追逮?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵酪刀,是天一觀的道長粹舵。 經(jīng)常有香客問我,道長骂倘,這世上最難降的妖魔是什么眼滤? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮历涝,結(jié)果婚禮上诅需,老公的妹妹穿的比我還像新娘。我一直安慰自己荧库,他們只是感情好堰塌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著电爹,像睡著了一般蔫仙。 火紅的嫁衣襯著肌膚如雪料睛。 梳的紋絲不亂的頭發(fā)上丐箩,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音恤煞,去河邊找鬼屎勘。 笑死,一個胖子當(dāng)著我的面吹牛居扒,可吹牛的內(nèi)容都是我干的概漱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼喜喂,長吁一口氣:“原來是場噩夢啊……” “哼瓤摧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玉吁,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤照弥,失蹤者是張志新(化名)和其女友劉穎富岳,沒想到半個月后颈走,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怀吻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年影斑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了给赞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矫户,死狀恐怖片迅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情皆辽,我是刑警寧澤柑蛇,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布罐旗,位于F島的核電站,受9級特大地震影響唯蝶,放射性物質(zhì)發(fā)生泄漏九秀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一粘我、第九天 我趴在偏房一處隱蔽的房頂上張望鼓蜒。 院中可真熱鬧,春花似錦征字、人聲如沸都弹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畅厢。三九已至,卻和暖如春氮昧,著一層夾襖步出監(jiān)牢的瞬間框杜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工袖肥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咪辱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓椎组,卻偏偏與公主長得像油狂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寸癌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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