汇编(四)

2021-04-11  本文已影响0人  浅墨入画

前言:

内存分区是编译器帮我们做的,属于硬件相关。
macho文件是由代码、数据、macho的描述信息等组成
进行下面的学习之前请先了解一下内存五大区
接下来让我们一起来探索全局变量、常量 、静态区的内存

一. 全局变量和常量

创建空工程Demo,为了防止ViewController中OC代码的干扰,我们在main.m文件中编写代码探讨

// main.m文件代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int g = 12;

int func(int a,int b){
    printf("haha");
    int c = a + g;
    return c;
}

int main(int argc, char * argv[]) {
    func(1, 2);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
image.png

继续执行上面汇编代码到这一步

image.png
// func函数的汇编代码如下
Demo`func:
->  0x104b82170 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    0x104b82174 <+4>:  stp    x29, x30, [sp, #0x10]
    0x104b82178 <+8>:  add    x29, sp, #0x10            ; =0x10 
    // 1 2 两个参数分别入栈
    0x104b8217c <+12>: stur   w0, [x29, #-0x4]
    0x104b82180 <+16>: str    w1, [sp, #0x8]
    // x0寄存器中存的是地址,这个地址是4个字节,存的是 haha
    // 下面这两条指令是怎么得到0x0000000104b83f8d内存地址呢?
    // adrp 全称address page,按页寻找内存地址  这里是先将1这个值左移12位得到0x1000,再把pc寄存器0x104b82184低12位清0得到0x104b82000,两者再相加得到0x104b83000
    // 0x104b83000 再添加0xf8d 得到0x0000000104b83f8d
    0x104b82184 <+20>: adrp   x0, 1
    0x104b82188 <+24>: add    x0, x0, #0xf8d            ; =0xf8d 
    // 调用printf函数
    0x104b8218c <+28>: bl     0x104b825ac               ; symbol stub for: printf
    // 这里拿到参数a 也就是 1 
    0x104b82190 <+32>: ldur   w8, [x29, #-0x4]
    // 这里通过偏移得到 0x104b89570,也就是参数g值为12 
    0x104b82194 <+36>: adrp   x9, 7
    0x104b82198 <+40>: add    x9, x9, #0x570            ; =0x570 
    // 把x9 读到w10寄存器
    0x104b8219c <+44>: ldr    w10, [x9]
    0x104b821a0 <+48>: add    w8, w8, w10
    0x104b821a4 <+52>: str    w8, [sp, #0x4]
    0x104b821a8 <+56>: ldr    w8, [sp, #0x4]
    // 把计算的结果给到x0寄存器,作为返回值
    0x104b821ac <+60>: mov    x0, x8
    0x104b821b0 <+64>: ldp    x29, x30, [sp, #0x10]
    0x104b821b4 <+68>: add    sp, sp, #0x20             ; =0x20 
    0x104b821b8 <+72>: ret 

上面执行完 0x104b82184 <+20>: adrp x0, 1得到0x104b83000,fff是4095加上0就是4096 刚好对应4k,而一页大小是4096,所以这里的0x104b83000刚好是某一页的开始,adrp就意味着是先找到某一页的地址,再加上偏移量#0xf8d找到x0 haha的地址

// 终端执行PAGESIZE得到4096,说明一页大小是4096
hello-world$ PAGESIZE
4096

这里为什么不能直接加到字符串的位置?

这里我们做个练习加深对adrp指令的理解?0x1002bf888 <+20>: adrp x0, 2

小结
全局adrp与常量drp是一样的,都是通过基值加上偏移来获取。上面两处adrp没法区分是全局变量还是常量,但是可以拿到内存地址在macho中查看是全局变量还是常量。

二. 还原高级代码

下面我们通过反汇编来还原高级代码,这里需要借助工具Hopper,首先修改代码如下

// main.m文件代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int g = 12;

int func(int a,int b){
    printf("haha");
    int c = a + g + b;
    return c;
}

int main(int argc, char * argv[]) {
    func(10, 20);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

编译Demo工程,选中Products目录下Demo.app -> Show in Finder -> 右键显示包内容 -> 找到可执行文件Demo -> 把Demo拖入Hopper

image.png image.png

下面我们通过上面工具的汇编代码来还原高级代码

image.png image.png image.png
        ; ================ B E G I N N I N G   O F   P R O C E D U R E ================
                     _func:
// 函数的开始
0000000100006168         sub        sp, sp, #0x20                               ; CODE XREF=_main+32
000000010000616c         stp        x29, x30, [sp, #0x10]
0000000100006170         add        x29, sp, #0x10

// 从这里可以看出函数为两个参数
0000000100006174         stur       w0, [x29, #-0x4]
0000000100006178         str        w1, [sp, #0x8]
// 还原出代码为 func(int a,int b)
func(int a,int b) { }

// 这一段代码调用的是 printf函数
000000010000617c         adrp       x0, #0x100007000                            ; argument #1 for method imp___stubs__printf
// 工具直接定位出常量 haha
0000000100006180         add        x0, x0, #0xf8d                              ; "haha"
0000000100006184         bl         imp___stubs__printf
// 注意这里的0x100007000 是工具已经帮我们计算好了偏移量之后的值,执行完add 计算出内存地址 0000000100007f8d
// 根据Hopper工具,我们可以得出地址为0000000100007f8d  值为haha
// 还原出代码为printf("haha");
printf("haha");

0000000100006188         ldur       w8, [x29, #-0x4]
// 还原出代码为 int w8 = a;
int w8 = a;

000000010000618c         adrp       x9, #0x10000d000
// 这里也定位出全局变量_g
0000000100006190         add        x9, x9, #0x570                              ; _g
// 根据Hopper工具,我们可以得出地址为000000010000d570 值为0x0c也就是12
// 还原出代码为 int g = 12; 注意⚠️ 因为是全局变量(使用烂苹果软件MachOView看出是在data区被改动,所以是全局变量),要写在func函数外面
g;

0000000100006194         ldr        w10, x9
// 还原出代码为 int w10 = g;
int w10 = g;

0000000100006198         add        w8, w8, w10
// 还原出代码为 w8 += w10;
w8 += w10;

000000010000619c         ldr        w10, [sp, #0x8]
// 还原出代码为 int w10 = b;
w10 = b;

00000001000061a0         add        w8, w8, w10
// 还原出代码为 w8 += w10;
w8 += w10;

00000001000061a4         str        w8, [sp, #0x4]
00000001000061a8         ldr        w8, [sp, #0x4]
00000001000061ac         mov        x0, x8
// 还原出代码为 return w8;
return w8;

// 函数的结束
00000001000061b0         ldp        x29, x30, [sp, #0x10]
00000001000061b4         add        sp, sp, #0x20
00000001000061b8         ret

根据上面汇编代码还原简化为下面代码

int g = 12;
func(int a,int b) {
    printf("haha");
    int w8 = a;
    int w10 = g;
    w8 += w10;
    w10 = b;
    w8 += w10;
    return w8;
}

// 接着简化代码如下
int g = 12;
func(int a,int b) {
    printf("haha");
    return b + g + a;
}

通过最终还原的代码与上面代码比对,发现汇编代码还原高级语言代码成功。注意⚠️ 还原代码必须从下往上还原,这就是逆向

三. if的识别

创建空工程002--if的识别,我们还在main.m文件中编写代码探讨

// main.m文件代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int g = 12;
void func(int a,int b){
    if (a > b) {
        g = a;
    }else{
        g = b;
    }
}

int main(int argc, char * argv[]) {
    func(1, 2);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

编译002--if的识别工程,找到可执行文件拖入Hopper,如果不是真机编译,会出现下图情况

image.png image.png

接下来我们用真机编译,可执行文件拖入Hopper

image.png
// func汇编代码如下
   ; ================ B E G I N N I N G   O F   P R O C E D U R E ================
                     _func:
0000000100006188         sub        sp, sp, #0x10                               ; CODE XREF=_main+32
000000010000618c         str        w0, [sp, #0xc]
0000000100006190         str        w1, [sp, #0x8]
0000000100006194         ldr        w8, [sp, #0xc]
0000000100006198         ldr        w9, [sp, #0x8]
// 通过尾数判断func函数,无返回值
// 上面还原出代码为 void func(int a, int b) {  int w8 = a; int w9 = b; }
void func(int a, int b) { 
    int w8 = a;
    int w9 = b;
}

// 两个参数的比较,cmp执行的是减法 不影响目标寄存器w8 w9,只看减法的结果,修改标记寄存器
000000010000619c         cmp        w8, w9
// b.le 相当于 小于等于
00000001000061a0         b.le       loc_1000061b8
// 还原出代码 if (w8 - w9) { }
if (w8 - w9) {  // 小于等于
}

// 执行大于逻辑
00000001000061a4         ldr        w8, [sp, #0xc]
00000001000061a8         adrp       x9, #0x10000d000
00000001000061ac         add        x9, x9, #0x568                              ; _g
00000001000061b0         str        w8, x9
00000001000061b4         b          loc_1000061c8
// 还原出代码 g = b;
g = b;

// 执行小于等于的逻辑
                     loc_1000061b8:
00000001000061b8         ldr        w8, [sp, #0x8]                              ; CODE XREF=_func+24
00000001000061bc         adrp       x9, #0x10000d000
00000001000061c0         add        x9, x9, #0x568                              ; _g
// 根据Hopper工具,我们可以得出地址为000000010000d568 值为0x0c也就是12,还原出代码为 int g = 12;
// 把w8 写入 x9,也就是写入g中
// 这里只能看出w8是4个字节,因为是低32位,这里有可能是Int 也有可能是uInt
00000001000061c4         str        w8, x9
// 还原出代码 g = w8;
g = w8;

                     loc_1000061c8:
00000001000061c8         add        sp, sp, #0x10                               ; CODE XREF=_func+44
00000001000061cc         ret
                     ; endp
// 最终通过汇编代码还原出的高级代码如下
int g = 12;
void func2(int a,int b){
    if (a > b) {
        g = a;
    }else{
        g = b;
    }
}

小结

cmp(Compare)比较指令

CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。一般CMP做完判断后会进行跳转,后面通常会跟上B指令!

注意⚠️ cmp指令下面执行的是 else 条件

四. 循环

创建空工程003--Loop,我们还在main.m文件中编写代码探讨

// main.m文件代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    int nSum = 0;
    int i = 0;
    do {
        nSum = nSum + 1;
        i++;
    } while (i < 100);  
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

真机编译,可执行文件拖入Hopper,获取汇编代码

// main汇编代码如下
                          _main:
00000001000061b8         sub        sp, sp, #0x40
00000001000061bc         stp        x29, x30, [sp, #0x30]
00000001000061c0         add        x29, sp, #0x30
00000001000061c4         stur       wzr, [x29, #-0x4]
00000001000061c8         stur       w0, [x29, #-0x8]
00000001000061cc         stur       x1, [x29, #-0x10]
00000001000061d0         stur       wzr, [x29, #-0x14]
// 把0放入一个内存地址中
00000001000061d4         str        wzr, [sp, #0x18]

                     loc_1000061d8:
// 这里是do的操作
00000001000061d8         ldur       w8, [x29, #-0x14]                           ; CODE XREF=_main+64
00000001000061dc         add        w8, w8, #0x1
00000001000061e0         stur       w8, [x29, #-0x14]
// 读出来给到w8寄存器
00000001000061e4         ldr        w8, [sp, #0x18]
// w8寄存器加1
00000001000061e8         add        w8, w8, #0x1
00000001000061ec         str        w8, [sp, #0x18]
00000001000061f0         ldr        w8, [sp, #0x18]
// 比较指令这里是while条件,0x64就是100,和100比较,小于就跳回来
00000001000061f4         cmp        w8, #0x64
00000001000061f8         b.lt       loc_1000061d8

00000001000061fc         ldur       w0, [x29, #-0x8]
0000000100006200         ldur       x1, [x29, #-0x10]
0000000100006204         adrp       x8, #0x10000d000
0000000100006208         add        x8, x8, #0x398                              ; objc_cls_ref_AppDelegate
000000010000620c         ldr        x8, x8
0000000100006210         str        w0, [sp, #0x14]
0000000100006214         mov        x0, x8
0000000100006218         str        x1, [sp, #0x8]
000000010000621c         bl         imp___stubs__objc_opt_class
0000000100006220         bl         imp___stubs__NSStringFromClass
0000000100006224         mov        x29, x29
0000000100006228         bl         imp___stubs__objc_retainAutoreleasedReturnValue
000000010000622c         ldr        w9, [sp, #0x14]
0000000100006230         str        x0, sp
0000000100006234         mov        x0, x9
0000000100006238         ldr        x1, [sp, #0x8]
000000010000623c         movz       x8, #0x0
0000000100006240         mov        x2, x8
0000000100006244         ldr        x3, sp
0000000100006248         bl         imp___stubs__UIApplicationMain
000000010000624c         stur       w0, [x29, #-0x4]
0000000100006250         ldr        x0, sp
0000000100006254         bl         imp___stubs__objc_release
0000000100006258         ldur       w0, [x29, #-0x4]
000000010000625c         ldp        x29, x30, [sp, #0x30]
0000000100006260         add        sp, sp, #0x40
0000000100006264         ret
                        ; endp

修改main.m代码如下

int main(int argc, char * argv[]) {
    int nSum = 0;
    int i = 0;
    while (i < 100) {
        nSum = nSum + 1;
        i++;
    } ;
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

// 核心汇编代码如下
                     loc_1000061d4:
00000001000061d4         ldr        w8, [sp, #0x18]                             ; CODE XREF=_main+68
00000001000061d8         cmp        w8, #0x64
// 不满足条件跳转执行loc_1000061fc
00000001000061dc         b.ge       loc_1000061fc

00000001000061e0         ldur       w8, [x29, #-0x14]
00000001000061e4         add        w8, w8, #0x1
00000001000061e8         stur       w8, [x29, #-0x14]
00000001000061ec         ldr        w8, [sp, #0x18]
00000001000061f0         add        w8, w8, #0x1
00000001000061f4         str        w8, [sp, #0x18]
00000001000061f8         b          loc_1000061d4
                     loc_1000061fc:
00000001000061fc         ldur       w0, [x29, #-0x8]                            ; CODE XREF=_main+40
0000000100006200         ldur       x1, [x29, #-0x10]
0000000100006204         adrp       x8, #0x10000d000
0000000100006208         add        x8, x8, #0x398                              ; objc_cls_ref_AppDelegate
000000010000620c         ldr        x8, x8
0000000100006210         str        w0, [sp, #0x14]
0000000100006214         mov        x0, x8
0000000100006218         str        x1, [sp, #0x8]
000000010000621c         bl         imp___stubs__objc_opt_class
0000000100006220         bl         imp___stubs__NSStringFromClass
0000000100006224         mov        x29, x29
... 

再次修改main.m代码如下

int main(int argc, char * argv[]) {
    int nSum = 0;
    for (int i = 0; i < 100; i++) {
        nSum = nSum + 1;
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

// 核心汇编代码如下
                     loc_1000061d4:
00000001000061d4         ldr        w8, [sp, #0x14]                             ; CODE XREF=_main+72
00000001000061d8         cmp        w8, #0x64
00000001000061dc         b.ge       loc_1000061fc

00000001000061e0         ldur       w8, [x29, #-0x14]
00000001000061e4         add        w8, w8, #0x1
00000001000061e8         stur       w8, [x29, #-0x14]
00000001000061ec         ldr        w8, [sp, #0x14]
00000001000061f0         add        w8, w8, #0x1
00000001000061f4         str        w8, [sp, #0x14]
00000001000061f8         b          loc_1000061d4

                     loc_1000061fc:
00000001000061fc         ldur       w0, [x29, #-0x8]                            ; CODE XREF=_main+44
0000000100006200         ldur       x1, [x29, #-0x10]
0000000100006204         adrp       x8, #0x10000d000
0000000100006208         add        x8, x8, #0x398                              ; objc_cls_ref_AppDelegate
000000010000620c         ldr        x8, x8
0000000100006210         str        w0, [sp, #0x10]
...
// 可以看出来跟while 汇编代码一摸一样,所以两者可以代替

五. 选择(上)

创建空工程004--选择,我们还在main.m文件中编写代码探讨

// main.m文件代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

void funcA(int a){
    switch (a) {//
        case 1:
            printf("打坐");
            break;
        case 2:
            printf("加红");
            break;
        case 3:
            printf("加蓝");
            break;
        default:
            printf("啥都不干");
            break;
    }
}
int main(int argc, char * argv[]) {
    funcA(1);
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
// funcA汇编代码如下
004--选择`funcA:
    0x100962114 <+0>:   sub    sp, sp, #0x20             ; =0x20 
    0x100962118 <+4>:   stp    x29, x30, [sp, #0x10]
    0x10096211c <+8>:   add    x29, sp, #0x10            ; =0x10 
    // wo 寄存器保存一下,然后再取出来
    0x100962120 <+12>:  stur   w0, [x29, #-0x4]
->  0x100962124 <+16>:  ldur   w8, [x29, #-0x4]
    // 比较,相等就跳转
    0x100962128 <+20>:  cmp    w8, #0x1                  ; =0x1 
    0x10096212c <+24>:  str    w8, [sp, #0x8]
    0x100962130 <+28>:  b.eq   0x100962158               ; <+68> at main.m
    0x100962134 <+32>:  b      0x100962138               ; <+36> at main.m
    0x100962138 <+36>:  ldr    w8, [sp, #0x8]
    // 比较,相等就跳转
    0x10096213c <+40>:  cmp    w8, #0x2                  ; =0x2 
    0x100962140 <+44>:  b.eq   0x100962168               ; <+84> at main.m
    0x100962144 <+48>:  b      0x100962148               ; <+52> at main.m
    0x100962148 <+52>:  ldr    w8, [sp, #0x8]
    // 比较,相等就跳转
    0x10096214c <+56>:  cmp    w8, #0x3                  ; =0x3 
    0x100962150 <+60>:  b.eq   0x100962178               ; <+100> at main.m
    0x100962154 <+64>:  b      0x100962188               ; <+116> at main.m
    // 拿出常量打印
    0x100962158 <+68>:  adrp   x0, 1
    0x10096215c <+72>:  add    x0, x0, #0xf6d            ; =0xf6d 
    0x100962160 <+76>:  bl     0x10096258c               ; symbol stub for: printf
    0x100962164 <+80>:  b      0x100962194               ; <+128> at main.m:30:1
    0x100962168 <+84>:  adrp   x0, 1
    0x10096216c <+88>:  add    x0, x0, #0xf74            ; =0xf74 
    0x100962170 <+92>:  bl     0x10096258c               ; symbol stub for: printf
    0x100962174 <+96>:  b      0x100962194               ; <+128> at main.m:30:1
    0x100962178 <+100>: adrp   x0, 1
    0x10096217c <+104>: add    x0, x0, #0xf7b            ; =0xf7b 
    0x100962180 <+108>: bl     0x10096258c               ; symbol stub for: printf
    0x100962184 <+112>: b      0x100962194               ; <+128> at main.m:30:1
    0x100962188 <+116>: adrp   x0, 1
    0x10096218c <+120>: add    x0, x0, #0xf82            ; =0xf82 
    0x100962190 <+124>: bl     0x10096258c               ; symbol stub for: printf
    0x100962194 <+128>: ldp    x29, x30, [sp, #0x10]
    0x100962198 <+132>: add    sp, sp, #0x20             ; =0x20 
    0x10096219c <+136>: ret    
// 可以发现上面汇编代码与if 汇编是一样的

修改main.m代码如下

void funcA(int a){
    switch (a) {//
        case 1:
            printf("打坐");
            break;
        case 2:
            printf("加红");
            break;
        case 3:
            printf("加蓝");
            break;
        case 4:
            printf("打怪");
            break;
        default:
            printf("啥都不干");
            break;
    }
}

// funcA汇编代码如下
004--选择`funcA:
    0x1024660f0 <+0>:   sub    sp, sp, #0x20             ; =0x20 
    0x1024660f4 <+4>:   stp    x29, x30, [sp, #0x10]
    0x1024660f8 <+8>:   add    x29, sp, #0x10            ; =0x10 
    // 参数入栈
    0x1024660fc <+12>:  stur   w0, [x29, #-0x4]
->  0x102466100 <+16>:  ldur   w8, [x29, #-0x4]
    // 这里没有了类似if的汇编,也没有了很多个cmp 
    // subs 让参数减1,这里会影响状态寄存器减1
    0x102466104 <+20>:  subs   w8, w8, #0x1              ; =0x1 
    0x102466108 <+24>:  mov    x9, x8
    0x10246610c <+28>:  ubfx   x9, x9, #0, #32
    // 判断是否等于3
    0x102466110 <+32>:  cmp    x9, #0x3                  ; =0x3 
    0x102466114 <+36>:  str    x9, [sp]
    // 跳转
    0x102466118 <+40>:  b.hi   0x102466174               ; <+132> at main.m
    0x10246611c <+44>:  adrp   x8, 0
    0x102466120 <+48>:  add    x8, x8, #0x18c            ; =0x18c 
    0x102466124 <+52>:  ldr    x11, [sp]
    0x102466128 <+56>:  ldrsw  x10, [x8, x11, lsl #2]
    0x10246612c <+60>:  add    x9, x8, x10
    0x102466130 <+64>:  br     x9
    0x102466134 <+68>:  adrp   x0, 1
    0x102466138 <+72>:  add    x0, x0, #0xf69            ; =0xf69 
    0x10246613c <+76>:  bl     0x102466588               ; symbol stub for: printf
    0x102466140 <+80>:  b      0x102466180               ; <+144> at main.m:30:1
    0x102466144 <+84>:  adrp   x0, 1
    0x102466148 <+88>:  add    x0, x0, #0xf70            ; =0xf70 
    0x10246614c <+92>:  bl     0x102466588               ; symbol stub for: printf
    0x102466150 <+96>:  b      0x102466180               ; <+144> at main.m:30:1
    0x102466154 <+100>: adrp   x0, 1
    0x102466158 <+104>: add    x0, x0, #0xf77            ; =0xf77 
    0x10246615c <+108>: bl     0x102466588               ; symbol stub for: printf
    0x102466160 <+112>: b      0x102466180               ; <+144> at main.m:30:1
    0x102466164 <+116>: adrp   x0, 1
    0x102466168 <+120>: add    x0, x0, #0xf7e            ; =0xf7e 
    0x10246616c <+124>: bl     0x102466588               ; symbol stub for: printf
    0x102466170 <+128>: b      0x102466180               ; <+144> at main.m:30:1
    0x102466174 <+132>: adrp   x0, 1
    0x102466178 <+136>: add    x0, x0, #0xf85            ; =0xf85 
    0x10246617c <+140>: bl     0x102466588               ; symbol stub for: printf
    0x102466180 <+144>: ldp    x29, x30, [sp, #0x10]
    0x102466184 <+148>: add    sp, sp, #0x20             ; =0x20 
    0x102466188 <+152>: ret    

小结

注意⚠️ switch 判断条件比较多的话,一直if else 会浪费时间,由于上面都是连续的数字,这个时候就会创建表,投入空间来换时间。如果上面判断条件不是连续的,底层就不会创建表来进行优化,又会执行if else 的判断。

// 下面这种形式,虽然条件语句是4个,但判断条件不是连续的,系统底层不会创建表来进行优化
void funcA(int a){
    switch (a) {
        case 1:
            printf("打坐");
            break;
        case 200:
            printf("加红");
            break;
        case 35:
            printf("加蓝");
            break;
        case 41:
            printf("打怪");
            break;
        default:
            printf("啥都不干");
            break;
    }
}

上面是怎么生成数据表?又是怎么找到对应的地址值?限于篇幅,我们之后继续探讨......

上一篇下一篇

猜你喜欢

热点阅读