Frida入門學(xué)習(xí)筆記-hook native中的函數(shù)(1)

如需轉(zhuǎn)載請(qǐng)注明出處 by SimonKoh

0x01 前言

關(guān)于android的hook以前一直用的xposed來(lái)hook java層的函數(shù)影涉,對(duì)于so層則利用adbi井氢,但是不知道為什么adbi給我的體驗(yàn)并不是很好蔬顾,剛好前段時(shí)間了解到frida框架支持android娃圆、ios褪储、linux疗韵、windows、macos李命,而且在android設(shè)備上可以同時(shí)hook java登淘、native十分方便,最重要的一點(diǎn)是不需要重啟手機(jī)封字,于是就研究了一下

0x02 搭建環(huán)境

操作系統(tǒng):windwos7
移動(dòng)設(shè)備:Nexus 4 (4.4.4)
首先你需要一個(gè)android手機(jī)形帮,建議使用google系,這樣會(huì)省去很多麻煩周叮,而且root的話也十分的方便,只需要去SuperSU下載卡刷包或者apk刷個(gè)root進(jìn)去便可界斜。說(shuō)回來(lái)仿耽,frida是python的一個(gè)模塊,所以使用frida的話還需要一些python的基礎(chǔ)各薇,一舉兩得..順便入了Python的門项贺,發(fā)現(xiàn)python是真他娘的好用 : D

安裝frida模塊

一定要確保你的windows有安裝python(2、3的版本我都安裝了)峭判,在cmd運(yùn)行下面命令安裝frida模塊:

pip install frida

如果你同時(shí)有兩個(gè)python版本开缎,你可以使用pip2或者pip3來(lái)代替pip

下載frida-server導(dǎo)入手機(jī)并運(yùn)行

點(diǎn)擊下載frida-server-10.6.28-android-arm.xz,下載解壓后用如下命令把frida-server-10.6.28-android-arm推送到手機(jī)上

adb push frida-server-10.6.28-android-arm /data/local/tmp/frida-server

更改權(quán)限并運(yùn)行

adb shell
su
cd /data/local/tmp
chmod 777 frida-server
./frida-server

測(cè)試frida能否成功交互

新開(kāi)一個(gè)cmd林螃,并轉(zhuǎn)發(fā)端口(好像不轉(zhuǎn)發(fā)也是可以的奕删,我后來(lái)用的話都沒(méi)有轉(zhuǎn)發(fā)端口)

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

好了,現(xiàn)在我們測(cè)試一下frida是否能成功交互疗认,在cmd中輸入如下命令:

frida-ps -U

測(cè)試交互

此時(shí)可以看到frida可以成功交互了完残。

0x03 研究frida

利用命令行工具h(yuǎn)ook libc.so的open()函數(shù)

首先我們知道frida是python的一個(gè)模塊伏钠,那么這樣我們當(dāng)然可以通過(guò)寫(xiě)python腳本import frida來(lái)實(shí)現(xiàn)對(duì)frida的利用,此外frida同時(shí)會(huì)提供幾個(gè)命令行工具(工具存放在python/Scripts目錄下面谨设,所以你可以添加到系統(tǒng)環(huán)境變量方便使用)熟掂,例如frida,可以通過(guò)

frida -help

查看使用幫助:


frida -help

此外還有frida-ps扎拣,還有frida-trace赴肚、與frida-dicover,官網(wǎng)上的資料也少二蓝,我大概看了看好像也沒(méi)啥好用的...

