Android 逆向筆記 —— 一個(gè)簡(jiǎn)單 CrackMe 的逆向總結(jié)

無意中在看雪看到一個(gè)簡(jiǎn)單的 CrackMe 應(yīng)用券册,正好就著這個(gè)例子總結(jié)一下逆向過程中基本的常用工具的使用滋捶,和一些簡(jiǎn)單的常用套路逆日。感興趣的同學(xué)可以照著嘗試操作一下,過程還是很簡(jiǎn)單的奠滑。APK 我已上傳至 Github丹皱,下載地址

首先安裝一下這個(gè)應(yīng)用宋税,界面如下所示:

image

要求就是通過注冊(cè)摊崭。爆破的方法很多,大致可以歸為三類杰赛,第一種是直接修改 smali 代碼繞過注冊(cè)呢簸,第二種是捋清注冊(cè)流程,得到正確的注冊(cè)碼乏屯。第三種是 hook 根时。下面就來說說這幾種爆破過程。

直接修改 smali 進(jìn)行爆破

要獲取 smali 代碼辰晕,首先得反編譯這個(gè) Apk蛤迎,通過 ApkTool 就可以完成。ApkTool 的使用過程就不在這里贅述了含友,執(zhí)行如下命令:

apktool d creackme.apk
I: Using Apktool 2.3.4-dirty on crackme.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/luyao/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

會(huì)在當(dāng)前目錄生成 crackme 文件夾替裆,文件夾目錄如下:

image

其中的 smali 文件夾就包含了該 Apk 的所有 smali 代碼校辩。閱讀和修改 smali 代碼的工具很多,我個(gè)人偏好將整個(gè)反編譯得到的文件夾導(dǎo)入 IDEA 或者 Android Studio 進(jìn)行閱讀和修改辆童,可能我是 Android 開發(fā)宜咒,用這兩個(gè)工具會(huì)比較順手,全局搜索功能也很給力胸遇。

導(dǎo)入 Android Studio 之后荧呐,看到了所有的 smali 代碼,那么我們?cè)搹暮蜗率帜刂侥鳎孔?cè)失敗的時(shí)候會(huì)彈一個(gè) Toast倍阐,“無效用戶名或注冊(cè)碼”,這就是突破口逗威。全局搜索這個(gè)字符串峰搪,


image

發(fā)現(xiàn)這個(gè)字符串定義在 string.xml 中的 unsuccessd ,在寫代碼的時(shí)候就是 R.string.unsuccessd凯旭,這是一個(gè) int 值概耻,編譯后就直接是一個(gè)數(shù)字了。我們?cè)賮砣炙阉?unsuccessd :

image

public.xml 中可以看到它的 id,代碼中直接使用的就是這個(gè) id了罐呼。全局搜索一下 0x7f05000b鞠柄,看一下這個(gè) Toast 是在哪里彈出的。

image

