吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 1090|回复: 13

[安卓逆向图文] 巧解一道XCTF题

[复制链接]
白云点缀的蓝 发表于 2022-7-31 17:14 | 显示全部楼层 |阅读模式

用到工具 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<address, jclass>
  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=1 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

<?xml version="1.0" encoding="UTF-8"?>
<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<xorData.length-1){
//                System.out.print(",");
//            }
            System.out.print(xorData[i]^xorDataMy[i]);
            if(i<xorData.length-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<xorData.length-1){
//                System.out.print(",");
//            }
            System.out.print(xorData[i]);
            if(i<xorData.length-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}

文章用到的apk https://starrysp.lanzoum.com/iLwj108r0v5c

评分

参与人数 17HB +15 THX +9 收起 理由
消逝的过去 + 1
花盗睡鼠 + 2 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
Jawon + 2
虚心学习 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
一路走来不容易 + 1
后学真 + 1
爱汇编爱汇编 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
agan8888 + 1
飞刀梦想 + 1
Cerolluo + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
我爱学习一 + 1 + 1
yexing + 1
风里去 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
temp + 1
学徒 + 1
zxjzzh + 2 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
程序员大明 + 2 + 1

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
素衣少年 发表于 2022-8-11 16:49 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
风里去 发表于 2022-8-11 19:08 | 显示全部楼层

谢谢楼主分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
智慧的猪猪 发表于 2022-8-19 13:12 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
吃个大西瓜 发表于 2022-9-3 17:44 | 显示全部楼层

谢谢大佬分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
别管我了行 发表于 2022-9-14 16:57 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
曾经沧海 发表于 2022-9-17 09:39 | 显示全部楼层

很有帮助,多多支持!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
爱汇编爱汇编 发表于 2022-9-24 07:46 | 显示全部楼层

感谢分享,学习下
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
曾经沧海 发表于 2022-10-2 08:57 | 显示全部楼层

先顶一下,收藏好,留个脚印,下次好找
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
爱汇编爱汇编 发表于 2022-10-5 23:24 | 显示全部楼层

感谢分享,学习下
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层
9层
10层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表