首页
社区
课程
招聘
6
[原创] seccon2024 F is for flag wp 动态二进制插桩trace 破解函数式编程混淆
发表于: 2024-12-11 16:34 8328

[原创] seccon2024 F is for flag wp 动态二进制插桩trace 破解函数式编程混淆

2024-12-11 16:34
8328

seccon2024 F is for flag wp

写在前面

上周没有更新,一是因为上周在看seccon2024的另外两道题(前菜),难度不大。直到遇到这道F is for flag(正餐),卡的时间比较久,一开始打算硬逆奈何功力不够(我知道有其他大佬硬逆做出来的),最后选择使用trace工具碰碰运气,然后又在frida和pyda之间来回反复,frida的hook效果不尽人意,最终选择pyda。

pyda是一款动态二进制插桩工具,可以通过编写python代码的方式实现hook非常方便。

官网介绍:Pyda combines Dynamorio-based instrumentation with a CPython interpreter, allowing you to write hooks in Python that directly manipulate registers/memory in the target, without going through GDB or ptrace.

https://github.com/ndrewh/pyda

题目背景

经典的flag检查:

1
2
3
/f
FLAG: SECCON{fUnCt10n4l_pRoGr4mM1n6_1s_pR4c7iC4lLy_a_pUr3_0bfu5c4T1oN}
"Correct"

0x01 ida 逆向初探

题目由c++编写,ida打开点开main函数,会发现main函数里面发现里面有大量的std:variant, lambda闭包调用,并且其他函数都是被mangle过的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v84 = 0;
std::variantint,std::string,std::shared_ptr>::variantint,void,void,unsigned int,void>(
  (__int64)v106,
  (__int64)&v84);
v83 = 0xB7E9A2A4;
std::variantint,std::string,std::shared_ptr>::variantint,void,void,unsigned int,void>(
  (__int64)v105,
  (__int64)&v83);
main::{lambda(std::variantint,std::string,std::shared_ptr>,std::variantint,std::string,std::shared_ptr>)#19}::operator()(
  (__int64)v107,
  (__int64)&v78,
  (__int64)v105,
  (__int64)v106,
  v3,
  v4);

在lamba#19中又有std::make_shared, std::variant,std::shared_ptr

1
2
3
4
5
6
7
8
std::make_sharedint,std::string,std::shared_ptr> &,std::variantint,std::string,std::shared_ptr> &>(
    (__int64)v7,
    a3,
    a4);
std::variantint,std::string,std::shared_ptr>::variant,void,void,std::shared_ptr,void>(
  a1,
  (__int64)v7);
std::shared_ptr::~shared_ptr(v7);

std::make_shared经过一层层调用,最终会call到Cons::Cons,将v83(0xB7E9A2A4)存到cons里,后面的逻辑以此类推

1
2
3
4
5
6
7
8
v5 = operator new(0x50uLL, a1);
v6 = std::forwardint,std::string,std::shared_ptr> &>(a3);
std::variantint,std::string,std::shared_ptr>::variant((__int64)v11, v6);
v7 = std::forwardint,std::string,std::shared_ptr> &>(a2);
std::variantint,std::string,std::shared_ptr>::variant((__int64)v10, v7);
Cons::Cons(v5, (__int64)v10, (__int64)v11);
std::variantint,std::string,std::shared_ptr>::~variant((__int64)v10);
std::variantint,std::string,std::shared_ptr>::~variant((__int64)v11);

到这里分析还算顺利,知道跟到这个std::function()(lambda#1)

1
2
3
std::functionint,std::string,std::shared_ptr> ()(void)>::functionvoid)#1},void>(
    (std::_Function_base *)v127,
    (__int64)v95);

