首页
社区
课程
招聘
【iOS逆向与安全】使用ollvm混淆你的源码
发表于: 2023-2-27 16:43 23937

【iOS逆向与安全】使用ollvm混淆你的源码

2023-2-27 16:43
23937

前言

当你在研究别人源码的时候,是不是期望着别人代码没有进行任何的防护和混淆。这时的你,是不是应该考虑一下自己代码的安全.本篇文章将告诉你,如何使用ollvm来混淆iOS端的代码【此文为入门贴,大佬请绕道】。


一、目标

编译ollvm工具,并在Xcode中来混淆你的ipa或动态库,增加别人破解你源码的难度。

二、工具

  • ollvm:下载地址:https://github.com/heroims/obfuscator
  • Xcode:iOS开发工具

三、步骤

1、基础知识

LLVM

LLVM(Low Level Virtual Machine)是一个开源的编译器基础架构,它包含了一组模块化、可重用的编译器和工具,支持多种编程语言和目标架构,包括x86、ARM和MIPS等。LLVM最初由美国伊利诺伊大学香槟分校(University of Illinois at Urbana–Champaign)的Chris Lattner教授开发,现在由LLVM社区进行维护和发展。

 

LLVM的核心思想是将编译器分为前端和后端两个部分,前端负责将源代码转换为中间表示(IR),后端负责将中间表示转换为目标机器的汇编代码。这种设计使得LLVM可以支持多种编程语言,因为只需要为每种语言编写一个前端,就可以利用后端的通用性支持多种目标架构。

 

除了编译器之外,LLVM还包括了一些工具,例如优化器、调试器、汇编器和反汇编器等,这些工具可以帮助开发者更好地分析和调试程序,提高代码的性能和可靠性。

 

LLVM已经成为了广泛使用的编译器基础架构,许多编程语言和工具链都采用了LLVM作为后端,例如C、C++、Objective-C、Swift、Rust、Go等。LLVM还被广泛应用于计算机体系结构研究、代码安全性分析、机器学习等领域。

 

Clang

Clang是基于LLVM框架的C、C++、Objective-C和Objective-C++编译器,它是一个开源项目,由LLVM社区进行开发和维护。Clang的设计目标是提供高质量的诊断、快速的编译速度、低内存占用和良好的可移植性。

 

Clang的编译器前端使用了现代的编译器架构,包括基于词法分析器和语法分析器的语法分析,生成抽象语法树(AST)并进行类型检查和语义分析等步骤。这些步骤的优化和并行化使得Clang能够快速地进行编译,同时提供了更好的错误和警告信息,有助于开发者更快地发现和修复代码中的问题。

 

除了作为独立的编译器之外,Clang还可以作为其他工具的库使用,例如静态分析工具、编辑器插件和代码重构工具等。Clang的模块化设计和良好的API使得它可以轻松地被集成到其他工具中,从而提供更好的编程体验。

 

由于Clang的优秀性能和良好的设计,它已经成为了许多项目的首选编译器,例如LLVM自身、macOS和iOS的默认编译器等。同时,许多开发者和组织也在积极地开发和贡献Clang的代码,使得它在未来仍有广阔的发展空间。

OLLVM

OLLVM(Obfuscator-LLVM)是基于LLVM框架的混淆器,它可以对程序进行混淆以提高程序的安全性。OLLVM的设计目标是提供一种灵活的、可定制的混淆方案,使得攻击者更难理解和分析程序的行为。

 

OLLVM通过对程序进行多种混淆操作来实现混淆效果,例如代码替换、函数内联、控制流平坦化、加密等。这些混淆操作可以改变程序的控制流图和数据流图,使得程序更难以被理解和逆向分析。同时,OLLVM还提供了一些额外的安全机制,例如加密程序的字符串、使用栈保护和位置无关代码等,以增加程序的安全性。

 

