Android的安全機制(SEANDROID)

歷史

? ? ? ?Android的安全模型是基于一部分應用程序沙箱(sandbox)的概念, 每個應用程序都運行在自己的沙箱之中。在Android 4.3之前的版本,系統在應用程序安裝時為每一個應用程序創建一個獨立的uid,基于uid來控制訪問進程來訪問資源,這種安全模型是基于Linux傳統 的安全模型DAC(Discretionary Access Control,翻譯為自主訪問控制)來實現的。從Android 4.3開始,安全增強型Linux (SElinux)用于進一步定義應用程序沙箱的界限。作為Android安全模型的一部分,Android使用SELinux的強制訪問控制(MAC) 來管理所有的進程,即使是進程具有root(超級用戶權限)的能力,SELinux通過創建自動話的安全策略(sepolicy)來限制特權進程來增強 Android的安全性。從Android 4.4開始Android打開了SELinux的Enforcing模式,使其工作在默認的AOSP代碼庫定義的安全策略(sepolicy)下。在 Enforcing模式下,違反SELinux安全策略的的行為都會被阻止,所有不合法的訪問都會記錄在dmesg和logcat中。因此,我們通過查看dmesg或者logcat, 可以收集有關違背SELinux策略的錯誤信息,來完善我們自己的軟件和SELinux策略。https://source.android.com/devices/tech/security/selinux/index.html

查看和設置SELinux的運行狀態:

$ getenforce

返回結果有兩種:Enforcing和Permissive. Permissive 代表SELinux關閉,不會阻止進程違反SELinux策略訪問資源的行為。Enforcing 代表SELinux處于開啟狀態,會阻止進程違反SELinux策略訪問資源的行為。

# setenforce [ Enforcing | Permissive | 1 | 0 ]

該命令可以立刻改變 SELinux 運行狀態,在 Enforcing 和 Permissive 之間切換,結果保持至關機。注意,執行setenforce命令需要root權限。修改某一個文件的scontext(感謝邱峰友情贊助這個命令):

$ adb shell

root@ferrari:/system/xbin # ls -Z strace

-rwxr-xr-x root? ? shell? ? ? ? ? ? u:object_r:system_file:s0 strace

root@ferrari:/system/xbin # chcon u:object_r:strace_exec:s0 strace

root@ferrari:/system/xbin # ls -Z strace

-rwxr-xr-x root shell u:object_r:strace_exec:s0 strace

DAC和MAC的區別:

DAC核心思想:進程理論上所擁有的權限與執行它的用戶的權限相同。比如,以root用戶啟動Browser,那么Browser就有root用戶的權限,在Linux系統上能干任何事情。

MAC核心思想:即任何進程想在SELinux系統中干任何事情,都必須先在安全策略配置文件中賦予權限。凡是沒有出現在安全策略配置文件中的權限,進程就沒有該權限。

DAC和MAC的聯系:

Linux系統先做DAC檢查,如果沒有通過DAC權限檢查,則操作直接失敗。通過DAC檢查之后,再做MAC權限檢查。

SEAndroid簡介

SELinux 全稱 Security Enhanced Linux (安全強化 Linux),是MAC (Mandatory Access Control,強制訪問控制系統)的一個實現。其目的在于明確的指明某個進程可以訪問哪些資源(文件、網絡端口等)。Android系統基于Linux實現。針對傳統Linux系統,NSA開發了一套安全機制SELinux,用來加強安全性。然而,由于Android系 統有著獨特的用戶空間運行時,因此SELinux不能完全適用于Android系統。為此,NSA同Google一起針對Android系統,在SELinux基礎上開發了 SEAndroid。整體框架


圖1 SEAndroid整體框架圖

如圖1所示,SEAndroid安全機制包含有內核空間和用戶空間兩部分支持。

? ? 內核空間的selinux lsm module模塊負責內核資源的安全訪問控制。

? ? sepolicy描述的是資源安全訪問策略。系統在啟動的時候,init進程會將內核空間安全訪問策略加載內核空間的selinux lsm模塊中去,而用戶空間的安全訪問策略則直接保存到普通文件中。

? ? 用戶空間的SecurityServer一方面需要檢索用戶空間的安全策略,另一方面也需要檢索內核空間的安全策略。

