【安卓】《入门级crackMe》的算法分析

1061 1
小菜鸟一枚 2022-3-6 16:50:56 | 显示全部楼层 |阅读模式
练习程序和笔记见文末!

0x1 静态分析
  1.用Android killer反编译apk文件,然后卡住了???



  2.关闭Androidkiller重开,点刚刚反编译过的工程,点是即可,但是看不到apk对应的java源码,这个只有不影响反编译即可。



  3.我还是想先看看源码,使用jadx-gui工具,打开apk,就可以查看源码了。



  4.首先还是运行apk,点击注册,输入用户名和密码,会提示注册失败。



  5.查看j反编译出的ava源码,可以看到,判断我输入的是否为空,不为空进入下一步调用JNI函数。

复制代码 隐藏代码
public class MainActivity extends AppCompatActivity {
    String name = BuildConfig.FLAVOR;
    String code = BuildConfig.FLAVOR;

    public native String stringFromJNI(String str, String str2);

    static {
        System.loadLibrary("native-lib");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.SupportActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ((Button) findViewById(R.id.bt)).setOnClickListener(new View.OnClickListener() { // from class: com.cm.shuair.crackme1.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                MainActivity.this.name = ((EditText) MainActivity.this.findViewById(R.id.name)).getText().toString();
                MainActivity.this.code = ((EditText) MainActivity.this.findViewById(R.id.code)).getText().toString();
                if (MainActivity.this.name.equals(BuildConfig.FLAVOR) || MainActivity.this.code.equals(BuildConfig.FLAVOR)) {
                    Toast.makeText(MainActivity.this, "不能为空哈", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, MainActivity.this.stringFromJNI(MainActivity.this.name, MainActivity.this.code), 0).show();
                }
            }
        });
    }
}
  6.因为我的雷电模拟器是x86架构,所以选择x86目录下的so文件,通过IDA加载,根据java调用的函数名,找到对应的函数名。



  7.F5反编译一下,调用时传递了name,code,0三个参数,然后返回值是v8,最后将v8展示在屏幕上。

复制代码 隐藏代码
int __cdecl Java_com_cm_shuair_crackme1_MainActivity_stringFromJNI(_JNIEnv *a1, int a2, int a3, int a4)
{
  int v5; // [esp+2Ch] [ebp-20h]
  char *v6; // [esp+30h] [ebp-1Ch]
  char *v7; // [esp+34h] [ebp-18h]
  int v8; // [esp+38h] [ebp-14h]

  v7 = (char *)Jstring2CStr(a1, a3);
  v6 = (char *)Jstring2CStr(a1, a4);
  v5 = n(v7);
  if ( v5 == c(v6) )
    v8 = _JNIEnv::NewStringUTF(a1, byte_102B);
  else
    v8 = _JNIEnv::NewStringUTF(a1, byte_1062);
  return v8;
}
  8.v8的两次赋值我们点进去看看是什么,一串十六进制数据,猜测是中文字符串,放在在线HEX解码工具,也就是v5==c(v6)就会提示注册成功。

复制代码 隐藏代码
.rodata:00001062 byte_1062       db 0E6h, 0B3h, 0A8h, 0E5h, 86h, 8Ch, 0E5h, 0A4h, 0B1h
.rodata:00001062                                         ; DATA XREF: Java_com_cm_shuair_crackme1_MainActivity_stringFromJNI+CA↑o
.rodata:00001062                 db 0E8h, 0B4h, 0A5h, 0

.rodata:0000102B byte_102B       db 0E6h, 0B3h, 0A8h, 0E5h, 86h, 8Ch, 0E6h, 88h, 90h, 0E5h
.rodata:0000102B                                         ; DATA XREF: Java_com_cm_shuair_crackme1_MainActivity_stringFromJNI+A6↑o
.rodata:0000102B                 db 8Ah, 9Fh, 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh
.rodata:0000102B                 db 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h
.rodata:0000102B                 db 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh
.rodata:0000102B                 db 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh, 81h, 0EFh, 0BCh
.rodata:0000102B                 db 81h, 0EFh, 0BCh, 81h, 0