由于OLLVM是基于LLVM框架开发的,它可以与现有的LLVM工具和编译器集成,例如Clang和LLDB等。这使得开发者可以轻松地在现有的开发环境中使用OLLVM,并且可以使用现有的工具对混淆后的程序进行调试和分析。

 

尽管OLLVM的主要目的是提高程序的安全性,但它也可以用于其他领域,例如代码保护、代码压缩和代码优化等。由于其灵活性和可定制性,OLLVM已经被广泛应用于许多领域,例如网络安全、游戏开发和金融等。

 

 

IR之间的pass,就是混淆器工作的地方。相关代码位于obfuscator/llvm/lib/Transforms/Obfuscation/

2、编译ollvm

命令如下:

1
2
3
4
5
6
7
8
witchan@witchandeMacBook-Air ~ % git clone -b llvm-13.x https://github.com/heroims/obfuscator.git
witchan@witchandeMacBook-Air ~ % $cd obfuscator
witchan@witchandeMacBook-Air ~ % mkdir build
witchan@witchandeMacBook-Air ~ % cd build
witchan@witchandeMacBook-Air ~ % cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_CREATE_XCODE_TOOLCHAIN=ON -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" -DLLVM_ENABLE_NEW_PASS_MANAGER=OFF ../llvm
witchan@witchandeMacBook-Air ~ % make -j8
witchan@witchandeMacBook-Air ~ % sudo make install-xcode-toolchain
witchan@witchandeMacBook-Air ~ % mv /usr/local/Toolchains  /Library/Developer/

注:make -j8这命令编译的快慢,取决于你的硬件设备

 

挨个执行以上命令后,ollvm就编译并安装完成,效果如下:

 

image-20230225211909806

3、混淆命令及效果对比

混淆命令简介

  • fla:该选项使用函数级别的混淆来隐藏程序的结构。这通过随机重命名函数、添加不必要的控制流和删除调用的函数来实现。这增加了反编译和分析代码的难度。
  • bcf:该选项使用基本块级别的混淆来隐藏代码的结构。这通过改变基本块之间的控制流、添加不必要的基本块和移除基本块之间的条件分支来实现。
  • -sub:该选项使用字符串混淆来隐藏代码中的常量字符串。这通过将字符串分成几个小块、将其存储在数组中并在运行时重新组合来实现。这使得分析代码和查找敏感信息更加困难。
  • split:该选项使用控制流混淆来增加程序的复杂性。这通过将函数分成几个基本块、添加随机的跳转指令和在运行时随机重组这些基本块来实现。这使得代码的流程更难以跟踪,从而增加了破解和反编译的难度。
  • sobf:该选项使用源代码混淆技术来隐藏代码的逻辑和结构。这通过使用类似加密的方式对代码进行变换,使其难以理解和分析。这可以通过运行时解密来执行,从而隐藏代码的真实功能。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testMethod {
    NSString *name = @"wit";
    int age = 18;
 
    NSArray *list = @[@1,@3,@5,@18];
    for (int i=0; i<list.count; i++) {
        int value = [list[i] intValue];
        if (value == age) {
            age += 10;
            name = @"chan";
        }
    }
    NSLog(@"name = %@", name);
    NSLog(@"age = %d", age);
}

