【Android】【攻防世界】easy-app 一开始想用ida进行动调,但是失败了
丢进jadx发现其主要flag检查逻辑都在native层中
于是就直接去native层查看,有点繁杂,先从check开始查看:最后进行了一次base64
base64是魔改base64,换表+密文换位,加入正常的base密文是0123,这个密文就是2013
所以解base64的时候需要进行换位置然后解正常的base64
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 import base64import structENCODED_STR = "e)n*pNe%PQy!^oS(@HtkUu+Cd$#hmmK&ieytiWwYkIA=" TEA_KEY = [0x42 , 0x37 , 0x2c , 0x21 ] DELTA = 0x9E3779B9 k = struct.unpack("<4I" , bytes (TEA_KEY * 4 )) def tea_decrypt_block (v0, v1, k ): total = DELTA * 32 for _ in range (32 ): v1 = (v1 - (((v0 << 4 ) + k[2 ]) ^ (v0 + total) ^ ((v0 >> 5 ) + k[3 ])) & 0xFFFFFFFF ) v0 = (v0 - (((v1 << 4 ) + k[0 ]) ^ (v1 + total) ^ ((v1 >> 5 ) + k[1 ])) & 0xFFFFFFFF ) total = (total - DELTA) & 0xFFFFFFFF return v0, v1 custom_table = "abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/" standard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" reordered_str = "" for i in range (0 , len (ENCODED_STR), 4 ): group = ENCODED_STR[i:i+4 ] if len (group) == 4 : reordered_str += group[2 ] + group[0 ] + group[1 ] + group[3 ] else : reordered_str += group translated_str = '' .join(standard_table[custom_table.index(char)] if char in custom_table else char for char in reordered_str) if len (translated_str) % 4 != 0 : translated_str += '=' * (4 - len (translated_str) % 4 ) encrypted_data = base64.b64decode(translated_str) for i in range (0 , len (encrypted_data), 4 ): dword = struct.unpack("<I" , encrypted_data[i:i+4 ])[0 ] print (f"0x{dword:08X} ," ,end='' ) print ('\n' )
再往上看发现有tea加密但是我们不知道密钥,静态的是一个假密钥
于是用frida进行动调,拿到真正的密钥
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 function hookTest999(){ Java.perform(function(){ var libc_base = Module.getBaseAddress("libnative-lib.so" ) var keys = libc_base.add(0x3A010 ); console.log("libc_base : " , libc_base); console.log("keys_address : " , keys); var size = 0x30 ; try { var instructionBytes = Memory.readByteArray(keys, size); console.log("instructionBytes:\n" , instructionBytes); if (typeof Instruction !== 'undefined') { var instructions = Instruction.parse(keys, instructionBytes); console.log("Disassembly:" , instructions); } else { console.warn("Instruction parser not available" ); } } catch (e) { console.error("Memory read failed:" , e); } var MainActivity = Java.use("com.example.myapplication.MainActivity" ); MainActivity.check.implementation = function(input) { readInstructions(); var result = this.check(input); console.log(`Native check("${input}" ) returned: ${ result} `); return result; } ; console.log("[*] check() hook installed. Ready for input..." ); } ) }
进行tea解密:
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 #include <stdio.h> #include <stdint.h> void Decrypt (unsigned int * vq,unsigned int * vp,unsigned int * k) { unsigned int n=32 ,sum,y=*vq,z=*vp; unsigned int delta=0x9E3779B9 ; sum = 0x9E3779B9 * 32 ; while (n-->0 ){ z-=( ((y>>5 )+k[3 ]) ^ ((y<<4 )+k[2 ]) ^ (sum + y) ); y-=( ((z<<4 )+k[0 ]) ^ ((z>>5 )+k[1 ]) ^ (sum + z)); sum-=delta; } *vq=y; *vp=z; } int main () { unsigned int v[8 ] = {0x10E14834 ,0x3D635EFC ,0xA2F3D91A ,0xFBCABA4D ,0x37702685 ,0x20C3B847 ,0x58138160 ,0xAB90BC8E }; unsigned int k[4 ]={0x42 , 0x37 , 0x2c , 0x21 }; for (int j=0 ;j<=7 ;j+=2 ) { Decrypt(&v[j],&v[j+1 ], k); printf ("%x %x\n" ,v[j],v[j+1 ]); } return 0 ; } #36346065 38673534 #65353537 62303531 #61333232 34363160 #63316239 35356761
再往上分析发现有两个CheckM和check1,他们两个的作用是将输入的字符串每个byte的高位分离出来,然后把前16个数字和后16个数字进行交换,然后再和原来的低位进行组合(用动调可知,测试数据:flag{11111111BBBBBBBBSSSSSSSSdddddddd})
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 56 57 58 59 60 61 62 63 64 65 66 function test666(){ Java.perform(function() { const moduleName = "libnative-lib.so" ; const funcOffset = 0xff40 ; let baseAddr = Module.getBaseAddress(moduleName); if (!baseAddr || baseAddr.isNull()) { console.log("Error: Module " + moduleName + " not found or base address is null" ); return; } console.log("Base address: " + baseAddr); let hookAddr = baseAddr.add(funcOffset); console.log("Hook address: " + hookAddr); Interceptor.attach(hookAddr, { onEnter: function(args) { } , onLeave: function(retval) { try { let v38Addr = this.context.rsp.add(0x58 ); console.log("RSP: " + this.context.rsp); console.log("v38Addr: " + v38Addr); if (!v38Addr || v38Addr.isNull()) { console.log("Error: v38Addr is invalid or null" ); return; } let flag = v38Addr.readU8(); console.log("Flag: " + flag); let isSso = (flag & 1 ) === 0 ; let dataPtr, dataLen; if (isSso) { dataLen = flag >> 1 ; dataPtr = v38Addr.add(1 ); console.log("SSO mode - dataLen: " + dataLen + ", dataPtr: " + dataPtr); } else { dataLen = Process.arch === 'x64' ? v38Addr.add(8 ).readU64() : v38Addr.add(8 ).readU32(); dataPtr = v38Addr.add(16 ).readPointer(); console.log("Heap mode - dataLen: " + dataLen + ", dataPtr: " + dataPtr); } if (!dataPtr || dataPtr.isNull() || dataLen <= 0 ) { console.log("Error: Invalid dataPtr or dataLen" ); return; } let instructionBytes = Memory.readByteArray(dataPtr, 320 ); console.log("new instructionBytes :\n" , instructionBytes); } catch (e) { console.log("Error in onLeave: " + e); } } } ); } );}
所以还要把刚才tea解出的结果拼成一个长hex串这个混淆然后转换成字符
1 2 3 4 5 6 7 8 9 10 11 tea="6560343634356738373535653135306232323361603136343962316361673535" tea1=tea[::2 ] tea2=tea[1 ::2 ] temp=tea1[16 :]+tea1[:16 ] flag="" for i in range (len (temp)): flag+=temp[i]+tea2[i] print (bytes .fromhex(flag))