栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

unidbg第三讲 com.github.unidbg.android.AndroidTest

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

unidbg第三讲 com.github.unidbg.android.AndroidTest

讲解例子

类似的例子还有

com.github.unidbg.android.Android64Test
学习内容
  1. 手动调用native方法
  2. 手动调用JNI_OnLoad方法
  3. 手动调用模拟器入口方法
  4. 对jni里的env调用callStaticFloatMethod、newObject、setFloatField、SetDoubleField、
    getStaticBooleanField等方法进行hook
课前准备及了解的知识:
  1.  从源码中知道test文件和libnative.so文件的生成分别对应test.cpp和native.c
  2. 模拟器入口对应test.cpp里面的main方法
  3. JNI_OnLoad在native.c里定义
  4. native方法的实现也在native.c里实现
代码实现及备注:
package com.github.unidbg.android;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.BackendFactory;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.linux.android.AndroidARMEmulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.baseVM;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.VarArg;
import com.github.unidbg.linux.file.Stdout;
import com.github.unidbg.linux.struct.Dirent;
import com.github.unidbg.linux.thread.ThreadJoinVisitor;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;
import com.sun.jna.Pointer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.util.Collections;

public class AndroidTest extends AbstractJni {
    private static final Log log = LogFactory.getLog(AndroidTest.class);
//    函数入口
    public static void main(String[] args) throws IOException {
        new AndroidTest().test();
    }

    private final AndroidEmulator emulator;
    private final Module module;
    private final DvmClass cJniTest;

    private static class MyARMSyscallHandler extends ARM32SyscallHandler {
        private MyARMSyscallHandler(SvcMemory svcMemory) {
            super(svcMemory);
        }
        @Override
        protected int fork(Emulator emulator) {
            //    获取线程id
            return emulator.getPid();
        }
    }

    private AndroidTest() {
        final File executable = new File("unidbg-android/src/test/native/android/libs/armeabi-v7a/test");
//        创建模拟器
        emulator = new AndroidARMEmulator(executable.getName(),
                new File("target/rootfs"),
                Collections.singleton(new Unicorn2Factory(true))) {
            @Override
            protected UnixSyscallHandler createSyscallHandler(SvcMemory svcMemory) {
//                todo1  这个的作用我暂时也不知道
                return new MyARMSyscallHandler(svcMemory);
            }
        };
//        获取模拟器内存对象
        Memory memory = emulator.getMemory();

        // 设置是否打印Jni调用细节
        emulator.getSyscallHandler().setVerbose(false);
        AndroidResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);

// todo ,推测patchThread表示对某个线程进行hook
        resolver.patchThread(emulator, HookZz.getInstance(emulator), new ThreadJoinVisitor() {
            @Override
            public boolean canJoin(Pointer start_routine, int threadId) {
                log.info("canJoin start_routine=" + start_routine + ", threadId=" + threadId);
                return true;
            }
        });
//加载模块
        module = emulator.loadLibrary(executable, true);
//创建虚拟机
        VM vm = emulator.createDalvikVM();
        // 设置是否打印Jni调用细节
        vm.setVerbose(true);
        vm.setJni(this);

//加载模块libnative.so
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/native/android/libs/armeabi-v7a/libnative.so"), true);
//手动调用JNI_onLoad
        dm.callJNI_onLoad(emulator);
//        需要模拟的类
        this.cJniTest = vm.resolveClass("com/github/unidbg/android/JniTest");