每一个std:function至少需要三层调用才能到达真实逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// level 1
*((_QWORD *)a1 + 3) = std::_Function_handlerint,std::string,std::shared_ptr> ()(void),main::{lambda(void)#1}>::_M_invoke;
 
//level 2
std::__invoke_rint,std::string,std::shared_ptr>,main::{lambda(void)#1} &>(a1, pointer);
 
//level 3
std::__invoke_implint,std::string,std::shared_ptr>,main::{lambda(void)#1} &>(a1, v2);
 
//level 4
main::{lambda(void)#1}::operator()(a1, v2);
 
//level5
v8 = __readfsqword(0x28u);
  v2 = *(_QWORD **)a2;
  v3 = *(_QWORD *)(a2 + 32);
  v4 = *(_QWORD *)(a2 + 8);
  ZNKR3fixIZ4mainEUlT_St7variantIJjNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt10shared_ptrI4ConsEEESB_E_EclIJRS7_EEEDcDpOT_(
    v6,
    *(_QWORD *)(a2 + 16),
    *(_QWORD *)(a2 + 24));
  ZNKR3fixIZ4mainEUlT_St7variantIJjNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt10shared_ptrI4ConsEEESB_E0_EclIJSB_EEEDcDpOT_(
    v7,
    v4,
    v6);
  ZNKR3fixIZ4mainEUlT_St7variantIJjNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt10shared_ptrI4ConsEEESB_SB_E3_EclIJSB_RSB_EEEDcDpOT_(
    a1,
    v2,
    (__int64)v7,
    v3);
  std::variantint,std::string,std::shared_ptr>::~variant((__int64)v7);
  std::variantint,std::string,std::shared_ptr>::~variant((__int64)v6);
  return a1

一层层跟进这个调用,函数调用递归一层接一层把我绕晕了,而且每一层代码还不少,人工分析工程量很大,而且递归调用的地方不止一处,对人的记忆力也有很高的要求。总之,人工分析的话,工程量大,难度大,并且有的地方不用真的逆。所以我们思路,让工具辅助我们,找到主逻辑,然后我们再打开ida逆向

0x02 pyda trace

使用pyda trace来跟踪程序的主逻辑

首先搭建环境:

1
2
3
4
5
6
7
8
9
10
11
FROM ubuntu:24.04 as target
 
FROM ghcr.io/ndrewh/pyda
 
COPY --from=target /usr/lib/x86_64-linux-gnu/ /target_libs/
 
RUN apt update && apt install -y patchelf
 
COPY F /F
RUN patchelf --set-interpreter /target_libs/ld-linux-x86-64.so.2 --set-rpath /target_libs/ /F
RUN apt install -y binutils

trace cmp - 确定密文长度

首先trace cmp,使用仓库中的example/cmplog.py trace cmp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pyda import *
from pwnlib.elf.elf import ELF
from pwnlib.util.packing import u64, u32
import string
import sys
import subprocess
from collections import defaultdict
 
p = process()
 
e = ELF(p.exe_path)
e.address = p.maps[p.exe_path].base
 
plt_map = { e.plt[x]: x for x in e.plt }
 
def get_cmp(proc):
    p = subprocess.run(f"objdump -M intel -d {proc.exe_path} | grep cmp", shell=True, capture_output=True)
 
    output = p.stdout.decode()
    cmp_locs = {}
    for l in output.split("\n"):
        if len(l) <= 1:
            continue
 
        # TODO: memory cmp
        if "QWORD PTR" in l:
            continue
 
        if ":\t" not in l:
            continue
 
        cmp_locs[int(l.split(":")[0].strip(), 16)] = l.split()[-1]
 
    return cmp_locs
 
cmp_locs_unfiltered = get_cmp(p)
cmp_locs = {}
for (a, v) in cmp_locs_unfiltered.items():
    info = v.split(",")
    if len(info) != 2:
        continue
    if "[" in info[0] or "[" in info[1]:
        continue
 
    if "0x" in info[0] or "0x" in info[1]:
        continue
 
    cmp_locs[a] = info
 
print(f"cmp_locs: {len(cmp_locs)}")
 
eq_count = 0
neq_count = 0
reg_map = {
    "eax": "rax",
    "ebx": "rbx",
    "ecx": "rcx",
    "edx": "rdx",
    "esi": "rsi",
    "edi": "rdi",
    "ebp": "rbp",
    "esp": "rsp",
    "r8d": "r8",
}
 
counts_by_pc = defaultdict(int)
good_cmps = defaultdict(int)
def cmp_hook(p):
    global eq_count, neq_count
    info = cmp_locs[p.regs.pc - e.address]
 
    counts_by_pc[p.regs.pc - e.address] += 1
 
    reg1 = reg_map.get(info[0], info[0])
    reg2 = reg_map.get(info[1], info[1])
    r1 = p.regs[reg1]
    r2 = p.regs[reg2]
    eq = r1 == r2
 
    if eq:
        eq_count += 1
    else:
        neq_count += 1
 
    print(f"cmp @ {hex(p.regs.rip - e.address)} {reg1}={hex(r1)} {reg2}={hex(r2)} {eq}")
 
for x in cmp_locs:
    p.hook(e.address + x, cmp_hook)
 
p.run()
  1. 开始trace:
1
2
3
4
5
6
pyda cmplog.py -- /F
cmp_locs: 46
FLAG: AAAAAAAAAAAAAAAA
//.. TOO LONG NOT TO SHOW
cmp @ 0x182a7 rcx=0x10 rdx=0x40 False
"Wrong"

可以看到程序输入“Wrong”之前的最后一个cmp,rcx=0x10 正好是我们输入的长度,所以猜测rdx=0x40是flag的真正长度。

  1. 输入正确长度的字符串,再次trace:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pyda cmplog.py -- /F
cmp_locs: 46
FLAG: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
//.. TOO LONG NOT TO SHOW
cmp @ 0x1891b rcx=0xc3df45f3 rdx=0x11793013 False
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000001 rax=0x100000001 True
cmp @ 0x15787 rdx=0x100000002 rax=0x100000001 False
"Wrong"

在“Wrong”往上找到一个很明显的cmp false trace

1
cmp @ 0x1891b rcx=0xc3df45f3 rdx=0x11793013 False

这里0x11793013已经在main函数中出现过,就在main函数开始的前16组的最后一个,所以猜测rdx=0x11793013为密文,rcx=0xc3df45f3为加密后的密文

  1. 输入不同的长度正确的字符串,再次trace:
1
2
3
4
5
6
7
pyda cmplog.py -- /F
cmp_locs: 46
FLAG:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
//.. TOO LONG NOT TO SHOW
cmp @ 0x1891b rcx=0x5b0608cd rdx=0x11793013 False
//.. TOO LONG NOT TO SHOW
"Wrong"

可以发现确实rcx发生了变化,可以断定这就是最后的flag checker部分

trace Cons:Cons - find transformation

现在我们确定了密文的长度,接下来我们关心的密文是怎么被加密的,经过前期的逆向工作,我们已经知道Con:Cons() 最终会被调用来存储一个unsigned int,密文存储于此,那么明文也可能存储于此。

写脚本开始trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from pyda import *
from pwnlib.elf.elf import ELF
from pwnlib.util.packing import *
 
p = process(io=True)
 
e = ELF(p.exe_path)
 
e.address = p.maps[p.exe_path].base
 
base_address = e.address
print(hex(base_address))
 
def cons(p):
    print(f"cons {hex(p.regs.rip-base_address),hex(u32(p.read(p.regs.rdi, 4))), hex(u32(p.read(p.regs.rsi, 4))), hex(u32(p.read(p.regs.rdx, 4)))}")
 
p.hook(e.address + 0x15a06, cons)
 
p.recvuntil("FLAG: ")
p.sendline(("ABCDEFGH").ljust(0x40,"A"))
p.run()

trace 结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//enc
cons ('0x15a06', '0x0', '0xb7e9a2a4', '0x0')
cons ('0x15a06', '0x0', '0x1904c652', '0xdbbfdbf0')
cons ('0x15a06', '0x0', '0xbe8afe4d', '0xdbbfdc60')
cons ('0x15a06', '0x0', '0xbd18775a', '0xdbbfdcd0')
cons ('0x15a06', '0x0', '0x82841cf4', '0xdbbfdd40')
cons ('0x15a06', '0x0', '0xd2c1d5af', '0xdbbfddb0')
cons ('0x15a06', '0x0', '0xf389c4a', '0xdbbfde20')
cons ('0x15a06', '0x0', '0x451f151a', '0xdbbfde90')
cons ('0x15a06', '0x0', '0xd5689a8c', '0xdbbfdf00')
cons ('0x15a06', '0x0', '0x927b5bd9', '0xdbbfdf70')
cons ('0x15a06', '0x0', '0xf86c82d7', '0xdbbfdfe0')
cons ('0x15a06', '0x0', '0x34bc7c60', '0xdbbfe050')
cons ('0x15a06', '0x0', '0x97aef869', '0xdbbfe0c0')
cons ('0x15a06', '0x0', '0x2c0cccdd', '0xdbbfe130')
cons ('0x15a06', '0x0', '0x88d2ec9b', '0xdbbfe1a0')
cons ('0x15a06', '0x0', '0x11793013', '0xdbbfe210')
 
//sbox
cons ('0x15a06', '0x0', '0x7', '0x0')
cons ('0x15a06', '0x0', '0x0', '0xdbbfe2f0')
cons ('0x15a06', '0x0', '0xc', '0xdbbfe360')
cons ('0x15a06', '0x0', '0xd', '0xdbbfe3d0')
cons ('0x15a06', '0x0', '0x2', '0xdbbfe440')
cons ('0x15a06', '0x0', '0xf', '0xdbbfe4b0')
cons ('0x15a06', '0x0', '0xb', '0xdbbfe520')
cons ('0x15a06', '0x0', '0x8', '0xdbbfe590')
cons ('0x15a06', '0x0', '0x6', '0xdbbfe600')
cons ('0x15a06', '0x0', '0x5', '0xdbbfe670')
cons ('0x15a06', '0x0', '0x9', '0xdbbfe6e0')
cons ('0x15a06', '0x0', '0x4', '0xdbbfe750')
cons ('0x15a06', '0x0', '0xa', '0xdbbfe7c0')
cons ('0x15a06', '0x0', '0x1', '0xdbbfe830')
cons ('0x15a06', '0x0', '0xe', '0xdbbfe8a0')
cons ('0x15a06', '0x0', '0x3', '0xdbbfe910')
 
//input
cons ('0x15a06', '0x0', '0x41414141', '0x0')
cons ('0x15a06', '0x0', '0x41414141', '0xdbbffe30')
cons ('0x15a06', '0x0', '0x41414141', '0xdbbffea0')
cons ('0x15a06', '0x0', '0x41414141', '0xdbbfff10')
cons ('0x15a06', '0x0', '0x41414141', '0xdbbfff80')
cons ('0x15a06', '0x0', '0x41414141', '0xdbbffff0')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00060')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc000d0')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00140')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc001b0')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00220')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00290')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00300')
cons ('0x15a06', '0x0', '0x41414141', '0xdbc00370')
cons ('0x15a06', '0x0', '0x48474645', '0xdbc003e0')
cons ('0x15a06', '0x0', '0x44434241', '0xdbc00450')
 
//1 sub_byte
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0x0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00650')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc007a0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc008f0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00960')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc009d0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00a40')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00ab0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00b20')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00b90')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00c00')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00c70')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00ce0')
cons ('0x15a06', '0x862acd7e', '0x4e4e4e4e', '0xdbc00d50')
cons ('0x15a06', '0x862acd7e', '0x48464549', '0xdbc00dc0')
cons ('0x15a06', '0x862acd7e', '0x444a414e', '0xdbc00e30')
 