未使用混淆:

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
__int64 __fastcall -[ViewController testMethod](__int64 a1, __int64 a2)
{
  void *v2; // x0
  __int64 v3; // ST70_8
  void *v4; // x0
  __int64 v5; // ST68_8
  void *v6; // x0
  __int64 v7; // ST60_8
  void *v8; // x0
  __int64 v9; // ST58_8
  void *v10; // x0
  void *v11; // x0
  void *v12; // ST20_8
  int v13; // ST2C_4
  __int64 result; // x0
  int i; // [xsp+7Ch] [xbp-54h]
  void *v16; // [xsp+80h] [xbp-50h]
  int v17; // [xsp+8Ch] [xbp-44h]
  __int64 v18; // [xsp+90h] [xbp-40h]
  __int64 v19; // [xsp+98h] [xbp-38h]
  __int64 v20; // [xsp+A0h] [xbp-30h]
  __int64 v21; // [xsp+A8h] [xbp-28h]
  __int64 v22; // [xsp+B0h] [xbp-20h]
  __int64 v23; // [xsp+B8h] [xbp-18h]
  __int64 v24; // [xsp+C0h] [xbp-10h]
  __int64 v25; // [xsp+C8h] [xbp-8h]
 
  v25 = *(_QWORD *)__stack_chk_guard_ptr;
  v20 = a1;
  v19 = a2;
  v18 = objc_retain(&stru_100008078);
  v17 = 18;
  v2 = objc_msgSend(off_10000D2D0, (const char *)off_10000D290, 1LL);
  v3 = objc_retainAutoreleasedReturnValue(v2);
  v21 = v3;
  v4 = objc_msgSend(off_10000D2D0, (const char *)off_10000D290, 3LL);
  v5 = objc_retainAutoreleasedReturnValue(v4);
  v22 = v5;
  v6 = objc_msgSend(off_10000D2D0, (const char *)off_10000D290, 5LL);
  v7 = objc_retainAutoreleasedReturnValue(v6);
  v23 = v7;
  v8 = objc_msgSend(off_10000D2D0, (const char *)off_10000D290, 18LL);
  v9 = objc_retainAutoreleasedReturnValue(v8);
  v24 = v9;
  v10 = objc_msgSend(off_10000D2D8, (const char *)off_10000D298, &v21, 4LL);
  v16 = (void *)objc_retainAutoreleasedReturnValue(v10);
  objc_release(v9);
  objc_release(v7);
  objc_release(v5);
  objc_release(v3);
  for ( i = 0; i < (unsigned __int64)objc_msgSend(v16, (const char *)off_10000D2A0); ++i )
  {
    v11 = objc_msgSend(v16, (const char *)off_10000D2A8, i);
    v12 = (void *)objc_retainAutoreleasedReturnValue(v11);
    v13 = (unsigned __int64)objc_msgSend(v12, (const char *)off_10000D2B0);
    objc_release(v12);
    if ( v13 == v17 )
    {
      v17 += 10;
      objc_storeStrong(&v18);
    }
  }
  NSLog(&stru_1000080B8);
  NSLog(&stru_1000080D8);
  objc_storeStrong(&v16);
  result = objc_storeStrong(&v18);
  *(_QWORD *)__stack_chk_guard_ptr;
  return result;
}

开启ollvm混淆:

 

image-20230225230816138

 

image-20230225230917524

 

image-20230225230955135

 

image-20230225231054363

 

image-20230225231343858

 

混淆参数:-mllvm -fla -mllvm -bcf -mllvm -sub -mllvm -split -mllvm -sobf

 

