RE 好多的安卓逆向啊,但是都差一点😫😫😫
hook_fish 算是签到题目来着
拿到雷电模拟器里面直接启动,会出现什么什么 鱼
,jadx启动!!搜索关键字 鱼
,然后就有下面的函数了:
但这些不重要,注意中间有一个encrypt
的调用,
返回值就是加密之后的函数,但是有什么用呢?往下看
这里调用了hoo_fish.dex的check方法,但这个文件哪来的呢?我们全局搜索字符串可以看到:
访问这个网站就可以下载到了,然后用dex2jar将dex转为apk文件,继续 jadx启动!!!
全部代码就不展示了,里面是一个自定义的hashmap映射表的加密函数,并将加密之后的函数与strr比较,相同就check过了,大致就是这样,然后直接写脚本模拟即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 def decode_fish (encoded_str ): fish_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' } decoded_str = '' .join(fish_dcode[encoded_str[i:i+5 ]] for i in range (0 , len (encoded_str), 5 )) return decoded_str strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji" def decode_swap (a, index=0 ): if index >= len (a) - 1 : return a[index], a[index + 1 ] = a[index + 1 ], a[index] decode_swap(a, index + 2 ) def decrypt (encrypted_str ): encrypted_chars = list (encrypted_str) for i in range (len (encrypted_chars)): if '0' <= encrypted_chars[i] <= '9' : encrypted_chars[i] = chr (ord (encrypted_chars[i]) + ord ('1' ) - (i % 4 )) else : encrypted_chars[i] = chr (ord (encrypted_chars[i]) - ord ('7' ) - (i % 10 )) decode_swap(encrypted_chars, 0 ) hex_str = '' .join(encrypted_chars) byte_data = bytes .fromhex(hex_str) original_bytes = bytes ([b - 68 for b in byte_data]) return original_bytes.decode('utf-8' ) encrypted_text = decode_fish(strr) decrypted_text = decrypt(encrypted_text) print (decrypted_text)
Fuko’s_starfish 常规的windows exe逆向,但是关键操作都在dll里面,和软件系统安全赛初赛的花指令一样的,难崩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 hLibModule = LoadLibraryW(L"starfish.dll" ); if ( hLibModule ) { Sleep(0x7D0 u); sub_140001170("\nhello ctfer\n" ); Sleep(0x7D0 u); sub_140001170("I am Ibuki Fuko!\n" ); Sleep(0x7D0 u); sub_140001170("If you want to get my starfish\n" ); Sleep(0x7D0 u); sub_140001170("You need to complete my three games!\n" ); Sleep(0x7D0 u); sub_140001170("Are you ready?\n" ); Sleep(0x7D0 u); sub_140001170("Let's start!\n" ); Sleep(0x7D0 u); system("cls" ); sub_140001490(); sub_140001170("Goodbye\n" ); FreeLibrary(hLibModule); return 0 ; }
加载dll,然后进行下面的操作,操作是在 sub_140001490();
,就是个小游戏的代码,但是不重要,因为只有一个游戏在这里面,还有另外两个小游戏在dll里面,下面就直接看dll代码:
fdwReason:
值
宏定义
说明
1
DLL_PROCESS_ATTACH
进程加载 DLL
2
DLL_THREAD_ATTACH
进程创建新线程时调用
3
DLL_THREAD_DETACH
线程退出时调用
0
DLL_PROCESS_DETACH
进程卸载 DLL
所以exe文件刚开始加载dll的时候会进入这里,然后创建了一个线程,但是重点也不在这里,而是在后面的 ThreadProc
里面,这个留在后面说,然后直接查看字符串,进入第二个小游戏的地方:
查看win之后程序在哪:
就是中间这个 sub_1800025F0
函数,这里要去掉花指令:
把 ret
给nop掉,因为这个改变了程序正常运行的分支,再 f5
就可以看正常的程序了,代码不展示了,简单来说就是 AES/ECB
加密然后与给定字符串比较,相同就对了,但是在生成密钥的地方有问题(函数sub_180001650
):
获取密钥,刚好16字节:
继续往下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 CurrentProcess = GetCurrentProcess(); CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent); if ( pbDebuggerPresent ) { v131 = v5; v132 = v4; v133 = v3; v134 = v9; v124 = v2; v11 = v8; v135 = v7; v136 = v6; v12 = v126; v13 = v125; v14 = v127; LOWORD(v147) = 7481 ; *(_DWORD *)Str = 964328031 ; for ( i = 0 i64; strlen (Str) > i; ++i ) Str[i] ^= 0x17 u; sub_1800010B0((char *)"%s" ); v16 = v14; v17 = v138; v18 = v12; v19 = v136; v20 = v128; v21 = v135; v22 = v11; v23 = v124; v24 = v134; v25 = v133; v26 = v132; v27 = v131; } else { v124 = v2 ^ 0x17 ; v28 = v3 ^ 0x17 ; v29 = v4 ^ 0x17 ; v27 = v5 ^ 0x17 ; v21 = v7 ^ 0x17 ; v22 = v8 ^ 0x17 ; v24 = v9 ^ 0x17 ; v17 = v138 ^ 0x17 ; v13 = v125 ^ 0x17 ; v19 = v6 ^ 0x17 ; v18 = v126 ^ 0x17 ; v20 = v128 ^ 0x17 ; LOBYTE(v137) = v137 ^ 0x17 ; LOBYTE(v130) = v130 ^ 0x17 ; v16 = v127 ^ 0x17 ; LOBYTE(v129) = v129 ^ 0x17 ; v23 = v124; v25 = v28; v26 = v29; }
检测是否在调试状态,是的话就输出 Hmm.
,不是的话,就将密钥 ^0x17,然后再加密,但是密钥其实在其他地方被改变了,而不是静态写在data段的数据,所以直接看 byte_18000E1E0…………^0x17
是不对的,我们看到数据,然后 x
查看被谁引用了
发现就是开头说过的 ThreadProc
函数,跟进去看,是一堆随机数生成,然后得到的数据,但是我们并不知道随机数种子是什么,dll动调也不太会,但其实往函数最下面看,还是有个一样的花指令
同样的看汇编 nop掉retn,再看代码就知道了:
那我们就可以模拟随机数生成,然后就可以知道密钥是多少了
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> #include <stdlib.h> int main () { unsigned char key[16 ]; srand(114514u ); for (int i = 0 ; i < 16 ; i++) { int v = rand(); key[i] = (v + v / 0xff ) ^ 0x17 ; printf ("%02x" , key[i]); } }
然后直接cyberchelf一把梭
写个脚本也行
1 2 3 4 5 6 7 8 9 10 11 12 from Crypto.Cipher import AESimport binasciihex_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)
kotlindroid 主要部分如下:
前面的key很好获得,key1^23,key2^8拼接起来就是了:atrikeyssyekirta
跟进check继续看看:
可以发现是 AES/GCM加密,密文是: MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==
,iv是114514
,key就是上面得到的,其中函数GCMParameterSpec设置了tag为128位,其实就是密文最后16字节,但是GCM模式还需要一个aad才可以解密,下面找一找aad:
先跟进sec后面有个 SearchActivityKt$sec$1
函数
发现从JNI类获得了bytes数据然后更新为AAD
调用了so文件中的 native_natget方法,从里面传入了上面的数组
由于每次的AAD都是一样的,可以直接frida或者动调获得。这里简单介绍一下动调出AAD
用JEB的动调,关于下载安装可以看这篇文章
https://plastic-tire-e58.notion.site/JEB-1a2ca45ea8a480a0b576f249b42306bc?pvs=73
进入之后,我们找到核心的函数:
也就是这里会添加AAD,我们在开头处下断点,注意要先tap到字节码的页面,然后才能下断点(ctrl+b下断点)
在这里下一个断点,然后步过到这里的时候转为步入,因为该处是native层的处理,直接步过我们无法看到具体的数据
然后就可以看到数据了 mysecretadd
直接模拟该逻辑用java写个代码直接输出就行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.util.Base64;import java.nio.charset.StandardCharsets;public class AESGCMExample { public static String aesGcmDecryptWithAAD (byte [] cipherTextWithTag, byte [] key, byte [] iv, byte [] aad) throws Exception { byte [] tag = new byte [16 ]; Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding" ); GCMParameterSpec spec = new GCMParameterSpec (128 , iv); cipher.init(Cipher.DECRYPT_MODE, new javax .crypto.spec.SecretKeySpec(key, "AES" ), spec); cipher.updateAAD(aad); byte [] decryptedData = cipher.doFinal(cipherTextWithTag); return new String (decryptedData, StandardCharsets.UTF_8); } public static void main (String[] args) { try { String cipherTextBase64 = "HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==" ; byte [] key = "atrikeyssyekirta" .getBytes(); byte [] iv = "114514" .getBytes(); byte [] cipherTextWithTag = Base64.getDecoder().decode(cipherTextBase64); String aad = "mysecretadd" ; byte [] aadBytes = aad.getBytes(StandardCharsets.UTF_8); String decryptedText = aesGcmDecryptWithAAD(cipherTextWithTag, key, iv, aadBytes); System.out.println("解密后的文本: " + decryptedText); } catch (Exception e) { e.printStackTrace(); } }