? ? 用戶空間的libselinux庫封裝了對SELinux文件系統接口的讀寫操作。用戶空間的SecurityServer訪問內核空間的selinux lsm模塊時,都是間接地通過libselinux進行的。用戶空間的SecurityServer檢索用戶空間的安全策略時,同樣也是通過 libselinux庫來進行的。

安全策略

安全上下文

SEAndroid是一種基于安全策略的MAC 安全機制。這種安全策略又是建立在對象的安全上下文的基礎上的。這里所說的對象分為兩種類型,一種稱 主體(Subject),一種稱為客體(Object)。主體通常就是指進程,而客體就是指進程所要訪問的資源,例如文件、系統屬性等。安全上下文實際上就是一個附加在對象上的標簽(label)。這個標簽實際上就是一個字符串,它由四部分內容組成,分別是SELinux用戶、SELinux 角色、類型、安全級別,每一個部分都通過一個冒號來分隔,格式為“user:role:type:rank”。

查看一個進程的安全上下文:

root@ferrari:/ # ps -Z init

LABEL? ? ? ? ? ? ? ? ? ? ? ? ? USER? ? PID? PPID? NAME

u:r:init:s0? ? ? ? ? ? ? ? ? ? root? ? ? 1? ? 0? ? /init

u為user的意思。SEAndroid中定義了一個SELinux用戶,值為u。

r 為role的意思。role是角色之意,它是SELinux中一種比較高層次,更方便的權限管理思路,即 Role Based Access Control(基于角色的訪問控制,簡稱為RBAC)。簡單點說,一個u可以屬于多個role,不同的role具 有不同的權限。

init代表該進程所屬的domain類型為init。

s0和SELinux為了滿足軍用和教育行業而設計的Multi-Level Security(MLS)機制有關。簡單點說,MLS將系統的進程和文件進行了分級,不同級別的資源需要對應級別的進程才能訪問。

查看一個文件的安全上下文:

root@ferrari:/ # ls -Z sepolicy

-rw-r--r-- root? ? root? ? ? ? ? ? ? u:object_r:rootfs:s0 sepolicy

u:同樣是user之意,它代表創建這個文件的SELinux user。

object_r:文件是被進程訪問的,它沒法扮演角色,所以在SELinux中,只能被進程訪問的資源都用object_r來表示它的role。

rootfs:資源文件的Type,和進程的Domain其實是一個意思。它表示root目錄對應的Type是rootfs。

s0:MLS的級別。

注意

在安全上下文中,只有類型(Type)才是最重要的,SELinux用戶、SELinux角色和安全級別都幾乎可以忽略不計的。正因為如此,SEAndroid安全機制又稱為是基于TE(Type Enforcement)策略的安全機制。

在SEAndroid中,只定義了一個SELinux用戶u,因此我們通過ps -Z和ls -Z命令看到的所有的進程和文件的安全上下文中的SELinux用戶都為u。同時,SEAndroid也只定義了一個SELinux角色r,因此,我們通 過ps -Z命令看到的所有進程的安全上下文中的SELinux角色都為r。

用戶(user)和角色(role):

路徑:external/sepolicy/users

user u roles { r } level s0 range s0 - mls_systemhigh;

上述語句聲明了一個SELinux用戶u,它可用的SELinux角色為r,它的默認安全級別為s0,可用的安全級別范圍為s0~mls_systemhigh,其中,mls_systemhigh為系統定義的最高安全級別。

路徑:external/sepolicy/roles

role r;

role r types domain;

第一個語句聲明了一個SELinux角色r;第二個語句允許SELinux角色r與類型domain關聯。

對于進程來說,SELinux用戶和SELinux角色只是用來限制進程可以標注的類型,以前面列出的 external/sepolicy/users和external/sepolicy/roles文件內容來例,如果沒有出現其它的user或者 role聲明,那么就意味著只有u、r, domain 和mls 安全級別(s0 - mls_systemhigh)可以組合在一起形成一個合法的安全上下文,而其它形式的安全上下文定義均是非法的。

安全級別(rank):