        {
//   动态分配栈
            Pointer pointer = memory.allocateStack(0x100);
//   todo : Dirent -> UnidbgStructure ->Structure
            System.out.println(new Dirent(pointer));
        }
    }

    @Override
    public DvmObject newObject(baseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if ("com/github/unidbg/android/AndroidTest->()V".equals(signature)) {
            return dvmClass.newObject(null);
        }
        return super.newObject(vm, dvmClass, signature, varArg);
    }

    @Override
    public void setFloatField(baseVM vm, DvmObject dvmObject, String signature, float value) {
        if ("com/github/unidbg/android/AndroidTest->floatField:F".equals(signature)) {
            System.out.println("floatField value=" + value);
            return;
        }
        super.setFloatField(vm, dvmObject, signature, value);
    }

    @Override
    public void setDoubleField(baseVM vm, DvmObject dvmObject, String signature, double value) {
        if ("com/github/unidbg/android/AndroidTest->doubleField:D".equals(signature)) {
            System.out.println("doubleField value=" + value);
            return;
        }
        super.setDoubleField(vm, dvmObject, signature, value);
    }

    @Override
    public float callStaticFloatMethod(baseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if ("com/github/unidbg/android/AndroidTest->testStaticFloat(FD)F".equals(signature)) {
            float f1 = varArg.getFloatArg(0);
            double d2 = varArg.getDoubleArg(1);
            System.out.printf("callStaticFloatMethod f1=%s, d2=%s%n", f1, d2);
            return 0.0033942017F;
        }

        return super.callStaticFloatMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public double callStaticDoubleMethod(baseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if ("com/github/unidbg/android/AndroidTest->testStaticDouble(FD)D".equals(signature)) {
            return 0.0023942017D;
        }
        return super.callStaticDoubleMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public boolean getStaticBooleanField(baseVM vm, DvmClass dvmClass, String signature) {
        if ("com/github/unidbg/android/AndroidTest->staticBooleanField:Z".equals(signature)) {
            return true;
        }

        return super.getStaticBooleanField(vm, dvmClass, signature);
    }

    @Override
    public void setStaticDoubleField(baseVM vm, DvmClass dvmClass, String signature, double value) {
        if ("com/github/unidbg/android/AndroidTest->staticDoubleField:D".equals(signature)) {
            System.out.println("staticDoubleField value=" + value);
            return;
        }

        super.setStaticDoubleField(vm, dvmClass, signature, value);
    }

    @Override
    public void setStaticFloatField(baseVM vm, DvmClass dvmClass, String signature, float value) {
        if ("com/github/unidbg/android/AndroidTest->staticFloatField:F".equals(signature)) {
            System.out.println("staticFloatField value=" + value);
            return;
        }

        super.setStaticFloatField(vm, dvmClass, signature, value);
    }

    private void test() {
//        模拟调用native函数
        cJniTest.callStaticJniMethod(emulator, "testJni(Ljava/lang/String;JIDZSFDBJF)V",
                getClass().getName(), 0x123456789abcdefL,
                0x789a, 0.12345D, true, 0x123, 0.456f, 0.789123D, (byte) 0x7f,
                0x89abcdefL, 0.123f);

//        Logger.getLogger(Stdout.class).setLevel(Level.WARN);
//
//        调用入口函数,这里指的是test.cpp里的main方法
        int exitCode = module.callEntry(emulator);
//        按任意键后打印下面的信息
        Backend backend = emulator.getBackend();
        System.err.println("exit code: " + exitCode + ", backend=" + backend);
    }

}
test.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 

#include 
#include 

#include "test.h"

static int sdk_int = 0;

static void *start_routine(void *arg) {
  void *ret = &sdk_int;
  printf("test_pthread start_routine arg=%p, ret=%pn", arg, ret);
  return ret;
}

static void test_pthread() {
  pthread_t thread = 0;
  void *arg = &thread;
  int ret = pthread_create(&thread, NULL, start_routine, arg);
  void *value = NULL;
  int join_ret = pthread_join(thread, &value);
  printf("test_pthread arg=%p, ret=%d, thread=0x%lx, join_ret=%d, value=%pn", arg, ret, thread, join_ret, value);
}

#define INFINITY_LIFE_TIME      0xFFFFFFFFU
#define NIPQUAD(addr) 
    ((unsigned char *)&addr)[0], 
    ((unsigned char *)&addr)[1], 
    ((unsigned char *)&addr)[2], 
    ((unsigned char *)&addr)[3]

static void test_netlink() {
  static __u32 seq = 0;
  struct rtattr *rta;
  int fd = socket(AF_NETlink, SOCK_DGRAM, NETlink_ROUTE);
  if(fd == -1) {
    printf("test_netlink code=%d, msg=%sn", errno, strerror(errno));
    return;
  }
  struct {
    struct nlmsghdr n;
    struct ifaddrmsg r;
    int pad[4];
  } req;
  memset(&req, 0, sizeof(req));
  req.n.nlmsg_len = sizeof(req);
  req.n.nlmsg_type = RTM_GETADDR;
  req.n.nlmsg_flags = NLM_F_MATCH | NLM_F_REQUEST;
  req.n.nlmsg_pid = 0;
  req.n.nlmsg_seq = seq;
  req.r.ifa_family = AF_UNSPEC;

  
  rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
  rta->rta_len = RTA_LENGTH(4);

  char buf[16384];
  hex(buf, &req, sizeof(req));
  int ret = sendto(fd, (void *)&req, sizeof(req), 0, NULL, 0);
  printf("test_netlink fd=%d, sizeof(req)=%zu, buf=%s, ret=%dn", fd, sizeof(req), buf, ret);

  memset(buf, 0, sizeof(buf));
  int status = recv(fd, buf, sizeof(buf), 0);
  if (status < 0) {
      perror("test_netlink");
      return;
  }
  if(status == 0) {
      printf("test_netlink EOFn");
      return;
  }
  char str[16384];
  hex(str, buf, status);
  printf("test_netlink status=%d, buf=%sn", status, str);

  struct nlmsghdr *nlmp;
  struct ifaddrmsg *rtmp;
  struct rtattr *rtatp;
  int rtattrlen;
  struct in_addr *inp;
  struct ifa_cacheinfo *cache_info;

  

  next_nlmp:
      for(nlmp = (struct nlmsghdr *)buf; status > sizeof(*nlmp);){
          int len = nlmp->nlmsg_len;
          int req_len = len - sizeof(*nlmp);

          if (req_len<0 || len>status) {
              printf("test_netlink errorn");
              return;
          }

          if (!NLMSG_OK(nlmp, status)) {
              printf("test_netlink NLMSG not OKn");
              return;
          }

          rtmp = (struct ifaddrmsg *)NLMSG_DATA(nlmp);
          rtatp = (struct rtattr *)IFA_RTA(rtmp);

          

          int ctl_sock = socket(AF_INET, SOCK_DGRAM, 0);
          struct ifreq ifr;
          memset(&ifr, 0, sizeof(ifr));
          ifr.ifr_ifindex = rtmp->ifa_index;
          ioctl(ctl_sock, SIOCGIFNAME, &ifr);
          ioctl(ctl_sock, SIOCGIFFLAGS, &ifr);
          printf("Index Of Iface: %d, name=%s, flags=0x%xn", rtmp->ifa_index, ifr.ifr_name, ifr.ifr_flags);
          close(ctl_sock);

          rtattrlen = IFA_PAYLOAD(nlmp);

          for (; RTA_OK(rtatp, rtattrlen); rtatp = RTA_NEXT(rtatp, rtattrlen)) {

              

              if(rtatp->rta_type == IFA_LABEL){
                  const char *label = (const char *)RTA_DATA(rtatp);
                  printf("  label: %sn", label);
              }

              if(rtatp->rta_type == IFA_CACHEINFO){
                  cache_info = (struct ifa_cacheinfo *)RTA_DATA(rtatp);
                  if (cache_info->ifa_valid == INFINITY_LIFE_TIME)
                      printf("  valid_lft forevern");
                  else
                      printf("  valid_lft %usecn", cache_info->ifa_valid);

                  if (cache_info->ifa_prefered == INFINITY_LIFE_TIME)
                      printf("  preferred_lft forevern");
                  else
                      printf("  preferred_lft %usecn",cache_info->ifa_prefered);
              }

              

              if(rtatp->rta_type == IFA_ADDRESS){
                  inp = (struct in_addr *)RTA_DATA(rtatp);
                  //  in6p = (struct in6_addr *)RTA_DATA(rtatp);
                  //  printf("addr0: " NIP6_FMT "n",NIP6(*in6p));
                  printf("  addr0: %u.%u.%u.%un",NIPQUAD(*inp));
              }

              if(rtatp->rta_type == IFA_LOCAL){
                  inp = (struct in_addr *)RTA_DATA(rtatp);
                  //  in6p = (struct in6_addr *)RTA_DATA(rtatp);
                  //  printf("addr1: " NIP6_FMT "n",NIP6(*in6p));
                  printf("  addr1: %u.%u.%u.%un",NIPQUAD(*inp));
              }

              if(rtatp->rta_type == IFA_BROADCAST){
                  inp = (struct in_addr *)RTA_DATA(rtatp);
                  //  in6p = (struct in6_addr *)RTA_DATA(rtatp);
                  //  printf("bcataddr: " NIP6_FMT "n",NIP6(*in6p));
                  printf("  Bcast addr: %u.%u.%u.%un",NIPQUAD(*inp));
              }

              if(rtatp->rta_type == IFA_ANYCAST){
                  inp = (struct in_addr *)RTA_DATA(rtatp);
                  //  in6p = (struct in6_addr *)RTA_DATA(rtatp);
                  //  printf("anycastaddr: "NIP6_FMT"n",NIP6(*in6p));
                  printf("  anycast addr: %u.%u.%u.%un",NIPQUAD(*inp));
              }

          }
          status -= NLMSG_ALIGN(len);
          nlmp = (struct nlmsghdr*)((char*)nlmp + NLMSG_ALIGN(len));

      }

  status = recv(fd, buf, sizeof(buf), 0);
  printf("test_netlink status=%dn", status);
  if(status > 0) {
    goto next_nlmp;
  }
  close(fd);
}

static void test_stat() {
  struct stat st;
  printf("st_nlink=0x%lx, st_blocks=0x%lx, st_rdev=0x%lx, st_uid=0x%lx, st_mtime=0x%lx, size=%lun", (long) &st.st_nlink - (long) &st, (long) &st.st_blocks - (long) &st, (long) &st.st_rdev - (long) &st, (long) &st.st_uid - (long) &st, (long) &st.st_mtime - (long) &st, (unsigned long) sizeof(st));
}

static void test_dirent() {

  struct dirent dt;
  fprintf(stdout, "dirent size=%lun", (unsigned long) sizeof(dt));
}

static void test_ioctl() {
  struct ifconf ifc;
  struct ifreq ibuf[256];
  ifc.ifc_len = sizeof ibuf;
  ifc.ifc_buf = (caddr_t)ibuf;
  int fd = socket(AF_INET, SOCK_DGRAM, 0);
  ioctl(fd, SIOCGIFCONF, (char *)&ifc);

  printf("sizeof ifconf=%lu, ifreq=%lun", (unsigned long) sizeof(struct ifconf), (unsigned long) sizeof(struct ifreq));
  int i = 0;
  for (; i < ifc.ifc_len / sizeof(*ifc.ifc_ifcu.ifcu_req); ++i) {
    printf("ioctl %d  %zu  %s %dn", i, strlen(ibuf[i].ifr_name), ibuf[i].ifr_name, ibuf[i].ifr_addr.sa_family);
  }
  close(fd);
}

// 3.不可靠信号的丢失
static void signal_handler(int signo) {
    printf("received a signal: %dn", signo);
}

static void test_signal() {
    pid_t pid;
    sigset_t set;
    sigset_t oset;

    sigemptyset(&set);          //清空
    sigaddset(&set, SIGINT);          //添加2号信号
    sigaddset(&set, SIGRTMIN);         //添加34号信号
    sigprocmask(SIG_SETMASK, &set, &oset);     //将这个集合设置为这个进程的阻塞信号集

    //绑定信号
    signal(SIGINT, signal_handler);
    signal(SIGRTMIN, signal_handler);

    sigprocmask(SIG_SETMASK, &oset, NULL); //解除绑定
}

static void handler(int signo, siginfo_t *resdata, void *unknowp) {
    printf("signo=%dn", signo);
    printf("return data: %dn", resdata->si_value.sival_int);
}

static void test_signalaction() {
    pid_t pid = fork();
    if(pid == -1) {
        perror("create fork");
        return;
    } else if(pid == 0) { // 子进程
        sleep(1);
        //发送信号
        kill(getppid(), SIGINT);
        printf("send signal: %d success!n", SIGINT);
        kill(getppid(), SIGRTMIN);
        printf("send signal: %d success!n", SIGRTMIN);
        exit(0);
    } else {
        struct sigaction act;
        //初始化sa_mask
        sigemptyset(&act.sa_mask);
        act.sa_handler = signal_handler;
        act.sa_sigaction = handler;
        //一旦使用了sa_sigaction属性,那么必须设置sa_flags属性的值为SA_SIGINFO
        act.sa_flags = SA_SIGINFO;

        //注册信号
        sigaction(SIGINT, &act, NULL);
        sigaction(SIGRTMIN, &act, NULL);
    }
}

__attribute__((constructor))
void init() {
  char sdk[PROP_VALUE_MAX];
  __system_property_get("ro.build.version.sdk", sdk);
  sdk_int = atoi(sdk);
  printf("constructor sdk=%dn", sdk_int);
}

static void test_backtrace() {
}

static void test_statfs() {
  struct statfs stb;
  int ret = statfs("/data/app", &stb);
  char buf[1024];
  hex(buf, &stb, sizeof(stb));
  printf("test_statfs size=%d, ret=%d, hex=%sn", (int) sizeof(stb), ret, buf);
}

static float sFloat = 0.0023942017f;

static float* float_func() {
  return &sFloat;
}

static void test_float() {
  float *f = float_func();
  void *ptr = f;
  unsigned int *ip = (unsigned int *) ptr;
  printf("test_float size=%zu, ip=0x%xn", sizeof(float), *ip);
}

static void test_jni_float() {
}

static void test_sched() {
    int cpus = 0;
    int  i = 0;
    cpu_set_t mask;
    cpu_set_t get;
    int pid = gettid();

    cpus = sysconf(_SC_NPROCESSORS_ONLN);
    printf("cpus: %d, pid: %dn", cpus, pid);

    CPU_ZERO(&get);
    if (sched_getaffinity(pid, sizeof(get), &get) != 0) {
        printf("Get CPU affinity failure, ERROR: %sn", strerror(errno));
    } else {
        for(int i = 0; i < cpus; i++) {
            if(CPU_ISSET(i, &get)) {
                printf("Running processor : %dn", i);
            }
        }
        char buf[1024];
        hex(buf, &get, sizeof(get));
        printf("Get CPU affinity success: buf=%sn", buf);
    }

    CPU_ZERO(&mask);
    CPU_SET(cpus - 1, &mask);
    if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) {
        printf("Set CPU affinity failure, ERROR: %sn", strerror(errno));
    } else {
        char buf[1024];
        hex(buf, &mask, sizeof(mask));
        printf("Set CPU affinity success: buf=%sn", buf);
    }

    CPU_ZERO(&get);
    if (sched_getaffinity(pid, sizeof(get), &get) != 0) {
        printf("Get CPU affinity failure, ERROR: %sn", strerror(errno));
    } else {
        for(int i = 0; i < cpus; i++) {
            if(CPU_ISSET(i, &get)) {
                printf("Running processor : %dn", i);
            }
        }
        char buf[1024];
        hex(buf, &get, sizeof(get));
        printf("Get CPU affinity success: buf=%sn", buf);
    }
}