混淆后效果如下:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
__int64 __fastcall -[ViewController testMethod](__int64 a1, __int64 a2)
{
  void *v2; // x0
  void *v3; // x0
  __int64 v4; // ST58_8
  void *v5; // x0
  __int64 v6; // ST50_8
  void *v7; // x0
  __int64 v8; // ST48_8
  void *v9; // x0
  void *v10; // x0
  signed int v11; // w8
  BOOL v12; // w9
  signed int v13; // w8
  BOOL v14; // w9
  signed int v15; // w8
  BOOL v16; // w14
  signed int v17; // w8
  void *v18; // x0
  void *v19; // ST20_8
  int v20; // ST2C_4
  BOOL v21; // w12
  signed int v22; // w8
  BOOL v23; // w9
  signed int v24; // w8
  BOOL v25; // w12
  signed int v26; // w8
  signed int v27; // w8
  BOOL v28; // w9
  signed int v29; // w8
  BOOL v30; // w12
  signed int v31; // w8
  BOOL v32; // w14
  signed int v33; // w8
  BOOL v34; // w9
  signed int v35; // w8
  void *v37; // x0
  void *v38; // ST08_8
  int v39; // ST14_4
  signed int v40; // [xsp+94h] [xbp-DCh]
  int v41; // [xsp+98h] [xbp-D8h]
  int v42; // [xsp+9Ch] [xbp-D4h]
  void *v43; // [xsp+A0h] [xbp-D0h]
  int v44; // [xsp+ACh] [xbp-C4h]
  __int64 v45; // [xsp+B0h] [xbp-C0h]
  __int64 v46; // [xsp+B8h] [xbp-B8h]
  __int64 v47; // [xsp+C0h] [xbp-B0h]
  __int64 v48; // [xsp+C8h] [xbp-A8h]
  __int64 *v49; // [xsp+D0h] [xbp-A0h]
  const char *v50; // [xsp+D8h] [xbp-98h]
  void *v51; // [xsp+E0h] [xbp-90h]
  __int64 v52; // [xsp+E8h] [xbp-88h]
  unsigned __int64 v53; // [xsp+F0h] [xbp-80h]
  const char *v54; // [xsp+F8h] [xbp-78h]
  void *v55; // [xsp+100h] [xbp-70h]
  void *v56; // [xsp+108h] [xbp-68h]
  __int64 v57; // [xsp+110h] [xbp-60h]
  bool v58; // [xsp+11Fh] [xbp-51h]
  __int64 *v59; // [xsp+120h] [xbp-50h]
  int v60; // [xsp+128h] [xbp-48h]
  int v61; // [xsp+12Ch] [xbp-44h]
  __int64 v62; // [xsp+130h] [xbp-40h]
  __int64 v63; // [xsp+138h] [xbp-38h]
  __int64 v64; // [xsp+140h] [xbp-30h]
  __int64 v65; // [xsp+148h] [xbp-28h]
  __int64 v66; // [xsp+150h] [xbp-20h]
  __int64 v67; // [xsp+158h] [xbp-18h]
 
  v67 = *(_QWORD *)__stack_chk_guard_ptr;
  v47 = a1;
  v46 = a2;
  v45 = objc_retain(&stru_10000C078);
  v44 = 18;
  v2 = objc_msgSend(off_1000112D0, (const char *)off_100011290, 1LL);
  v48 = objc_retainAutoreleasedReturnValue(v2);
  v63 = v48;
  v49 = &v64;
  v50 = (const char *)off_100011290;
  v51 = off_1000112D0;
  v40 = -1933834049;
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                while ( v40 == -1944675086 )
                {
                  v23 = x_24 * (x_24 - 1) % 2u == 0;
                  if ( ((unsigned __int8)v23 & (y_25 < 10) | v23 ^ (y_25 < 10)) & 1 )
                    v24 = -484710506;
                  else
                    v24 = -757913245;
                  v40 = v24;
                }
                if ( v40 != -1933834049 )
                  break;
                v3 = objc_msgSend(v51, v50, 3LL);
                v52 = objc_retainAutoreleasedReturnValue(v3);
                v40 = -1240448313;
              }
              if ( v40 != -1917278325 )
                break;
              v40 = 1916623537;
            }
            if ( v40 != -1863636706 )
              break;
            if ( v58 )
              v27 = -1295475565;
            else
              v27 = -706815919;
            v40 = v27;
          }
          if ( v40 != -1847141909 )
            break;
          v10 = objc_msgSend(v55, v54);
          if ( v53 >= (unsigned __int64)v10 )
            v11 = 1801130365;
          else
            v11 = 1380523063;
          v40 = v11;
        }
        if ( v40 != -1846767803 )
          break;
        v40 = -267481379;
      }
      if ( v40 != -1502917571 )
        break;
      v54 = (const char *)off_1000112A0;
      v55 = v43;
      v40 = -1847141909;
    }
    if ( v40 == -1361227290 )
      break;
    switch ( v40 )
    {
      case -1295475565:
        v44 += 10;
        v40 = 1987329712;
        break;
      case -1240448313:
        v4 = v52;
        *v49 = v52;
        v5 = objc_msgSend(off_1000112D0, (const char *)off_100011290, 5LL);
        v6 = objc_retainAutoreleasedReturnValue(v5);
        v65 = v6;
        v7 = objc_msgSend(off_1000112D0, (const char *)off_100011290, 18LL);
        v8 = objc_retainAutoreleasedReturnValue(v7);
        v66 = v8;
        v9 = objc_msgSend(off_1000112D8, (const char *)off_100011298, &v63, 4LL);
        v43 = (void *)objc_retainAutoreleasedReturnValue(v9);
        objc_release(v8);
        objc_release(v6);
        objc_release(v4);
        objc_release(v48);
        v42 = 0;
        v40 = -381231803;
        break;
      case -1088106110:
        v40 = -1361227290;
        break;
      case -1009849083:
        v61 = v60 + 1;
        v40 = -792637388;
        break;
      case -792637388:
        v42 = v61;
        v40 = -381231803;
        break;
      case -757913245:
        v40 = -484710506;
        break;
      case -706815919:
        v40 = 975706093;
        break;
      case -484710506:
        v58 = v41 == v44;
        v25 = x_24 * (x_24 - 1) % 2u == 0;
        if ( (!v25 ^ (y_25 >= 10) | (v25 && y_25 < 10)) & 1 )
          v26 = -1863636706;
        else
          v26 = -757913245;
        v40 = v26;
        break;
      case -381231803:
        v53 = v42;
        v40 = -1502917571;
        break;
      case -267481379:
        v56 = v43;
        v57 = v42;
        v14 = x_24 * (x_24 - 1) % 2u == 0;
        if ( ((unsigned __int8)v14 & (y_25 < 10) | v14 ^ (y_25 < 10)) & 1 )
          v15 = -1917278325;
        else
          v15 = -1846767803;
        v40 = v15;
        break;
      case 265667512:
        v60 = v42;
        v30 = x_24 * (x_24 - 1) % 2u == 0;
        if ( (!v30 ^ (y_25 >= 10) | (v30 && y_25 < 10)) & 1 )
          v31 = 1998138173;
        else
          v31 = 1042960370;
        v40 = v31;
        break;
      case 543048662:
        NSLog(&stru_10000C0B8);
        NSLog(&stru_10000C0D8);
        v34 = x_24 * (x_24 - 1) % 2u == 0;
        if ( ((unsigned __int8)v34 & (y_25 < 10) | v34 ^ (y_25 < 10)) & 1 )
          v35 = -1088106110;
        else
          v35 = 1393219629;
        v40 = v35;
        break;
      case 818848043:
        v37 = objc_msgSend(v56, (const char *)off_1000112A8, v57);
        v38 = (void *)objc_retainAutoreleasedReturnValue(v37);
        v39 = (unsigned __int64)objc_msgSend(v38, (const char *)off_1000112B0);
        objc_release(v38);
        v41 = v39;
        v40 = 1041770448;
        break;
      case 975706093:
        v28 = x_24 * (x_24 - 1) % 2u == 0;
        if ( ((unsigned __int8)v28 & (y_25 < 10) | v28 ^ (y_25 < 10)) & 1 )
          v29 = 265667512;
        else
          v29 = 1042960370;
        v40 = v29;
        break;
      case 1041770448:
        v18 = objc_msgSend(v56, (const char *)off_1000112A8, v57);
        v19 = (void *)objc_retainAutoreleasedReturnValue(v18);
        v20 = (unsigned __int64)objc_msgSend(v19, (const char *)off_1000112B0);
        objc_release(v19);
        v41 = v20;
        v21 = x_24 * (x_24 - 1) % 2u == 0;
        if ( (!v21 ^ (y_25 >= 10) | (v21 && y_25 < 10)) & 1 )
          v22 = 2069967268;
        else
          v22 = 818848043;
        v40 = v22;
        break;
      case 1042960370:
        v40 = 265667512;
        break;
      case 1146841418:
        v32 = x_24 * (x_24 - 1) % 2u == 0;
        if ( (v32 ^ (y_25 < 10) | (v32 && y_25 < 10)) & 1 )
          v33 = 543048662;
        else
          v33 = 1393219629;
        v40 = v33;
        break;
      case 1380523063:
        v12 = x_24 * (x_24 - 1) % 2u == 0;
        if ( ((unsigned __int8)v12 & (y_25 < 10) | v12 ^ (y_25 < 10)) & 1 )
          v13 = -267481379;
        else
          v13 = -1846767803;
        v40 = v13;
        break;
      case 1393219629:
        NSLog(&stru_10000C0B8);
        NSLog(&stru_10000C0D8);
        v40 = 543048662;
        break;
      case 1801130365:
        v62 = v45;
        v40 = 1146841418;
        break;
      case 1916623537:
        v16 = x_24 * (x_24 - 1) % 2u == 0;
        if ( (v16 ^ (y_25 < 10) | (v16 && y_25 < 10)) & 1 )
          v17 = 1041770448;
        else
          v17 = 818848043;
        v40 = v17;
        break;
      case 1987329712:
        v59 = &v45;
        v40 = 2123926658;
        break;
      case 1998138173:
        v40 = -1009849083;
        break;
      case 2069967268:
        v40 = -1944675086;
        break;
      case 2123926658:
        objc_storeStrong(v59);
        v40 = -706815919;
        break;
    }
  }
  objc_storeStrong(&v43);
  return objc_storeStrong(&v45);
}