安全級別實際上也是一個MAC安全機制,它是建立在TE的基礎之上的。在SELinux中,安全級別是可選的,也就是說,可以選擇啟用或者不啟用。安全級別最開始的目的是用來對政府分類文件進行訪問控制的。在基于安全級別的MAC安全機制中,主體(subject)和客體(object)都關聯有一 個安全級別。其中,安全級別較高的主體可以讀取安全級別較低的客體,而安全級別較低的主體可以寫入安全級別較高的客體。前者稱為“read down”,而后者稱為“write up”。通過這種規則,可以允許數據從安全級別較低的主體流向安全級別較高的主體,而限制數據從安全級別較高的主體流向安全級別較低的主體,從而有效地保護了數據。注意: 如果主體和客體的安全級別是相同的,那么主體是可以對客體進行讀和寫的。在實際使用中,安全級別(rank)是由敏感性(Sensitivity)和類別(Category)兩部分內容組成的,格式為 “sensitivity[ :category_set]”,其中,category_set是可選的。例如,假設我們定義有s0、s1兩個 Sensitivity,以c0、c1、c2三個Category,那么“s0:c0,c1”表示的就是Sensitivity為s0、Category為c0和c1的一個安全級別。

類型(type):

在 SEAndroid中,我們通常將用來標注文件的安全上下文中的類型稱為file_type,而用來標注進程的安全上下文的類型稱為domain,并且每 一個用來描述文件安全上下文的類型都將file_type設置為其屬性,每一個用來進程安全上下文的類型都將domain設置為其屬性。init進程類型定義:

路徑:external/sepolicy/init.te

# init switches to init domain (via init.rc).

type init, domain;


這樣就可以表明init描述的類型是用來描述進程的安全上下文的。sepolicy文件類型定義:

路徑:external/sepolicy/file_contexts

/sepolicy? ? ? ? u:object_r:rootfs:s0

路徑:external/sepolicy/file.te

type rootfs, fs_type;

上述語句表明類型rootfs具有屬性file_type,即它是用來描述文件的安全上下文的。

安全策略文件模塊:

AOSP提供的所有Android策略文件都在路徑external/sepolicy目錄下面,在編譯完成之后一共會生成如下個module:

sepolicy:

sepolicy 其主要用于配置進程的安全上下文用來控制進程訪問內核資源(文件,端口等等)策略和設置虛擬文件系統的安全上下文,系統啟動之后,會由init進程在 /sys/fs /selinux中安裝一個selinux虛擬文件系統,接著再加載SEAndroid安全策略到內核空間的selinux lsm模塊中去。其是由如下所示的所有te文件編譯而成

路徑:external/sepolicy/Android.mk

sepolicy_build_files := security_classes \

initial_sids \

access_vectors \

global_macros \

mls_macros \

mls \

policy_capabilities \

te_macros \

attributes \

*.te \

roles \

users \

initial_sid_contexts \

fs_use \

genfs_contexts \

port_contexts


其中,genfs_contexts描述的是系統設置虛擬文件系統的安全上下文,例如

路徑: external/sepolicy/genfs_contextfs

# proc labeling can be further refined (longest matching prefix).

genfscon proc / u:object_r:proc:s0


從這里可以看出將proc虛擬文件系統的安全上下文配置成u:object_r:proc:s0, 這以為這只有哪些允許訪問type為proc的文件的進程才可以訪問proc虛擬文件系統,也就是/proc目錄下的文件。

file_contexts:

file_contexts模塊用于設置打包在ROM里面的文件的安全上下文。其是由external/sepolicy/file_contexts文件編譯而成。例如在build systemimage時會將這個file_contexts文件路徑傳遞給命令make_ext4fs時,就會根據它設置的規則給打包在 system.img里面的文件關聯安全上下文。這樣我們就獲得了一個關聯有安全上下文的system.img鏡像文件了。這樣通過fastboot命令將system.img刷入system分區mount到/system目錄之后,因為設置了相應的安全上下文,這樣就能控制進程訪問system目錄下相關文件.

seapp_contexts和mac_permissions.xml:

seapp_contexts是負責設置APP數據文件的安全上下文,mac_permissions.xml是負責設置APP進程的安全上下文.

路徑: external/sepolicy/mac_permissons.xml

文件mac_permissions.xml給不同簽名的App分配不同的seinfo字符串,例如,在AOSP源碼環境下編譯并且使用平臺簽名的App獲得的seinfo為“platform”,使用第三方簽名安裝的App獲得的seinfo簽名為”default”。這個seinfo描述的是其實并不是安全上下文中的Type,它是用來在另外一個文件seapp_contexts中查找對應的type的。

路徑:external/sepolicy/seapp_contexts

