Harshfeudal

Writeups

Misc / Farewell Gift - DDC 2025

A writeup on the Misc / Farewell Gift challenge from DDC 2025

Loading... 4 min readUpdated: Loading...
Language: Vietnamese
Author: @harshfeudal
#CTF#Misc#Machine Learning#TensorFlow#2025

Đề bài

Farewell Gift

500

Droplet

Daenerys, the lead machine learning engineer at DAD Corporation, has resigned following a conflict with the CEO over the company's direction. As a parting gesture, she left the model she was working on as a "farewell gift". The sample results appear good, but we suspect that something suspicious is going on with this model. Can you find out what it is?

Hint 1: Look carefully at the model layers and their attributes

Hint 2: This is a fine-tuned model; finding the original model will help (2:00AM hint lmao)

Hướng giải

Ok thì bài này mình thấy nó na ná với HTB Apocalypse mình đã từng chơi nên mình đã cố gắng giải nó, nhưng mà do thứ 7 mình có việc nên về khá trễ, hầu như không giúp được cho team nhiều nên là team mình không thể solve được nó (ờ mấy bạn bảo lý do này nọ thì cũng chịu).

Thì do đã có kinh nghiệm nên mình đã biết một bài CTF ML vận hành thế nào rồi. Điều đầu tiên mình làm là sẽ summary model đó:

Python
import tensorflow as tf
from tensorflow import keras

model = keras.models.load_model('models/dad-vision-final.keras')

model.summary()

python run.py > summary.txt thì nó sẽ ra một cái list đúng dài. Nhưng nếu ai có học qua 1 lớp AI trên trường hoặc tự học của Andrew Ng thì cũng nhận ra đây là EfficientNetV2. Sao mình biết? Do cách đặt tên:

block<number><letter>_<operation>

Với lại nếu không biết thì cứ bỏ vào Gia Phát Thịnh (GPT) hỏi nó là “tao đang làm tới đây rồi thì mô hình này giống mô hình gì?” thì nó sẽ trả lời ngay :D

Thì nếu tìm ra EfficientNetV2 rồi thì ở đây cái clue mình không giải ra được là gì?

image.png

Ye mình đã biết nó là EfficientNetV2 rồi nhưng vẫn không biết sao nhét flag ra, lmao. Mình ngồi đọc mấy cái writeup của HTB Apocalypse cũ thì mình chợt nhận ra khá ít người viết writeup này. Nhưng mà chắc do tối rồi nên mình không tỉnh táo. Thôi sáng mai dậy làm tiếp.

Lmao sáng dậy muộn, 10h mới dậy 💀

Nói chung event kết thúc thì cũng kệ, làm lấy kinh nghiệm.

Mình chợt nhớ ra là: nếu đã là CTF thì người ta phải thay đổi cấu trúc nó, đúng không? Mình hỏi Gia Phát Thịnh là “à tao muốn biết làm sao để tao compare giữa cái EfficientNetV2 gốc với cái này?” thì nó cho mình cái thư viện này:

Python
from tensorflow.keras.applications import EfficientNetV2S

Không phải chờ đợi, tôi sử dụng tính năng Vibe-Coding mình hiện có:

Python
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.applications import EfficientNetV2S

model = keras.models.load_model('models/dad-vision-final.keras')
original_model = EfficientNetV2S(weights='imagenet')
modified_layer_name = None

for dad_layer, original_layer in zip(model.layers, original_model.layers):
    dad_weights = dad_layer.get_weights()
    original_weights = original_layer.get_weights()

    if dad_weights and original_weights:
        for i in range(len(dad_weights)):
            if not np.array_equal(dad_weights[i], original_weights[i]):
                modified_layer_name = dad_layer.name
                print(f"Found modified weights in layer: '{modified_layer_name}'")
                break
    if modified_layer_name:
        break

if not modified_layer_name:
    print("No modified layers found.")

Thì console báo:

Found modified weights in layer: 'block5i_se_reduce'

Ye, thế là mình có clue tiếp.

Nếu ai học AI rồi thì sẽ biết rằng trong một model, người ta sẽ có Weight với Bias. Ở đây thì mình bắt đầu xuất cả hai ra:

Python
modified_layer = model.get_layer(modified_layer_name)

weights = modified_layer.get_weights()
print(f"Weights: {weights}")

bias_vector = weights[1]
print(f"Bias Vector: {bias_vector}")

Thì console báo:

Weights: [array([[[[-0.04686987,  0.0215106 ,  0.01236106, ...,  0.03371817,
           0.03543964, -0.00289477],
         [-0.0655726 , -0.04068068, -0.09533687, ..., -0.00772091,
          -0.08749958, -0.08933775],
         [ 0.02095766,  0.00260389,  0.02771965, ...,  0.00584774,
          -0.00793114,  0.03512393],
         ...,
         [-0.00499907, -0.01449113,  0.05294973, ...,  0.00270706,
           0.0440886 ,  0.0304351 ],
         [ 0.03093076, -0.02054276, -0.00243449, ..., -0.03394082,
          -0.01244923, -0.00475881],
         [ 0.02237011,  0.05259237, -0.04361826, ...,  0.05828479,
          -0.01201546,  0.01032932]]]],
      shape=(1, 1, 960, 40), dtype=float32), array([0.0068, 0.0068, 0.0067, 0.0123, 0.0121, 0.0111, 0.0085, 0.0095,
       0.007 , 0.0111, 0.0117, 0.011 , 0.0068, 0.0095, 0.0077, 0.0121,
       0.0095, 0.0104, 0.0049, 0.0068, 0.0068, 0.0101, 0.0078, 0.0095,
       0.0053, 0.0073, 0.0103, 0.011 , 0.0065, 0.0116, 0.0117, 0.0082,
       0.0101, 0.0095, 0.004 , 0.0094, 0.0111, 0.0094, 0.0041, 0.0125],
      dtype=float32)]
Bias Vector: [0.0068 0.0068 0.0067 0.0123 0.0121 0.0111 0.0085 0.0095 0.007  0.0111
 0.0117 0.011  0.0068 0.0095 0.0077 0.0121 0.0095 0.0104 0.0049 0.0068
 0.0068 0.0101 0.0078 0.0095 0.0053 0.0073 0.0103 0.011  0.0065 0.0116
 0.0117 0.0082 0.0101 0.0095 0.004  0.0094 0.0111 0.0094 0.0041 0.0125]

Ở đây, mình thấy là Bias Vector “khá” là ít giá trị, và các giá trị đều rất là lạ là làm tròn cỡ 3-4 chữ số, không như Weight. Nên mình hơi nghi, nếu như nhân với 10.000 thì nó thành số có 2 với 3 chữ số và không quá lớn (bảng ASCII đấy ae ơi). Nên là:

Python
flag = ""
for value in bias_vector:
    char_code = int(round(value * 10000.0))
    
    if 32 <= char_code <= 126:
        flag += chr(char_code)

print(flag)

Và kết quả là:

DDC{yoU_FounD_My_h1DDeN_5IgnAtuRe_(^o^)}

Lời kết

:D

Anyways, bài oke, Waguri/10

image.png