static int dl_iterate_phdr_callback(struct dl_phdr_info *info, size_t size, void *data) {
   const char *type;
   int p_type;

   printf("dl_iterate_phdr_callback Name: "%s" (%d segments) => %pn", info->dlpi_name,
              info->dlpi_phnum, info->dlpi_name);

   if(!info->dlpi_name) {
     return 0;
   }

   for (int j = 0; j < info->dlpi_phnum; j++) {
       p_type = info->dlpi_phdr[j].p_type;
       type =  (p_type == PT_LOAD) ? "PT_LOAD" :
               (p_type == PT_DYNAMIC) ? "PT_DYNAMIC" :
               (p_type == PT_INTERP) ? "PT_INTERP" :
               (p_type == PT_NOTE) ? "PT_NOTE" :
               (p_type == PT_INTERP) ? "PT_INTERP" :
               (p_type == PT_PHDR) ? "PT_PHDR" :
               (p_type == PT_TLS) ? "PT_TLS" :
               (p_type == PT_GNU_EH_frame) ? "PT_GNU_EH_frame" :
               (p_type == PT_GNU_STACK) ? "PT_GNU_STACK" :
               (p_type == PT_GNU_RELRO) ? "PT_GNU_RELRO" : NULL;

       printf("    %2d: [%14p; memsz:%7jx] flags: %#jx; ", j,
               (void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr),
               (uintmax_t) info->dlpi_phdr[j].p_memsz,
               (uintmax_t) info->dlpi_phdr[j].p_flags);
       if (type != NULL)
           printf("%sn", type);
       else
           printf("[other (%#x)]n", p_type);
   }

   return strcmp("libnative.so", info->dlpi_name) == 0 ? size : 0;
}

