攻防世界 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和方法名