寫給Android開發(fā)者的混淆使用手冊(cè)

https://yq.aliyun.com/articles/62980?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&2017314&utm_content=m_13399

毫無疑問著隆,混淆是打包過程中最重要的流程之一哼蛆,在沒有特殊原因的情況下躏碳,所有 app 都應(yīng)該開啟混淆双揪。

首先箕别,這里說的的混淆其實(shí)是包括了代碼壓縮居兆、代碼混淆以及資源壓縮等的優(yōu)化過程搂橙。依靠 ProGuard往弓,混淆流程將主項(xiàng)目以及依賴庫(kù)中未被使用的類旦装、類成員页衙、方法、屬性移除,這有助于規(guī)避64K方法數(shù)的瓶頸店乐;同時(shí)艰躺,將類、類成員眨八、方法重命名為無意義的簡(jiǎn)短名稱腺兴,增加了逆向工程的難度。而依靠 Gradle 的 Android
插件廉侧,我們將移除未被使用的資源页响,可以有效減小 apk 安裝包大小。

本文由兩部分構(gòu)成伏穆,第一部分給出混淆的最佳實(shí)踐拘泞,力求讓零基礎(chǔ)的新手都可以直接使用混淆;第二部分會(huì)介紹一下混淆的整體枕扫、自定義混淆規(guī)則的語(yǔ)法與實(shí)踐陪腌、自定義資源保持的規(guī)則等。

一烟瞧、Android混淆最佳實(shí)踐

  1. 混淆配置

一般情況下诗鸭,app module 的
build.gradle
文件默認(rèn)會(huì)有如下結(jié)構(gòu):



android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
</br>
因?yàn)殚_啟混淆會(huì)使編譯時(shí)間變長(zhǎng),所以debug模式下不應(yīng)該開啟参滴。我們需要做的是:

將release下minifyEnabled的值改為true强岸,打開混淆;

加上shrinkResources true砾赔,打開資源壓縮蝌箍。

修改后文件內(nèi)容如下:

android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

  1. 自定義混淆規(guī)則


app module 下默認(rèn)生成了項(xiàng)目的自定義混淆規(guī)則文件
proguard-rules.pro,多方調(diào)研后暴心,一份適用于大部分項(xiàng)目的混淆規(guī)則最佳實(shí)踐如下:

指定壓縮級(jí)別

-optimizationpasses 5

不跳過非公共的庫(kù)的類成員

-dontskipnonpubliclibraryclassmembers

混淆時(shí)采用的算法

-optimizations !code/simplification/arithmetic,!field/,!class/merging/

把混淆類中的方法名也混淆了

-useuniqueclassmembernames

優(yōu)化時(shí)允許訪問并修改有修飾符的類和類的成員

-allowaccessmodification

將文件來源重命名為“SourceFile”字符串

-renamesourcefileattribute SourceFile

保留行號(hào)

-keepattributes SourceFile,LineNumberTable

保持所有實(shí)現(xiàn) Serializable 接口的類成員

-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}

Fragment不需要在AndroidManifest.xml中注冊(cè)妓盲,需要額外保護(hù)下

-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment

保持測(cè)試相關(guān)的代碼

-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

真正通用的、需要添加的就是上面這些专普,除此之外悯衬,需要每個(gè)項(xiàng)目根據(jù)自身的需求添加一些混淆規(guī)則:

第三方庫(kù)所需的混淆規(guī)則。正規(guī)的第三方庫(kù)一般都會(huì)在接入文檔中寫好所需混淆規(guī)則檀夹,使用時(shí)注意添加筋粗。

在運(yùn)行時(shí)動(dòng)態(tài)改變的代碼,例如反射炸渡。比較典型的例子就是會(huì)與 json 相互轉(zhuǎn)換的實(shí)體類娜亿。假如項(xiàng)目命名規(guī)范要求實(shí)體類都要放在model包下的話,可以添加類似這樣的代碼把所有實(shí)體類都保持装龆隆:-keep public class .Model.** {*;}