可以看到這個(gè) id 在 MainActivity.smali 中的 433 行使用到了嫉柴,我們定位到這個(gè)文件:

    .line 117
    if-nez v0, :cond_0  # 如果 v0 不等于 0 厌杜,跳轉(zhuǎn)到 cond_0

    .line 119
    const v0, 0x7f05000b

    .line 118
    invoke-static {p0, v0, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object v0

    .line 119
    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

這段邏輯很簡(jiǎn)單。判斷寄存器 v0 的值是否為 0计螺,不為 0 的話則彈出 “無效用戶名或注冊(cè)碼” 夯尽。所以最簡(jiǎn)單的改法,邏輯反一下登馒,v0 為 0 的時(shí)候彈出該 Toast匙握,把 if-nez 改為 if-ez 即可。修改之后使用 ApkTool 重打包陈轿,重打包命令如下:

apktool b crackme -o crackme_new.apk

會(huì)在當(dāng)前目錄生成 crackme_new.apk 文件圈纺,注意這個(gè)安裝包是未簽名的,無法直接安裝济欢,需要先簽名赠堵。使用 jarsinger 或者 apksigner 都可以。簽名之后安裝法褥,輸入用戶名:

image

這樣就注冊(cè)成功了茫叭。方法雖然有點(diǎn) low ,但好歹爆破成功了半等。下面我們不修改 smali 代碼揍愁,通過閱讀 smali 代碼理解其注冊(cè)碼生成邏輯呐萨,通過正規(guī)方式來注冊(cè)。

獲取注冊(cè)碼爆破

我們之前已經(jīng)找到了具體的邏輯是在 MainActivity.smali 中莽囤,找到這個(gè)按鈕的 onClick() 事件谬擦,來看一下具體邏輯:

.line 116
invoke-direct {p0, v0, v1}, Lcom/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z

move-result v0

.line 117
if-eqz v0, :cond_0

.line 119
const v0, 0x7f05000b

.line 118
invoke-static {p0, v0, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

.line 119
invoke-virtual {v0}, Landroid/widget/Toast;->show()V

goto :goto_0

這里只截取了 onClick 中的部分核心代碼,調(diào)用 checkSN() 方法獲得一個(gè) Boolean 值朽缎,根據(jù)這個(gè)值來判斷是否注冊(cè)成功惨远。這個(gè) checkSN() 方法就是我們需要重點(diǎn)關(guān)注的,我對(duì)這個(gè)方法的 smali 代碼逐行添加了注釋话肖,還是很容易理解的北秽,感興趣的同學(xué)可以看一下:

.method private checkSN(Ljava/lang/String;Ljava/lang/String;)Z
    .locals 10  # 使用 10 個(gè)寄存器
    .param p1, "userName"   # Ljava/lang/String; 參數(shù)寄存器 p1 保存的是用戶名 userName
    .param p2, "sn"    # Ljava/lang/String; 參數(shù)寄存器 p2 保存的是注冊(cè)碼 sn

    .prologue
    const/4 v7, 0x0 # 將 0x0 存入寄存器 v7

    .line 45
    if-eqz p1, :cond_0  # 如果 p1,即 userName 等于 0最筒,跳轉(zhuǎn)到 cond_0

    :try_start_0
    invoke-virtual {p1}, Ljava/lang/String;->length()I # 調(diào)用 userName.length()

    move-result v8  # 將 userName.length() 的執(zhí)行結(jié)果存入寄存器 v8

    if-nez v8, :cond_1 # 如果 v8 不等于 0贺氓,跳轉(zhuǎn)到 cond_1

    .line 69
    :cond_0
    :goto_0
    return v7

    .line 47
    :cond_1
    if-eqz p2, :cond_0  # 如果 p2,即注冊(cè)碼 sn 等于 0床蜘,跳轉(zhuǎn)到 cond_0

    invoke-virtual {p2}, Ljava/lang/String;->length()I  # 執(zhí)行 sn.length()

    move-result v8  # 將 sn.length() 執(zhí)行結(jié)果存入寄存器 v8

    const/16 v9, 0x10 # 將 0x10 存入寄存器 v9

    if-ne v8, v9, :cond_0   # 如果 sn.length != 0x10 辙培,跳轉(zhuǎn)至 cond_0

    .line 49
    const-string v8, "MD5"  # 將字符串 "MD5" 存入寄存器 v8

    # 調(diào)用靜態(tài)方法 MessageDigest.getInstance("MD5")
    invoke-static {v8}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

    move-result-object v1   # 將上一步方法的返回結(jié)果賦給寄存器 v1,這里是 MessageDigest 對(duì)象

    .line 50
    .local v1, "digest":Ljava/security/MessageDigest;
    invoke-virtual {v1}, Ljava/security/MessageDigest;->reset()V # 調(diào)用 digest.reset() 方法

    .line 51
    invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B   # 調(diào)用 userName.getByte() 方法

    move-result-object v8   # 上一步得到的字節(jié)數(shù)組存入 v8

    invoke-virtual {v1, v8}, Ljava/security/MessageDigest;->update([B)V # 調(diào)用 digest.update(byte[]) 方法

    .line 52
    invoke-virtual {v1}, Ljava/security/MessageDigest;->digest()[B  # 調(diào)用 digest.digest() 方法

    move-result-object v0   # 上一步的執(zhí)行結(jié)果存入 v0邢锯,是一個(gè) byte[] 對(duì)象

    .line 53
    .local v0, "bytes":[B
    const-string v8, "" # 將字符串 "" 存入 v8

    # 調(diào)用 MainActivity 中的 toHexString(byte[] b,String s) 方法
    invoke-static {v0, v8}, Lcom/droider/crackme0201/MainActivity;->toHexString([BLjava/lang/String;)Ljava/lang/String;

    move-result-object v3   # 上一步方法返回的字符串存入 v3

    .line 54
    .local v3, "hexstr":Ljava/lang/String;
    new-instance v5, Ljava/lang/StringBuilder;  # 新建 StringBuilder 對(duì)象

    invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V    # 執(zhí)行 StringBuilder 的構(gòu)造函數(shù)

    .line 55
    .local v5, "sb":Ljava/lang/StringBuilder;   # 聲明變量 sb 指向剛才創(chuàng)建的 StringBuilder 實(shí)例
    const/4 v4, 0x0 # v4 = 0x0

    .local v4, "i":I    # i = 0x0
    :goto_1 # for 循環(huán)開始
    invoke-virtual {v3}, Ljava/lang/String;->length()I  # 獲取 hexstr 字符串的長度

    move-result v8  # v8 = hexstr.length()

    if-lt v4, v8, :cond_2   # 如果 v4 小于 v8扬蕊,即 i < hexstr.length(), 跳轉(zhuǎn)到 cond_2

    .line 58
    # 這里已經(jīng)跳出 for 循環(huán)
    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v6   # v6 = sb.toString()

    .line 63
    .local v6, "userSN":Ljava/lang/String;  # userSN = sb.toString()

    # userSN.equalsIgnoreCase(sn)
    invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z

    move-result v8  # v8 = userSN.equalsIgnoreCase(sn)

    if-eqz v8, :cond_0 # 如果 v8 等于 0,跳轉(zhuǎn)到 cond_0丹擎,即 userSN != sn

    .line 69
    const/4 v7, 0x1

    goto :goto_0    # 跳轉(zhuǎn)到 goto_0厨相,結(jié)束 checkSN() 方法并返回 v7

    .line 56
    .end local v6    # "userSN":Ljava/lang/String;
    :cond_2
    invoke-virtual {v3, v4}, Ljava/lang/String;->charAt(I)C # 執(zhí)行 hexstr.charAt(i)

    move-result v8  # v8 = hexstr.charAt(i)

    # 調(diào)用 sb.append(v8)
    invoke-virtual {v5, v8}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
    :try_end_0
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

    .line 55
    add-int/lit8 v4, v4, 0x2    # v4 自增 0x2,即 i+=2

    goto :goto_1    # 跳轉(zhuǎn)到 goto_1鸥鹉,形成 循環(huán)

    .line 65
    .end local v0    # "bytes":[B
    .end local v1    # "digest":Ljava/security/MessageDigest;
    .end local v3    # "hexstr":Ljava/lang/String;
    .end local v4    # "i":I
    .end local v5    # "sb":Ljava/lang/StringBuilder;
    :catch_0
    move-exception v2

    .line 66
    .local v2, "e":Ljava/security/NoSuchAlgorithmException;
    invoke-virtual {v2}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V

    goto :goto_0
.end method

大致邏輯就是對(duì)輸入的用戶名 UserName 作 MD5 運(yùn)算得到 Hash 值,再轉(zhuǎn)成十六進(jìn)制字符串就是注冊(cè)碼了庶骄。那么毁渗,如何獲取注冊(cè)碼呢 ?一般有三種方式单刁,打 log灸异,動(dòng)態(tài)調(diào)試 smali,自己寫注冊(cè)機(jī)羔飞。下面逐個(gè)說明一下肺樟。

打 log 日志

其實(shí)在逆向過程中,注入 log 代碼是很常見的操作逻淌。適當(dāng)?shù)拇?log么伯,可以很好的幫助我們理解代碼執(zhí)行流程。在這里例子中卡儒,最終會(huì)拿我們輸入的注冊(cè)碼和正確的注冊(cè)碼進(jìn)行比較田柔,在比較的時(shí)候我們就可以通過打 log 把正確的注冊(cè)碼打印出來俐巴,這樣我們就可以直接輸入注冊(cè)碼進(jìn)行注冊(cè)了。

打 log 的 smali 代碼是固定的硬爆,一般格式如下:

const-string vX, "TAG"
invoke-static {vX,vX}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

vX 都是指寄存器欣舵。把這兩行代碼加到注冊(cè)碼的檢驗(yàn)操作之前就可以了:

.line 63
.local v6, "userSN":Ljava/lang/String;  # userSN = sb.toString()

const-string v8, "TAG"
invoke-static {v8,v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

# userSN.equalsIgnoreCase(sn)
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z

再次重新打包運(yùn)行,輸入用戶名和注冊(cè)碼缀磕,就會(huì)有如下日志:

image

這樣就拿到正確的注冊(cè)碼了缘圈。

動(dòng)態(tài)調(diào)試 smali

動(dòng)態(tài)調(diào)試 smali 來的更加直截了當(dāng)。不管是你自己寫程序袜蚕,還是做逆向糟把,debug 永遠(yuǎn)都是快速理清邏輯的好方法。smali 也是可以進(jìn)行動(dòng)態(tài)調(diào)試的廷没,依賴于 Smalidea 插件糊饱,你可以在 Android Studio 的 Plugin 中進(jìn)行安裝,也可以下載下來本地安裝颠黎。

第一步另锋,我們要保證我們的應(yīng)用處于 debug 版本,在 AndroidManifest.xml 中加上 android:debuggable="true" 即可狭归,重打包再安裝到手機(jī)上夭坪。

第二步,將之前反編譯得到的 smali 文件夾導(dǎo)入 Android Studio 或者 IDEA过椎,并配置遠(yuǎn)程調(diào)試環(huán)境室梅。選擇 Run -> Edit Configurations,點(diǎn)擊左上角 + 號(hào)疚宇,選擇 Remote亡鼠,彈出配置窗口,如下圖所示:

image

注意記住自己填寫的端口號(hào)敷待,端口號(hào)不是固定的间涵,只要未被占用即可。配置完成后榜揖,記得在合適的地方打上斷點(diǎn)勾哩,我這里就在 checkSN() 方法內(nèi)打上斷點(diǎn)。

第三步举哟,命令行啟動(dòng)進(jìn)程調(diào)試等待模式思劳。首先執(zhí)行:

adb shell am start -D -n com.droider.crackme0201/.MainActivity

應(yīng)用此時(shí)會(huì)進(jìn)入等待調(diào)試模式,如下圖所示:

image

然后建立端口轉(zhuǎn)發(fā)妨猩,輸入如下命令:

adb forward tcp:8700 jdwp:pid

用你自己的應(yīng)用的 pid 替換進(jìn)去潜叛。關(guān)于 pid 的獲取,可以通過 psgrep 組合:

adb shell ps | grep com.droider.crackme0201
u0_a364   30110 537   2166480 30204 futex_wait 0000000000 S com.droider.crackme0201

我這里的 pid 就是 30010 壶硅。

最后在 Android Studio 或 IDEA 中啟動(dòng) debug 钠导。 點(diǎn)擊 Run -> Debug震嫉,應(yīng)用就進(jìn)入調(diào)試模式了。之后的操作就和我們開發(fā)中的 debug 模式一模一樣了牡属。我們可以在運(yùn)行中看到寄存器中的值票堵,運(yùn)行邏輯一覽無遺。運(yùn)行至注冊(cè)碼校驗(yàn)處的斷點(diǎn)逮栅,截圖如下:

image

userName 是用戶名悴势,sn 是我輸入的注冊(cè)碼,userSN 是正確的注冊(cè)碼措伐。

注冊(cè)機(jī)

注冊(cè)機(jī)其實(shí)就是自己重寫注冊(cè)碼生成過程了特纤,看懂了 smali 就可以自己寫個(gè)程序來生成注冊(cè)碼了。這個(gè)就不多說了侥加。

Hook

具體的 Hook 操作由于篇幅原因就不在這里演示了捧存。關(guān)于 Java 層的 Hook 工具很多,最普遍的就是 Xposed担败,直接 hook checkSN 方法的返回值昔穴,或者打印出正確的注冊(cè)碼。如果你沒有 Root 設(shè)備提前,還有一系列基于 VirtualApp 的 hook 框架吗货,例如支持 Xposed 應(yīng)用的 VirtualXposed 等等,當(dāng)然 VirtualApp 本身也支持 hook 操作狈网。另外宙搬,還有 Frida 等等框架,也可以進(jìn)行類似的操作拓哺。

JADX

最后再介紹一個(gè)反編譯利器 JADX 勇垛,它可以直接將 Apk 反編譯成 Java 代碼進(jìn)行查看,畢竟 smali 代碼不是那么人性化士鸥。我拿到一個(gè) Apk窥摄,基本上第一件事就是丟到 JADX 中進(jìn)行查看,它同時(shí)支持命令行操作和圖形化界面础淤。我們就用 JADX 打開這個(gè) CrackMe 應(yīng)用看一下:

image

直接就可以看到對(duì)應(yīng)的 Java 代碼,理清邏輯之后再去閱讀 smali 代碼進(jìn)行修改哨苛,事半功倍鸽凶。支持反編譯 Java 代碼的工具還有很多,例如基于 Python 實(shí)現(xiàn)的 Androgurad 等等建峭,大家也可以嘗試去使用一下玻侥。

總結(jié)

就逆向難度來說,這個(gè) CrackMe 還是很簡(jiǎn)單的亿蒸,但本文主旨在于介紹一些逆向相關(guān)的知識(shí)凑兰,實(shí)際逆向過程中你面對(duì)的任何一個(gè) Apk 肯定都比這復(fù)雜的多掌桩。看到這里姑食,你應(yīng)該了解到了下面這些知識(shí)點(diǎn):

  • 使用 ApkTool 反編譯以及重打包
  • smali 代碼的基本閱讀能力
  • smali 代碼中注入 log 日志
  • 動(dòng)態(tài)調(diào)試 smali 代碼
  • 常用 hook 框架
  • jadx 使用

關(guān)于 smali 語法我之前也寫過幾篇文章波岛,往期目錄:

Class 文件格式詳解

Smali 語法解析——Hello World

Smali —— 數(shù)學(xué)運(yùn)算,條件判斷音半,循環(huán)

Smali 語法解析 —— 類

Android逆向筆記 —— AndroidManifest.xml 文件格式解析

Android逆向筆記 —— DEX 文件格式解析

下一篇來寫寫 Android Apk 中資源包文件 resources.arsc 的文件結(jié)構(gòu)则拷,同樣會(huì)配套思維導(dǎo)圖和 Java 源碼解析。

文章首發(fā)微信公眾號(hào): 秉心說 曹鸠, 專注 Java 煌茬、 Android 原創(chuàng)知識(shí)分享,LeetCode 題解彻桃。

更多 JDK 源碼解析坛善,掃碼關(guān)注我吧!

green.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邻眷,一起剝皮案震驚了整個(gè)濱河市眠屎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耗溜,老刑警劉巖组力,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抖拴,居然都是意外死亡燎字,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門阿宅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來候衍,“玉大人,你說我怎么就攤上這事洒放◎嚷梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵往湿,是天一觀的道長妖异。 經(jīng)常有香客問我,道長领追,這世上最難降的妖魔是什么他膳? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮绒窑,結(jié)果婚禮上棕孙,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好蟀俊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布钦铺。 她就那樣靜靜地躺著,像睡著了一般肢预。 火紅的嫁衣襯著肌膚如雪矛洞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天误甚,我揣著相機(jī)與錄音缚甩,去河邊找鬼。 笑死窑邦,一個(gè)胖子當(dāng)著我的面吹牛擅威,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冈钦,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼郊丛,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了瞧筛?” 一聲冷哼從身側(cè)響起厉熟,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎较幌,沒想到半個(gè)月后揍瑟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍炉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年绢片,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岛琼。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡底循,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出槐瑞,到底是詐尸還是另有隱情熙涤,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布困檩,位于F島的核電站祠挫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悼沿。R本人自食惡果不足惜等舔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望显沈。 院中可真熱鬧,春花似錦、人聲如沸拉讯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魔慷。三九已至只锭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間院尔,已是汗流浹背蜻展。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邀摆,地道東北人纵顾。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像栋盹,于是被迫代替她去往敵國和親施逾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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