<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    巧解一道CTF Android題

    VSole2022-08-10 16:15:40

    用到工具:

    1:jeb

    2:ida

    3:Pycharm

    4:idea

    5:010editor

    6:frida

    1.背景

    網上能看到的相關解題方法基本都是窮舉爆破,還原代碼,這里我巧解一下,

    用到的辦法是XOR解密。無須還原代碼,窮舉爆破。

    原理:經過XOR異或加密的字符串都可以再次異或進行解密獲得key。

    2.開始分析

    把app安裝到手機:

    輸入注冊碼,點擊注冊,提示我們“您的注冊碼已保存”:

    我們獲取一下最頂層activity。最頂層activity是com.gdufs.xman/.RegActivity。

    我們打開jeb工具,定位到當前activity。

    代碼如下:

    package com.gdufs.xman; import android.app.Activity;import android.app.AlertDialog.Builder;import android.content.DialogInterface.OnClickListener;import android.content.DialogInterface;import android.os.Bundle;import android.os.Process;import android.view.View.OnClickListener;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast; public class RegActivity extends Activity {    private Button btn_reg;    private EditText edit_sn;     @Override  // android.app.Activity    public void onCreate(Bundle arg3) {        super.onCreate(arg3);        this.setContentView(0x7F04001B);  // layout:activity_reg        this.btn_reg = (Button)this.findViewById(0x7F0B0054);  // id:button1        this.edit_sn = (EditText)this.findViewById(0x7F0B0055);  // id:editText1        this.btn_reg.setOnClickListener(new View.OnClickListener() {            @Override  // android.view.View$OnClickListener            public void onClick(View arg5) {                String sn = RegActivity.this.edit_sn.getText().toString().trim();                if(sn == null || sn.length() == 0) {                    Toast.makeText(RegActivity.this, "您的輸入為空", 0).show();                    return;                }                 ((MyApp)RegActivity.this.getApplication()).saveSN(sn);                new AlertDialog.Builder(RegActivity.this).setTitle("回復").setMessage("您的注冊碼已保存").setPositiveButton("好吧", new DialogInterface.OnClickListener() {                    @Override  // android.content.DialogInterface$OnClickListener                    public void onClick(DialogInterface arg2, int arg3) {                        Process.killProcess(Process.myPid());                    }                }).show();            }        });    }}
    

    這個是獲取注冊碼編輯框內容:

    String sn = RegActivity.this.edit_sn.getText().toString().trim();
    

    把注冊碼傳入saveSN方法:

    ((MyApp)RegActivity.this.getApplication()).saveSN(sn);
    

    我們看一下saveSN方法,可以看到這是一個native方法。

    package com.gdufs.xman; import android.app.Application;import android.util.Log; public class MyApp extends Application {    public static int m;     static {        MyApp.m = 0;        System.loadLibrary("myjni");    }     public native void initSN() {    }     @Override  // android.app.Application    public void onCreate() {        this.initSN();        Log.d("com.gdufs.xman m=", String.valueOf(MyApp.m));        super.onCreate();    }     public native void saveSN(String arg1) {    }     public native void work() {    }}
    

    我們解包一下apk,獲取到so文件。

    下面進入ida分析。導出函數并沒有相關java的native方法,說明是動態注冊。

    我們看下JNI_ONLOAD函數:

    jint JNI_OnLoad(JavaVM *vm, void *reserved){  if ( !(*vm)->GetEnv(vm, (void **)&g_env, 65542) )  {    _android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");    native_class = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)g_env + 24))(g_env, "com/gdufs/xman/MyApp");    if ( !(*(int (__fastcall **)(int, int, char **, int))(*(_DWORD *)g_env + 860))(g_env, native_class, off_5004, 3) )    {      _android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");      return 65542;    }    _android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");  }  return -1;}
    

    雙擊紅色箭頭的地方:

    可以看到動態注冊的函數。

    下面我們用frida hook一下函數地址。

    frida代碼如下:

    var RevealNativeMethods = function() {  var pSize = Process.pointerSize;  var env = Java.vm.getEnv();  var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html  var jclassAddress2NameMap = {};  function getNativeAddress(idx) {    return env.handle.readPointer().add(idx * pSize).readPointer();  }  // intercepting FindClass to populate Map  Interceptor.attach(getNativeAddress(FindClassIndex), {    onEnter: function(args) {      jclassAddress2NameMap[args[0]] = args[1].readCString();    }  });  // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977  Interceptor.attach(getNativeAddress(RegisterNatives), {    onEnter: function(args) {      for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) {        /*          https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129          typedef struct {             const char* name;             const char* signature;             void* fnPtr;          } JNINativeMethod;        */        var structSize = pSize * 3; // = sizeof(JNINativeMethod)        var methodsPtr = ptr(args[2]);        var signature = methodsPtr.add(i * structSize + pSize).readPointer();        var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr        var jClass = jclassAddress2NameMap[args[0]].split('/');    var methodName = methodsPtr.add(i * structSize).readPointer().readCString();      var str_name_so = "libmyjni.so";    //需要hook的so名      var n_addr_so = Module.findBaseAddress(str_name_so); //加載到內存后 函數地址 = so地址 + 函數偏移        console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({          module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol          package: jClass.slice(0, -1).join('.'),          class: jClass[jClass.length - 1],          method: methodName, // methodsPtr.readPointer().readCString(), // char* name          signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java          address: (fnPtr-n_addr_so).toString(16)        }), '\x1b[39;49;00m');      }    }  });} Java.perform(RevealNativeMethods);
    

    hook結果:

    可以看到saveSN的地址為11f9。

    [Redmi K20 Pro Premium Edition::com.gdufs.xman ]->  {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"initSN","signature":"()V","address":"13b1"} {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"saveSN","signature":"(Ljava/lang/String;)V","address":"11f9"} {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"work","signature":"()V","address":"14cd"}
    

    我們直接ida定位。

    int __fastcall n2(_DWORD *a1, int a2, int a3){  FILE *v5; // r7  _DWORD *v7; // r4  const char *v8; // r3  int v9; // r0  int v10; // r1  _WORD *v11; // r5  _DWORD *v12; // r0  int v13; // r4  int v14; // r3  signed int v15; // r6  const char *v16; // r9  char *v17; // r5  signed int v18; // r10  char v19; // r2  char v20; // r3  _BYTE v21[56]; // [sp+0h] [bp-38h] BYREF   v5 = fopen("/sdcard/reg.dat", "w+");  if ( !v5 )    return j___android_log_print(3, "com.gdufs.xman", byte_2DCA);  v7 = v21;  v8 = "W3_arE_whO_we_ARE";  do  {    v9 = *(_DWORD *)v8;    v8 += 8;    v10 = *((_DWORD *)v8 - 1);    *v7 = v9;    v7[1] = v10;    v11 = v7 + 2;    v7 += 2;  }  while ( v8 != "E" );  v12 = a1;  v13 = 2016;  *v11 = *(_WORD *)v8;  v14 = *a1;  v15 = 0;  v16 = (const char *)(*(int (__fastcall **)(_DWORD *, int, _DWORD))(v14 + 676))(v12, a3, 0);  v17 = (char *)v16;  v18 = strlen(v16);  while ( v15 < v18 )  {    if ( v15 % 3 == 1 )    {      v13 = (v13 + 5) % 16;      v19 = v21[v13 + 1];    }    else if ( v15 % 3 == 2 )    {      v13 = (v13 + 7) % 15;      v19 = v21[v13 + 2];    }    else    {      v13 = (v13 + 3) % 13;      v19 = v21[v13 + 3];    }    v20 = *v17;    ++v15;    *v17++ = v20 ^ v19;  }  fputs(v16, v5);  return j_fclose(v5);}
    

    為了方便分析這邊導入一下jni頭文件。

    修改一下第一個參數為jnienv,第三個參數為我們的注冊碼。 

    如下代碼在sd卡目錄創建了一個文件叫reg.dat。

    v5 = fopen("/sdcard/reg.dat", "w+");
    

    如下代碼進行寫入:

    fputs(v16, v5);
    

    我們看一下v16相關邏輯。

    可以看到v16給了v17,v17每一個字符進行異或操作。

    *v17++ = v20 ^ v19;
    

    也就是說有多少字符就異或出多少個字符,我們去sdcard把文件拉出來。

    拖入010editor,可以看到我們輸入的是13個1,異或出13個數據。

    我們再去分析一下是如何讀取這個文件的。

    因為當我們輸入注冊碼后,點擊確定就結束進程了,那么啟動程序肯定會讀取的。

    new AlertDialog.Builder(RegActivity.this).setTitle("回復").setMessage("您的注冊碼已保存").setPositiveButton("好吧", new DialogInterface.OnClickListener() {                  @Override  // android.content.DialogInterface$OnClickListener                  public void onClick(DialogInterface arg2, int arg3) {                      Process.killProcess(Process.myPid());                  }              }).show();
    

    我們去看一下入口activity,可以看到入口activity是com.gdufs.xman.MainActivity。

    <manifest android:versionCode="1" android:versionName="1.0" package="com.gdufs.xman" platformBuildVersionCode="23" platformBuildVersionName="6.0-2704002" xmlns:android="http://schemas.android.com/apk/res/android">  <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23"/>  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>  <application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/aaron" android:label="@string/app_name" android:name="com.gdufs.xman.MyApp" android:theme="@style/AppTheme">    <activity android:label="@string/app_name" android:name="com.gdufs.xman.MainActivity">      <intent-filter>        <action android:name="android.intent.action.MAIN"/>        <category android:name="android.intent.category.LAUNCHER"/>      intent-filter>    activity>    <activity android:label="@string/title_activity_reg" android:name="com.gdufs.xman.RegActivity"/>  application>manifest>
    

    我們定位到這個activity:

    package com.gdufs.xman; import android.app.Activity;import android.app.AlertDialog.Builder;import android.content.ComponentName;import android.content.DialogInterface.OnClickListener;import android.content.DialogInterface;import android.content.Intent;import android.os.Bundle;import android.os.Process;import android.util.Log;import android.view.Menu;import android.view.View.OnClickListener;import android.view.View;import android.widget.Button;import android.widget.Toast; public class MainActivity extends Activity {    private Button btn1;    private static String workString;     public void doRegister() {        new AlertDialog.Builder(this).setTitle("注冊").setMessage("Flag就在前方!").setPositiveButton("注冊", new DialogInterface.OnClickListener() {            @Override  // android.content.DialogInterface$OnClickListener            public void onClick(DialogInterface dialog, int which) {                Intent intent = new Intent();                intent.setComponent(new ComponentName("com.gdufs.xman", "com.gdufs.xman.RegActivity"));                MainActivity.this.startActivity(intent);                MainActivity.this.finish();            }        }).setNegativeButton("不玩了", new DialogInterface.OnClickListener() {            @Override  // android.content.DialogInterface$OnClickListener            public void onClick(DialogInterface dialog, int which) {                Process.killProcess(Process.myPid());            }        }).show();    }     @Override  // android.app.Activity    public void onCreate(Bundle savedInstanceState) {        String str2;        super.onCreate(savedInstanceState);        this.setContentView(0x7F04001A);  // layout:activity_main        Log.d("com.gdufs.xman m=", "Xman");        this.getApplication();        int m = MyApp.m;        if(m == 0) {            str2 = "未注冊";        }        else {            str2 = m == 1 ? "已注冊" : "已混亂";        }         this.setTitle("Xman" + str2);        this.btn1 = (Button)this.findViewById(0x7F0B0054);  // id:button1        this.btn1.setOnClickListener(new View.OnClickListener() {            @Override  // android.view.View$OnClickListener            public void onClick(View v) {                MainActivity.this.getApplication();                if(MyApp.m == 0) {                    MainActivity.this.doRegister();                    return;                }                 ((MyApp)MainActivity.this.getApplication()).work();                Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();            }        });    }     @Override  // android.app.Activity    public boolean onCreateOptionsMenu(Menu menu) {        this.getMenuInflater().inflate(0x7F0D0000, menu);  // menu:menu_main        return 1;    }     public void work(String str) {        MainActivity.workString = str;    }}
    

    可以看到當m=0時提示未注冊,等于1時提示注冊。

    if(m == 0) {           str2 = "未注冊";       }       else {           str2 = m == 1 ? "已注冊" : "已混亂";       }
    

    當m=0時調用了另外的方法doRegister。這個方法其實是前面分析的方法,調用了saveSN方法。

    if(MyApp.m == 0) {                   MainActivity.this.doRegister();                   return;               }
    

    我們看一下后面這個塊代碼,調用了work方法,這個方法的實現是在native層,我們定位一下。

    ((MyApp)MainActivity.this.getApplication()).work();               Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
    public native void work() { }
    

    work在ida的地址是14cd。

    [Redmi K20 Pro Premium Edition::com.gdufs.xman ]->  {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"initSN","signature":"()V","address":"13b1"} {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"saveSN","signature":"(Ljava/lang/String;)V","address":"11f9"} {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"work","signature":"()V","address":"14cd"}
    

    我們去ida看一下:

    int __fastcall n3(int a1){  int Value; // r0  int v3; // r0  void *v4; // r1  bool v5; // zf   n1(a1);  Value = getValue(a1);  if ( Value )  {    v5 = Value == 1;    v3 = a1;    if ( v5 )      v4 = &unk_2E6B;    else      v4 = &unk_2E95;  }  else  {    v3 = a1;    v4 = &unk_2E5B;  }  return callWork(v3, v4);}
    

    我們進入一下n1函數,可以看到這里打開了reg.dat文件進行讀取操作:

    int __fastcall n1(int a1){  FILE *v2; // r0  FILE *v3; // r4  int v4; // r0  int v5; // r7  void *v6; // r5  int v8; // r0  int v9; // r1   v2 = fopen("/sdcard/reg.dat", "r+");  v3 = v2;  if ( !v2 )  {    v4 = a1;    return setValue(v4, 0);  }  fseek(v2, 0, 2);  v5 = ftell(v3);  v6 = malloc(v5 + 1);  if ( !v6 )  {    fclose(v3);    v4 = a1;    return setValue(v4, 0);  }  fseek(v3, 0, 0);  fread(v6, v5, 1u, v3);  *((_BYTE *)v6 + v5) = 0;  if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") )  {    v8 = a1;    v9 = 1;  }  else  {    v8 = a1;    v9 = 0;  }  setValue(v8, v9);  return j_fclose(v3);}
    

    我們看一下關鍵代碼塊。v6是從reg.dat文件里讀取出來的數據。進行比較,如果相同就設置為1,不相同就設置為0。

    strcmp函數比較返回值如果相同返回0,所以需要取反。

    if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") ){  v8 = a1;  v9 = 1;}else{  v8 = a1;  v9 = 0;}
    

    我們看一下setvalue方法。這個方法把0,1這兩個值進行了設置。

    進入后我們改一下第一個參數為JNIEnv*,方便識別。

    int __fastcall setValue(_JNIEnv *a1, int a2){  jclass v4; // r5  jfieldID v5; // r0   v4 = a1->functions->FindClass(a1, "com/gdufs/xman/MyApp");  v5 = a1->functions->GetStaticFieldID(a1, v4, "m", "I");  return ((int (__fastcall *)(_JNIEnv *, jclass, jfieldID, int))a1->functions->SetStaticIntField)(a1, v4, v5, a2);}
    

    可以看到這里獲取了com/gdufs/xman/MyApp類里面的m屬性,類型為int類型,并設置了屬性值。

    對應java代碼如下:

    package com.gdufs.xman; import android.app.Application;import android.util.Log; public class MyApp extends Application {    public static int m;     static {        MyApp.m = 0;        System.loadLibrary("myjni");    }     public native void initSN() {    }     @Override  // android.app.Application    public void onCreate() {        this.initSN();        Log.d("com.gdufs.xman m=", String.valueOf(MyApp.m));        super.onCreate();    }     public native void saveSN(String arg1) {    }     public native void work() {    }}
    

    我們已經知道,如果m等于1,那么就是注冊成功。

    那么怎樣才會等于1呢?只要v6的值為EoPAoY62@ElRD就行,v6的值來源于reg.dat,EoPAoY62@ElRD這個是真碼。

    為13位的,也就是說需要輸入13位注冊碼,才能異或出這個真碼。

    !strcmp((const char *)v6, "EoPAoY62@ElRD")
    

    那我們直接反解真碼即可。如下是輸入的注冊碼與對應reg.dat里面的數據:

    1111111111111
    31 31 31 31 31 31 31 31 31 31 31 31 31
    FnPFnPFnPFnPF
    46 6E 50 46 6E 50 46 6E 50 46 6E 50 46
    

    我們反解一下密碼,代碼如下:

    public static  void Xor(){         int xorData[]={0x46,0x6E,0x50,0x46,0x6E,0x50,0x46,0x6E,0x50,0x46,0X6E,0X50,0X46};        int xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};        System.out.print("[");        for (int i = 0; i < xorData.length; i++) { //            System.out.print("0x"+Integer.toHexString (xorData[i]^xorDataMy[i]));//            if(i//                System.out.print(",");//            }            System.out.print(xorData[i]^xorDataMy[i]);            if(i-1){                System.out.print(",");            }        }        System.out.print("]");     }
    

    獲得的XOR密碼為:

    [119,95,97,119,95,97,119,95,97,119,95,97,119]

    我們開始解密,真碼的十六進制

    45 6F 50 41 6F 59 36 32 40 45 6C 52 44

    我們打印一下需要異或的真碼數據。

      public static  void Xor1(){         int xorData[]={0x45,0x6f,0x50,0x41,0x6f,0x59,0x36,0x32,0x40,0x45,0x6c,0x52,0x44};        System.out.print("[");        for (int i = 0; i < xorData.length; i++) { //            System.out.print("0x"+Integer.toHexString (xorData[i]^xorDataMy[i]));//            if(i//                System.out.print(",");//            }            System.out.print(xorData[i]);            if(i-1){                System.out.print(",");            }        }        System.out.print("]");     }
    

    我們寫個Python代碼進行解密:

    import binascii xorkey =[119,95,97,119,95,97,119,95,97,119,95,97,119]realkey=[69,111,80,65,111,89,54,50,64,69,108,82,68] def XorDecy(data, l):    ret = []    for i in range(l):        ret.append(data[i] ^ xorkey[i])    s = ''    for i in ret:        s += chr(i)    print(s)    return ret    XorDecy(realkey,len(realkey))
    

    解密結果為:201608Am!2333

    我們輸入解密結果:

    得到flag為:xman{201608Am!2333}

    charchar函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    無意中看到ch1ng師傅的文章覺得很有趣,不得不感嘆師傅太厲害了,但我一看那長篇的函數總覺得會有更騷的東西,所幸還真的有,借此機會就發出來一探究竟,同時也不得不感慨下RFC文檔的妙處,當然本文針對的技術也僅僅只是在流量層面上waf的繞過。Pre很神奇對吧,當然這不是終點,接下來我們就來一探究竟。前置這里簡單說一下師傅的思路部署與處理上傳war的servlet是?
    記一次網站滲透過程
    2022-09-13 08:37:27
    前幾天記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。
    一、序言 記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。 二、滲透過程 1. 無意點開一個網站,發現網站比較小,且看起來比較老,然后發現logo沒有改,于是乎去百度搜索這個cms,發現有通用漏洞,這里貼一個鏈接:Beecms 通用漏洞(https://lin
    釣魚小技巧-XLM
    2022-01-21 21:30:11
    隨后保存為啟用宏的文檔。而在實戰環境中,我們更關注的是能否執行我們的shellcode。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    最近在分析JDK7u21的Gadgets,有兩個不解之處,閱讀前輩們的文章發現并未提起。1.為什么有的POC入口是LinkedHashSet,有的是HashSet,兩個都可以觸發嗎?
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类