JNI中調(diào)用的類暇唾。

WebView中JavaScript調(diào)用的方法

Layout布局使用的View構(gòu)造函數(shù)、android:onClick等。

  1. 檢查混淆結(jié)果

混淆過的包必須進(jìn)行檢查策州,避免因混淆引入的bug。

一方面宫仗,需要從代碼層面檢查够挂。使用上文的配置進(jìn)行混淆打包后在
<module-name>/build/outputs/mapping/release/
目錄下會(huì)輸出以下文件:

dump.txt
描述APK文件中所有類的內(nèi)部結(jié)構(gòu)

mapping.txt
提供混淆前后類、方法藕夫、類成員等的對(duì)照表

seeds.txt
列出沒有被混淆的類和成員

usage.txt
列出被移除的代碼

我們可以根據(jù)
seeds.txt
文件檢查未被混淆的類和成員中是否已包含所有期望保留的孽糖,再根據(jù) usage.txt
文件查看是否有被誤移除的代碼。

另一方面毅贮,需要從測(cè)試方面檢查办悟。將混淆過的包進(jìn)行全方面測(cè)試,檢查是否有 bug 產(chǎn)生滩褥。

  1. 解出混淆棧

混淆后的類病蛉、方法名等等難以閱讀,這固然會(huì)增加逆向工程的難度瑰煎,但對(duì)追蹤線上 crash 也造成了阻礙铺然。我們拿到 crash 的堆棧信息后會(huì)發(fā)現(xiàn)很難定位,這時(shí)需要將混淆反解酒甸。


<sdk-root>/tools/proguard/
路徑下有附帶的的反解工具(Window 系統(tǒng)為 proguardgui.bat魄健,Mac
或 Linux 系統(tǒng)為 proguardgui.sh)。

這里以 Window 平臺(tái)為例插勤。雙擊運(yùn)行
proguardgui.bat
后沽瘦,可以看到左側(cè)的一行菜單。點(diǎn)擊 ReTrace农尖,選擇該混淆包對(duì)應(yīng)的
mapping 文件(混淆后在 <module-name>/build/outputs/mapping/release/
路徑下會(huì)生成 mapping.txt
文件析恋,它的作用是提供混淆前后類、方法卤橄、類成員等的對(duì)照表)绿满,再將 crash 的 stack trace 黏貼進(jìn)輸入框中,點(diǎn)擊右下角的
ReTrace
窟扑,混淆后的堆棧信息就顯示出來了喇颁。

以上使用 GUI 程序進(jìn)行操作,另一種方式是利用該路徑下的
retrace
工具通過命令行進(jìn)行反解嚎货,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事項(xiàng):

  1. 所有在
    AndroidManifest.xml
    涉及到的類已經(jīng)自動(dòng)被保持橘霎,因此不用特意去添加這塊混淆規(guī)則。(很多老的混淆文件里會(huì)加殖属,現(xiàn)在已經(jīng)沒必要)

proguard-android.txt
已經(jīng)存在一些默認(rèn)混淆規(guī)則姐叁,沒必要在 proguard-rules.pro
重復(fù)添加,該文件具體規(guī)則見附錄1:

二、混淆簡(jiǎn)介

Android中的“混淆”可以分為兩部分外潜,一部分是 Java 代碼的優(yōu)化與混淆原环,依靠 proguard 混淆器來實(shí)現(xiàn);另一部分是資源壓縮处窥,將移除項(xiàng)目及依賴的庫(kù)中未被使用的資源(資源壓縮嚴(yán)格意義上跟混淆沒啥關(guān)系嘱吗,但一般我們都會(huì)放一起講)。

  1. 代碼壓縮

代碼混淆是包含了代碼壓縮滔驾、優(yōu)化谒麦、混淆等一系列行為的過程。如上圖所示哆致,混淆過程會(huì)有如下幾個(gè)功能:

壓縮绕德。移除無效的類、類成員摊阀、方法耻蛇、屬性等;