繼續(xù)說(shuō)回frida這個(gè)命令行工具誉券,在上圖的幫助信息中我們看到 "-U" 參數(shù)代表我們連接的是遠(yuǎn)程USB server,同理你也可以使用其他參數(shù)來(lái)連接侣夷,"-f "參數(shù)則表示在手機(jī)端啟動(dòng)一個(gè)你指定的android程序横朋,那個(gè)FILE則表示應(yīng)用的包名,通常"-f"這個(gè)參數(shù)配合"--no-pause"參數(shù)來(lái)使用百拓,因?yàn)榭赡懿蛔屵M(jìn)程恢復(fù)的話可能會(huì)有奇怪的問(wèn)題琴锭,"-p""-n"命令分別表示attach到進(jìn)程的名字或者pid,"-l"參數(shù)則是代表需要注入的javascript腳本衙传,而這個(gè)javascript的腳本就是我們所寫(xiě)的hook代碼决帖,完成函數(shù)的hook,內(nèi)存的dump等一系列功能蓖捶,所以順便又可以學(xué)一手node.js豈不美滋滋....
當(dāng)我們使用frida這個(gè)命令行工具成功attach到目標(biāo)進(jìn)程的時(shí)候地回,frida會(huì)給我們返回一個(gè)Frida CLI,說(shuō)明白點(diǎn)就是一個(gè)交互窗口俊鱼,下面我們就能看到刻像。

好了,下面我們就利用frida命令行工具來(lái)hook一下chrome瀏覽器中l(wèi)ibc.so的open函數(shù)

frida -U -f com.android.chrome --no-pause

此次我用的是"-f"參數(shù)并闲,也就表示细睡,我需要重新啟動(dòng)這個(gè)chrome瀏覽器,并且attach上去帝火,當(dāng)然如果你僅僅只想attach到正在運(yùn)行的應(yīng)用程序的某一個(gè)進(jìn)程溜徙,你可以用"-p"參數(shù),那么你肯定好奇了犀填,你不是說(shuō)要hook open函數(shù)嗎蠢壹?? 你的代碼呢九巡?图贸?逗大家玩呢?? 哈哈求妹,很好你發(fā)現(xiàn)了問(wèn)題乏盐,如果我們只是這樣啟動(dòng)程序肯定是不會(huì)hook open函數(shù)的,還記得我剛才說(shuō)的javascript代碼嗎制恍? 我們hook的代碼可都在那個(gè)里面父能,你可以在剛才的命令中加"-l"參數(shù)指定你的js hook代碼,load到目標(biāo)進(jìn)程净神。

Frida CLI

很好何吝,目前我們就有這樣一個(gè)交互窗口了,我們剛才忘了使用"-l"參數(shù)鹃唯,不過(guò)不要緊爱榕,我們可以在交互窗口中用"%load"命令來(lái)指定需要加載的js代碼(如果你需要在程序啟動(dòng)的時(shí)候就hook程序的某個(gè)函數(shù),那么我不希望你忘掉用"-l"參數(shù) :D )坡慌,js代碼:

Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {
        log("open() called!")
    },
    onLeave:function(retval){
    
    }
});

有一點(diǎn)要補(bǔ)充下黔酥,我看到國(guó)外有位大佬說(shuō)要在js代碼外面包裹個(gè)setImmediate();好像會(huì)避免一些超時(shí)的問(wèn)題,不過(guò)我目前還沒(méi)發(fā)現(xiàn)有什么問(wèn)題洪橘,你喜歡的話可以加上:D

setImmediate(function() {
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {
        log("open() called!")
    },
    onLeave:function(retval){
    
    }
});
});

將代碼load進(jìn)去跪者!


frida CLI load js腳本 并 hook open函數(shù)

順手點(diǎn)幾下屏幕,發(fā)現(xiàn)這個(gè)open函數(shù)已經(jīng)hook到了熄求,當(dāng)然這個(gè)只是CLI的很小一部分功能渣玲,它支持命令補(bǔ)全,具體詳細(xì)的用法或命令可以去看官網(wǎng)的JavaScript API

利用python frida模塊hook libc.so的open()函數(shù)

前面說(shuō)的是frida提供的命令行工具弟晚,那么下面我們就要自己寫(xiě)python代碼來(lái)利用frida模塊了忘衍,我們還是hook chrome的open函數(shù)(open函數(shù):我招你惹你了? : / )卿城,python代碼如下:

# hook chrome進(jìn)程的libc.so中的函數(shù)open
import frida
import sys

