某日遊mmap模塊化保護簡單分析

查看 108|回复 11
作者:ngiokweng   
前言
前前後後大概分析了這樣本4次左右,前3次都以失敗告終,或許對於普通人來說,失敗才是人生的主旋律,接觸逆向後對這句話越來越有感觸。
本文主要分析的目標是frida/hook檢測。
閃退情況描述
frida hook後會立即閃退,hook dlopen後可知是在加載lib__6dba__.so時閃退,具體是在lib__6dba__.so的.init_array裡。而.init_array中只有一個start函數。
frida hook了一次之後,下次就算不hook正常打開APP也會閃退,大概率檢測了frida的maps特徵。
start分析
一開始會調用get_custom_scetion獲取lib__6dba__.so中的加密數據。


image.png (24.73 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

具體實現如下:
首先用openat、lseek、read等系統調用打開並讀取lib__6dba__.so,然後遍歷獲取最後一個loadable segment的結束地址,記為last_loadseg_end。


image1.png (32.38 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

用010查看last_loadseg_end偏移指向的數據,可以看出明顯是一些高熵數據,記這些數據為enc_data。


image2.png (49.2 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

繼續向下看,它又遍歷shdr table獲取自定義的一個section。


image3.png (37.7 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

從010可以看出,該section同樣是指向上述last_loadseg_end那附近。


image4.png (20.56 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

雖然不知為何要分別通過phdr和shdr來定位enc_data,但總的來說get_custom_scetion函數的功能就是獲取enc_data。
回到start函數,獲取完enc_data後,調用decrypt1和decrypt2來解密。


image5.png (48.06 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

解密出來的數據其實是一些可執行的邏輯,由於它是通過mmap映射 + mprotect賦予可執行權限的方式來執行,因此記這種形式為mmap模塊,根據創建順序記為mmap1模塊、mmap2模塊、…,如此類推。frida的檢邏邏輯明顯就在其中。


image6.png (33.11 KB, 下载次数: 0)
下载附件
2025-5-6 22:51 上传

注:該保護使用了大量的系統調用( 上述的mmap和mprotect都是指系統調用 ),一些基礎函數如strcpy、strlen、memset等都是自實現的。
hook & dump mmap模塊
一開始我選擇通過動調來分析上述的mmap1模塊,發現mmap1中會創建和調用mmap2、mmap3、mmap4模塊,同理mmap2 ~ 4模塊又分別會創建和調用更多的mmap模塊,如此一來使得動調難以分析( 最主要是因為在mmap模塊中記錄的注釋、重命名變量名、函數名等都無法持久地保存 )。
但動調也並非毫無收獲,可以得知以下幾點:
[ol]
  • 每個mmap模塊的結構是非常相似的( 動調後會明白這句話的意思 )。
  • 每個mmap模塊的大部份函數實現是一樣的,如字符串解密函數。
  • 每個mmap模塊都有封裝系統調用,因此可以很方便地hook。
  • 每個mmap模塊創建&調用另一個mmap模塊的方法是一樣的,都是通過mmap + mprotect系統調用
    [/ol]
    由於難以動調,只好以純hook的方式來分析,在此之前要先將所有mmap模塊dump下來,遊戲閃退前創建的mmap模塊共有13個。
    可以通過frida或qbdi等方式來dump和trace所有mmap模塊,dump文件記為mmap___.bin,trace文件記為log.txt( 主要記錄函數調用關系,用利用qbdi可以很方便實現 )。
    然後按字節特徵來判斷mmap1 ~ mmap13,獲取分別的基址,以此進行hook。hook mmap1 ~ 4的例子如下所示。
    let hooked = false;
    let mmap_history = {}
    function hook_func_init(soName) {
        if (hooked) return;
        hooked = true;
        function hook_syscall() {
            function is_mmap1 (addr) {
                let byte_arr = [
                    0xF0, 0x7B, 0xBF, 0xA9, 0x30, 0x01, 0x00, 0xB0, 0x11, 0x86,
                    0x42, 0xF9, 0x10, 0x22, 0x14, 0x91, 0x20, 0x02, 0x1F, 0xD6
                ]
                let offset = 0x440;
                for(let i = 0; i
    mmap13模塊分析
    閃退前的最後一個模塊是mmap13,大概率會包含檢測frida的邏輯,因此重點分析這個模塊。
    基礎分析
    先找到字符串解密函數,其特徵如下,返回值就是解密後的字符串:


    image7.png (28.64 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    hook輸出如下:
    [hook_mmap13_decrypt_str] retval:  %s/lib
    [hook_mmap13_decrypt_str] retval:  %s/lib
    [hook_mmap13_decrypt_str] retval:  /lib
    [hook_mmap13_decrypt_str] retval:  arm64
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  /proc/self/maps
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    [hook_mmap13_decrypt_str] retval:  %s/%s
    比較可疑的是/proc/self/maps,打印調用棧發現在mmap13!0xF348,而該地址所在函數的交叉引用在0x4D28。


    image8.png (39.4 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    bl sub_F2E8所在地址是0x4D28,加上mmap13的基址是0x7AB2D21D28。


    image9.png (16.98 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    在log.txt裡搜0x7AB2D21D28找到對應地方查看函數調用關系,發現以下函數調用順序:
    [ol]
  • openat + lseek + read讀取了/proc/self/maps中的數據。
  • 通過vsnprintf拼接了APP自身3個so庫的完整路徑,其中就包括lib__6dba__.so的完整路徑。
    [/ol]
    0x14043be0 (0x7ab2d2bbe0): sub_1404712c() {
        0x140470c4 (0x7ab2d2f0c4): sub_14047068() {
            0x14047080 (0x7ab2d2f080): [SVC] sysno(0x38) -> openat(-100, "/proc/self/maps") => fd: 0x27
        }
    }
    0x14043c1c (0x7ab2d2bc1c): sub_140439a0() {
        0x140439b4 (0x7ab2d2b9b4): sub_14045e70() {
        }
        0x140439d4 (0x7ab2d2b9d4): sub_1404709c() {
            0x140470c4 (0x7ab2d2f0c4): sub_14047068() {
                0x14047080 (0x7ab2d2f080): [SVC] sysno(0xde) -> mmap(0x0, 0x80000, 0x3) => mmap address: 0x7ab1660000
            }
        }
    }
    0x14043c40 (0x7ab2d2bc40): sub_14043a44() {
        0x14043aa8 (0x7ab2d2baa8): sub_1404709c() {
            0x140470c4 (0x7ab2d2f0c4): sub_14047068() {
                0x14047080 (0x7ab2d2f080): [SVC] sysno(0x3e) -> lseek
            }
        }
        0x14043ad4 (0x7ab2d2bad4): sub_1404709c() {
            0x140470c4 (0x7ab2d2f0c4): sub_14047068() {
                0x14047080 (0x7ab2d2f080): [SVC] sysno(0x3f) -> read(0x27, "12c00000-12c40000 rw-p 00000000 ", 0x80000) => real read bytes: 0xf96
            }
        }
        0x14043ad4 (0x7ab2d2bad4): sub_1404709c() {
            0x140470c4 (0x7ab2d2f0c4): sub_14047068() {
                0x14047080 (0x7ab2d2f080): [SVC] sysno(0x3f) -> read(0x27, "71124000-71125000 rw-p 0003d000 ", 0x7f06a) => real read bytes: 0xfb7
            }
        }
    // ...
    0x1403b658 (0x7ab2d23658): sub_14040bb8() {
        0x14040c48 (0x7ab2d28c48): sub_14040ab0() {
            0x14040af4 (0x7ab2d28af4): sub_14037590() {     // nglog: mmap13 => 0xBAF4
                0x14040af4 (0x7ab2d28af4): [ExternalCall] vsnprintf("/data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/lib__6dba__.so", 0x400, "%s/%s") => res: 0x4b
            }
            }
    }
    由此猜測可能是在檢查自身的so庫有沒有被hook。
    嘗試hook mmap13的vsnprintf,將lib__6dba__.so替換為另一個沒被hook的庫libpad.so ( 這個庫也是APP本身的 )。
    function hook_vsnprintf () {
        Interceptor.attach(mmap_base.add(0xBBB8), {
            onEnter: function (args) {
                this.a0 = args[0];
            },
            onLeave: function (retval) {
                if (this.a0.readCString().indexOf("lib__6dba__.so") != -1) {
                    console.log("replace!!!!!!!!!!!!")
                    Memory.writeUtf8String(this.a0, this.a0.readCString().replace("lib__6dba__.so", "libpad.so"))
                }
                console.log("[mmap13_vsnprintf] this.a0: ", this.a0.readCString());
            }
        })
    }
    替換前,vsnprintf的輸出如下:
    [mmap13_vsnprintf] this.a0:  /data/user/0/jp.gungho.padHT/lib
    [mmap13_vsnprintf] this.a0:  /data/user/0/jp.gungho.padHT/lib
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/lib__6dba__.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/lib__6dba__.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/lib__6dba__.so
    替換後,vsnprintf的輸出如下,可以看到多了兩行關於libc.so的日志
    [mmap13_vsnprintf] this.a0:  /data/user/0/jp.gungho.padHT/lib
    [mmap13_vsnprintf] this.a0:  /data/user/0/jp.gungho.padHT/lib
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    replace!!!!!!!!!!!!
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    replace!!!!!!!!!!!!
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    replace!!!!!!!!!!!!
    [mmap13_vsnprintf] this.a0:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [mmap13_vsnprintf] this.a0:  /vendor/lib64/libc.so
    [mmap13_vsnprintf] this.a0:  /system/lib64/libc.so
    用同樣方法將libc.so替換為libz.so,發現APP終於不會在mmap13模塊之後馬上閃退,反而又再創建了其他模塊。


    image10.png (83.52 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    簡單小結,mmap13模塊應該是先檢測了lib__6dba__.so( APP本身的so庫 )有沒有被hook,若前者通過檢測,則再檢測libc.so( 系統so庫 )有沒有被hook,都通過後才會創建新模塊進行其他檢測,否則就用某些手段讓程序退出。
    手動patch mmap13模塊後,多了很多新模塊,是在mmap3模塊裡創建的,索引由14開始,共有mmap14 ~ mmap30模塊。
    if (this.sysno == 0xe2) {
        console.log("[hook_mmap3_syscall] mprotect addr: ", this.a0, "size: ", this.a1 ,"prot: ", this.a2);
        if (mmap_history[this.a0]) {
            console.log(`\t[hook_mmap3_syscall] mmap addr: ${this.a0}  size: ${mmap_history[this.a0]}`);
            // after patch mmap13 detect, use this to dump new mmap module
            if (is_hook_mmap13) {
                saveData(`/data/data/jp.gungho.padHT/mmap_${this.a0}_${mmap_history[this.a0]}_${idx++}.bin`, this.a0, mmap_history[this.a0].toInt32());
            }
        }
        // ...
    local lib檢測分析
    上一小節通過trace日志 + 經驗猜測的方式成功bypass了lib__6dba__.so中的hook檢測,這一小節嘗試分析看看具體的檢測原理。
    hook mmap13模塊封裝的syscall,在系統調用是openat且path包含lib__6ba__.so時打印調用棧,然後一路向上跟,最終發現是在mmap13!0x3BF0裡打開lib__6ba__.so的。
    詳細調用鏈如下:( ins addr代表指令地址,func addr代表函數起始地址 )
    0x394C(ins addr) -> 0x485C(ins addr) -> 0x433C(ins addr) -> 0x3F7C(ins addr) -> 0x3BF0(func addr)
    0x394C( 調用sub_4684的指令地址 )附近的邏輯如下,記所在函數為mmap13_main。
    測試發現,按上述「hook mmap13的vsnprintf,將lib__6dba__.so替換為另一個沒被hook的庫libpad.so」後,sub_4C20函數會返回1,否則返回0。
    由此可知sub_4C20要麼是具體的檢測函數,要麼是處理檢測結果的函數。記sub_4C20為mb_detect_func。


    image11.png (26.23 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    進入mb_detect_func分析,一路通過hook驗證,會發現get_so_info這個比較關鍵的函數。
    一開始以為get_so_info是具體的檢測函數,因為hook發現get_so_info共調用了3次,而且hook mmap13的openat系統調用時,看到它打開了3個自身的so庫,正好與之對應。由此猜測前2次get_so_info執行後的a1為0是因為我沒有hook libopenal.so和libpad.so,而第3次不為0是因為hook了lib__6dba__.so被檢測到。


    image12.png (27.1 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    // hook mmap13 openat log:
    [hook_mmap13_openat] a1:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libopenal.so
    [hook_mmap13_openat] a1:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/libpad.so
    [hook_mmap13_openat] a1:  /data/app/jp.gungho.padHT-RB7leURHfwOLGhr-1wOUew==/lib/arm64/lib__6dba__.so
    // hook get_so_info log:
    [mmap13_get_so_info] this.a1.readPointer:  0x0
    [mmap13_get_so_info] this.a1.readPointer:  0x0
    [mmap13_get_so_info] this.a1.readPointer:  0x7cfb84e000
    但後來詳細分析get_so_info後發現它其實只是在解析、保存/proc/pid/maps裡的信息( so_info[0]保存著so的二進制信息 ),前2次的a1為0是因為這時機還未加載那兩個lib庫,因此才為0。
    繼續向下看,so_info( so_img )之後會傳入do_something1函數,返回值保存在dest,然後會與*(_DWORD*)(v8+0x3C)對比,若不相等會導致最終走向wrong_branch。
    由此猜測*(_DWORD *)(v8 + 0x3C)應該是原始lib__6dba_.so .text段的hash值,dest是/proc/pid/maps裡lib__6dba__.so .text段的hash值。


    image13.png (28.25 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    進入do_something1,一開始在通過so_img解析重定向表,但沒看出來有什麼用。


    image14.png (34.6 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    繼續向下可以看到關鍵的while循環。


    image15.png (27.47 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    其中的hash_sum是一堆計算,應該是在計算類似哈希值的東西,嘗試hook該函數會發現args[0]曾出現過lib__6dba__.so的.text段,args[1]是.text段的大小,args[2]保存計算結果。


    image16.png (63.8 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    而後發現,針對自身的每個so,總共會調用2次hash_sum( 在兩處不同的位置 )來計算哈希值:
    [ol]
  • 第1次會對整個文件進行哈希,從下圖第1部份可以看出,0x1860df正是lib__6dba__.so的文件大小,而且在此之前調用openat打開了lib__6dba__.so。調用棧在mmap13!0x3DF0。
  • 第2次會對.text段進行哈希,從下圖第2部份可以看出,0xcaf4正是lib__6dba__.so的.text段大小,而且在此之前調用openat打開了/proc/self/maps,因此可知這部份是從其中獲取的。調用棧在mmap13!0x6B98,這正是上述的do_something1那裡。
    [/ol]
    第1次大概是為了校驗完整性之類的,第2次顯然就是在校驗是否被hook,這樣使得常規的IO重定向似乎無法直接繞過?


    image17.png (85.13 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    小結:對於local lib( APP自身的庫 ),會調用hash_sum函數進行校驗,與之對比的值應該是提前計算好內置到so中的。
    system lib檢測分析
    通過上述的local lib檢測後,才會繼續調用check_libc函數來檢測libc.so( 貌似只檢測了libc這個系統庫 )。下圖所在函數是mmap13_main。


    image18.png (28.4 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    check_libc中調用了do_something2函數。


    image19.png (33.18 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    接下來詳細分析do_something2函數。
    首先調用parse_elf_data函數來解析指定so,args[0]是libc.so映像的地址( 該映像是在此之前通過openat系統調用打開&讀取的 )。解析結果保存在soinfo中( 這並非linker那個soinfo )。


    image20.png (31.11 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    然後解密了一個關鍵字符串.text,傳入了get_section_info函數,它會返回libc.so的.bss段中的某段數據,其中包含指定section的信息,記為section_info。
    如*(section_info+0x10)就是指定section的offset。


    image21.png (27.06 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    之後會遍歷maps_item( /proc/pid/maps的每一行我稱為一個maps_item ),當遍歷到libc.so的.text段的下一段時,才會滿足下圖的第1個if條件。
    正常手機沒有啟動過frida時,會滿足第2個if條件( 即.text段的下一段一定大於等於.text段結束的位置 ),最終走到真正檢測libc的地方。


    image22.png (38.36 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    當不滿足上述第2個if條件時,會走下圖這裡,而且會循環多次。
    第1個紅框代表最多循環10次,若遍歷完.text段的後10個maps_item仍沒有發現大於.text段結束的,代表有問題,最終會導致程序走向閃退的錯誤分支。
    正常沒有被frida干預的程序流會在第2個紅框那裡直接goto LABEL 49。


    image23.png (68.98 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    而goto LABEL 49最終會走到這裡,調用do_check_libc進行真正的libc校驗。


    image24.png (24.51 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    do_check_libc函數裡有些關鍵字符串信息,如下。


    image25.png (26.31 KB, 下载次数: 0)
    下载附件
    2025-5-6 22:51 上传

    而do_check_libc的具體原理,有興趣的靚仔可以自己分析看看。
    完全繞過所有hook檢測的思路
    通過hook mmap13模塊的vsnprintf繞過對lib__6dba__.so和libc.so的校驗後,會加載libopenal.so和libpad.so( 它們是APP自身的so庫 ),然後發現這兩個so庫同樣存在與lib__6dba__.so一樣的start函數,同樣存在上述的mmap模塊檢測,同樣會檢驗local lib和system lib。
    好消息是它們大致使用了相同的mmap模塊來進行檢測,不同的只有mmap模塊創建的數量,如libopenal.so創建的mmap11模塊其實是lib__6dba__.so創建的mmap13模塊。
    而mmap模塊會調用vsnprintf來拼接庫的完整路徑,因此可以hook vsnprintf來改變指定庫路徑,重定位到其他沒有被hook的庫,以此來繞過檢測。具體方式在上文中已經給出,就不再重複。
    結語
    這個遊戲的保護是我遇到數一數二難的,難點在於它十分麻煩,且只能以hook的方式來調試,但找對方法後還是可以一點一點分析並解決的,不至於像一些VM那樣無從下手。
    同時本文只大致分析了其中的一個模塊,各位讀者有興趣可以自己看看其他模塊,大概有29個模塊,也是挺有意思的。

    下载次数, 下载附件

  • bfloat16   

    最近出的小圆也是这壳子,Java层的root检测 + 模拟器检测,native层的firda特征检测+hook检测+dump检测(具体表现为dd命令dump内存会导致进程强制崩溃)
    果断跑路换iOS版本的分析(逃
    ngiokweng
    OP
      


    bfloat16 发表于 2025-5-14 00:12
    最近出的小圆也是这壳子,Java层的root检测 + 模拟器检测,native层的firda特征检测+hook检测+dump检测(具 ...

    稍微看了下確實應該是同一個保護, 用文中給出的bypass方法應該也能過。還以為這個保護是P&D那家遊戲公司寫的, 沒想到別的公司也在用
    ngiokweng
    OP
      

    樣本: jp.gungho.padHT
    版本: 22.1.0
    yunxin0yu   

    wow,太强了楼主,学习到了
    ngiokweng
    OP
      


    yunxin0yu 发表于 2025-5-6 23:14
    wow,太强了楼主,学习到了

    大晚上還在學習
    芽衣   

    智龙迷城老游戏了,之前玩过几个月卸载了,不是我的菜。
    ngiokweng
    OP
      


    芽衣 发表于 2025-5-7 09:07
    智龙迷城老游戏了,之前玩过几个月卸载了,不是我的菜。

    經典老遊戲了, 個人感覺這類遊戲挺好玩的
    shenwq   

    这个真的不错,必须要支持一下的。
    longxy001   

    已经很NB了老哥,学习了
    您需要登录后才可以回帖 登录 | 立即注册