優(yōu)化驹溃。分析和優(yōu)化方法的二進(jìn)制代碼城丧;根據(jù)proguard-android-optimize.txt中的描述,優(yōu)化可能會(huì)造成一些潛在風(fēng)險(xiǎn)豌鹤,不能保證在所有版本的Dalvik上都正常運(yùn)行亡哄。

混淆。把類名布疙、屬性名蚊惯、方法名替換為簡(jiǎn)短且無意義的名稱;

預(yù)校驗(yàn)灵临。添加預(yù)校驗(yàn)信息截型。這個(gè)預(yù)校驗(yàn)是作用在Java平臺(tái)上的,Android平臺(tái)上不需要這項(xiàng)功能儒溉,去掉之后還可以加快混淆速度宦焦。

這四個(gè)流程默認(rèn)開啟。

在 Android 項(xiàng)目中我們可以選擇將“優(yōu)化”和“預(yù)校驗(yàn)”關(guān)閉顿涣,對(duì)應(yīng)命令是-dontoptimize波闹、-dontpreverify(當(dāng)然,默認(rèn)的
proguard-android.txt
文件已包含這兩條混淆命令涛碑,不需要開發(fā)者額外配置)精堕。

  1. 資源壓縮

資源壓縮將移除項(xiàng)目及依賴的庫(kù)中未被使用的資源,這在減少 apk 包體積上會(huì)有不錯(cuò)的效果蒲障,一般建議開啟歹篓。具體做法是在
build.grade
文件中瘫证,將 shrinkResources
屬性設(shè)置為 true。需要注意的是庄撮,只有在用minifyEnabled true開啟了代碼壓縮后背捌,資源壓縮才會(huì)生效。

資源壓縮包含了“合并資源”和“移除資源”兩個(gè)流程洞斯。

“合并資源”流程中载萌,名稱相同的資源被視為重復(fù)資源會(huì)被合并。需要注意的是巡扇,這一流程不受shrinkResources屬性控制,也無法被禁止垮衷,
gradle 必然會(huì)做這項(xiàng)工作厅翔,因?yàn)榧偃绮煌?xiàng)目中存在相同名稱的資源將導(dǎo)致錯(cuò)誤。gradle 在四處地方尋找重復(fù)資源:

src/main/res/
路徑

不同的構(gòu)建類型(debug搀突、release等等)

不同的構(gòu)建渠道

項(xiàng)目依賴的第三方庫(kù)

合并資源時(shí)按照如下優(yōu)先級(jí)順序:

依賴 -> main -> 渠道 -> 構(gòu)建類型

舉個(gè)例子刀闷,假如重復(fù)資源同時(shí)存在于main文件夾和不同渠道中,gradle
會(huì)選擇保留渠道中的資源仰迁。

同時(shí)甸昏,如果重復(fù)資源在同一層次出現(xiàn),比如src/main/res/
和 src/main/res/徐许,則
gradle 無法完成資源合并施蜜,這時(shí)會(huì)報(bào)資源合并錯(cuò)誤。

“移除資源”流程則見名知意雌隅,需要注意的是翻默,類似代碼,混淆資源移除也可以定義哪些資源需要被保留恰起,這點(diǎn)在下文給出修械。

三、自定義混淆規(guī)則

在上文“混淆配置”中有這樣一行代碼

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

這行代碼定義了混淆規(guī)則由兩部分構(gòu)成:位于 SDK 的
tools/proguard/
文件夾中的 proguard-android.txt
的內(nèi)容以及默認(rèn)放置于模塊根目錄的 proguard-rules.pro
的內(nèi)容检盼。前者是 SDK 提供的默認(rèn)混淆文件(內(nèi)容見附錄1)肯污,后者是開發(fā)者自定義混淆規(guī)則的地方。

  1. 常見混淆命令:

optimizationpasses

dontoptimize

dontusemixedcaseclassnames

dontskipnonpubliclibraryclasses

dontpreverify

dontwarn

verbose

optimizations

keep

keepnames

keepclassmembers

keepclassmembernames

keepclasseswithmembers

keepclasseswithmembernames

