攻防世界 NO.GFSJ0166 先用die检查,发现是安卓,放到模拟器里面运行,要求一个密码
在mainactivity里面看到
把这个字符串比较了,尝试输入这个字符串,得到
但是flag输入不对,在main中看到有flagactivity这个类,进去看看
有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FlagActivity extends Activity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(C0074R.layout.activity_flag); String flag = "" ; int [] d = {102 , 108 , 97 , 103 , 123 , 119 , 52 , 110 , 110 , 52 , 95 , 106 , 52 , 114 , 95 , 109 , 121 , 95 , 100 , 51 , 120 , 125 }; for (int i = 0 ; i < 22 ; i++) { flag = flag.concat(String.valueOf((char ) d[i])); } TextView flagText = (TextView) findViewById(C0074R.C0076id.flagText); flagText.setText(flag); }
感觉也没问题……最后发现flag不能加flag,直接w4nn4_j4r_my_d3x即可。
变换检验而不变换输入
BUUCTF newstarctf2023reweek2:AndroDbgMe 用jadx打开:
这个字符串真是太抽象了!
用模拟器打开apk:
发现它只是根据输入做变换然后输出,并没有校验???第一次遇到这种题
同时输出是以toast消息显示的,所以看看有没有和toast有关的:搜索后发现
这个时候我发现静态分析太难了,看到题目名字:dbgme想到会不会要动态调试
(请教大佬安卓调试ing)
然后整理安卓调试流程如下:
前置流程:
先下载adb(部分模拟器自带adb,在模拟器根目录)
打开模拟器的root和开发者模式,在开发者模式里面开usb调试
然后:
用adb连接模拟器:adb connect 127.0.0.1:xxxxx(xxxxx根据模拟器而定),不要关闭
打开jadx的debug功能,顶上的 ,然后应该能看到下面的设备出现了各种线程
找到对应进程,开始调试
然后来到这个界面,需要smali语法。。
这时无论输入什么都会得到正确flag,原来是个教学题
安卓调试
sctf2019 who is he 打开压缩包,发现是UNITY游戏,根据网上教程下载ILSpy分析assembly-CSharp.dll
找到 ,打开函数查看:
发现有一个初始字符串,一个加密函数,一个解密函数
start是空函数并且最后的onclick函数是检测输入是否正确:
所以分析加密和解密的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private string Encrypt (string str ) { try { byte [] bytes = Encoding.Unicode.GetBytes(encryptKey); byte [] bytes2 = Encoding.Unicode.GetBytes(str); DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider(); MemoryStream memoryStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateEncryptor(bytes, bytes), CryptoStreamMode.Write); cryptoStream.Write(bytes2, 0 , bytes2.Length); cryptoStream.FlushFinalBlock(); byte [] inArray = memoryStream.ToArray(); cryptoStream.Close(); memoryStream.Close(); return Convert.ToBase64String(inArray); } catch { return str; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private string Decrypt (string str ) { try { byte [] bytes = Encoding.Unicode.GetBytes(encryptKey); byte [] array = Convert.FromBase64String(str); DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider(); MemoryStream memoryStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Write); cryptoStream.Write(array, 0 , array.Length); cryptoStream.FlushFinalBlock(); byte [] bytes2 = memoryStream.ToArray(); cryptoStream.Close(); memoryStream.Close(); return Encoding.Unicode.GetString(bytes2); } catch { return str; } }
所以加密和解密流程对应,加密是先DES然后base64,解密相反,那么根据观察str是加密后的字符,因此把解密函数和字符串,密钥放在一个类里面计算:
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 using System;using System.IO;using System.Runtime.InteropServices;using System.Security.Cryptography;using System.Text;public class MyClass { private static string encryptKey = "1234" ; private string EncryptData = "1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA==" ; private string Decrypt (string str ) { try { byte [] bytes = Encoding.Unicode.GetBytes(encryptKey); byte [] array = Convert.FromBase64String(str); DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider(); MemoryStream memoryStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Write); cryptoStream.Write(array, 0 , array.Length); cryptoStream.FlushFinalBlock(); byte [] bytes2 = memoryStream.ToArray(); cryptoStream.Close(); memoryStream.Close(); return Encoding.Unicode.GetString(bytes2); } catch { return str; } } public static void Main (string [] args ) { MyClass myClass = new MyClass(); string decryptedData = myClass.Decrypt(myClass.EncryptData); Console.WriteLine(decryptedData); } } #He_P1ay_Basketball_Very_We11!Hahahahaha!
问题来了,结果不对,不是很能理解,去网上看wping
最后跟着教程做,一直都做不出来,在大佬指点之下,发现是因为电脑报毒把某个文件删了还不提醒,找到那个文件:,打开发现是二进制文件,放进die分析,是pe文件,加了UPX壳,用upx的官方程序脱壳
打开之后
大致是加载了一个dll
终于点按钮有提示了:
根据网上博客,CE分析mono
发现和之前那个很类似,并且这个dll在恢复报毒文件之后才出现
用mono分析功能把main放到窗口下方,按按钮
得到密钥test,然后勾选上UTF-16
搜Emmmm提示词,发现出现了两次,第二次的位置打开内存窗口:
这是第二组密文,下面也有密钥test,复制下来,解密
1 785A57445A614B4568574E4D43626947595042496C59332B61726F7A4F397A6F6E7772594C69564C346E6A53657A3252594D32577773476E736E6A43446E4873374E34336146764E4535346E6F53616450394638654570765473355150472B4B4C305444452F34306E62553D
转为
1 xZWDZaKEhWNMCbiGYPBIlY3 +arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40 nbU=
用密钥test和刚刚的算法得
1 She_P1ay_Black_Hole_Very_Wel1!LOL!XD!
c#,假答案,DES,base64,CE,mono
NSSCTF wordy 打开ida后发现没有main函数,start里面标红,怀疑有花指令,到汇编中查找main,用alt+t
键,找到后:
发现确实是花指令,先u,再跳一个数据c,多来几次,就会发现垃圾数据是0EBh,同时每两个eb之间都有putchar指令输出一个字符,由于0XEB是不可见的所以,打开hex窗口只会留下putchar的字符:
得flag:GFCTF{u_are2wordy}
花指令,hex,脚本
ganctf 2023 py?python! 打开文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def encrypt (data, key ): encrypted_data = [] key_len = len (key) for i in range (len (data)): encrypted_data.append(ord (data[i]) ^ ord (key[i % key_len])) return encrypted_data[::-1 ] data = [19 , 78 ,6 , 68 , 17 , 36 , 23 ,63 , 55 , 0 , 57 , 15 , 42 , 32 , 88 , 51 , 38 , 41 , 95 , 3 , 40 , 71 , 43 , 15 , 93 , 29 , 40 , 43 , 12 , 96 , 55 , 20 , 15 , 21 , 21 ,54 ] key = "Python" flag = input ("请输入flag" ) encrypted_data = encrypt(flag, key) if encrypted_data == data: print ("Good!You find it" ) else : print ("Sorry,Try again" )
很简单的加密算法,直接逆向即可
1 2 3 4 5 6 7 8 9 last = [19 , 78 ,6 , 68 , 17 , 36 , 23 ,63 , 55 , 0 , 57 , 15 , 42 , 32 , 88 , 51 , 38 , 41 , 95 , 3 , 40 , 71 , 43 , 15 , 93 , 29 , 40 , 43 , 12 , 96 , 55 , 20 , 15 , 21 , 21 ,54 ] key = "Python" last =last[::-1 ] be = '' for i in range (len (last)): be += (chr ((last[i]) ^ ord (key[i % len (key)]))) print (be)
Ezmaze 打开ida,检查main,化简后大概是:
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 int __fastcall main (int argc, const char **argv, const char **envp) { int c; char path[112 ]; __int64 tab[5 ]; int len; __int64 check; int b; __int64 a; int ch1,ch2; _main(argc, argv, envp); qmemcpy(tab, "@01111010110011110011110100#01" , 30 ); a = 0 i64; gets(path); len = strlen(path); b = 0 ; while ( 1 ) { check = a; c = path[b]; if ( c == 100 ) { ch2++; } else if ( c > 100 ) { if ( c == 115 ) { ch1++; } else if ( c == 119 ) { ch1--; } } else if ( c == 97 ) { ch2--; } if ( ++b > len || ch1 > 5 || (unsigned int )check > 4 || (tab[5 *ch1+ch2]) != '0' && (tab[5 *ch1+ch2]) != '@' && (tab[5 *ch1+ch2]) != '#' ) { break ; } a = check; } if ( *((_BYTE *)tab + 5 * HIDWORD(a) + (int )a) == '#' ) { } else { } return 0 ; }
分析check的值可知它的高32位储存纵方向数值,低32位储存横方向数值,a,w为减sd为加。跳出循环条件表明至少s5次,d4次,把tab改成迷宫:
@0111
10101
10011
11001
11101
00#01
可得@为开始位置,#为末位置
dssdsdssa即可(但是d也没有4次呀?奇怪)
flag{dssdsdssa}
逆向分析,正向求解
easy_xor 这个题也很简单,ida分析后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 _main(); *(_QWORD *)enc = 0x6E6E717F64636D66 i64; strcpy (&enc[8 ], "{V;xSLQi|pun" );printf ("Enter the flag: " );scanf ("%s" , flag);if ( strlen (flag) != 20 ){ puts ("Invalid flag length! Exiting..." ); exit (0 ); } for ( j = 0 ; j < strlen (flag); ++j ) flag[j] ^= j; if ( !strcmp (flag, enc) ) puts ("Good!" ); else puts ("No!" ); return 0 ;
直接动态调试找enc的地址异或
1 2 print ('' .join([chr (get_wide_byte(0x62FDF0 +i)^i) for i in range (20 )] ))
easy_upx 这个在upx官方的脱壳软件脱壳后打开main转换字符串即可,或直接打开hex视图:
flag{UXP_yyds!}
UPX,hex
try_reverse_it 有一个upx壳,先脱掉,然后进入main查看:
发现是rc4加密,之前没有学过,所以现学:
整个加密分为三个阶段:
初始化,S盒初始化为顺序线性列表,用已知的key填满T盒如果key长度小于T盒大小,就一直重复直到填满
混乱化:在循环中,把第i位的S和一个由这个S和T盒生成的算法计算出的S[j]交换
用混乱化之后的S盒和flag进行加密i次每次取1位,由于rc4加密是对称的所以一般用异或,同时每次加密会同时交换由S[i]变换的来的S[j]交换,所以每次加密都是在上一次基础上完成的
而这道题也只是把每一位flag和那一位计算的S异或,所以完全可以顺着先把S盒求出来,根据异或的性质直接把最后的值异或得到答案
补充一个细节:之前我写的脚本:
1 2 3 4 t = '' for i in range (32 ): t.join(chr (flag[i])) print (t)
1 2 3 for i in range (32 ): t='' .join(chr (flag[i])) print (t)
这两个都是错误的,第一个的t.join(chr(flag[i]))意思是返回把后方传入的列表变成一个字符串间隔字符是t
的字符串,但是我并没有用字符串接收这个返回值,导致没有改变
第二个是因为’’.join每次都会初始化t,改成+=就好了
exp:
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 key = [0x6C , 0x65 , 0x74 , 0x27 , 0x73 , 0x5F , 0x72 , 0x65 , 0x76 , 0x65 , 0x72 , 0x73 , 0x5F , 0x69 , 0x74 ] last = [0x6C , 0xF8 , 0x90 , 0xFF , 0x8B , 0x18 , 0xE5 , 0x76 , 0xB5 , 0xFB , 0x50 , 0xF5 , 0x2C , 0x5F , 0xF3 , 0x10 , 0xCB , 0x3E , 0xEA , 0x9A , 0xC8 , 0x37 , 0x2D , 0x4F , 0xCC , 0x8F , 0x21 , 0x29 , 0x17 , 0x06 , 0xCA , 0x67 ] S = [0 ]*256 for i in range (256 ): S[255 -i] = i T = [0 ]*256 for i in range (256 ): T[i] = key[i % len (key)] a = b = 0 for i in range (256 ): a = S[i] b = (a+T[i]+b) % 256 S[i] = S[b] S[b] = a m = n = 0 flag = [0 ]*32 for i in range (32 ): m = (m + 1 ) % 256 k = S[m] n = (k+n) % 256 S[m] = S[n] S[n] = k flag[i] = last[i] ^ S[(k + S[m]) % 256 ] t ='' for i in range (32 ): t +='' .join(chr (flag[i])) print (t)
UPX,RC4
BrownFox ida直接打开,先动态调试把improvefox函数之后的fox拿出来,再把最后的检测函数拿出来,变换全在tofox里面所以详细看tofox,化简之后:
1 2 3 4 5 6 7 8 for ( i = 0 ; i != num;i++ ) { k = flag[i] >> 3 ; check[2 * i] = fox[flag[i] & 7 ]; check[2 * i+ 1 ] = fox[k] } check += 2 * num; *check = 0 ;
这里的变换是非连续性的所以k<<3+check[2*i]在fox中的位置可得flag
注:fox一开始是有重复字符的,但是在improvefox之后没有了
最后又被坑了,注意python中位移优先度低于+-,异或也是
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 fox = [0x41 , 0x71 , 0x75 , 0x69 , 0x63 , 0x6B , 0x62 , 0x72 , 0x6F , 0x77 , 0x6E , 0x66 , 0x30 , 0x78 , 0x6A , 0x31 , 0x6D , 0x70 , 0x73 , 0x32 , 0x76 , 0x65 , 0x33 , 0x74 , 0x68 , 0x34 , 0x6C , 0x61 , 0x7A , 0x79 , 0x64 , 0x35 , 0x67 ] sng = [0x62 , 0x30 , 0x63 , 0x78 , 0x71 , 0x30 , 0x72 , 0x30 , 0x69 , 0x31 , 0x71 , 0x66 , 0x41 , 0x62 , 0x6B , 0x6A , 0x72 , 0x66 , 0x69 , 0x78 , 0x62 , 0x78 , 0x72 , 0x78 , 0x72 , 0x6A , 0x72 , 0x66 , 0x75 , 0x6F , 0x41 , 0x6F , 0x69 , 0x6A , 0x6B , 0x30 , 0x72 , 0x66 , 0x62 , 0x6A , 0x6B , 0x30 , 0x75 , 0x6A , 0x71 , 0x31 , 0x72 , 0x66 , 0x72 , 0x6A , 0x6B , 0x30 , 0x71 , 0x62 , 0x71 , 0x62 , 0x6B , 0x31 , 0x41 , 0x41 ] def up (x ): for i in range (len (fox)): if (fox[i] == x): return i flag = '' for i in range (0 ,60 ,2 ): t = up(sng[i])+((up(sng[i+1 ]))<<3 ) flag += '' .join(chr (t)) print (flag)
算法,c语言
find_flag 先看一下程序:发现会输入两次flag,不知道什么用意
还是用ida打开,在判断函数之前看到了加密函数根据1640531527可知这是一个TAE加密,但是由于知道有输入了两个flag先看看之前的flag1怎么样
1 2 3 4 5 6 7 8 void __fastcall sub_401AB0 () { if ( !strnum ) { strnum = 1 ; check1(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void __fastcall check1 () { void (**pfunc)(void ); __int64 *v1; unsigned int i; for ( i = 0 ; bigq[i + 1 ]; ++i ) ; if ( i ) { pfunc = (void (**)(void ))&bigq[i]; v1 = &bigq[i - (unsigned __int64)(i - 1 ) - 1 ]; do (*pfunc--)(); while ( pfunc != (void (**)(void ))v1 ); } sub_401510((int (__cdecl *)())sub_401A00); }
可以看到check1中有一个指向函数指针的指针这个指针指向了bigq的地址,而打开可以看到bigq的地址后面跟着两个函数的地址,所以当pfunc这个指针指向这个地址的时候,它解引用就是这个函数地址在(*pfunc–)();处调用并减小,可以把bigq当作一个函数指针数组
注意for后面有;所以只增加了i,后面不在循环里,所以相当于从后向前执行那两个函数,那么先看那两个函数,第一个调用的函数(这个是func2)是一个退出函数??暂时不管。
打开func1:
看到很多数组,在读取时动态调试下断点,发现这里是真的检查处
真的检查函数是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 for ( k = 0 ; k <= 4 ; ++k ) sub_401550(&flag1[2 * k]); if ( !memcmp (flag1, &v3[3 ], 0x28 ui64) )void __fastcall sub_401550 (unsigned int *a1) { unsigned int i; int e; unsigned int b2; unsigned int b1; b1 = *a1; b2 = a1[1 ]; e = 0 ; for ( i = 0 ; i <= 0x1F ; ++i ) { e -= 1640531527 ; b1 += (b2 + e) ^ (16 * b2 + 137 ) ^ ((b2 >> 5 ) + 118 ); b2 += (b1 + e) ^ (16 * b1 + 84 ) ^ ((b1 >> 5 ) + 50 ); } *a1 = b1; a1[1 ] = b2; }
也是一个TEA
注意此处读取前40个值且取test数组的第四位开始,而flag是char类型,后面转成unint类型,鉴于不好python和c大数计算有差别,所以用c写:
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 #include <stdio.h> void dec (unsigned int *a) { unsigned int b2; unsigned int b1; b1 = *a; b2 = a[1 ]; int e = 0 ; for (int i = 0 ; i <= 0x1F ; ++i) { e -= 1640531527 ; } for (int i = 0 ; i <= 0x1f ; ++i) { b2 -= (b1 + e) ^ (16 * b1 + 84 ) ^ ((b1 >> 5 ) + 50 ); b1 -= (b2 + e) ^ (16 * b2 + 137 ) ^ ((b2 >> 5 ) + 118 ); e += 1640531527 ; } *a = b1; a[1 ] = b2; } int main (void ) { unsigned int data[10 ] = { 0x9F1F796B , 0xE4AF7378 , 0x72FCDFD5 , 0x4A57FFE0 , 0x1672BA07 , 0xAA057BE6 , 0x9E171F8E , 0xDD153E4F , 0x41079E59 , 0x807578DD }; for (int k = 0 ; k < 5 ; k++) { dec(&data[k*2 ]); } for (int i = 0 ; i < 10 ; i++) printf ("%#X" , data[i]); }
然后字符串转换:
666C61677B57307721596F755F46316E645F6D45215F636F4E67724074756C4174316F6E73217D
转string:flag{W0w!You_F1nd_mE!_coNgr@tulAt1ons!}
c语言,函数指针数组和指向函数指针数组的指针,TEA
总结 RC4加密 详见专栏:RC4
TAE加密 详见专栏:TEA
安卓调试流程
前置流程:
先下载adb(部分模拟器自带adb,在模拟器根目录)
打开模拟器的root和开发者模式,在开发者模式里面开usb调试
然后:
用adb连接模拟器:adb connect 127.0.0.1:xxxxx(xxxxx根据模拟器而定),不要关闭
打开jadx的debug功能,顶上的 ,然后应该能看到下面的设备出现了各种线程
找到对应进程,开始调试
CE的mono功能 mono是Novell开发的跨平台.NET运行环境,而Csharp是这个环境上的主要编程语言,Unity的使用的就是Csharp,所以CE的mono功能可以对大部分unity游戏使用
开启mono后,Ctrl+D打开反汇编程序窗口后可以直接看到类名和函数名,也可以在mono窗口查看DLL内容,相应类和方法,变量等,不仅可以方便写CElua脚本还能方便反编译器lispy或dnspy找DLL和方法名