吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9404|回复: 73
收起左侧

[Android CTF] 学破解第187天,《入门级crackMe》的算法分析

  [复制链接]
小菜鸟一枚 发表于 2022-3-6 16:37

前言:

  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1582287-1-1.html

立帖为证!--------记录学习的点点滴滴

0x1 静态分析

  1.用Android killer反编译apk文件,然后卡住了???

https://s1.ax1x.com/2022/03/06/bBrGnO.png

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

https://s1.ax1x.com/2022/03/06/bBr03t.png

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

https://s1.ax1x.com/2022/03/06/bByiLT.png

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

https://s1.ax1x.com/2022/03/06/bBy3wD.png

  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调用的函数名,找到对应的函数名。

https://s1.ax1x.com/2022/03/06/bB61cq.png

  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上去。

https://s1.ax1x.com/2022/03/06/bBT42T.png

  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跑起来,注意别把这个窗口关了。

https://s1.ax1x.com/2022/03/06/bBHTE9.png

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

https://s1.ax1x.com/2022/03/06/bBbQCq.png

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

https://s1.ax1x.com/2022/03/06/bBLupq.png

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

https://s1.ax1x.com/2022/03/06/bBjthj.png

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

  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 注册机编写

  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]; ++i)
    {
        v3 = name[i];
        if (v3 < 65)
            break;
        if (v3 <= 90)
            v2 = v3;
        else
            v2 = v3 - 32;
        n_name += v2;
    }
    n_name ^= 0x9988;

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

}

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

  for ( i = 0; a1[i]; ++i )
    v2 = a1[i] + 10 * v2 - 48;

    每次将自身乘以10+a[i]-48
    反过来应该是每次将自身除以10+48再减去a1[i]的值,那么a1[i]就可以看做是余数

  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

  4.实在是想不到哪里出错了,vscode里面lanuch.json文件这里false改成true,"externalConsole": true,然后打上断点,单步调试,可以看到ncode异或的值35511,最后赋值出来的code字符串是11553,试试输入35511提示注册成功。

https://s1.ax1x.com/2022/03/06/bDmH3t.png

  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]; ++i)
    {
        v3 = name[i];
        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");
}

  6.现在试试注册机,生成三组code,都是成功:

name:baidu
code:35515

name:WwWAdminWwW
code:34989

name:MySqlOracle
code:34962

https://s1.ax1x.com/2022/03/06/bDQeL6.png

0x4 总结

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

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

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

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

0x5 参考资料

  1.CM来源地址:新出炉的入门级crackMe
https://www.52pojie.cn/thread-956163-1-1.html
(出处: 吾爱破解论坛)

免费评分

参与人数 23威望 +2 吾爱币 +120 热心值 +19 收起 理由
香芋 + 1 + 1 用心讨论,共获提升!
#sky# + 1 + 1 热心回复!
SFY110 + 1 + 1 谢谢@Thanks!
Stoneone + 1 + 1 我很赞同!
acesec + 1 谢谢@Thanks!
夏末随风 + 1 我很赞同!
Quanta + 1 + 1 我很赞同!
tracyshenzl + 1 + 1 谢谢@Thanks!
qwaszx1 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
石碎大胸口 + 1 + 1 用心讨论,共获提升!
Chenda1 + 1 + 1 我很赞同!
seei + 1 谢谢@Thanks!
Dark天使 + 1 + 1 用心讨论,共获提升!
ZhiweiHu + 1 我很赞同!
sanyuebeichen + 1 我很赞同!
tuanyu + 1 谢谢@Thanks!
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
tandz + 1 我很赞同!
suuu7 + 1 我很赞同!
sansanla + 1 + 1 用心讨论,共获提升!
Nattevak + 3 + 1 我很赞同!
lynxtang + 1 + 1 谢谢@Thanks!
夜步城 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

solly 发表于 2022-3-24 10:52
本帖最后由 solly 于 2022-3-24 11:36 编辑

感觉code 就是一个系统函数,atoi(),把字符串转换成整数,应该就是把 name 的转换和与0x9988异或再与0x1256异或后,转换成字符串就是 code 了,用 itoa() 就可以了,差不多这样:itoa(calc_sum(name) ^ 0x9988 ^ 0x1256, code, 10),或者 printf("%d",n_code)直接输出。

[C] 纯文本查看 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int getCode(char * name);

int main(int argc, char** argv) {
        char name[256];
        printf("input your name: ");
        scanf("%s", name);
        int code = getCode(name);
        printf("your code: %d\n", code);
        return 0;
}

int getCode(char * name) {
        int sum = 0;
        for(int i=0; name[i]; i++) {
                if(name[i] < 0x41) {  //65, 'A'
                        break;
                }
                if(name[i] <= 0x5A) { /// 90, 'Z'
                        sum += (int)name[i];
                } else {
                        sum += (int)name[i] - 0x20;  /// 32, 'a' - 'A'
                }
        }
        
        return sum ^ 0x9988 ^ 0x1256;
}
861078848 发表于 2022-3-15 13:01
用python遍历注册码
[Python] 纯文本查看 复制代码
def func1(s):
    v4 = 0
    for i in s:
        v3 = ord(i)
        if v3 < 0x41:
            break
        elif v3 <= 0x5A:
            v2 = v3
        else:
            v2 = v3 - 32
        v4 += v2
    result = v4 ^ 0x9988
    # print(hex(result))
    return result

def func2(s):
    v2 = 0
    for i in s:
        v2 = 10 * v2 + ord(i) - 48 # x = v2 + 48 - 10 * v2
    result = v2 ^ 0x1256
    # print(hex(result))
    # print(result)
    return result


# 仅使用数字0-9
option = [i for i in range(48,57)]

# 仅计算长度为5的注册码
def backtrack(nums,tree,v2=0,length=5):
    if int(v2) ^ 0x1256 > nums:
        return
    if int(v2) ^ 0x1256 == nums:
        print("".join(list(map(chr, tree))))
        return
    if len(tree) == length:
        return
    for i in option:
        v2 = 10 * v2 + i - 48
        tree.append(i)
        backtrack(nums,tree,v2)
        tree.pop()
        v2 = (v2 + 48 - i) / 10


def getValue(str):
    tree = []
    backtrack(func1(str),tree)

# 计算注册码
getValue("iloveyou")#35234

免费评分

参与人数 2吾爱币 +3 热心值 +2 收起 理由
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
小菜鸟一枚 + 1 + 1 用心讨论,共获提升!

查看全部评分

灵魂深处 发表于 2022-3-6 16:48
fredchen 发表于 2022-3-6 18:11
过来支持一下,帖子不错学习了,哈哈
wen1102 发表于 2022-3-6 18:15
这个牛这个牛
坎德沃 发表于 2022-3-6 18:44
以前的jeb和ida的教程不在了,希望能看一看
lynxtang 发表于 2022-3-6 20:22
这对于像我等的菜鸟来说非常友好。
头像被屏蔽
明珠一颗 发表于 2022-3-6 21:08
提示: 作者被禁止或删除 内容自动屏蔽
allycn 发表于 2022-3-7 07:50
在学习中,谢谢分享
liujiata 发表于 2022-3-7 09:27
对于一个新手来说,此等教程,非常奈斯
俄稀罕你 发表于 2022-3-7 13:46
我初中文凭,能学了么?主要不会英语数学
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-28 06:32

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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