iOS的越狱检测和反越狱检测剖析

iOS的越狱检测和反越狱检测原理剖析

为什么要检测越狱?因为越狱后会大幅降低安全性。对于一些金融类的APP或者游戏类的,因为监管原因、资金安全问题,甚至防止使用越狱分析等,需要进行检测。不过其实越狱与反越狱就像矛与盾一样,都没有完美的方案。用一些反越狱插件可以防99%的越狱检测方式,本质上因为越狱后可以hook已知的所有检测越狱的方法,包括我下面的几种常用的。对于具体的反越狱插件可以用一些特定的方案来辅助检测。

具体代码清参考我的github

一、 越狱检测方案

1. 检测动态库

1.1 判断动态库stat是否是系统的库,并利用stat 来检测一些特定的文件权限

stat 命令时OS系统中用来判断文件信息的,但是对于私有的路径调用命令返回的是-1,如果越狱后,因为权限变化,可以通过stat返回私有目录下的文件信息。具体命令可以参考官方文档

代码实现:

BOOL isStatNotSystemLib() {
    if(TARGET_IPHONE_SIMULATOR)return NO;
    int ret ;
    Dl_info dylib_info;
    int (*func_stat)(const char *, struct stat *) = stat;
    if ((ret = dladdr(func_stat, &dylib_info))) {
        NSString *fName = [NSString stringWithUTF8String: dylib_info.dli_fname];
        if(![fName isEqualToString:@"/usr/lib/system/libsystem_kernel.dylib"]){
            return YES;
        }
    }
    
    char *JbPaths[] = {"/Applications/Cydia.app",
    "/usr/sbin/sshd",
    "/bin/bash",
    "/etc/apt",
    "/Library/MobileSubstrate",
    "/User/Applications/"};
    
    for (int i = 0;i < sizeof(JbPaths) / sizeof(char *);i++) {
        struct stat stat_info;
        if (0 == stat(JbPaths[i], &stat_info)) {
            return YES;
        }
    }
    
    return NO;
}

1.2 判断是否注入了动态库

利用_dyld_get_image_name来获取动态库的名字,并查看是否有相关的动态库,这个相对来说最为准确,因为这个系统库运行的更早,且很多越狱的也需要依赖这个库的正常运行,所以更难被绕过

BOOL isInjectedWithDynamicLibrary()
{
    int i=0;
    char *substrate = "/Library/MobileSubstrate/MobileSubstrate.dylib";
    while(true){
        // hook _dyld_get_image_name方法可以绕过
        const char *name = _dyld_get_image_name(i++);
        if(name==NULL){
            break;
        }
        if (name != NULL) {
                    if (strcmp(name,substrate)==0) {
                            return YES;
                    }
        }
    }
    return NO;
}

2. 判断是否有越狱相关文件或权限

2.1 判断是否能打开越狱软件

利用URL Scheme来查看是否能够代开比如cydia这些越狱软件

    //Check cydia URL hook canOpenURL 来绕过
    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.avl.com"]])
    {
        return YES;
    }

    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]])
    {
        return YES;
    }

2.2 判断是否可以访问一些越狱的文件

越狱后会产生额外的文件,通过判断是否存在这些文件来判断是否越狱了,可以用fopen和FileManager两个不同的方法去获取

BOOL fileExist(NSString* path)
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = NO;
    if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){
        return YES;
    }
    return NO;
}

BOOL directoryExist(NSString* path)
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = YES;
    if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){
        return YES;
    }
    return NO;
}