在第一部分 Android 混淆最佳實(shí)踐中已介紹部分需要使用到的混淆命令吨枉,這里不再贅述蹦渣,詳情請(qǐng)查閱官網(wǎng)。需要特別介紹的是與保持相關(guān)元素不參與混淆的規(guī)則相關(guān)的幾種命令:

命令 作用
-keep 防止類和成員被移除或者被重命名
-keepnames 防止類和成員被重命名
-keepclassmembers 防止成員被移除或者被重命名
-keepnames 防止成員被重命名
-keepclasseswithmembers 防止擁有該成員的類和成員被移除或者被重命名
-keepclasseswithmembernames 防止擁有該成員的類和成員被重命名

  1. 保持元素不參與混淆的規(guī)則

形如:

[保持命令] [類] {
[成員]
}

“類”代表類相關(guān)的限定條件东羹,它將最終定位到某些符合該限定條件的類剂桥。它的內(nèi)容可以使用:

具體的類

訪問修飾符(public、protected属提、private)

通配符*权逗,匹配任意長(zhǎng)度字符美尸,但不含包名分隔符(.)

通配符**,匹配任意長(zhǎng)度字符斟薇,并且包含包名分隔符(.)

extends师坎,即可以指定類的基類

implement,匹配實(shí)現(xiàn)了某接口的類

$堪滨,內(nèi)部類

“成員”代表類成員相關(guān)的限定條件胯陋,它將最終定位到某些符合該限定條件的類成員。它的內(nèi)容可以使用:

匹配所有構(gòu)造器
匹配所有域
匹配所有方法

通配符*袱箱,匹配任意長(zhǎng)度字符遏乔,但不含包名分隔符(.)

通配符**,匹配任意長(zhǎng)度字符发笔,并且包含包名分隔符(.)

通配符***盟萨,匹配任意參數(shù)類型

…,匹配任意長(zhǎng)度的任意類型參數(shù)了讨。比如void
test(…)就能匹配任意 void test(String a) 或者是
void test(int a, String b) 這些方法捻激。

訪問修飾符(public、protected前计、private)

舉個(gè)例子胞谭,假如需要將name.huihui.test包下所有繼承Activity的public類及其構(gòu)造函數(shù)都保持住,可以這樣寫:

-keep public class name.huihui.test.** extends Android.app.Activity {
<init>
}

  1. 常用的自定義混淆規(guī)則

不混淆某個(gè)類
-keep public class name.huihui.example.Test { *; }

不混淆某個(gè)包所有的類
-keep class name.huihui.test.** { *; }

不混淆某個(gè)類的子類
-keep public class * extends name.huihui.example.Test { *; }

不混淆所有類名中包含了“model”的類及其成員
-keep public class .model.** {*;}

不混淆某個(gè)接口的實(shí)現(xiàn)
-keep class * implements name.huihui.example.TestInterface { *; }

不混淆某個(gè)類的構(gòu)造方法
-keepclassmembers class name.huihui.example.Test {
public <init>();
}

不混淆某個(gè)類的特定的方法
-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
四男杈、自定義資源保持規(guī)則

  1. keep.xml

用shrinkResources true開啟資源壓縮后丈屹,所有未被使用的資源默認(rèn)被移除。假如你需要定義哪些資源必須被保留势就,在
res/raw/
路徑下創(chuàng)建一個(gè) xml 文件泉瞻,例如 keep.xml。

通過一些屬性的設(shè)置可以實(shí)現(xiàn)定義資源保持的需求苞冯,可配置的屬性有:

tools:keep
定義哪些資源需要被保留(資源之間用“,”隔開)

tools:discard
定義哪些資源需要被移除(資源之間用“,”隔開)

tools:shrinkMode
開啟嚴(yán)格模式

當(dāng)代碼中通過
Resources.getIdentifier()
用動(dòng)態(tài)的字符串來獲取并使用資源時(shí)袖牙,普通的資源引用檢查就可能會(huì)有問題。例如舅锄,如下代碼會(huì)導(dǎo)致所有以“img_”開頭的資源都被標(biāo)記為已使用鞭达。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