static void test_dl_iterate_phdr() {
  int ret = dl_iterate_phdr(dl_iterate_phdr_callback, NULL);
  printf("test_dl_iterate_phdr sizeof(dl_phdr_info)=0x%x, sizeof(Phdr)=0x%x, ret=%dn", (unsigned int) sizeof(struct dl_phdr_info), (unsigned int) sizeof(ElfW(Phdr)), ret);
}

int main() {
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
  fprintf(stderr, "Start test, stdin=%p, stdout=%p, stderr=%p, size=%lun", stdin, stdout, stderr, (unsigned long) sizeof(*stdout));
  test_stat();
  test_dirent();
  test_ioctl();
  if(sdk_int > 19) {
    test_signal();
    test_signalaction();
  }
  test_backtrace();
  test_statfs();
  test_sched();
  test_float();
  test_jni_float();
  char sdk[PROP_VALUE_MAX];
  __system_property_get("ro.build.version.sdk", sdk);
  test_dl_iterate_phdr();
  test_netlink();
  test_pthread();
  printf("Press any key to exit: cmp=%dn", strcmp("23", sdk));
  getchar();
  return 0;
}
遗留问题:
  1. MyARMSyscallHandler起什么作用?
  2. new Dirent(pointer)起什么作用?
  3. resolver.patchThread(emulator, HookZz.getInstance(emulator), new ThreadJoinVisitor() 这个起什么作用?
  4. test.cpp里main函数执行的多个逻辑,还需要一个个的梳理
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/685287.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号