device = frida.get_usb_device()
pid = device.spawn(["com.android.chrome"])
session = device.attach(pid)
device.resume(pid)

scr = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {
        send("open called!");
    },
    onLeave:function(retval){
    
    }
});
"""
def on_message(message ,data):
    print(message['payload'])


script = session.create_script(scr)
script.on("message" , on_message)
script.load()
sys.stdin.read()

效果如下:


python hook open函數(shù)

ok枚钓,是不是很干脆? 我們來(lái)看一下代碼:

device = frida.get_usb_device()

表示連接usb設(shè)備瑟押,我們?cè)趇python看一下這個(gè)device是個(gè)啥:


device

好像是一個(gè)類對(duì)象吧搀捷,我最近才接觸python,很多不是很懂... 繼續(xù)tab一下看看device都有什么屬性:


device屬性

在tab過(guò)后我們發(fā)現(xiàn)有如上這么多屬性勉耀,而上面我們代碼中用到的spawn()方法,它的參數(shù)是python列表[“com.android.chrome”]蹋偏,然后返回值傳遞給pid 這樣我們便可以啟動(dòng)chrome這個(gè)應(yīng)用:


spawn

可以看到返回值是int類型的便斥,目前我們發(fā)現(xiàn)程序還并沒(méi)有啟動(dòng),main thread還處于阻塞狀態(tài)威始,而我們通過(guò)
device.resume(pid)便可以讓?xiě)?yīng)用恢復(fù)運(yùn)行枢纠,不過(guò)在此之前,我們需要通過(guò)device.attach(pid)方法獲取session對(duì)象來(lái)附加到目標(biāo)進(jìn)程中黎棠,我們看一下session都有什么屬性:

session

這些屬性大家可以自己嘗試下镰绎,我們通過(guò)script = session.create_script(scr)來(lái)創(chuàng)建js腳本畴栖,scr就是我們需要load到目標(biāo)進(jìn)程中的js代碼吗讶,創(chuàng)建腳本后返回給script

script

可以看到script是frida.core.Script的一個(gè)對(duì)象,大家有能力的話可以去閱讀一下源碼膜毁,script包含的屬性如下:

script屬性

在執(zhí)行script.on("message" , on_message)script.load()后我們就成功把js代碼注入到com.android.chrome進(jìn)程中了瘟滨,如果進(jìn)程調(diào)用open函數(shù)室奏,就會(huì)通過(guò)js代碼中的send函數(shù)發(fā)回message胧沫,我們這回直接打印message:

message

可以看到send傳回的message在python中是字典類型绒怨,其中'payload'字段就是我們send所寫(xiě)的內(nèi)容“open called!”

看回js代碼

Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {
        send("open called!");
    },
    onLeave:function(retval){
    
    }
});

在進(jìn)程調(diào)用open函數(shù)后send只是去打印"open called!"南蹂,那我不光想打印open函數(shù)有沒(méi)有調(diào)用念恍,我還想看看它的值是什么峰伙,能做到嗎?
當(dāng)然可以 :D策彤,這個(gè)args其實(shí)就包括我們想要的東西店诗,onEnter 表示在函數(shù)調(diào)用之前執(zhí)行的代碼捧弃,onLeave 表示在函數(shù)執(zhí)行后需要執(zhí)行的代碼塔橡,所以這個(gè)retval也就包括了返回值葛家,關(guān)于我們用的Interceptor API癞谒,frida官方的JavaScript API 文檔是這么寫(xiě)的:

Interceptor

Interceptor.attach()第一個(gè)參數(shù)是一個(gè)NativePointer指針,在之前我們用的Module.findExportByName("libc.so" , "open")的返回值枢希,關(guān)于Moudle同樣在文檔中可以找到桌吃,第二個(gè)參數(shù)則就是我們的js代碼塊,我們所要的打印參數(shù)與返回值也就是在這里完成茅诱。

Module.findExportByName

我們完善一下代碼,關(guān)于libc.so的open函數(shù)參數(shù)是這樣定義的瑟俭,int open( const char * pathname, int flags);
那我們就嘗試打印出來(lái)pathname,并把結(jié)果寫(xiě)入到文件中契邀,畢竟總是輸出到cmd會(huì)看的很亂也不好找,修改后的代碼如下:

# hook chrome進(jìn)程的libc.so中的函數(shù)open
import frida
import sys
import io

device = frida.get_usb_device()

session = device.attach(int(sys.argv[1]))

scr = """
setImmediate(function() {
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {

        send("open called! args[0]:",Memory.readByteArray(args[0],256));
    },
    onLeave:function(retval){
    
    }
});

});
"""

def on_message(message ,data):
    file_object=open("d:\\log.txt",'ab+')
    file_object.write(message['payload'].encode())
    file_object.write(data.split(b'\x00')[0])
    file_object.write('\n'.encode())
    file_object.close()


script = session.create_script(scr)
script.on("message" , on_message)
script.load()
sys.stdin.read()

log如下:

open called! args[0]:/data/data/com.android.chrome/shared_prefs/com.android.chrome_preferences.xml
open called! args[0]:/data/data/com.android.chrome/cache/Cache/3e0cddc5ac3f04af_0
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.XETk74
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.XETk74
open called! args[0]:/data/data/com.android.chrome/shared_prefs/com.android.chrome_preferences.xml
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/app_tabs/0/tab_state0
open called! args[0]:/data/data/com.android.chrome/cache/Cache/c94e1f14f3976339_0
open called! args[0]:/data/data/com.android.chrome/cache/Cache/3fc24daa4a4425e7_0
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.E0rQGy
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.E0rQGy
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/app_textures/32
open called! args[0]:/data/data/com.android.chrome/cache/Cache/e1c32483ea7ba39f_0
open called! args[0]:/dev/ashmem

細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)坯门,我這次沒(méi)有用spawn()函數(shù)來(lái)啟動(dòng)chrome應(yīng)用微饥,因?yàn)槿绻阆雋ook的動(dòng)作如果不是在程序開(kāi)始就進(jìn)行的話古戴,而是可控的動(dòng)作,那么你直接attach到其相應(yīng)的進(jìn)程上就好了简软,這樣可以避免我從程序一開(kāi)始就大量hook open函數(shù),此次我們啟動(dòng)的方式為python open.py 27032 其中第一個(gè)參數(shù)就是pid 指定我們需要hook的進(jìn)程疼蛾,我選擇的是chrome的主進(jìn)程的進(jìn)程號(hào),我們繼續(xù)往下看赠法。

send("open called! args[0]:",Memory.readByteArray(args[0],256));

可以看到args[0]表示open的第一個(gè)參數(shù)砖织,由于我并不知道第一個(gè)參數(shù)的長(zhǎng)度有多長(zhǎng)新锈,所以我用Memory.readByteArray(args[0],256)來(lái)讀取256個(gè)字節(jié)(文件名一般不會(huì)長(zhǎng)過(guò)256個(gè)字節(jié)吧妹笆? :D)晾浴,然后傳遞給send(message[, data])的第二個(gè)參數(shù)data脊凰,關(guān)于send(message[, data])的介紹也在官網(wǎng)的JS API中狸涌,如下截圖:

send api

可見(jiàn)第二個(gè)參數(shù)是需要ArrayBuffer類型的帕胆,而Memory.readByteArray(args[0],256)的返回值剛好是ArrayBuffer類型的般渡,所以直接傳值就可以芙盘,那么有的同學(xué)會(huì)問(wèn)儒老,如果不是ArrayBuffer類型的怎么辦记餐,比如說(shuō)文檔中的hexdump(target[, options])函數(shù)片酝,它的返回值不是ArrayBuffer類型的练湿,那么我們就需要利用下面的函數(shù)來(lái)轉(zhuǎn)換:

function str2ab(str) {
            var buf = new ArrayBuffer(str.length); // 1 bytes for each char
            var bufView = new Uint8Array(buf);
            for (var i=0, strLen=str.length; i < strLen; i++) {
                bufView[i] = str.charCodeAt(i);
                }
            return buf;
        }

將上面這段代碼放入hook代碼中鞠鲜,然后調(diào)用它便可以解決問(wèn)題。好了霞捡,數(shù)據(jù)通過(guò)send(message[, data])傳遞給python的on_message(message,data)函數(shù),其中第一個(gè)參數(shù)我們前面已經(jīng)介紹過(guò)了碧信,他是一個(gè)python字典類型砰碴,其中的message['payload']存放的就是send(message[, data])的第一個(gè)參數(shù)內(nèi)容,on_message(message,data)第二個(gè)參數(shù)data則是send(message[, data])的第二個(gè)參數(shù)板丽,不過(guò)是以bytes的類型傳遞給python data參數(shù)。所以我們寫(xiě)入文件的話埃碱,如果文件打開(kāi)方式為"ab+"那么直接可以寫(xiě)進(jìn)去,否則如果是"a+"來(lái)打開(kāi)文件的話砚殿,那么需要data.decode()轉(zhuǎn)換為str類型來(lái)寫(xiě)入,那細(xì)心的同學(xué)可能問(wèn)了似炎,你為啥寫(xiě)的data.split(b'\x00')[0]辛萍,首先我打開(kāi)方式是"ab+",所以不需要轉(zhuǎn)換類型贩毕,其次由于我之前不知道args[0]有多長(zhǎng)耳幢,所以我讀取了256個(gè)字節(jié)睛藻,那這256個(gè)字節(jié)肯定不是我全想要的店印,我們知道文件名中肯定不能存在0x00字節(jié)倒慧,而且C語(yǔ)言中字符串是要以0x00'\0'作為截?cái)嗟娜伊拢晕矣?strong>split(b'\x00')[0]分割字符串并且取列表中的第一項(xiàng)輸出到文件炫贤。

到此我們的open函數(shù)就算是hook完了(open函數(shù) :D ),下一期我們?cè)賖ook一下別的東西付秕,敬請(qǐng)期待哦

0x04 參考文章與相關(guān)資源:

frida-docs
Android逆向之hook框架frida篇
HACKING ANDROID APPS WITH FRIDA I
HACKING ANDROID APPS WITH FRIDA II - CRACKME
HACKING ANDROID APPS WITH FRIDA III - OWASP UNCRACKABLE 2
Android OWASP crackmes: Write-up UnCrackable Level 3
frida - learn by example

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兰珍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子询吴,更是在濱河造成了極大的恐慌掠河,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猛计,死亡現(xiàn)場(chǎng)離奇詭異唠摹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)奉瘤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門勾拉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人盗温,你說(shuō)我怎么就攤上這事望艺。” “怎么了肌访?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵找默,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吼驶,道長(zhǎng)惩激,這世上最難降的妖魔是什么店煞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮风钻,結(jié)果婚禮上顷蟀,老公的妹妹穿的比我還像新娘。我一直安慰自己骡技,他們只是感情好鸣个,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著布朦,像睡著了一般囤萤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上是趴,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天涛舍,我揣著相機(jī)與錄音,去河邊找鬼唆途。 笑死富雅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肛搬。 我是一名探鬼主播没佑,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼温赔!你這毒婦竟也來(lái)了图筹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤让腹,失蹤者是張志新(化名)和其女友劉穎远剩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骇窍,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓜晤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腹纳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢掠。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嘲恍,靈堂內(nèi)的尸體忽然破棺而出足画,到底是詐尸還是另有隱情,我是刑警寧澤佃牛,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布淹辞,位于F島的核電站,受9級(jí)特大地震影響俘侠,放射性物質(zhì)發(fā)生泄漏象缀。R本人自食惡果不足惜蔬将,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望央星。 院中可真熱鬧霞怀,春花似錦、人聲如沸莉给。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颓遏。三九已至徐矩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間州泊,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工漂洋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遥皂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓刽漂,卻偏偏與公主長(zhǎng)得像演训,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贝咙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353