静态分析java层 2014 ASIS Cyber Security Contest Finals Numdroid 逆向分析 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 protected  void  onCreate (Bundle savedInstanceState)      super.onCreate (savedInstanceState);     setContentView (R.layout.activity_main);     ArrayTools.each_with_index (buttons, new  EachIndexAction <Integer>() {          @Override              public  void  action (final  int  i, Integer element) {             ((Button) MainActivity.this .findViewById (element.intValue ())).setOnClickListener (new  View.OnClickListener () {                  @Override                      public  void  onClick (View arg0) {                     MainActivity.this .clicked (i);                 }             });         }     });     this .mScreen = (EditText) findViewById (R.id.editText1);     this .mScreen.setEnabled (false );     this .mOk = (ImageButton) findViewById (R.id.ok);     this .mDel = (ImageButton) findViewById (R.id.del);     this .mOk.setOnClickListener (new  View.OnClickListener () {          @Override              public  void  onClick (View arg0) {             MainActivity.this .ok_clicked ();         }     });     this .mDel.setOnClickListener (new  View.OnClickListener () {          @Override              public  void  onClick (View arg0) {             MainActivity.this .del_clicked ();         }     }); } 
首先遍历按钮数组,并进行初始化和设置点击事件 MainActivity.this.clicked(i); 
然后对OK和del按钮进行初始化并设置点击事件分别为 MainActivity.this.ok_clicked(); 和 MainActivity.this.del_clicked(); 
 
1 2 3 4 5 public  void  clicked (int  i)      DebugTools.log ("number: "  + i);     this .mScreen.setText (this .mScreen.getText ().append ((CharSequence) Integer.toString (i)));     DebugTools.log ("current Pass: "  + ((Object) this .mScreen.getText ())); } 
点击按钮时候会先log记录下来,然后再将数字转为String显示到屏幕上,然后再log记录下当前的passwd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected  void  ok_clicked ()      DebugTools.log ("clicked password: "  + ((Object) this .mScreen.getText ()));     boolean result = Verify.isOk (this , this .mScreen.getText ().toString ());     DebugTools.log ("password is Ok? : "  + result);     if  (result) {         Intent i = new  Intent (this , (Class<?>) LipSum.class );         Bundle b = new  Bundle ();         b.putString ("flag" , this .mScreen.getText ().toString ().substring (0 , 7 ));         i.putExtras (b);         startActivity (i);         return ;     }     Toast.makeText (this , R.string.wrong, 1 ).show ();     this .mScreen.setText ("" ); } 
点击OK按钮,会先log记录当前的passwd,然后通过Verify.isOk对其进行check,如果密码正确会进入到分支,然后会把flag放到屏幕上,显然这就是胜利条件了
第一个idea,对isOk进行hook,测试之后发现,他的flag是md5的密码,显然偷鸡失败(/(ㄒoㄒ)/~~
那么就要研究isOk函数的逻辑:
1 2 3 4 5 6 7 8 9 public  static  boolean isOk (Context c, String _password)      String password = _password;     if  (_password.length () > 7 ) {         password = _password.substring (0 , 7 );      }     String r = OneWayFunction (password);     DebugTools.log ("digest: "  + password + " => "  + r);     return  r.equals ("be790d865f2cea9645b3f79c0342df7e" ); } 
password只取前7个数字进OneWayFunction函数,之后将其返回的值与一个hash进行比对
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 private  static  String OneWayFunction (final  String password)      String[] hashes = {"MD2" , "MD5" , "SHA-1" , "SHA-256" , "SHA-384" , "SHA-512" };     List<byte[]> bytes = ArrayTools.map (ArrayTools.select (ArrayTools.map (hashes, new  MapAction <String, byte[]>() {          @Override              public  byte[] action (String element) {             try  {                 MessageDigest digest = MessageDigest.getInstance (element);                 digest.update (password.getBytes ());                 return  digest.digest ();             } catch  (NoSuchAlgorithmException e) {                 return  null;             }         }     }), new  SelectAction <byte[]>() {          @Override              public  boolean action (byte[] element) {             return  element != null;         }     }), new  MapAction <byte[], byte[]>() {          @Override              public  byte[] action (byte[] element) {             byte[] b = new  byte[8 ];             for  (int  i = 0 ; i < b.length / 2 ; i++) {                 b[i] = element[i];             }             for  (int  i2 = 0 ; i2 < b.length / 2 ; i2++) {                 b[(b.length / 2 ) + i2] = element[(element.length - i2) - 2 ];             }             return  b;         }     });     byte[] b2 = new  byte[bytes.size () * 8 ];     for  (int  i = 0 ; i < b2.length; i++) {         b2[i] = bytes.get (i % bytes.size ())[i / bytes.size ()];     }     try  {         MessageDigest digest = MessageDigest.getInstance ("MD5" );         digest.update (b2);         byte[] messageDigest = digest.digest ();         StringBuilder hexString = new  StringBuilder ();         for  (byte aMessageDigest : messageDigest) {             String h = Integer.toHexString (aMessageDigest & 255 );             while  (h.length () < 2 ) {                 h = "0"  + h;             }             hexString.append (h);         }         return  hexString.toString ();     } catch  (NoSuchAlgorithmException e) {         return  "" ;     } } 
发现hash基本都是单向的,发现如果只是爆破的话只有10^7的大小,感觉可以尝试
方案1: 网上师傅的方案:
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 .line 26   :cond_0   const/4 v0, 0x0   .local v0, "i":I   :goto_0   const v2, 0x98967f   if-lt v0, v2, :cond_1   .line 38   return-void   .line 28   :cond_1   invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;   move-result-object v2   invoke-static {v2}, Lcom/example/forloop/MainActivity;->isOk(Ljava/lang/String;)Z   move-result v1   .line 29   .local v1, "ok":Z   const v2, 0x186a0   rem-int v2, v0, v2   if-nez v2, :cond_2   .line 31   invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;   move-result-object v2   invoke-virtual {p0, v2}, Lcom/example/forloop/MainActivity;->log(Ljava/lang/String;)V   .line 33   :cond_2   if-eqz v1, :cond_3   .line 35   invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;   move-result-object v2   invoke-virtual {p0, v2}, Lcom/example/forloop/MainActivity;->log(Ljava/lang/String;)V   .line 26   :cond_3   add-int/lit8 v0, v0, 0x1   goto :goto_0 
1 2 3 4 5 :cond_0 const/4 v0, 0x0 .local v0, "i":I :goto_0 
这里首先初始化v0=0,然后初始化循环器i(存储在v0中)
1 2 const v2, 0x98967f       # 加载最大值 9,999,999 (0x98967F) if-lt v0, v2, :cond_1    # 如果 i >= 9,999,999,跳转到 :cond_1(退出循环) 
 if v0 less than v2(即v0<v2)就会跳转到cond_1