BOOL canOpen(NSString* path)
{
    FILE *file = fopen([path UTF8String], "r");
    if(file==nil){
        return fileExist(path) || directoryExist(path);
    }
    fclose(file);
    return YES;
}

 
 NSArray* checks = [[NSArray alloc] initWithObjects:@"/Application/Cydia.app",
                       @"/Library/MobileSubstrate/MobileSubstrate.dylib",
                       @"/bin/bash",
                       @"/usr/sbin/sshd",
                       @"/etc/apt",
                       @"/usr/bin/ssh",
                       @"/private/var/lib/apt",
                       @"/private/var/lib/cydia",
                       @"/private/var/tmp/cydia.log",
                       @"/Applications/WinterBoard.app",
                       @"/var/lib/cydia",
                       @"/private/etc/dpkg/origins/debian",
                       @"/bin.sh",
                       @"/private/etc/apt",
                       @"/etc/ssh/sshd_config",
                       @"/private/etc/ssh/sshd_config",
                       @"/Applications/SBSetttings.app",
                       @"/private/var/mobileLibrary/SBSettingsThemes/",
                       @"/private/var/stash",
                       @"/usr/libexec/sftp-server",
                       @"/usr/libexec/cydia/",
                       @"/usr/sbin/frida-server",
                       @"/usr/bin/cycript",
                       @"/usr/local/bin/cycript",
                       @"/usr/lib/libcycript.dylib",
                       @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
                       @"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
                       @"/Applications/FakeCarrier.app",
                       @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
                       @"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
                       @"/usr/libexec/ssh-keysign",
                       @"/usr/libexec/sftp-server",
                       @"/Applications/blackra1n.app",
                       @"/Applications/IntelliScreen.app",
                       @"/Applications/Snoop-itConfig.app"
                       @"/var/lib/dpkg/info", nil];
    //Check installed app
    for(NSString* check in checks)
    {
        if(canOpen(check))
        {
            return YES;
        }
    }

2.3 查看是否有权限写入私有目录

通过检测是否可以写入私有目录来判断,是否越狱了

NSString *path = @"/private/avl.txt";
    NSFileManager *fileManager = [NSFileManager defaultManager];
    @try {
        NSError* error;
        NSString *test = @"AVL was here";
        [test writeToFile:path atomically:NO encoding:NSStringEncodingConversionAllowLossy error:&error];
        [fileManager removeItemAtPath:path error:nil];
        if(error==nil)
        {
            return YES;
        }

        return NO;
    } @catch (NSException *exception) {
        return NO;
    }

3. 利用系统命令来判断

3.1 通过lstat命令来判断系统的一些目录是否存在还是变成了链接

因为越狱后会变动一些文件,这些文件目录会迁移到其他区域,但是原来的文件位置必须有效,所以会创建符号链接,链接到原来的路径,我们可以检测这些符号链接是否存在,存在说明就越狱了

//symlink verification
    struct stat sym;
    // hook lstat可以绕过
    if(lstat("/Applications", &sym) || lstat("/var/stash/Library/Ringtones", &sym) ||
       lstat("/var/stash/Library/Wallpaper", &sym) ||
       lstat("/var/stash/usr/include", &sym) ||
       lstat("/var/stash/usr/libexec", &sym)  ||
       lstat("/var/stash/usr/share", &sym) ||
       lstat("/var/stash/usr/arm-apple-darwin9", &sym))
    {
        if(sym.st_mode & S_IFLNK)
        {
            return YES;
        }
    }

3.2 是否能够fork一个子进程

未越狱的设备是无法fork子进程的,可以通过这个检测,还有其他类似的命令:方法posix_spawn,kill,popen等

//Check process forking
    // hook fork
    int pid = fork();
    if(!pid)
    {
        exit(1);
    }
    if(pid >= 0)
    {
        return YES;
    }

4. 查看是否有异常类和异常的动态库

4.1 检测是否有异常类

//     查看是否有注入异常的类,比如HBPreferences 是越狱常用的类,这里无法绕过,只要多找一些特征类就可以,注意,很多反越狱插件会混淆,所以可能要通过查关键方法来识别
    NSArray *checksClass = [[NSArray alloc] initWithObjects:@"HBPreferences",nil];
    for(NSString *className in checksClass)
    {
      if (NSClassFromString(className) != NULL) {
        return YES;
      }
    }
  

4.2 检测是否有异常的动态库

这个和1.2章检测注入动态库的区别是,一般反越狱插件会hook_dyld_get_image_name这个方法,把越狱使用的一些动态库给影藏掉(比如返回其他动态库名称,或者返回正常的),导致匹配不到,可以利用image加载时的回调来从MachO Header中去动态库信息,需要注意的是使用dladdr检测库信息的时候,也可能被强制返回错误,需要进一步做一下判断,具体看下面代码。

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _dyld_register_func_for_add_image(_check_image);
  });
}