//2 mul 0xe14de95e
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0x0')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc006c0')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00730')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00f10')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00810')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00880')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00f80')
cons ('0x15a06', '0x862acd7e', '0x93af4e5e', '0xdbc00570')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc005e0')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc00ff0')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc01060')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc010d0')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc01140')
cons ('0x15a06', '0x0', '0x93af4e5e', '0xdbc011b0')
cons ('0x15a06', '0x0', '0xd36975c1', '0xdbc01220')
cons ('0x15a06', '0x0', '0xe14de95e', '0xdbc01290')
 
//3  rotl(0x93af4e5e, 29) ^ rotl(0x93af4e5e, 17) ^ rotl(0xd36975c1, 7) ^ 0xe14de95e
0xd 0xc
cons ('0x15a06', '0x862acd8c', '0x93af4e5e', '0x0')
cons ('0x15a06', '0x862acd8c', '0x93af4e5e', '0xdbc01370')
cons ('0x15a06', '0x862acd8c', '0x93af4e5e', '0xdbc00b20')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00e30')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00dc0')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00d50')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00ce0')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00c70')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00c00')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00b90')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00ea0')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc00650')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc007a0')
cons ('0x15a06', '0x862acd8c', '0xac0af82', '0xdbc008f0')
cons ('0x15a06', '0x862acd8c', '0x4a06941d', '0xdbc00960')
cons ('0x15a06', '0x862acd8c', '0x1b3fc722', '0xdbc009d0')

可以看到第三组就是输入的明文

下面看第一组transform


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-12-11 17:05 被SleepAlone编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
赞赏记录
参与人
雪币
留言
时间
5m10v3
+1
谢谢你的细致分析,受益匪浅!
2024-12-20 19:55
hkdong
为你点赞!
2024-12-13 11:33
Zard_
非常支持你的观点!
2024-12-13 09:57
0x指纹
+2
你的分享对大家帮助很大,非常感谢!
2024-12-12 08:55
jelasin
非常支持你的观点!
2024-12-11 17:40
mb_wpitiize
+1
这个讨论对我很有帮助,谢谢!
2024-12-11 16:56
最新回复 (4)
雪    币: 3092
活跃值: (5533)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
2
前排支持SleepAlone大佬
2024-12-11 17:03
0
雪    币: 4259
活跃值: (1365)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
3
禁止复制粘贴
2024-12-11 17:09
0
雪    币: 472
活跃值: (179)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
SleppAlone师傅线下人挺随和的
2024-12-11 17:58
0
雪    币: 4259
活跃值: (1365)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
5
peiwithhao SleppAlone师傅线下人挺随和的
SleppAlone真不熟
2024-12-11 18:00
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册