注册成功:E6B3A8E5868CE68890E58A9F
注册失败:E6B3A8E5868CE5A4B1E8B4A5
  9.比较流程很清楚了n(v7)==c(v6),而v6和v7是调用时传进来的参数,看so里面的调用约定是__cdecl从右往左入栈,那么a3是?晕了晕了,一会动态看看。

0x2 动态调试
  1.翻一翻笔记,ida调试步骤:

复制代码 隐藏代码
  1.将android-server拷贝到手机,赋予可执行权限并运行

  2.adb shell am start -D -n包名/.入口界面

  3.使用ida附加目标进程,下断点

  4. jdb -connect com.sun.jdi.SocketAttach:hostname= localhost,port=8700(ddms中看端口)
  2.在IDA目录中找到对应版本的server,直接在目录上输入cmd打开命令行,就不用切换目录,直接push上去。



  3.首先输入adb shell,然后输入mount -o rw,remount /重新挂载目录可读可写,exit退出shell环境,输入adb push android_x86_server /sbin,将文件上传到目录,adb shell再次进入shell环境,chmod +x /sbin/android_x86_server,给它可执行权限,然后输入android_x86_server跑起来,注意别把这个窗口关了。



  4.在开一个窗口输入adb forward tcp:23946 tcp:23946,转发一下端口到本地,这里因为没有反调试,所以没有开ddms看端口再以debug模式启动app。



  5.在模拟器中将apk运行起来,然后IDA attach,注意选remote linux调试器,然后ip填127.0.0.1即可,找到crackme包名,双击附加程序,运行起来,搜索so文件,找到模块中对应的函数,双击过来,看到地址复制一下在反汇编窗口按g键粘贴地址,就到了我前面静态分析时看到的函数了。



  6.下一个断点,name输入123456,code输入654321,点注册成功断了下来:



  7.F8运行两次,然后看看v7和v6的值,对着v7双击过来,全是未定义的数据,右键double word,就显示出来了v6是code,v7是name。

复制代码 隐藏代码
[stack]:CFFFD890 dd offset a654321                       ; "654321"    v6
[stack]:CFFFD894 dd offset a123456                       ; "123456"    v7
  8.继续F8向下走,接着执行n函数,将name进行处理,for循环遍历字符串,小于65直接跳出循环,小于等于90,存起来,大于90减去32,对照ascii码表,这段代码的意思就是小写字母转大写字母,大写字母就是直接存起来,将name的每一位大写字母ascii值加起来最后异或0x9988。

复制代码 隐藏代码
int __cdecl Z1nPc(int a1)
{
  int v2; // [esp+4h] [ebp-14h]
  char v3; // [esp+Fh] [ebp-9h]
  int v4; // [esp+10h] [ebp-8h]
  int i; // [esp+14h] [ebp-4h]

  v4 = 0;
  for ( i = 0; *(_BYTE *)(a1 + i); ++i )
  {
    v3 = *(_BYTE *)(a1 + i);
    if ( v3 < 65 )
      break;
    if ( v3 <= 90 )
      v2 = v3;
    else
      v2 = v3 - 32;
    v4 += v2;
  }
  return v4 ^ 0x9988;
}
&#8195;&#8195;9.继续F8向下走,同样遍历code,将code的的每一位减去48,48对应数字0的ascii码值,每次对v2乘以10再累加,最终返回v2与0x1256的结果。

复制代码 隐藏代码
int __cdecl Z1cPc(int a1)
{
  int v2; // [esp+10h] [ebp-8h]
  int i; // [esp+14h] [ebp-4h]

  v2 = 0;
  for ( i = 0; *(_BYTE *)(a1 + i); ++i )
    v2 = *(char *)(a1 + i) + 10 * v2 - 48;
  return v2 ^ 0x1256;
}
0x3 注册机编写
&#8195;&#8195;1.到这里整个程序的算法是分析完成了,接下来就可以编写算法注册机了,算法注册机就是利用name计算code,那么对name进行处理的函数我就可以直接拿来用,对code处理的函数颠倒过来。