// 监听image加载,从这里判断动态库是否加载,因为其他的检测动态库的方案会被hook
static void _check_image(const struct mach_header *header,
                                      intptr_t slide) {
  // hook Image load
  if (SCHECK_USER) {
    // 检测后就不在检测
    return;
  }

  // 检测的lib
  NSSet *dylibSet = [NSSet setWithObjects:
                     @"/usr/lib/CepheiUI.framework/CepheiUI",
                     @"/usr/lib/libsubstitute.dylib"
                     @"/usr/lib/substitute-inserter.dylib",
                     @"/usr/lib/substitute-loader.dylib",
                     nil];
  
  Dl_info info;
  // 0表示加载失败了,这里大概率是被hook导致的
  if (dladdr(header, &info) == 0) {
    char *dlerro = dlerror();
    // 获取失败了 但是返回了dli_fname, 说明被人hook了,目前看的方案都是直接返回0来绕过的
    if(dlerro == NULL && info.dli_fname != NULL) {
      NSString *libName = [NSString stringWithUTF8String:info.dli_fname];
      // 判断有没有在动态列表里面
      if ([dylibSet containsObject:libName]) {
        SCHECK_USER = YES;
      }
    }
    return;
  }
  
  
}

5. 检测是否在调试

5.1 查看是否有环境变量DYLD_INSERT_LIBRARIES

#pragma mark 通过环境变量DYLD_INSERT_LIBRARIES检测是否越狱
BOOL dyldEnvironmentVariables ()
{
    if(TARGET_IPHONE_SIMULATOR)return NO;
    return !(NULL == getenv("DYLD_INSERT_LIBRARIES"));
}

5.2 判断当前进程是否为调试模式

使用sysctl方法来获取当前进程的相关信息,从而确实是否在进行pTraced调试,具体参考sysctl,方法来自于官方https://developer.apple.com/library/archive/qa/qa1361/_index.html, 这里插一句,之前可以通过sysctl获取所有运行的程序,后来被苹果禁止了

BOOL isDebugged()
{
    int junk;
    int mib[4];
    struct kinfo_proc info;
    size_t size;
    info.kp_proc.p_flag = 0;
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC;
    mib[2] = KERN_PROC_PID;
    mib[3] = getpid();
    size = sizeof(info);
    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
    assert(junk == 0);
    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}

6. 阻止DYLD_INSERT_LIBRARIES生效 (iOS 10以下才有效)

反越狱插件基本上都是通过DYLD_INSERT_LIBRARIES来做注入的,这个是官方提供的一套修改动态库的方案,看一下相关说明:

DYLD_INSERT_LIBRARIES
    This is a colon separated list of dynamic libraries to load before the ones specified in the
    program. This lets you test new modules of existing dynamic shared libraries that are used in
    flat-namespace images by loading a temporary dynamic shared library with just the new modules.
    Note that this has no effect on images built a two-level namespace images  using a dynamic
    shared library unless DYLD_FORCE_FLAT_NAMESPACE is also used.

查看下dylib的源码pruneEnvironmentVariables方法里,会移出DYLD开头的环境变量。

//
// For security, setuid programs ignore DYLD_* environment variables.
// Additionally, the DYLD_* enviroment variables are removed
// from the environment, so that any child processes don't see them.
//
static void pruneEnvironmentVariables(const char* envp[], const char*** applep)
{
    // delete all DYLD_* and LD_LIBRARY_PATH environment variables
  ***
    if ( removedCount != 0 ) {
        dyld::log("dyld: DYLD_ environment variables being ignored because ");
        switch (sRestrictedReason) {
            case restrictedNot:
                break;
            case restrictedBySetGUid:
                dyld::log("main executable (%s) is setuid or setgid\n", sExecPath);
                break;
            case restrictedBySegment:
                dyld::log("main executable (%s) has __RESTRICT/__restrict section\n", sExecPath);
                break;
            case restrictedByEntitlements:
                dyld::log("main executable (%s) is code signed with entitlements\n", sExecPath);
                break;
        }
    }
    
  ***
}