1 2 3 4 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2     # 将 i 转为字符串 invoke-static {v2}, Lcom/example/forloop/MainActivity;->isOk(Ljava/lang/String;)Z move-result v1            # 调用 isOk() 并存储结果到 v1(ok) 
v0转换为字符串,然后将结果存储到v2,之后调用isOk,参数是v2,然后将返回值放入v1
1 2 3 4 5 6 7 8 const v2, 0x186a0        # 加载 100,000 (0x186A0) rem-int v2, v0, v2       # 计算 i % 100,000 if-nez v2, :cond_2       # 如果余数为0(每100,000次),执行打印 #  打印当前进度 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2 invoke-virtual {p0, v2}, Lcom/example/forloop/MainActivity;->log(Ljava/lang/String;)V 
if v2 no equal zero,就是v2!=0就会跳转到cond_2标签处执行,否则继续执行下一条指令
1 2 3 4 5 6 7 :cond_2 if-eqz v1, :cond_3       # 如果 isOk() 返回 true,执行成功逻辑 #  打印成功的数字 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2 invoke-virtual {p0, v2}, Lcom/example/forloop/MainActivity;->log(Ljava/lang/String;)V 
如果v1==0就会打印成功的数字,调用的是log进行打印
1 2 3 :cond_3 add-int/lit8 v0, v0, 0x1  # i++ goto :goto_0              # 跳回循环开头 
如果没满足,就执行i++然后跳转回去
但是为了将其粘贴到apk中就要对其进行修改
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 const/4 v0, 0x0 .local v0, "i":I :goto_3 const v2, 0x98967f if-lt v0, v2, :cond_3 return-void .line 28 :cond_3 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2 invoke-static {p0, v2}, Lio/asis/ctf2014/numdriod/Verify;->isOk(Landroid/content/Context;Ljava/lang/String;)Z move-result v1 .line 29 .local v1, "ok":Z const v4, 0x186a0 rem-int v4, v0, v4 if-nez v4, :cond_6 .line 31 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2 invoke-static {v2}, Lio/asis/ctf2014/numdriod/tools/DebugTools;->log(Ljava/lang/String;)V :cond_6 if-eqz v1, :cond_2 goto :goto_4 .line 31 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v2 invoke-static {v2}, Lio/asis/ctf2014/numdriod/tools/DebugTools;->log(Ljava/lang/String;)V .line 26 :cond_2 add-int/lit8 v0, v0, 0x1 goto :goto_3 
方案2: 这个方案是直接将Verify中加个main函数,然后修改一些报错,直接运行即可,参照https://gist.github.com/volpino/2edea8503822aefb4c9e 
得到答案是
1 2 ................> java .\sample\Verify.java FOUND!! 3130110 
2014 Sharif University Quals CTF Commercial Application 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 private  void  checkLicenceKey (final  Context context)        if  (this .app.getDataHelper ().getConfig ().hasLicence ()) {            showAlertDialog (context, OK_LICENCE_MSG);           return ;       }       LayoutInflater li = LayoutInflater.from (context);       View promptsView = li.inflate (R.layout.propmt, (ViewGroup) null);       AlertDialog.Builder alertDialogBuilder = new  AlertDialog.Builder (context);       alertDialogBuilder.setView (promptsView);       final  EditText userInput = (EditText) promptsView.findViewById (R.id.editTextDialogUserInput);       alertDialogBuilder.setCancelable (false ).setPositiveButton ("Continue" , new  DialogInterface.OnClickListener () {            @Override            public  void  onClick (DialogInterface dialog, int  id) {               String userEnteredValue = userInput.getText ().toString ();               String storedKey = MainActivity.this .app.getDataHelper ().getConfig ().getSecurityKey ();               String iv = MainActivity.this .app.getDataHelper ().getConfig ().getSecurityIv ();               boolean licenceKeyIsValid = KeyVerifier.isValidLicenceKey (userEnteredValue, storedKey, iv);               if  (licenceKeyIsValid) {                   MainActivity.this .app.getDataHelper ().updateLicence (2014 );                   MainActivity.isRegisterd = true ;                   MainActivity.this .showAlertDialog (context, MainActivity.OK_LICENCE_MSG);                   return ;               }               MainActivity.this .showAlertDialog (context, MainActivity.NOK_LICENCE_MSG);           }       }).setNegativeButton ("Cancel" , new  DialogInterface.OnClickListener () {            @Override            public  void  onClick (DialogInterface dialog, int  id) {               dialog.cancel ();           }       });       AlertDialog inputLicenceDialog = alertDialogBuilder.create ();       inputLicenceDialog.show ();   } 
关键函数,处理key过程如下
按下continue之后,usrinput会被转为string进入
1 2 3 4 String  userEnteredValue  =  userInput.getText().toString();String  storedKey  =  MainActivity.this .app.getDataHelper().getConfig().getSecurityKey();String  iv  =  MainActivity.this .app.getDataHelper().getConfig().getSecurityIv();boolean  licenceKeyIsValid  =  KeyVerifier.isValidLicenceKey(userEnteredValue, storedKey, iv);
会从配置中得到storedKey和iv,之后进入是否有效的判断
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 public  static  final  String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding" ;public  static  final  String VALID_LICENCE = "29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84" ;public  static  boolean isValidLicenceKey (String userInput, String secretKey, String iv)      String encryptUserInputData = encrypt (userInput, secretKey, iv);     return  encryptUserInputData.equals (VALID_LICENCE); } public  static  String encrypt (String userInput, String secretKey, String iv)      try  {         SecretKeySpec secretKeySpec = new  SecretKeySpec (hexStringToBytes (secretKey), "AES" );         Cipher cipher = Cipher.getInstance (CIPHER_ALGORITHM);         IvParameterSpec ivSpec = new  IvParameterSpec (iv.getBytes ());         cipher.init (1 , secretKeySpec, ivSpec);         byte[] encryptedBytes = cipher.doFinal (userInput.getBytes ());         String encryptedText = bytesToHexString (encryptedBytes);         return  encryptedText;     } catch  (Exception e) {         e.printStackTrace ();         return  "" ;     } } public  static  String bytesToHexString (byte[] bytes)      StringBuilder sb = new  StringBuilder ();     for  (byte b : bytes) {         sb.append (String.format("%02x" , Integer.valueOf (b & 255 )));     }     return  sb.toString (); } public  static  byte[] hexStringToBytes (String s) {    int  len = s.length ();     byte[] data = new  byte[len / 2 ];     for  (int  i = 0 ; i < len; i += 2 ) {         data[i / 2 ] = (byte) ((Character.digit (s.charAt (i), 16 ) << 4 ) + Character.digit (s.charAt (i + 1 ), 16 ));     }     return  data; } 
主要是通过encrypt函数进行加密,然后和invalid_license进行比较
在getconfig中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  AppConfig getConfig ()  {    AppConfig  agency  =  new  AppConfig ();     Cursor  cursor  =  this .myDataBase.rawQuery(SELECT_QUERY, null );     if  (cursor.moveToFirst()) {         agency.setId(cursor.getInt(0 ));         agency.setName(cursor.getString(1 ));         agency.setInstallDate(cursor.getString(2 ));         agency.setValidLicence(cursor.getInt(3 ) > 0 );         agency.setSecurityIv(cursor.getString(4 ));         agency.setSecurityKey(cursor.getString(5 ));         agency.setDesc(cursor.getString(7 ));     }     return  agency; } 
可以发现在这里设置了iv和Key
1 2 3 4 5 6 7 private  final  Context myContext;private  SQLiteDatabase myDataBase;private  static  String DB_PATH = "/data/data/edu.sharif.ctf/databases/" ;private  static  String DB_NAME = "db.db" ;private  static  String TABLE_NAME = "config" ;public  static  final  String UPDATE_QUERY = "UPDATE "  + TABLE_NAME + " SET d=?" ;public  static  final  String SELECT_QUERY = "SELECT  * FROM "  + TABLE_NAME + " WHERE a=1" ;
是从数据库中得到的,且有给出路径,那么直接
1 adb pull /data/data/edu.sharif.ctf/databases/db.db 
获得数据库,然后就能得到iv和Key了
1 2 iv=a5efdbd57b84ca36  key=37eaae0141f1a3adf8a1dee655853714 
因为AES CBC是对称加密的,所以加解密的Key都是一样的
写个解密AES/CBC/PKCS5Padding的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 import  javax.crypto.Cipher;import  javax.crypto.spec.IvParameterSpec;import  javax.crypto.spec.SecretKeySpec;public  class  Main  {    public  static  final  String iv="a5efdbd57b84ca36" ;     public  static  final  String Key="37eaae0141f1a3adf8a1dee655853714" ;     public  static  final  String en_text="29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84" ;     public  static  void  main (String[] args)           String data=decrypt (en_text,Key,iv);         System.out.println (data);     }     public  static  String decrypt (String paramString1, String paramString2, String paramString3)           try  {             SecretKeySpec localSecretKeySpec = new  SecretKeySpec (hexStringToBytes (paramString2), "AES" );             Cipher localCipher = Cipher.getInstance ("AES/CBC/PKCS5Padding" );             localCipher.init (Cipher.DECRYPT_MODE, localSecretKeySpec, new  IvParameterSpec (paramString3.getBytes ()));             byte[] bytes = localCipher.doFinal (hexStringToBytes (paramString1));             String flag = "" ;             for  (byte b : bytes) {                 flag += (char ) b;             }             return  flag;         } catch  (Exception localException) {             localException.printStackTrace ();         }         return  "" ;     }     public  static  String bytesToHexString (byte[] bytes)           StringBuilder sb = new  StringBuilder ();         for  (byte b : bytes) {             sb.append (String.format("%02x" , Integer.valueOf (b & 255 )));         }         return  sb.toString ();     }     public  static  byte[] hexStringToBytes (String s) {         int  len = s.length ();         byte[] data = new  byte[len / 2 ];         for  (int  i = 0 ; i < len; i += 2 ) {             data[i / 2 ] = (byte) ((Character.digit (s.charAt (i), 16 ) << 4 ) + Character.digit (s.charAt (i + 1 ), 16 ));         }         return  data;     } } 
成功得到fl-ag-IS-se-ri-al-NU-MB-ER
2015-0CTF-vezel 1 2 3 4 5 6 7 8 9 10 11 public  void  confirm (View v)  {    String  s  =  getPackageName();     String  first  =  String.valueOf(getSig(s));     String  next  =  getCrc();     String  flag  =  "0CTF{"  + first + next + "}" ;     if  (flag.equals(this .et.getText().toString())) {         Toast.makeText(this , "Yes!" , 0 ).show();     } else  {         Toast.makeText(this , "0ops!" , 0 ).show();     } } 
主要代码如上,发现是通过getSig和getCrc来拼接获得flag的
那么考虑直接hook getSig和getCrc获得对应的值
1 2 3 4 5 6 7 8 9 10 11 12 13 Java.perform(function() {     var  MainActivity  =  Java.use('com.ctf.vezel.MainActivity' );     MainActivity.getSig.implementation = function(packageName) {         var  result  =  this .getSig(packageName);         console.log("Signature hashCode: "  + result);         return  result;     };     MainActivity.getCrc.implementation = function() {         var  result  =  this .getCrc();         console.log("Crs: "  + result);         return  result;     }; }); 
1 2 Signature hashCode: -183971537  Crs: 1189242199  
得到flag为0CTF{-1839715371189242199}
2017 XMAN HelloSmali2 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 package  com.example.hellosmali.hellosmali;public  class  Digest  {    public  static  boolean  check (String input)  {         if  (input != null  && input.length() != 0 ) {             char [] charinput = input.toCharArray();             StringBuilder  v2  =  new  StringBuilder ();             for  (char  c : charinput) {                 String  intinput  =  Integer.toBinaryString(c);                 while  (intinput.length() < 8 ) {                     intinput = "0"  + intinput;                 }                 v2.append(intinput);             }             while  (v2.length() % 6  != 0 ) {                  v2.append("0" );             }             String  v1  =  String.valueOf(v2);             char [] v4 = new  char [v1.length() / 6 ];             for  (int  i  =  0 ; i < v4.length; i++) {                 int  v6  =  Integer.parseInt(v1.substring(0 , 6 ), 2 );                 v1 = v1.substring(6 );                 v4[i] = "+/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" .charAt(v6);             }             StringBuilder  v3  =  new  StringBuilder (String.valueOf(v4));             if  (input.length() % 3  != 1 ) {                 if  (input.length() % 3  == 2 ) {                     v3.append("!" );                 }             } else  {                 v3.append("!?" );             }             String  key  =  String.valueOf(v3);             if  (key.equals("xsZDluYYreJDyrpDpucZCo!?" )) {                 return  true ;             }             return  false ;         }         return  false ;     } } 
逆向函数:
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 public  class  re1  {    public  static  void  main (String[] args)  {         String key="xsZDluYYreJDyrpDpucZCo!?" ;         String charset="+/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ;         key=key.substring(0 ,key.length()-2 );         System.out.println(key);         StringBuilder binary_string=new  StringBuilder ();         for (int  i=0 ;i<key.length();i++){             char  c=key.charAt(i);             int  index=charset.indexOf(c);             String binary=String.format("%6s" ,Integer.toBinaryString(index)).replace(' ' ,'0' );             binary_string.append(binary);         }         StringBuilder result=new  StringBuilder ();         String binary=binary_string.toString();         for (int  i=0 ;i<binary.length();i+=8 ){             if (i+8 >binary.length()){                 break ;             }             String byteStr=binary.substring(i,i+8 );             int  charCode=Integer.parseInt(byteStr,2 );             result.append((char )charCode);         }         System.out.println(result);     } } 
1 2 xsZDluYYreJDyrpDpucZCo eM_5m4Li_i4_Ea5y 
得到结果
静态分析原生层 2015 - 海峡两岸 - 一个 APK,逆向试试吧 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 public  native boolean testFlag (String str) static  {    System.loadLibrary ("mobicrackNDK" ); } @Override      protected  void  onCreate (Bundle savedInstanceState)       super.onCreate (savedInstanceState);     setContentView (R.layout.activity_crack_me);     this .inputButton = (Button) findViewById (R.id.input_button);     this .pwdEditText = (EditText) findViewById (R.id.pwd);     this .inputButton.setOnClickListener (new  View.OnClickListener () {          @Override              public  void  onClick (View v) {             CrackMe.this .input = CrackMe.this .pwdEditText.getText ().toString ();             if  (CrackMe.this .input != null) {                 if  (CrackMe.this .testFlag (CrackMe.this .input)) {                     Toast.makeText (CrackMe.this , CrackMe.this .input, 1 ).show ();                 } else  {                     Toast.makeText (CrackMe.this , "Wrong flag" , 1 ).show ();                 }             }         }     }); } 
不难看出,testFlag就是核心的函数,加载的mobicrackNDK
但是so文件dump下来直接搜索符号是没有的,因此考虑寻找JNI_Onload函数(通过 JNI_OnLoad 动态注册)
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 jint JNI_OnLoad (JavaVM *vm, void  *reserved)    int  v3;    char  *v4;    int  v5;    FILE *stream;    int  v8;    v8 = 0 ;   printf ("JNI_OnLoad" );   if  ( (*vm)->GetEnv (vm, (void  **)&v8, 65540 ) )     goto  LABEL_6;   v3 = v8;   v4 = classPathName[0 ];   stream = (FILE *)((char  *)&_sF + 168 );   fprintf ((FILE *)((char  *)&_sF + 168 ), "RegisterNatives start for '%s'" , classPathName[0 ]);   v5 = (*(int  (__fastcall **)(int , char  *))(*(_DWORD *)v3 + 24 ))(v3, v4);   if  ( !v5 )   {     fprintf (stream, "Native registration unable to find class '%s'" , v4); LABEL_6:     fputs ("GetEnv failed" , (FILE *)((char  *)&_sF + 168 ));     return  -1 ;   }   if  ( (*(int  (__fastcall **)(int , int , char  **, int ))(*(_DWORD *)v3 + 860 ))(v3, v5, off_400C, 2 ) < 0  )   {     fprintf (stream, "RegisterNatives failed for '%s'" , v4);     goto  LABEL_6;   }   return  65540 ; } 
1 if  ( (*(int  (__fastcall **)(int , int , char  **, int ))(*(_DWORD *)v3 + 860 ))(v3, v5, off_400C, 2 ) < 0  )
这就是注册的函数
1 2 3 4 5 6 7 8 9 .data:0000400 C off_400C        DCD aTestflag           ; DATA XREF: JNI_OnLoad+60 ↑o .data:0000400 C                                         ; JNI_OnLoad+68 ↑o ... .data:0000400 C                                         ; "testFlag"  .data:00004010                  DCD aLjavaLangStrin_0   ; "(Ljava/lang/String;)Z"  .data:00004014                  DCD abcdefghijklmn+1  .data:00004018                  DCD aHello              ; "hello"  .data:0000401 C                 DCD aLjavaLangStrin_1   ; "()Ljava/lang/String;"  .data:00004020                  DCD native_hello+1  .data:00004020  ; .data         ends 
可以看到testFlag就是abcdefghijklmn函数,第二个参数括弧里的是参数,括弧外的是返回值的类型,可以看到testFlag函数的参数类型是String,参数返回类型是bool型
1 bool  __fastcall abcdefghijklmn (int  a1, int  a2, int  a3) 
a1:JNIEnv* 指针(通过 jniEnv 全局变量缓存)a2:未使用a3:输入的 Java 字符串对象(jstring) 
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 bool  __fastcall abcdefghijklmn (JNIEnv *a1, int  a2, void  *a3)   int  v5;    size_t  i;    jmethodID v7;    struct  _jfieldID  *key;    jobject v9;    const  char  *v10;    jclass v12;    const  char  *user_input;    char  s2[12 ];    char  v15[140 ];    if  ( !jniEnv )     jniEnv = a1;                                   memset (&v15[12 ], 0 , 0x80 u);   user_input = (*jniEnv)->GetStringUTFChars (jniEnv, a3, 0 );   v5 = 0 ;   if  ( strlen (user_input) == 0x10  )   {     for  ( i = 0 ; i != 8 ; ++i )       s2[i] = user_input[i] - i;                     v5 = 0 ;     s2[8 ] = 0 ;     if  ( !strcmp (seed[0 ], s2) )                      {       v12 = (*jniEnv)->FindClass (jniEnv, "com/example/mobicrackndk/Calc" );       if  ( !v12 )       {         _android_log_print(4 , "log" , "class,failed" );         goto  LABEL_11;       }       v7 = (*jniEnv)->GetStaticMethodID (jniEnv, v12, "calcKey" , "()V" );       if  ( !v7 )       {         _android_log_print(4 , "log" , "method,failed" ); LABEL_11:         exit (1 );       }       _JNIEnv::CallStaticVoidMethod (jniEnv, v12, v7);       key = (*a1)->GetStaticFieldID (a1, v12, "key" , "Ljava/lang/String;" );       if  ( !key )         _android_log_print(4 , "log" , "fid,failed" );       v9 = (*a1)->GetStaticObjectField (a1, v12, key);       v10 = (*jniEnv)->GetStringUTFChars (jniEnv, v9, 0 );       while  ( i < strlen (v10) + 8  )       {         s2[i + 4 ] = user_input[i] - i;                   ++i;       }       v15[8 ] = 0 ;       return  strcmp (v10, v15) == 0 ;      }   }   return  v5; } 
逆向得到,我们的输入的前八个字节减去索引要与seed[0]相等,后八个字节减去索引要与key相同
1 2 3 4 5 6 7 8 9 10 11 package com.example.mobicrackndk; public  class  Calc  {    public  static  String key;     public  static  void  calcKey ()           StringBuffer sb = new  StringBuffer ("c7^WVHZ," );         key = sb.reverse ().toString ();     } } 
key=",ZHVW^7c"
所以写个脚本即可
1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  My  {    public  static  void  main (String[] args)           String key = "QflMn`fH,ZHVW^7c" ;         char [] chars = key.toCharArray ();         StringBuilder result = new  StringBuilder ();         for  (int  i = 0 ; i < chars.length; i++) {             result.append ((char )(chars[i] + i));           }         System.out.println (result.toString ());     } } 
但是wp说这是错误的
发现是在_init_my中存在对seed的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 size_t  _init_my(){   size_t  i;    size_t  result;    for  ( i = 0 ; ; ++i )   {     result = strlen (seed[0 ]);     if  ( i >= result )       break ;     t[i] = seed[0 ][i] - 3 ;   }   seed[0 ] = t;   byte_4038 = 0 ;   return  result; } 
都减了3,对脚本进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  My  {    public  static  void  main (String[] args)           String key = "QflMn`fH,ZHVW^7c" ;         char [] chars = key.toCharArray ();         StringBuilder result = new  StringBuilder ();         for  (int  i = 0 ; i < chars.length; i++) {             if (i<8 ){                 chars[i]-=3 ;             }             result.append ((char )(chars[i] + i));           }         System.out.println (result.toString ());     } } 
成功
静态分析综合题目 2017 ISCC Crackone 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected  void  onCreate (Bundle savedInstanceState)       super.onCreate (savedInstanceState);      setContentView (R.layout.activity_main);      this .editFlag = (EditText) findViewById (R.id.textView3);      this .button = (Button) findViewById (R.id.button);      this .button.setOnClickListener (new  View.OnClickListener () {           @Override           public  void  onClick (View v) {              String flag = Digest.encode (MainActivity.this .editFlag.getText ().toString ()).trim ();              Log.i ("ISCC" , flag);              int  result = MainActivity.this .checkFlag (flag.getBytes (), flag.length ());              if  (result == 1 ) {                  Toast.makeText (MainActivity.this , "Flag验证成功!" , 0 ).show ();              } else  {                  Toast.makeText (MainActivity.this , "Flag验证失败!" , 0 ).show ();              }          }      });  } 
checkFlag为核心函数
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 _BOOL4 __cdecl native_checkFlag (JNIEnv *a1, int  a2, void  *usrinput, int  size)    char  *usrinput_arr;    int  idx;    char  *end;    char  tmp;    jbyte *src;    src = (jbyte *)malloc (size);   (*a1)->GetByteArrayRegion (a1, usrinput, 0 , size, src);   usrinput_arr = (char  *)malloc (size + 1 );   memset (usrinput_arr, 0 , size + 1 );   memcpy (usrinput_arr, src, size);   if  ( size / 2  > 0  )   {     idx = 0 ;     end = &usrinput_arr[size];     do      {       tmp = usrinput_arr[idx] - 5 ;       usrinput_arr[idx++] = *(end - 1 );       *--end = tmp;     }     while  ( idx != size / 2  );   }   usrinput_arr[size] = 0 ;   free (usrinput_arr);   free (src);   return  strcmp (usrinput_arr, "=0HWYl1SE5UQWFfN?I+PEo.UcshU" ) == 0 ; } 
逆向得到脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  My  {    public  static  void  main (String[] args)  {         StringBuffer  key  =  new  StringBuffer ("=0HWYl1SE5UQWFfN?I+PEo.UcshU" );         String re_key=key.reverse().toString();         char [] chars = re_key.toCharArray();         int  idx=re_key.length();         StringBuffer result=new  StringBuffer ();         for (int  i=0 ; i<idx;i++)         {             if (i<idx/2 )             {                 result.append((char )(chars[i]+5 ));                 continue ;             }             result.append(chars[i]);         }         System.out.println(result);     } } 
1 ZmxhZ3tJU0NDSkFWQU5ES1lYWH0= 
但是在进入checkFlag之前,还对其进行了编码
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 package org.isclab.iscc; public  class  Digest  {    private  static  final  String TAG = "Util/Digest" ;     private  static  String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;     public  static  String encode (String srcStr)           if  (srcStr != null && srcStr.length () != 0 ) {             char [] srcStrCh = srcStr.toCharArray ();             StringBuilder asciiBinStrB = new  StringBuilder ();             for  (char  c : srcStrCh) {                 String asciiBin = Integer.toBinaryString (c);                 while  (asciiBin.length () < 8 ) {                     asciiBin = "0"  + asciiBin;                 }                 asciiBinStrB.append (asciiBin);             }             while  (asciiBinStrB.length () % 6  != 0 ) {                 asciiBinStrB.append ("0" );             }             String asciiBinStr = String.valueOf (asciiBinStrB);             char [] codeCh = new  char [asciiBinStr.length () / 6 ];             for  (int  i = 0 ; i < codeCh.length; i++) {                 int  index = Integer.parseInt (asciiBinStr.substring (0 , 6 ), 2 );                 asciiBinStr = asciiBinStr.substring (6 );                 codeCh[i] = str.charAt (index);             }             StringBuilder code = new  StringBuilder (String.valueOf (codeCh));             if  (srcStr.length () % 3  == 1 ) {                 code.append ("==" );             } else  if  (srcStr.length () % 3  == 2 ) {                 code.append ("=" );             }             for  (int  i2 = 76 ; i2 < code.length (); i2 += 76 ) {                 code.insert (i2, "\r\n" );             }             code.append ("\r\n" );             return  String.valueOf (code);         }         return  srcStr;     } } 
简单分析,可以发现是标准base64,就不用自己写脚本了,cyberchef得到
成功
2018 强网杯 picture lock 在android中,onActivityResult是一个android.app.Activity类预先定义好的回调函数,只有当内部方法调用startActivityForResult(Intent intent, int requestCode)来启动另一个Activity的时候才会调用这个回调函数,被启动的Activity必须在结束前调用setResult来设置返回结果和finish来关闭自己,关闭完之后原Activity就会调用onActivityResult方法,并将之前启动的Activity的resultCode和intent以及一开始调用新Activity传入的requestCode一起传入onActivityResult
MediaStore内容提供器中,_data代表绝对路径
逆向分析: 
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 67 private  String j ()  {    try  {          Signature[] signatureArr = getPackageManager().getPackageInfo("com.a.sample.picturelock" , 64 ).signatures;          MessageDigest  messageDigest  =  MessageDigest.getInstance("MD5" );          for  (Signature signature : signatureArr) {             messageDigest.update(signature.toByteArray());          }         byte [] digest = messageDigest.digest();          StringBuilder  sb  =  new  StringBuilder ();         for  (byte  b : digest) {              int  i  =  b & 255 ;             if  (i < 16 ) {                 sb.append("0" );             }             sb.append(Integer.toHexString(i));         }         return  sb.toString();      } catch  (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {         return  "" ;     } } @Override  protected  void  onActivityResult (int  i, int  i2, Intent intent)  {    super .onActivityResult(i, i2, intent);     switch  (i) {         case  1 :             if  (intent != null ) {                 String[] strArr = {"_data" };                 Cursor  query  =  getContentResolver().query(intent.getData(), strArr, null , null , null );                 query.moveToFirst();                 String  file_path  =  query.getString(query.getColumnIndex(strArr[0 ]));                  query.close();                 enc(file_path, getFilesDir().getAbsolutePath() + file_path.substring(file_path.lastIndexOf("/" )) + ".lock" , j());                 i();                 Toast.makeText(this , String.format("%s encrypting" , file_path), 1 ).show();                 break ;             }             break ;     } } @Override  protected  void  onCreate (Bundle bundle)  {    super .onCreate(bundle);     setContentView(R.layout.activity_main);     findViewById(R.id.encrypt).setOnClickListener(new  View .OnClickListener() {          @Override           public  void  onClick (View view)  {             if  (a.a(this , "android.permission.WRITE_EXTERNAL_STORAGE" ) != 0 ) {                  android.support.v4.a.a.a((Activity) this , new  String []{"android.permission.WRITE_EXTERNAL_STORAGE" }, 1 );                 return ;             }             Intent  intent  =  new  Intent ("android.intent.action.PICK" , (Uri) null );              intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*" );              MainActivity.this .startActivityForResult(intent, 1 );         }     });     findViewById(R.id.refresh).setOnClickListener(new  View .OnClickListener() {          @Override           public  void  onClick (View view)  {             MainActivity.this .i();         }     });     i(); } 
看native层的enc函数 参数(文件的真实路径,目标路径,文件签名的md5),第三个参数可以hook看一眼即可得到:f8c49056e4ccf9a11e090eaf471f418d
不hook也可以直接得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PS D:\android_re\CTF\wiki\picture> apksigner verify --verbose --print-certs picturelock.apk  Verifies Verified using  v1 scheme  (JAR signing) : true Verified using v2 scheme (APK Signature Scheme v2): true Verified using v3 scheme (APK Signature Scheme v3): false Verified using v3.1  scheme (APK Signature Scheme v3.1 ): false Verified using v4 scheme (APK Signature Scheme v4): false Verified for SourceStamp: false Number of signers: 1  Signer #1  certificate DN: CN= a, OU=b, O=c, L=d, ST=e, C=ffSigner #1  certificate SHA-256  digest: ba12c13fd60e0def17ae3aee4e6a816782d0367ff02e37ccad5d6e86870c8e38 Signer #1  certificate SHA-1  digest: 48e7045 ee60d9d8a257c5275e3650609a5cca13e Signer #1  certificate MD5 digest: f8c49056e4ccf9a11e090eaf471f418d Signer #1  key algorithm: RSA Signer #1  key size  (bits): 2048  Signer #1  public  key SHA-256  digest: 4 de55872bc804bf5fccc61c6e42377b24cd6eefc183a800c5807f759f7f9796d Signer #1  public  key SHA-1  digest: dba8196f6f49a1d1291e4c03102bc7225b6f1f25 Signer #1  public  key MD5 digest: d246e0ae52179dfe9daca889ee3133ef 
看到Signer #1 certificate MD5 digest: f8c49056e4ccf9a11e090eaf471f418d就能得到
so程序的enc程序在调试的时候,老是出现错误,因此就在此留下一个小坑吧,等我变强之后再回来搞定😎
android逆向之动态调试 java层调试 工具:android studio、apktools、smalidea、已经root的手机
首先apktools反编译apk为output
1 apktool d app.apk -o output 
然后
1 2 3 4 5 adb shell #adb进入命令行模式 su #切换至超级用户 magisk resetprop ro.debuggable 1 stop start #一定要通过该方式重启 
设置手机的ro.debuggable为1,这样任何程序都可以在我们的手机上进行调试(但是缺点就是每次重启都会失效,因此在启动之后都要设置一次
找到对应的调试进程
1 u0_a137       9261  7805 1120068  56804 SyS_epoll_wait      0 S com.a.sample.picturelock 
PID为9261
因此设置
1 2 PS C:\Users\Lenovo> adb forward tcp:8700  jdwp:9261  8700 
这个8700是android studio中设置的
来到android studio中
设置如上的部分,output是apktool反编译apk为smali的文件,其中output中的smali文件夹要将其Mark为Sources Root权限
之后直接到output中进行调试即可
原生层调试 1 adb shell am start -D -n com.a.sample.picturelock/.MainActivity 
然后在adb shell中push进去ida/dbgsrv下对应的server,运行,自动监听 23946 port
然后
1 adb forward tcp:23946  tcp:23946  
将PC端的端口转发到手机上对应的服务端口
之后ida
Debug->attach->localhost:23946
选中对应的服务即成功attack上
然后android studio也attach上java层即可