我們可以設(shè)置
tools:shrinkMode
為 strict
來開啟嚴(yán)格模式,使只有確實(shí)被使用的資源被保留皇忿。

以上就是自定義資源保持規(guī)則相關(guān)的配置畴蹭,舉個(gè)例子:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used_c,@layout/l_used_a,@layout/l_used_b"
tools:discard="@layout/unused2"
tools:shrinkMode="strict"/>

  1. 移除替代資源

一些替代資源,例如多語(yǔ)言支持的
strings.xml鳍烁,多分辨率支持的
layout.xml
等叨襟,在我們不需要使用又不想刪除掉時(shí),可以使用資源壓縮將它們移除幔荒。

我們使用
resConfig
屬性來指定需要支持的屬性糊闽,例如

android {
defaultConfig {
...
resConfigs "en", "fr"
}
}

其他未顯式聲明的語(yǔ)言資源將被移除梳玫。

參考資料

Shrink Your Code and Resources

proguard

Android安全攻防戰(zhàn),反編譯與混淆技術(shù)完全解析(下)

Android混淆從入門到精通

Android代碼混淆之ProGuard
附錄

proguard-android.txt文件內(nèi)容

包名不混合大小寫

-dontusemixedcaseclassnames

不跳過非公共的庫(kù)的類

-dontskipnonpubliclibraryclasses

混淆時(shí)記錄日志

-verbose

關(guān)閉預(yù)校驗(yàn)

-dontpreverify

不優(yōu)化輸入的類文件

-dontoptimize

保護(hù)注解

-keepattributes Annotation

保持所有擁有本地方法的類名及本地方法名

-keepclasseswithmembernames class * {
native <methods>;
}

保持自定義View的get和set相關(guān)方法

-keepclassmembers public class * extends android.view.View {
void set(*);
*** get
();
}

保持Activity中View及其子類入?yún)⒌姆椒?/h1>

-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

枚舉

-keepclassmembers enum * {
**[] $VALUES;
public *;
}

Parcelable

-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}

R文件的靜態(tài)成員

-keepclassmembers class *.R$ {
public static <fields>;
}

-dontwarn android.support.**

keep相關(guān)注解

-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末右犹,一起剝皮案震驚了整個(gè)濱河市提澎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌念链,老刑警劉巖盼忌,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異掂墓,居然都是意外死亡谦纱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門君编,熙熙樓的掌柜王于貴愁眉苦臉地迎上來服协,“玉大人,你說我怎么就攤上這事啦粹。” “怎么了窘游?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵唠椭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我忍饰,道長(zhǎng)贪嫂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任艾蓝,我火速辦了婚禮力崇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赢织。我一直安慰自己亮靴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布于置。 她就那樣靜靜地躺著茧吊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪八毯。 梳的紋絲不亂的頭發(fā)上搓侄,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音话速,去河邊找鬼讶踪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泊交,可吹牛的內(nèi)容都是我干的乳讥。 我是一名探鬼主播柱查,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼雏婶!你這毒婦竟也來了物赶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤留晚,失蹤者是張志新(化名)和其女友劉穎酵紫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體错维,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奖地,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赋焕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片参歹。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖隆判,靈堂內(nèi)的尸體忽然破棺而出犬庇,到底是詐尸還是另有隱情,我是刑警寧澤侨嘀,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布臭挽,位于F島的核電站,受9級(jí)特大地震影響咬腕,放射性物質(zhì)發(fā)生泄漏欢峰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一涨共、第九天 我趴在偏房一處隱蔽的房頂上張望纽帖。 院中可真熱鬧,春花似錦举反、人聲如沸懊直。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吹截。三九已至,卻和暖如春凝危,著一層夾襖步出監(jiān)牢的瞬間波俄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工蛾默, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留懦铺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓支鸡,卻偏偏與公主長(zhǎng)得像冬念,于是被迫代替她去往敵國(guó)和親趁窃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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