检测是否需要拒绝的代码在这里processRestricted,其中geteuid是禁止使用的,所以可以用hasRestrictedSegment方法可以处理。

static bool processRestricted(const macho_header* mainExecutableMH)
{
    // all processes with setuid or setgid bit set are restricted
    if ( issetugid() ) {
        sRestrictedReason = restrictedBySetGUid;
        return true;
    }
        
    const uid_t euid = geteuid();
    if ( (euid != 0) && hasRestrictedSegment(mainExecutableMH) ) {
        // existence of __RESTRICT/__restrict section make process restricted
        sRestrictedReason = restrictedBySegment;
        return true;
    }
    
#if __MAC_OS_X_VERSION_MIN_REQUIRED    
    // ask kernel if code signature of program makes it restricted
    uint32_t flags;
    if ( syscall(SYS_csops /* 169 */,
                0 /* asking about myself */,
                CS_OPS_STATUS,
                &flags,
                sizeof(flags)) != -1) {
        if (flags & CS_RESTRICT) {
            sRestrictedReason = restrictedByEntitlements;
            return true;
        }
    }
#endif
    return false;
}

检索hasRestrictedSegment,可以指定如果段里面有 __RESTRICT,并且节里面有 __restrict,就返回true

static bool hasRestrictedSegment(const macho_header* mh)
{
    const uint32_t cmd_count = mh->ncmds;
    const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header));
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        switch (cmd->cmd) {
            case LC_SEGMENT_COMMAND:
            {
                const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                
                //dyld::log("seg name: %s\n", seg->segname);
                if (strcmp(seg->segname, "__RESTRICT") == 0) {
                    const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                    const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                    for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                        if (strcmp(sect->sectname, "__restrict") == 0) 
                            return true;
                    }
                }
            }
            break;
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
        
    return false;
}

所以只需要在xcode中Other Linker Flags 加入 -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null。创建一个空的section来解决(也可以放一个文件,利用getsectdata API获取),不过注意的是,千万不要在非发布环境的地方开启这个,因为会影响到比如Instruments等依赖动态注入来debug的工具,而且苹果在iOS10以后放弃了检测。具体可以参考:Blocking Code Injection on iOS and OS X

注意: 新的dylib实现中,这个检测逻辑只有MAC可用,iPhone已经不可用了。

if __MAC_OS_X_VERSION_MIN_REQUIRED

二、防越狱检测的方案和对策

越狱注入的基本原理是:MobileSubstrate会将 SpringBoard 的 [FBApplicationInfo environmentVariables] 函式做 hook ,将环境变量DYLD_INSERT_LIBRARIES设定新增需要载入的动态库,但是应用的二进制包无需做任何变化,dyld会在载入应用的时候因为DYLD_INSERT_LIBRARIES去插入具体的库。 具体可以参考:https://itw01.com/84SJREC.html

2.1 shadow的防检测方法

有一些越狱的插件可以做到防越狱检测,这里以shadow为例,来解释下原理,知己知彼。shadow现在是开源的,可以在这里找到: https://github.com/jjolano/shadow/tree/old(用old分支)。

首先需要利用Tweak软件,大家可以参考这里iOS 越狱的Tweak开发,最主要的是使用cydia Substrate 软件来做动态库注入。具体使用可以参考这里cydiasubstrate,如果有时间会分析下具体的实现,这是个跨平台的开发方案,也支持Android平台。相关源码可以参考substrate.

简单来说就是设置环境变量,用DYLD_INSERT_LIBRARIES来加载subsrate的注入代码:
https://github.com/jevinskie/substrate/blob/97fa4bae349b867ae789bb756f6c45c311d16e7d/Environment.hpp#L25-L26 。并利用__attribute__((constructor)),关键字hook的相关代码在mian函数执行前执行。

从这里分析可以知道,本质是通过DYLD_INSERT_LIBRARIES,详细的原理可以学习下这篇博文:macOS/OSX中的DYLDINSERTLIBRARIES DYLIB注入技术详解。所以可以利用上面提到的6. 阻止DYLD_INSERT_LIBRARIES生效来防止。

然后看下源码,shadow主要逻辑为:

  1. shadow会维护一个列表,检索哪些文件是越狱需要保护的文件
  2. hook相关的类,如果要检索这些文件,就影藏,返回修改后的结果。