isSystemServer=true domain=system_server

user=system domain=system_app type=system_app_data_file

user=bluetooth domain=bluetooth type=bluetooth_data_file

user=nfc domain=nfc type=nfc_data_file

user=radio domain=radio type=radio_data_file

user=shared_relro domain=shared_relro

user=shell domain=shell type=shell_data_file

user=_isolated domain=isolated_app

user=_app seinfo=platform domain=platform_app type=app_data_file

user=_app domain=untrusted_app type=app_data_file


例如,于使用平臺簽名的App來說,它的seinfo為“platform”。用戶空間的SecurityServer(比如installd)在為它查找 對應的Type時,使用的user輸入為”_app”。這樣在seapp_contexts文件中,與它匹配的一行即為:user=_app seinfo=platform domain=platform_app type=app_data_file 這樣我們就可以知道,使用平臺簽名的App所運行在的進程domain為“platform_app”,并且它的數據文件的file_type為“app_data_file”。應用程序安裝服務PackageManagerService在啟動的時候,會在/system/etc/security目錄中找到 mac_permissions.xml文件,然后對它進行解析,得到App簽名或者包名與seinfo的對應關系。當 PackageManagerService安裝App的時候,它就會根據其簽名或者包名查找到對應的seinfo,并且將這個 seinfo傳遞給另外一 個守護進程installed。守護進程installd負責創建App數據目錄。在創建App數據目錄的時候,需要給它設置安全上下文,使得 SEAndroid安全機制可以對它進行安全訪問控制。installd根據PackageManagerService傳遞過來的 seinfo,并且調用libselinux庫提供的 selabel_lookup函數到seapp_contexts文件中查找到對應的type。有了這個Type之后,installd就 可以調用libselinux庫提供的lsetfilecon函數來給正在安裝的App的數據目錄設置安全上下文了。

property_contexts:

在Android系統中,有一種特殊的資源——屬性,App通過讀寫它們能夠獲得相應的信息,以及控制系統的行為,因此,SEAndroid也需要對它們進行保護。這意味著Android系統的屬性也需要關聯有安全上下文。

路徑:external/sepolicy/property_contexts

##########################

# property service keys

#

#

net.rmnet? ? ? ? ? ? ? u:object_r:net_radio_prop:s0

net.gprs? ? ? ? ? ? ? ? u:object_r:net_radio_prop:s0

net.ppp? ? ? ? ? ? ? ? u:object_r:net_radio_prop:s0

...

屬性的安全上下文與文件的安全上下文是類似的,將屬性net.rmnet設置為u:object_r:net_radio_prop:s0, 意味著只有有權限訪問type為net_radio_prop的資源的進程才可以訪問這個屬性。

service_contexts:

在AndroidL系統中,還將服務(屬于進程或線程)也作為一種資源來定義,給其定義對應的安全上下文,用來保護服務不被惡意進程訪問.

路徑:external/sepolicy/service_contexts

...

window? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? u:object_r:system_server_service:s0

*? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? u:object_r:default_android_service:s0

服務的安全上下文同樣與文件的安全上下文是類似的,將window服務設置為u:object_r:system_server_service:s0,意味著只有有權限訪問type為system_server_service的資源的進程才可以訪問這個服務。

配置實例:

接下來我們以miui自帶的服務shelld為例來描述如何給其配置安全上下文,使其能夠正常工作。shelld: 這是一個使用binder通訊的binder服務,其是從init進程fork出來運行的進程.我們給shelld配置的相關SEAndroid的安全上下文如下te文件所示:

路徑:miui/device/common/sepolicy/common/file_contexts

/system/xbin/shelld? ? u:object_r:shelld_exec:s0

如上所示te語句表明,將/system/xbin/shelld文件在打包system.img時設置安全上下文為u:object_r:shelld_exec:s0,用于保護其不被別的進程可以隨意調用執行/system/xbin/shelld, 只有具有權限執行type為shelld_exec的進程才可以執行shelld.為了使其能夠添加到servicemanager中去并且能夠訪問系統資源,我們作了如下配置:

路徑:miui/device/common/sepolicy/common/shelld.te

# shelld

type shelld, domain;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? # 設置shelld屬于domain域,是一個進程的type

type shelld_exec, exec_type, file_type;? ? ? # 設置shelld_exec屬于可執行文件的類型.