总结

简单的入门文章,希望能帮助到你。

提示:阅读此文档的过程中遇到任何问题,请关住工众好【移动端Android和iOS开发技术分享】或+99 君羊【812546729

 

image-20230225231548837


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

收藏
免费 1
支持
分享
最新回复 (11)
雪    币: 136
活跃值: (549)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2

sobf:该选项使用源代码混淆技术来隐藏代码的逻辑和结构。这通过使用类似加密的方式对代码进行变换,使其难以理解和分析。这可以通过运行时解密来执行,从而隐藏代码的真实功能。


你能不能解释一下IR是怎么上源代码混淆的技术的? 你写的这段话你自己看得懂吗?


https://github.com/heroims/obfuscator/blob/650605883b290ee835b1a00904d67efb0c32a311/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp#L177


static cl::opt<bool> StringObf("sobf", cl::init(false),cl::desc("Enable the string obfuscation"));


我英语不好, 原来String是源代码的意思, 学到了。 谢谢大佬


公众号二维码

感谢大佬分享这么多干货, 马上就关注

最后于 2023-3-1 10:28 被naville编辑 ,原因:
2023-3-1 10:21
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
请问大佬有没有ollvm14的版本啊
2023-6-6 10:35
0
雪    币: 982
活跃值: (426)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
看头像识人,陈哥
2023-6-6 21:19
0
雪    币: 3090
活跃值: (30881)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2023-6-7 09:04
1
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
招募ios逆向工程师,在线兼职接单,几百到几万项目应有尽有,有意者+vx:yangxiaodi1_2
2023-11-22 11:33
0
雪    币: 4764
活跃值: (1770)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
编不过啊老铁
2024-9-3 10:26
0
雪    币: 184
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
Xcode都16啦,这个还能用不
2024-10-31 15:47
0
雪    币: 580
活跃值: (3039)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2024-11-6 21:54
0
雪    币: 20
活跃值: (206)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
混淆完,功能都失效了。。。
2024-11-7 03:03
0
游客
登录 | 注册 方可回帖
返回
//