<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>

    分析一道簡單安卓中級題

    VSole2022-04-28 16:00:54

    安裝app后打開,點擊驗證提示我們flag格式錯誤,請重試。

    我們打開jeb分析工具進行分析。

    紅色圖部分為源碼目錄,也就是dex文件反編譯后的源碼。

    R文件存儲了資源相關的id,比如圖片資源,按鈕,文字等信息都存儲在這個R文件里。

    在需要使用時就用R.xxx.xxx調用即可。例如R.anim.abc_fade_in,因為都是static修飾的,

    所以可以直接打點直接調用,不需要創建對象。

     

    如何知道當前類是什么呢?

    執行如下命令即可獲取,方便進行分析。

    adb shell dumpsys activity top

     

    因為手機沒有root,所以這里以虛擬機演示。然后虛擬機又不支持26版本的sdkapp,所以只好以mt管理器為例子。

     

    bin.mt.plus為包

    .Main為activity,也就是類

    bin.mt.plus/.Main

    因為這里類就怎么一個MainActivity所以我直接分析了。

    如下MainActivity為關鍵類,里面有點擊按鈕后彈出的相關信息。

    源碼刨析

    package cn.pojie52.cm01;//當前類MainActivity所在的包,也就是文件夾 import android.os.Bundle;//導入Bundle相關方法,變量import android.view.View.OnClickListener;//導入按鈕點擊事件相關方法,變量import android.view.View;//導入視圖相關方法,變量。import android.widget.EditText;//導入編輯框相關方法,變量import android.widget.Toast;//導入吐司彈窗相關方法,變量import androidx.appcompat.app.AppCompatActivity;//主類,一個activity必須要繼承一個activity父類  //加載so文件的方法。//static{}為靜態代碼塊,在MainActivity創建時會優先執行里面的方法//調用System對象里面的LoadLibrary方法,傳入so文件的名字,去除lib,.so,后的名字//在調用LoadLibrary方法后,底層會把lib,so進行拼接,然后去lib目錄里尋找相關的so進行加載static {        System.loadLibrary("native-lib");} //如下是一個native方法,返回值為Boolean類型,也就是true和false,//方法名為check,參數為String類型//因為是native方法,所以實現邏輯必定在so里面,而so里面的代碼是由c或者c++編譯的//所以要執行這個System.loadLibrary("native-lib");代碼,把so文件提前加載到內存中public native boolean check(String arg1) {} @Override  // androidx.appcompat.app.AppCompatActivity//@Override說明這是一個重寫方法,//這個方法OnCreate在AppCompatActivity類里//因為主類MainActivity后面加了extends AppCompatActivity//說明AppCompatActivity里的方法,變量都會繼承過來,也就是說我們可以直接使用//參數為Bundle    protected void onCreate(Bundle arg3) {        super.onCreate(arg3);//調用父類的OnCreate方法放入arg3參數//父類也就是AppCompatActivity        this.setContentView(0x7F0A001C);  // layout:activity_main//調用setContentView方法初始化界面//也就是我們在開始的時候看到的那個界面,//有個編輯框,一個驗證按鈕        EditText v3 = (EditText)this.findViewById(0x7F070058);  // id:flag//調用findViewById方法傳入編輯框的id,然后轉為EditText,//因為獲取到的是一個view對象,這個view對象范圍太大了//我們直接轉為EditText方便些        this.findViewById(0x7F070045).setOnClickListener(new View.OnClickListener() {  // id:check//這個是一種鏈式編程寫法,簡便,省去了定義變量來接收他//這個是調用findViewById傳入按鈕的id,//this指的是當前activity,因為繼承了AppCompatActivity類//所以我們可以直接使用里面的findViewById方法//調用這個方法后返回的是一個對象,然后調用setOnclickListener方法//傳入匿名內部類對象,給按鈕綁定一個監聽事件//onClick方法是重寫的            @Override  // android.view.View$OnClickListener            public void onClick(View arg4) {             }        });} 在Onclick方法里的代碼邏輯         String v4 = v3.getText().toString().trim();//v3為編輯框,獲取編輯框里面的信息,轉為string,去除空格,//賦值為string變量v4                if(v4.length() != 30) {//判斷v4的長度是否等于30//如果不等于就提示flag格式錯誤,請重試//return為返回                    Toast.makeText(MainActivity.this, "flag格式錯誤,請重試", 0).show();//調用Toast對象里面的makeText方法,放入MainActiviy//因為onclick方法在內部類里面,所以是MainActivity.this,而不是this.MainActiviy                    return;                }                 if(MainActivity.this.check(v4)) {//調用native層的check方法,傳入v4字符串                    Toast.makeText(MainActivity.this, "恭喜你,驗證正確!", 0).show();//如果check方法的返回值為true那么彈出恭喜你,驗證正確的提示                    return;                }                 Toast.makeText(MainActivity.this, "flag錯誤,再接再厲", 0).show();//否則提示flag錯誤,再接再厲            }
    

    我們已經知道了具體邏輯是在so層,且密碼長度是30位的。

    接下來我們進入so層進行分析,我們需要用到的工具是ida。

    在拖入ida前,我們需要把apk包進行解壓,獲取lib目錄里面的so文件。

    拖入64位的ida。

    我們默認即可,點擊ok。

    因為這個check方法不是系統方法,所以在export就可以看到。

    Java_cn_pojie52_cm01_MainActivitycheck

    在ida中顯示為Java類名_方法名。

    我們雙擊進入。

    按tab鍵轉為c偽代碼。

    我們改一下參數,第一個參數固定為JNIEnv*。

    在改之前,我們需要導入jni的頭文件,Jni頭文件里存放了相關的jni方法,方便分析。

     

    第二個參數要么是jclass,要么是jobject。

    如果是類就是jclass,如果是對象就是jobject。

    可以看到這個check方法并沒有static修飾,也就是說要用對象才能調用,也就是說是jobject。

    我們對著int64的參數右鍵,把他修改成jobject。

    這個check方法的參數是一個string類型的,所以這個native方法第三個參數也是string類型的,在c語言中string被定義為jstring。

    查找jni頭文件可以知道。這個jstring是一個jstring,而jstring是jobject,所以這個jstring是jobject類型。

    Typedef是給一個變量進行重定義。*這個代表這是一個一級指針,多少個星花代表幾級指針。

    指針是一個地址,地址里面有一塊空間,用來存放變量的數據。

    比如int a=0;這個a是一個地址,給這個地址取名為a,地址里面存放了0這個數據。當我們&a時就是獲取a的地址,相當于把a還原成一個地址,我們可以用Printf(“%x”,&a);來打印這個地址。

    我們修改一下第三個參數。

    如下是改完后的完整代碼:

    __int64 __fastcall Java_cn_pojie52_cm01_MainActivity_check(_JNIEnv *a1, jobject a2, jstring a3){  const char *v5; // x21  size_t v6; // w0  int v7; // w0  __int64 v8; // x0  _BYTE *v9; // x0  int8x16_t v10; // q0  int8x16_t v11; // q4  int8x16_t v12; // q2  int8x16_t v13; // q5  int8x16_t v14; // q1  int8x16_t v15; // q0  __int64 v16; // x8  unsigned int v17; // w19  _BYTE v19[33]; // [xsp+0h] [xbp-A0h]  int v20; // [xsp+21h] [xbp-7Fh]  char v21; // [xsp+25h] [xbp-7Bh]  char v22; // [xsp+26h] [xbp-7Ah]  char v23; // [xsp+27h] [xbp-79h]  char v24; // [xsp+28h] [xbp-78h]  char dest[16]; // [xsp+38h] [xbp-68h] BYREF  __int128 v26; // [xsp+48h] [xbp-58h]  __int128 v27; // [xsp+58h] [xbp-48h]  __int128 v28; // [xsp+68h] [xbp-38h]  __int64 v29; // [xsp+78h] [xbp-28h]   v29 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);  if ( a1->functions->GetStringUTFLength((JNIEnv *)a1, a3) == 30 )  {    v5 = a1->functions->GetStringUTFChars(a1, a3, 0LL);    v28 = 0u;    v27 = 0u;    v26 = 0u;    *(_OWORD *)dest = 0u;    v6 = strlen(v5);    strncpy(dest, v5, v6);    a1->functions->ReleaseStringUTFChars((JNIEnv *)a1, a3, v5);    v7 = strlen(dest);    sub_B90((int)dest, v7, "areyousure??????");    v8 = strlen(dest);    v9 = (_BYTE *)sub_D90(dest, v8);    *(_OWORD *)v19 = unk_11A1;    *(_OWORD *)&v19[16] = unk_11B1;    *(_QWORD *)&v19[25] = unk_11BA;    v10.n128_u64[0] = 0xB2B2B2B2B2B2B2B2LL;    v10.n128_u64[1] = 0xB2B2B2B2B2B2B2B2LL;    v11.n128_u64[0] = 0xFEFEFEFEFEFEFEFELL;    v11.n128_u64[1] = 0xFEFEFEFEFEFEFEFELL;    v19[0] = 53;    v12 = veorq_s8(            vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[1], v10), (int8x16_t)xmmword_1130), (int8x16_t)xmmword_1140),            v11);    v13.n128_u64[0] = 0x101010101010101LL;    v13.n128_u64[1] = 0x101010101010101LL;    v14.n128_u64[0] = 0x3E3E3E3E3E3E3E3ELL;    v14.n128_u64[1] = 0x3E3E3E3E3E3E3E3ELL;    *(int8x16_t *)&v19[1] = vaddq_s8(                              veorq_s8(                                vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v12, 7uLL), vshlq_n_s8(v12, 1uLL))),                                (int8x16_t)xmmword_1150),                              v14);    v20 = 1782990162;    v15 = veorq_s8(            vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[17], v10), (int8x16_t)xmmword_1160), (int8x16_t)xmmword_1170),            v11);    v21 = ((1          - ((2 * ((((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE) & 0x80) != 0))) ^ 0x25)        + 62;    v16 = 0LL;    v22 = ((1          - ((2 * ((((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE) & 0x80) != 0))) ^ 0x26)        + 62;    *(int8x16_t *)&v19[17] = vaddq_s8(                               veorq_s8(                                 vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v15, 7uLL), vshlq_n_s8(v15, 1uLL))),                                 (int8x16_t)xmmword_1180),                               v14);    v23 = ((1          - ((2 * ((((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE) & 0x80) != 0))) ^ 0x27)        + 62;    v24 = ((1          - ((2 * ((((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE) & 0x80) != 0))) ^ 0x28)        + 62;    while ( v9[v16] == v19[v16] )    {      if ( v9[v16] )      {        if ( ++v16 != 41 )          continue;      }      v17 = 1;      goto LABEL_9;    }    v17 = 0;LABEL_9:    free(v9);  }  else  {    return 0;  }  return v17;}
    

    為了方便分析,我們把參數的名字改一下。

    重復前面的操作。

    //判斷password的長度是否為30if ( env->functions->GetStringUTFLength((JNIEnv *)env, password) == 30 )//這個env是一個指針,指針里面有一個函數指針,函數指針里面有一個GetStringUTFLength函數//這個函數用于判斷字符串的長度//這個涉及到了結構體相關知識//把password轉為c語言中的char類型env->functions->GetStringUTFChars(env, password, 0LL);
    

    為了方便,我提前改好了相關名字。

    password_length = strlen(password_);//獲取password_的長度//把password_拷貝到password__里面,拷貝長度為password_lengthstrncpy(password__, password_, password_length);  //釋放password_指向的字符串空間//也就是歸還空間,讓其他程序使用env->functions->ReleaseStringUTFChars((JNIEnv *)env, password, password_);//獲取password__的長度password__length = strlen(password__);
    

    細心的朋友可以知道這個16是有問題的,因為我們的password是30位的,然而這個password確是16位的。

    char password__[16]
    

    這里我改為了32。

    我們看一下下面的這個函數,可以看到傳入了我們password__,password_length,密碼與長度,還有"areyousure??????"。

    我們雙擊進入這個函數,然后修改一下如下參數。

    //獲取threeStr的長度threeStrLen = strlen(threeStr);do  {v9 = *((unsigned __int8 *)v20 + v7); //v7加上轉換為無符號int類型的指針v20,也就是加上無符號int類型的步長。然后取*取出相加后里面空間的值v10 = v8 + v9 + (unsigned __int8)threeStr[v7 % threeStrLen];//v9加上v8然后加上無符號整型的threeStr[v7%threeStrlen];//對v7以threeStrlen進行取余,然后threeStr[取余的值]取出threeStr里面的字符串數據    v11 = v10 + 255;//v10加上255賦值給v11    if ( v10 >= 0 )//如果v10大于等于0就執行v10賦值給v11的操作      v11 = v10;把v10賦值給v11    v8 = v10 - (v11 & 0xFFFFFF00);//對v11與0XFFFFFF00進行與運算,然后v10減去運算結果,賦值給v8*((_BYTE *)v20 + v7++) = *((_BYTE *)v20 + v8);//把v20轉為Byte*類型然后加上v8,步長為Byte *的長度,然后*取值//然后把值賦值給把v20轉為Byte*類型加上自增的v7,獲得值后進行取*,賦值給這個取*花后的值*((_BYTE *)v20 + v8) = v9;//把v9賦值給把v20轉為Byte*類型然后加上v8個Byte*步長里面的值  }  while ( v7 != 256 );如果v7不等于256就一直循環 //判斷如果password__length是否為空//在c語言中,非0就是true,也就是說可以用于判斷是否為空if ( password__length )  {    v12 = 0;//給v12賦值0    v13 = 0;給v13賦值0v14 = password__length;//把密碼長度賦值給v14    do    {      v15 = v12 + 1; //把v12加上1的值賦值給v15      if ( v12 + 1 >= 0 )//判斷v12+1是否大于等于0        v16 = v12 + 1;//把v12加1的值賦值給v16      else        v16 = v12 + 256;//把v12加上256賦值給v16      v12 = v15 - (v16 & 0xFFFFFF00);//對v16與0XFFFFFF00進行與運算,然后v15減去運算結果,賦值給v12      v17 = *((unsigned __int8 *)v20 + v12);//把v20轉為無符號int整型指針然后加上這個指針的步長,//對這個值進行取星花賦值給v17      v18 = v13 + v17;//把v13加上v17的值賦值給v18      v19 = v18 + 255;//把v18加上255的值賦值給v19      if ( v18 >= 0 )//判斷v18是否大于0        v19 = v18;//如果大于0就把v18賦值給v19      v13 = v18 - (v19 & 0xFFFFFF00);//對v19與0XFFFFFF00進行與運算,然后v18減去運算結果,賦值給v13      --v14;//v14減一      *((_BYTE *)v20 + v12) = *((_BYTE *)v20 + v13);//把v20轉為Byte*類型加上v13個Byte*的步長,然后取*取出里面的值,//把這個值賦值給v20轉為Byte*類型加上v12個Byte*步長的地址空間       *((_BYTE *)v20 + v13) = v17;//把v17的值賦值給v20轉為Byte*類型加上v13個Byte*步長的地址空間       *password__++ ^= *((_BYTE *)v20 + (unsigned __int8)(*((_BYTE *)v20 + v12) + v17));//取出password里面的值//把v20轉為Byte*類型,加上把V20轉為Byte*類型后加上v12進行取*加上v17后轉為無符號int類型//然后進行取*取出里面的值,把值與password進行異或然后賦值給password    }    while ( v14 );//如果v14不為空就一直循環  }
    

    這兩塊代碼分別操作著threeStr,還有password。

    如下代碼是關鍵:

    while ( v9[v16] == v19[v16] ) //循環對比v9[v16]的值與v19[v16]的值    {      if ( v9[v16] ) //判斷v9[16]里是否有值      {        if ( ++v16 != 41 )//如果v16加一后不等于41就continue跳過代碼          continue;      }      v17 = 1;//如果為1那么flag正確      goto LABEL_9;//跳到LABEL_9的位置    }    v17 = 0;//如果為0那么flag錯誤LABEL_9:    free(v9);//釋放內存  }  else  {    return 0;  }return v17;//影響驗證結果
    

    下面hook一下這個函數。看看這個password變成了什么。

    import frida, sys//因為是frida hook,所以要導入frida模塊,//讀取系統輸入需要sys模塊 jscode = ''' function inline_hook() { var so_addr = Module.findBaseAddress("libnative-lib.so");     if (so_addr) {        console.log("so_addr:", so_addr);        var addr_b90 = so_addr.add(0xb90);        var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);        var arg1 = Memory.allocUtf8String('111111111111111111111111111111');        var arg2 = 30;        var arg3 = Memory.allocUtf8String('areyousure??????');        var ret_b90 = sub_b90(arg1,arg2,arg3);        console.log(Memory.readByteArray(arg1,64));          var addr_d90 = so_addr.add(0xd90);        var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);        var arg1 = Memory.allocUtf8String('111111111111111111111111111111');        var arg2 = 30;        var ret_d90 = sub_d90(arg1,arg2);        console.log(Memory.readByteArray(ret_d90,64));      } }setImmediate(inline_hook) '''def on_message(message, data):    if message['type'] == 'send':        print(" {0}".format(message['payload']))    else:        print(message)pass#print(frida.enumerate_devices())# 查找USB設備并附加到目標進程device =  frida.get_remote_device()#pid = device.spawn(["com.live.xctv"]) #session = device.attach(pid)session =device.attach('cn.pojie52.cm01') #這里是要注入的apk包名# 在目標進程里創建腳本script = session.create_script(jscode)# 注冊消息回調script.on('message', on_message)print(' Start attach')# 加載創建好的javascript腳本script.load()# 讀取系統輸入sys.stdin.read()  var so_addr = Module.findBaseAddress("libnative-lib.so");  //查找so的基址。
    

    在ida中基址為0000000000000,在動態調試時基址會變,所以我們需要通過基址加上函數的偏移來定位一個函數。

    在這里我們需要hook的函數是sub_B90()。

    我們按tab鍵查看函數偏移。

    console.log("so_addr:", so_addr);//打印so的基址 var addr_b90 = so_addr.add(0xb90);//so的基址加上b90就是這個函數的地址 if (so_addr) //判斷基礎是否獲取到了  var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);//創建一個本地函數,類似于指針函數,給指針函數賦值函數的地址。//然后進行調用,參數為_BYTE *password__, unsigned int password__length, char *threeStr//返回值為unsigned __int64//返回值對應'int',參數對應著['pointer', 'int','pointer']下面為封裝函數的參數,進行調用//分配內存創建111111111111111111111111111111字符串var arg1 = Memory.allocUtf8String('111111111111111111111111111111');var arg2 = 30;//長度//分配內存創建areyousure??????字符串var arg3 = Memory.allocUtf8String('areyousure??????');//調用sub_b90函數,傳入arg1,arg2,arg3var ret_b90 = sub_b90(arg1,arg2,arg3);//返回值為ret_b90Memory.readByteArray(arg1,64)//讀取arg1內存中數據,也就是password,讀64位byte的數據console.log(Memory.readByteArray(arg1,64));//打印讀取出來的password
    

    這里還有一個函數傳入了password還有密碼的長度。

    var addr_d90 = so_addr.add(0xd90);//基址加上0xb90的偏移就是sub_d90函數的地址 var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);//創建一個本地函數,類似于指針函數,給指針函數賦值函數的地址。//然后進行調用,參數為char *a1, __int64 a2//返回值為void * 萬能指針,任何指針都可以賦值//返回值對應'pointer',參數對應著['pointer', 'int'] //分配內存創建111111111111111111111111111111字符串var arg1 = Memory.allocUtf8String('111111111111111111111111111111');//密碼長度var arg2 = 30;//調用sub_d90()函數,輸入arg1,arg2參數var ret_d90 = sub_d90(arg1,arg2);//返回值賦值給ret_d90讀取sub_d90返回值地址里面的空間的64位byte的數據console.log(Memory.readByteArray(ret_d90,64));
    

    以下是dump的數據。

    我們輸入的密碼是111111111111111111111111111111。當執行完異或操作后變為了下面的數據。

    30個1對應著16進制為30個0x31。在ASCII碼中,49的ASCII碼為‘1’,這個49是10進制的,轉為16進制就是0x31。

    e0,6b,37,a1,75,d7,f6,d4,ef,19,c6,c3,57,a0,f9,b4

    73,ee,c8,d1,b3,30,1a,0a,09,52,06,8c,1f,7c

    在計算機中,異或運算是可以進行解密的。

    10 xor 5 =15

    15 xor 10 = 5

    把10當成我們輸入的密碼,把5當成要異或的值,把15當成異或后的值

    我們知道了異或后的值,當我們再次異或我們的密碼時,就可以得到要異或的值。

    e0,6b,37,a1,75,d7,f6,d4,ef,19,c6,c3,57,a0,f9,b4

    73,ee,c8,d1,b3,30,1a,0a,09,52,06,8c,1f,7c

    與30個0x31進行異或即可得到密碼。

    public static  void Xor(){     int xorData[]={0xe0,0x6b,0x37,0xa1,0x75,0xd7,0xf6,0xd4,0xef,0x19,0xc6,0xc3,0x57,0xa0,0xf9,0xb4,    0x73,0xee,0xc8,0xd1,0xb3,0x30,0x1a,0x0a,0x09,0x52,0x06,0x8c,0x1f,0x7c};    int xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,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(xorData[i]^xorDataMy[i]);        if(i-1){        System.out.print(",");        }     }    System.out.print("]"); }
    

    得到結果如下:

    [209,90,6,144,68,230,199,229,222,40,247,242,102,145,200,133,66,223,249,224,130,1,43,59,56,99,55,189,46,77]

    如下是sub_d90函數的返回進行地址空間dump的數據,可以看到全是字母。

    我們可以嘗試推斷一下是不是某種加密算法,比如aes,base64,md5加密。

    aes加密是需要秘鑰的,有時還需要iv偏移,base64加密不需要秘鑰,也不需要iv偏移。

    Md5加密的話,要么全是大寫,全是小寫,aes加密后綴有個等于,很復雜。

    而這個sub_d90函數返回值不可能的aes加密,因為sub_d90中并沒有任何的秘鑰,iv偏移相關。

    Md5也不太可能,因為md5要么全是大寫字母,或者全是小寫字母,并且不規則。

    public static void baseEncode(){    System.out.println();    byte xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,            0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};    String by=Base64.getEncoder().encodeToString( xorDataMy);     System.out.println(by); }public static void md5(){    System.out.println(MD5Utils.stringToMD5("111111111111111111111111111111"));}
    

    可以看到base64加密的值與sub_d90的值一樣。

    我們回到這里。V9[v16]這個是返回的base64編碼的數據,V19[16]是真碼,也是base64的。只要我們把這個真碼的base64得到,然后通過解密base64,然后把解密后的數據與前面的key進行異或即可獲得flag。

    while ( v9[v16] == v19[v16] )    {      if ( v9[v16] )      {        if ( ++v16 != 41 )          continue;      }      v17 = 1;      goto LABEL_9;    }    v17 = 0;LABEL_9:    free(v9);  }  else  {    return 0;  }  return v17;如何獲取v19呢?如下是v19的定義,地址為xsp+0_BYTE v19[33]; // [xsp+0h] [xbp-A0h] while ( v9[v16] == v19[v16] )
    

    我們在v9[v16]這個位置按一下tab鍵即可看到偏移。

    這塊代碼的偏移的是b2c。

    我們需要在v19[v16]初始化后才能進行hook攔截,也就是獲取數據

    最好的時機是在sub_b90執行后進行截取。我們可以看到第一個參數是xsp+38h,也就是說xsp+0是xsp+38-38即可。

    我們需要攔截第一個參數獲取到地址,然后減去38就能獲取到xsp也就是v19。

    下面進行分析frida hook代碼。

    import frida, sys jscode = ''' var destAddr = '';  //定位xsp地址 function inline_hook() {    var so_addr = Module.findBaseAddress("libnative-lib.so");//尋找基址 //在ida中基址為0000000000000//在動態調試時基址會變//所以我們需要通過基址加上函數的偏移來定位一個函數      if (so_addr) {//是否獲取到基址,如果沒有獲取到里面的代碼就不會執行        console.log("so_addr:", so_addr);//打印so的地址         var addr_b90 = so_addr.add(0xB90);//獲取sub_b90的函數地址        var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int', 'pointer']);//創建一個本地函數,類似于指針函數,給指針函數賦值函數的地址。//然后進行調用,參數為_BYTE *password__, unsigned int password__length, char *threeStr//返回值為unsigned __int64//返回值對應'int',參數對應著['pointer', 'int','pointer'] //下面是一個攔截器,用于攔截函數的sub_b90的進入,與結束        Interceptor.attach(sub_b90, {            onEnter: function(args) //進入函數時執行的代碼            {             destAddr = args[0];//把第一個參數賦值給destAddr            console.log('onEnter B90'); //打印'onEnter B90,用于確認函數是否進入             },            //在進入函數之后執行的語句           onLeave:function(retval)            {//retval為函數的返回值            console.log('onLeave B90');//打印onLeave B90,用于確認函數是否離開            }        });          var addr_b2c = so_addr.add(0xb2c);//0xb2c為偏移,也就是說攔截 b2c的地址         console.log("The addr_b2c:", addr_b2c);//打印addr_b2c的地址        Java.perform(function() {            Interceptor.attach(addr_b2c, {//攔截b2c地址                onEnter: function(args) { //arg為參數                console.log("addr_b2c OnEnter :",  Memory.readByteArray(destAddr.sub(0x38),64) ); // Memory.readByteArray(destAddr.sub(0x38),64)//讀取xsp的內容,也就v19的值//也就是base64編碼的真碼     }            })        })    }}setImmediate(inline_hook)  ''' //用于接收消息的函數//第一個參數為需要打印的信息def on_message(message, data):    if message['type'] == 'send':        print(" {0}".format(message['payload']))    else:        print(message)Pass//跳過#print(frida.enumerate_devices())# 查找USB設備并附加到目標進程device =  frida.get_remote_device()//調用frida的函數,獲取設備#pid = device.spawn(["com.live.xctv"]) #session = device.attach(pid)//session =device.attach('cn.pojie52.cm01') #這里是要注入的apk包名//這個包名可以通過frida-ps -U 來進行獲取# 在目標進程里創建腳本script = session.create_script(jscode)# 注冊消息回調script.on('message', on_message)print(' Start attach')# 加載創建好的javascript腳本script.load()# 讀取系統輸入sys.stdin.read()
    

    下面的數據是左邊的十六進制。

    {0x35, 0x47, 0x68, 0x32, 0x2f, 0x79, 0x36, 0x50, 0x6f, 0x71, 0x32, 0x2f, 0x57, 0x49, 0x65, 0x4c, 0x4a, 0x66,0x6d, 0x68, 0x36, 0x79, 0x65, 0x73, 0x6e, 0x4b, 0x37, 0x6e, 0x64, 0x4b, 0x37, 0x6e, 0x64, 0x6e, 0x4a, 0x65, 0x57, 0x52, 0x45, 0x46, 0x6a, 0x52, 0x78, 0x38}

    下面的數據是十六進制對應的ASCII碼。

    5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8

    import base64//導入base64的包,因為我們要用到base64的解密函數 //xorkey是我們前面通過異或解密得出的數據xorkey = [209, 90, 6, 144, 68, 230, 199, 229, 222, 40, 247, 242, 102, 145, 200, 133, 66, 223, 249, 224, 130, 1, 43, 59,          56, 99, 55, 189, 46, 77] //第一個參數為base64解密后的數據//第二個參數為base64解密后的數據的長度def sub_B90(data, l):    ret = []    for i in range(l):        ret.append(data[i] ^ xorkey[i])//異或解密,把結果拼接到ret里面    s = ''    for i in ret://便利ret里面數據        s += chr(i) //把ret的解密結果轉換成字符,如何進行拼接。    print(s)//打印拼接后的解密數據    return ret//返回ret  def resv(data):    data = base64.b64decode(data,)//解密base64數據     t = sub_B90(data, len(data))//把解密后的base64數據傳入sub_b90進行異或解密    return (t) data="5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8"http://base64數據 resv(data)//調用這個函數進行解密base64數據與異或解密獲得真嗎
    

    輸入真碼后,提示我們恭喜你,驗證正確。


    函數調用賦值語句
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    動態函數PHP中支持一個功能叫 variable function ,變量函數的意思。//最終是system;當一個變量后邊帶括號,那他就被視作一個函數。編譯器會解析出變量的值,然后會去找當前是否存在名為“system()”的函數并執行它。這里就不給實例了,很多免殺案例中都用到了這個特性。也是被瘋狂查殺的特征。回調函數回調函數,簡單來說就是一個函數不是由我直接調用,而是通過另一個函數去調用它。
    在cgiHandler函數中,將用戶的HTTP請求參數作為環境變量,通過諸如LD_PRELOAD即可劫持進程的動態鏈接庫,實現遠程代碼執行。代碼首先拼接出用戶請求的cgi完整路徑并賦予cgiPath,然后檢查此文件是否存在以及是否為可執行文件。隨后代碼將cgiPath、envp、stdIn與stdOut作為參數傳入launchCgi函數中。
    棧與棧幀的調試
    2022-03-06 16:24:19
    再次執行pop EAX,ESP的值增加4個字節,變為0012FFC4。OD狀態變成最開始的狀態。
    稍后將會基于linux 2.4.x內核環境,演示這種感染技術。不過,由于內核模塊是elf格式的,所以閱讀后續內容之前,先要熟悉elf格式,并且要重點理解其符號表,之后才好明白,向正常內核模塊注入感染代碼的原理。----[ 2.1 - The .symtab section.symtab節區(符號表)的內容,是一個供鏈接器使用的Elf32_Sym結構數組,Elf32_Sym結構定義在內核的/usr/include/elf.h頭文件:typedef struct
    近年來,瀏覽器安全事件頻發,給人們帶來嚴重的損失。目前這兩類技術的研究重點主要在于對瀏覽器的JavaScript引擎的模糊測試,基礎思想都是首先將JS代碼轉換為語法樹AST,再在語法樹上進行相關變異操作。同時對其他部分進行變異,以便可以發現類似的或新的錯誤。⑤DIE記錄運行時覆蓋反饋信息決定新文件將被保存。此外,DIE同樣記錄自定義函數的參數和返回值的類型,以便在新構建的AST節點中進行合法調用。
    160個CrackMe-001 首先運行程序,發現程序是個注冊機,輸入用戶名與注冊碼。讀取注冊碼各部分的字符串 004039B8??RETN 將斷點斷在函數的起始部分,單步跟蹤查看 匯編代碼部分 可以看到,程序會先計算name的長度,若長度小于4則直接彈出錯誤信息窗口。
    因為程序肯定是病毒,我就不上傳殺毒網去查殺了。正常我們在分析一個未知惡意程序的時候,流程都是要先上傳殺毒網看看。 用PEID進行查殼,顯示未加殼,程序采用Delphi語言開發。
    EXP編寫學習之繞過GS
    2023-02-20 09:58:16
    棧中的守護天使 :GSGS原理向棧內壓入一個隨機的DWORD值,這個隨機數被稱為canary ,IDA稱為 Security Cookie。Security Cookie 放入 ebp前,并且data節中存放一個 Security Cookie的副本。棧中發生溢出時,Security Cookie首先被淹沒,之后才是ebp和返回地址。函數返回之前,會添加一個Security Cookie驗證操作,稱為Security Check。檢測到溢出時,系統將進入異常處理流程,函數不會正常返回,ret也不會被執行。函數使用無保護的關鍵字標記。緩沖區不是8字節類型 且 大小不大于4個字節。可以為函數強制啟用GS。
    漏洞評級:高危影響版本:Django 3.2、Django 3.1安全版本:Django >= 3.2.5、Django >= 3.1.13漏洞分析2.1 order_by()order_by是QuerySet下的一種查詢方法,作用是將查詢的結果根據某個字段進行排序,在字段前面加一個符號,結果會倒序輸出。
    漏洞復現根據官方公告,找到存在漏洞的二進制文件。官方公告:先用binwalk -Me DIR815A1_FW102b06.bin命令解壓固件包,再根據“漏洞描述”中的關鍵詞service.cgi進行查找:找到了所匹配的二進制文件htdocs/cgibin,將其拖進IDA中先進行靜態分析。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类