typeattribute shelld mlstrustedsubject;? ? ? ? # 設置shelld是一個可信任的主題

# init_daemon_domain是定義在external/sepolicy/te_macros中的一個宏,如果沒有設置該條語句當shelld 從init進程fork出來執行后,shelld會被設置成和init進程擁有一模一樣的安全上下文,其擁有和init進程一樣的權限

# 設置之后,則當其從init進程fork出來之后,將被設置成shelld.te為shelld進程配置的安全上下文,其擁有同init進程不一樣的權限.

init_daemon_domain(shelld)

#domain_auto_trans 也是定義在external/sepolicy/te_macros中的一個宏,設置了如下語句之后,表明shelld進程fork出shell進程來通 過exec執行shell程序時,shell程序將使用其自身的安全上下文,而不是shelld的

domain_auto_trans(shelld, shell_exec, shell)

#unix_socket_connect同樣是定義在external/sepolicy/te_macros中的一個宏,設置完如下語句,表明shelld進程可以通過socket同本地服務進行通訊.

unix_socket_connect(shelld, property, init);

#binder_use宏表明shelld可以使用Binder同servicemanager進程進行IPC通訊

binder_use(shelld)

#binder_call允許shelld同對應domain的Binder服務端進程進行binder IPC通訊

binder_call(shelld, binderservicedomain)

binder_call(shelld, appdomain)

#binder_service宏表明shelld是Binder通訊的服務端進程

binder_service(shelld)

#如下語句允許shelld進程來設置自身DAC控制系統權限

allow shelld self:capability

{ setgid setuid dac_override chown fowner fsetid };

...

#如下語句表明允許scontext=shelld的shelld進程來訪問tcontext=system_app_data_file安全上下文的目錄和文件

# /data/data subdirectory for system UID apps.

allow shelld system_app_data_file:dir create_dir_perms;

allow shelld system_app_data_file:file create_file_perms;

...

# shell

#如下語句表明允許shelld進程可以執行type為shell_exec的文件

allow shelld shell_exec:file { rx_file_perms };

...

#如下語句允許shelld進程將shelld_service添加到servicemanager進程中去

allow shelld shelld_service:service_manager add;

shelld_service是一種service的安全上下文,其定義如下所示:

路徑:android/device/common/sepolicy/common/service_contexts

*.shell? ? ? ? u:object_r:shelld_service:s0 #該條語句將名稱為miui.shell的binder進程設置安全上下文為 u:object_r:shelld_service:s0

在將shelld_service設置為service_manager_type屬性

路徑:android/device/common/sepolicy/common/service.te

type shelld_service,? ? ? ? service_manager_type; #該條語句將shelld_service表明為一個service_manager_type的屬性,具有這個屬性的service才能夠添加到 servicemanager中去.

進行了如下配置之后,再將如下添加到Makefile中的變 量BOARD_SEPOLICY_UNION,并將這些te配置文件所在的目錄名稱添加到BOARD_SEPOLICY_DIRS變量中,則運行命令$ make bootimage, 就能將對應的安全策略文件build進boot.img中在重啟之后就能夠由init進程加載到內核中去.或者可以將生成的sepolicy文件push到/data/security/current/sepolicy之后接著調用

$ adb push sepolicy /data/security/current/sepolicy

$ setprop selinux.reload_policy 1

使得init重新加載sepolicy,由于/data目錄下有了sepolicy,所以它將使用這個新的。

為違反SEAndroid策略的進程配置安全上下文:

一般如果有進程違反SEAndroid策略,我們將在logcat或者dmesg中發現如下類似的log:

<12>[? 12.835933] type=1400 audit(1325843.199:6): avc: denied { write } for pid=295 comm="installd" name="/" dev="mmcblk0p35" ino=2 scontext=u:r:installd:s0 tcontext=u:object_r:cache_file:s0 tclass=dir permissive=0

這樣我們可以直接到對應的installd進程的安全策略文件中去添加如下類似語句就能夠消除對應的違反規則:

路徑:external/sepolicy/installd.te

allow installd cache_file:dir { write };

其中,installd就是log中scontext=u:r:installd:s0表明的domain主體安全上下文,cache_file就是log中tcontext=u:object_r:cache_file:s0的客體安全上下文,客體的類型是tclass=dir表明的dir類型,違反的規則是{ write }。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容