最主要hook以下的方法

  1. hook c的类,主要是各种判断文件权限和执行命令的方法,比如:

    • access
    • getenv
    • fopen
    • freopen
    • stat
    • dlopen
  2. hook_NSFileManager | NSFileHandle | NSDirectoryEnumerator | hook_NSFileVersion | NSBundle

  3. hook_NSURL

  4. hook_UIApplication

  5. hook_NSBundle

  6. hook_CoreFoundation

  7. hook UIImage

  8. hook NSMutableArray | NSArray | NSMutableDictionary | NSDictionary | NSString

  9. hook 第三方库检测方法

  10. hook hook_debugging

    • sysctl 主要用来检测是否当前进程挂载了P_TRACED
    • getppid 返回当前的pid
    • _ptrace
  11. hook_dyld_image 。hook image动态加载的方法

    • _dyld_image_count 获取image的数量
    • _dyld_get_image_name 获取动态库的名字
  12. hook_dyld_dlsym。 hook 用来检测是否可以加载动态库。功能和dlopen一样

  13. hook系统一些私有方法: vfork | fork | hook_popen(打开管道)

  14. hook runtime

    • objc_copyImageNames hook 获取所有加载的Objective-C框架和动态库的名称,shadow并不能所有都hook,他会把应用app的image加载后就停止了,防止检测的执行后注入的动态库被查找到:

      • %hookf(const char * _Nonnull *, objc_copyImageNames, unsigned int *outCount) {
        const char * _Nonnull *ret = %orig;

        if(ret && outCount) {
            NSLog(@"copyImageNames: %d", *outCount);
        
            const char *exec_name = _dyld_get_image_name(0);
            unsigned int i;
        
            for(i = 0; i < *outCount; i++) {
                if(strcmp(ret[i], exec_name) == 0) {
                    // Stop after app executable.
                    *outCount = (i + 1);
                    break;
                }
            }
        }
        
        return ret;
        

        }

    • objc_copyClassNamesForImage 获取动态库里面对应的所有class名称

  15. hook_dladdr dladdr可以用来获取方法或image对应的信息,比如所属的动态库的名称,这里hook如果是忽略的文件,则返回0,所以如果返回0,要再判断下是否数据真的是空的。

    static int (*orig_dladdr)(const void *addr, Dl_info *info);
    static int hook_dladdr(const void *addr, Dl_info *info) {
        int ret = orig_dladdr(addr, info);
    
        if(!passthrough && ret) {
            NSString *path = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:info->dli_fname length:strlen(info->dli_fname)];
    
            if([_shadow isImageRestricted:path]) {
                return 0;
            }
        }
    
        return ret;
    }
    

2.3 如何防止shadow等插件绕过

几个策略:

  1. 检测这些插件的关键指纹,比如检测只有他们有的类, 参考4. 查看是否有异常类和异常的动态库的实现
  2. 阻止DYLD_INSERT_LIBRARIES生效, 参考 6. 阻止DYLD_INSERT_LIBRARIES生效 。(这个可以通过修改macho,重新打包来绕过)
  3. 生产发布前,使用objc_copyImageNames方法记录使用的所有动态库,做成白名单,在运行过程中,再运行objc_copyImageNames去查看当前的动态库是否一致

三、 参考

  1. Jailbreak Detection Methods
  2. system 函数被废除的替代方法](https://www.exchen.net/ios-hacker-system-%E5%87%BD%E6%95%B0%E8%A2%AB%E5%BA%9F%E9%99%A4%E7%9A%84%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%B3%95.html)
  3. https://github.com/theos/theos
  4. wiki: Cydia_Substrate
  5. LD_PRELOAD, DYLD_INSERT_LIBRARIES 和 Cydia Substrate
  6. Blocking Code Injection on iOS and OS X
  7. 动态库加载源码:dyld
  8. Simple code injection using DYLD_INSERT_LIBRARIES
  9. macOS/OSX中的DYLDINSERTLIBRARIES DYLIB注入技术详解
  10. A security review of 1,300 AppStore applications
  11. Tweak原理&防护
  12. Apple open source
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335