逆向 1.kotlindroid 先在手机上运行一下
发现字符串check your flag here
因为用jadx打开这个apk,然后传统方法找到MainActivity发现根本看不懂。就直接搜字符串吧
然后跳过去查看代码,虽然说 Kotlin 代码 不好看懂,不过这里大致看出什么逻辑
再结合手机上,我输错了就是wrong字符串看出,这里的check应该就是检查逻辑,前面有一大段代码都是什么初始化界面,这里的密文发现是base64编码,加密也 可以看出是AES/GCM,GCM的加密需要密钥,IV,tag
提示说tag就是密文的一部分,先不管
1.求密钥
这里就是两个keybyte数组,分别经过异或23,和8 然后拼接组成密钥,这里看出byte2来自Config类的方法
def generate_key (): key1 = [118 , 99 , 101 , 126 , 124 , 114 , 110 , 100 ] key2 = [123 , 113 , 109 , 99 , 97 , 122 , 124 , 105 ] modified_key1 = bytes ([b ^ 23 for b in key1]) modified_key2 = bytes ([b ^ 8 for b in key2]) final_key = modified_key1 + modified_key2 return final_key.decode() key = generate_key() print ("密钥:" , key)
2.IV
这里已经给了IV,是字符串114514,然后我就去套脚本发现不行,卡了很久,后面才发现AES的这个IV必须是bytes类型而不是字符串,包括那个key,这里的byte [] bytes也已经很明显了
有意思的是,我在用密文解密base的时候发现了密文前面就是那个IV
最后发现这个密文其实是由iV+密文+tag组成的,好像都是这样组成的,tag连同密文一起,不用管,后面就在脚本中分离IV
import base64from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadkey="atrikeyssyekirta" key=key.encode('utf-8' ) iv = "114514" .encode('utf-8' ) print (iv)encrypted_base64 = "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==" def decrypt (encrypted_base64, key, iv ): try : encrypted_bytes = base64.b64decode(encrypted_base64) encrypted_data = encrypted_bytes[len (iv):] cipher = AES.new(key, AES.MODE_GCM, nonce=iv) decrypted_bytes = cipher.decrypt(encrypted_data) try : decrypted_text = decrypted_bytes.decode('utf-8' ) return decrypted_text except UnicodeDecodeError: print (f"解密后的字节数据: {decrypted_bytes} " ) return None except Exception as e: print (f"解密出错: {e} " ) return None decrypted_text = decrypt(encrypted_base64, key, iv) if decrypted_text: print (f"解密后的文本: {decrypted_text} " )
2.fish 不知道是不是手机的原因,无法打开这个应用,只好拿出以前手机来开开
根据提示和这个输出的字符串发现,如果输入错误,鱼就会跑
这里的onclick的逻辑跟我们应用的时候一样
这里就是对输入进行加密,感觉类似于凯撒加密,逆向的时候可以对字符串先进行+’1‘-i2%4,推测是在哪个分支过来的
这里的大致逻辑就是通过输入的字符串进行encode加密,然后调用这个下载的dex文件里面的加密再次进行验证,密文就在dex文件里面,既然要下载这个密文,那么肯定有相应的地址,
网页搜索后就下载成功了,之后我们去用jadx打开这个dex文件
大致逻辑就算,输入的字符串先经过encrypt加密,再经过这个dex文件的encode加密,最终对比里面的字符串strr,这里的逆向就是通过一个decode,然后进行encrypy逆向,这里的逆向两个交换位置用的是分别异或
import binasciifish_dcode = { "iiijj" : 'a' , "jjjii" : 'b' , "jijij" : 'c' , "jjijj" : 'd' , "jjjjj" : 'e' , "ijjjj" : 'f' , "jjjji" : 'g' , "iijii" : 'h' , "ijiji" : 'i' , "iiiji" : 'j' , "jjjij" : 'k' , "jijji" : 'l' , "ijiij" : 'm' , "iijji" : 'n' , "ijjij" : 'o' , "jiiji" : 'p' , "ijijj" : 'q' , "jijii" : 'r' , "iiiii" : 's' , "jjiij" : 't' , "ijjji" : 'u' , "jiiij" : 'v' , "iiiij" : 'w' , "iijij" : 'x' , "jjiji" : 'y' , "jijjj" : 'z' , "iijjl" : '1' , "iiilj" : '2' , "iliii" : '3' , "jiili" : '4' , "jilji" : '5' , "iliji" : '6' , "jjjlj" : '7' , "ijljj" : '8' , "iljji" : '9' , "jjjli" : '0' } def decode_fish (encoded_str ): """解密 hook_fish.dex 里的密文""" plaintext = "" for i in range (0 , len (encoded_str), 5 ): chunk = encoded_str[i:i + 5 ] plaintext += fish_dcode.get(chunk) return plaintext def reverse_branch_transform (ciphertext ): result_chars = [] for i, c in enumerate (ciphertext): F = ord (c) candidate1 = F - (i % 4 ) + ord ('1' ) if ord ('a' ) <= candidate1 <= ord ('f' ): result_chars.append(chr (candidate1)) else : candidate2 = F - ord ('7' ) - (i % 10 ) if ord ('0' ) <= candidate2 <= ord ('9' ): result_chars.append(chr (candidate2)) else : result_chars.append(chr (candidate1)) return '' .join(result_chars) def reverse_swap (s ): """ 逆向 code() 中的交换操作。 加密时,对十六进制字符串的字符数组每两个字符进行了互换, 由于交换是自反操作,交换两次就能还原原序。 """ chars = list (s) for i in range (0 , len (chars) - 1 , 2 ): chars[i], chars[i + 1 ] = chars[i + 1 ], chars[i] return '' .join(chars) def decrypt_encrypt (ciphertext ): """ 完整的解密流程,逆向 encrypt() 的操作: 1. 对最终密文的每个字符进行逆向“分支变换” 2. 逆向相邻字符的交换(code()函数) 3. 得到原始十六进制字符串,将其转换为字节 4. 对每个字节减去 68,恢复原始明文字节 5. 将字节转换为字符串返回 """ after_branch = reverse_branch_transform(ciphertext) hex_str = reverse_swap(after_branch) enc_bytes = binascii.unhexlify(hex_str) original_bytes = bytes ([b - 68 for b in enc_bytes]) try : plaintext = original_bytes.decode('utf-8' ) except Exception: plaintext = original_bytes.decode('latin-1' ) return plaintext if __name__ == "__main__" : strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji" decrypted_text = decode_fish(strr) ciphertext = decrypted_text.strip() plaintext = decrypt_encrypt(ciphertext) print (plaintext)
3.fuko’s starfish 这个题的流程是进行了三个游戏,前两个都不是特别重要,主要是调用了dll里面的函数,不过我一调试就反调试了
1. _initterm_e (反调试) 假如有TLS回调函数,静态调试下修改没有用(可能在导出表看不到,可以在进_initterm_之前下断点,再动态中修改)
这个函数会在主函数之前加载,具体作用就是初始化一系列函数,参数是函数指针数组的起始地址和结尾地址,遍历一系列函数
这里对main函数进行交叉应用,再找到这个函数之后,去看它的参数的函数是不是有反调试函数
我在dll里面也看到过如上图的情形,当时以为没什么用就直接走了,结果看汇编才发现它竟然是花指令
2.花指令
这里的call $+5,值得注意,把下一个代码的地址压入栈中,再跳转到下一个代码执行,而且没有弹出,堆栈不平衡,而且看图也可以发现流程应该在右边,这里直接删除push eax到pop eax的所有代码
之后看到就是一个反调试代码,修改一下跳转就行,重要的是记得保存,包括在exe里面也要保存
apply patches
3.dll里面的反调试 这个dll里面的反调试也挺新颖的
这里我已经修改过了,如果程序没有被调试,dwFlags的值一般是1,记得保存
后面还有一个检测进程的反调试,出题人的解决方式是,nop掉要退出的代码
之后通过findcript发现是标准的AES代码
srand(114514 ); for (int i = 0 ; i < 16 ; i++) { key[i] = 0x17 ^(rand()%0xff ); if (key[i] < 16 ) printf ("0" ); printf ("%x" , key[i]); }
from Crypto.Cipher import AES import binascii hex_key = "09e5fdeb683175b6b13b840891eb78d2" hex_ciphertext = "3d011c190ba090815f672731a89aa47497362167ab2eb4a09418d37d93e646e7" key = binascii.unhexlify(hex_key) ciphertext = binascii.unhexlify(hex_ciphertext) cipher = AES.new(key, AES.MODE_ECB) decrypted = cipher.decrypt(ciphertext) print (decrypted)
4.幸运转盘 对于这种鸿蒙逆向的,都是把.hap文件解压之后去找ets目录的.abc文件拖进反编译工具,之后去找index.js(如果实在找不到逻辑可以 DevEco Studio模拟器安装并运行得到界面 )
**pages/index.ets** 或 **pages/index.js** 通常是鸿蒙应用的默认入口页面,相当于 Android 的 MainActivity。
寻找主要逻辑,可以一边找一边喂给AI,
这段代码主要用于页面跳转,同时调用了一个外部库函数处理数据,并将处理结果传递到目标页面
传入的参数就是输入和固定值24,去找so文件
跟普通的so文件不同,鸿蒙逆向的so找主要逻辑是找RegisterModule,相当于找Java的注册JNi一样了
找到主要逻辑
这个逻辑比较重要,刚才说传入的是输入和一个数字(应该是输入的长度)和一个固定的数,这调用了固定的c语言API进行平方和求根判断是不是等于40来判断长度,然后调用这个rc4的加密
这个多了一个异或40
之后就是一个标准的base64加密,返回结果,最后去js层看看代码
发现一个类似于密文的东西,找找调用看有没有判断
这里应该是判断,但是直接解密发现不成功
这里还有一个加密的操作,但是解密还是不对,
这里可能对它进行了加3,再减少3
import base64def base64_decode (encoded_data ): try : decoded_data = base64.b64decode(encoded_data) return decoded_data except Exception as e: print (f"Base64解码失败: {e} " ) return None 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, data_len ): i = 0 j = 0 key_stream = [] for _ in range (data_len): i = (i + 1 ) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] key_stream.append(S[(S[i] + S[j]) % 256 ]) return key_stream def rc4_decrypt (key, data ): key = [ord (c) for c in key] S = rc4_ksa(key) key_stream = rc4_prga(S, len (data)) decrypted_data = bytes ([(data[i] ^ key_stream[i])-3 for i in range (len (data))]) return decrypted_data def main (): base64_input = [101 , 74 , 76 , 49 , 101 , 76 , 117 , 87 , 55 , 69 , 118 , 68 , 118 , 69 , 55 , 67 , 61 , 83 , 62 , 111 , 81 , 77 , 115 , 101 , 53 , 73 , 83 , 66 , 68 , 114 , 109 , 108 , 75 , 66 , 97 , 117 , 93 , 127 , 115 , 124 , 109 , 82 , 93 , 115 ] key = "Take_it_easy" what=[] for i in base64_input: i=(i^7 )-1 what.append(i) decoded_data = base64_decode(bytes (what)) if decoded_data is None : return yes=[] for i in decoded_data: i^=40 yes.append(i) decrypted_data = rc4_decrypt(key, yes) print ("解密后的数据: " , decrypted_data.decode(errors='ignore' )) if __name__ == "__main__" : main()
密码学 1.easyMath from sympy import symbols, solve, Polyfrom Crypto.Util.number import long_to_bytesfrom sympy.ntheory.modular import crtfrom itertools import productc = 24884251313604275189259571459005374365204772270250725590014651519125317134307160341658199551661333326703566996431067426138627332156507267671028553934664652787411834581708944 polynomial_str = "x**3 - 15264966144147258587171776703005926730518438603688487721465*x**2 + 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923*x - 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619" x = symbols('x' ) poly = Poly(polynomial_str, x) roots = solve(poly, x) p1, p2, p3 = [int (root) for root in roots] def sqrt_mod (c, p ): """ 计算模 p 的平方根(p ≡ 3 mod 4)""" exponent = (p + 1 ) // 4 root = pow (c % p, exponent, p) return root, p - root roots = [ sqrt_mod(c, p1), sqrt_mod(c, p2), sqrt_mod(c, p3) ] all_combinations = product(*roots) solutions = [] for comb in all_combinations: m, _ = crt([p1, p2, p3], comb) solutions.append(m) N = p1 * p2 * p3 solutions += [N - x for x in solutions] unique_solutions = list (set (solutions)) print ("所有可能的解(数值 -> 字节):" )for idx, m in enumerate (unique_solutions): try : flag_utf8 = long_to_bytes(m).decode('utf-8' ) printable = all (32 <= ord (c) <= 126 for c in flag_utf8) print (f"[解{idx + 1 } ] 数值: {m} " ) print (f" UTF-8: {flag_utf8 if printable else '非打印字符' } " ) except UnicodeDecodeError: pass raw_bytes = long_to_bytes(m) print (f" 原始字节: {raw_bytes.hex ()} " ) print ("-" * 60 ) print ("请检查输出中符合语义的 flag" )