复制代码 隐藏代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char name[255] = "";
    printf("请输入用户名:\n");
    scanf("%s", name);

    //对name处理
    char v3;
    int v2,n_name,code;
    for (int i = 0; name; ++i)
    {
        v3 = name;
        if (v3 < 65)
            break;
        if (v3 <= 90)
            v2 = v3;
        else
            v2 = v3 - 32;
        n_name += v2;
    }
    n_name ^= 0x9988;

    //计算code
    code = n_name ^0x1256;

}
&#8195;&#8195;2.name不用改,可以直接套用,code该怎么去写呢?循环的退出条件应该是什么?抽丝剥茧后v2 = a1 + 10 v2 - 48;核心代码是这里,注意这里可不能像解方程一样,不然a1 = v2-10v2+48,不用想也知道不对,这和我之前分析的算法不一样,这里赋值是一个累加的过程。

复制代码 隐藏代码
  for ( i = 0; a1; ++i )
    v2 = a1 + 10 * v2 - 48;

    每次将自身乘以10+a-48
    反过来应该是每次将自身除以10+48再减去a1的值,那么a1就可以看做是余数
&#8195;&#8195;3.按照刚刚分析的思路,写出code的计算过程,输入验证一下,不成功。

复制代码 隐藏代码
   //计算code
    n_code = n_name ^ 0x1256;

    for (j = 0; n_code >= 10; ++j) //循环退出条件就是ncode小于10
    {
        code[j] = n_code % 10 + 48; //计算ncode对10的余数再加上48
        n_code /= 10;               //自身除以10
    }
    code[j] = n_code + 48; //最后一次不计算直接+48
    code[j+1] = 0;//末尾补0
&#8195;&#8195;4.实在是想不到哪里出错了,vscode里面lanuch.json文件这里false改成true,"externalConsole": true,然后打上断点,单步调试,可以看到ncode异或的值35511,最后赋值出来的code字符串是11553,试试输入35511提示注册成功。



&#8195;&#8195;5.好家伙,原因找到了,我的思路并没有错,但是最后35511,从高位到低位赋值1 1 5 5 3,所以我应该先计算出循环的次数,然后倒着赋值,得到最终版注册机。

复制代码 隐藏代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char name[255] = "";
    char code[255] = "";
    int i; //循环变量
    int j; //循环变量

    printf("Please enter name:\n");
    scanf("%s", name);

    //对name处理
    char v3 = 0;
    int v2 = 0, n_name = 0, n_code = 0;
    for (i = 0; name; ++i)
    {
        v3 = name;
        if (v3 < 65)
            break;
        if (v3 <= 90)
            v2 = v3;
        else
            v2 = v3 - 32;
        n_name += v2;
    }
    n_name ^= 0x9988;

    //计算code
    n_code = n_name ^ 0x1256;

    j = 0;
    v2 = n_code;
    while (v2)//计算循环的次数j
    {
        v2 /= 10;
        j++;
    }

    for (; j > 0; --j) //循环退出条件就是ncode小于0
    {
        code[j-1] = n_code % 10 + 48; //计算ncode对10的余数再加上48
        n_code /= 10;               //自身除以10
    }

    printf("Code is:%s\n", code);
    system("pause");
}
&#8195;&#8195;6.现在试试注册机,生成三组code,都是成功:

复制代码 隐藏代码
name:baidu
code:35515

name:WwWAdminWwW
code:34989

name:MySqlOracle
code:34962


0x4 总结
&#8195;&#8195;1.因为这题纯so层运算,显示的结果也是so层出来的,所以没有用到JEB动态调试。

&#8195;&#8195;2.字母大小写转换,数字除以10,我也喜欢这么写,比较熟悉,所以分析的比较顺利,在code反推的那个地方卡了一下,把它想复杂了。

&#8195;&#8195;3.code那里其实只会是数字,减去48就是字符到ascii值的转换,反推这里时我把他当成数列求和,越想越晕,好在最后从死胡同里绕出来了。

&#8195;&#8195;4.vs code运行的时候没找到输入窗口,最后百度了一下,改一下launch文件,成功弹出dos窗口了。


练习CM下载地址:
学习笔记下载地址:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行