吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 9379|回复: 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
我初中文凭,能学了么?主要不会英语数学
回复 支持

举报

高级模式
B Color Image Link Quote Code Smilies
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回列表

相关内容推荐

刑罚
朝政
我看三级片
夜访
9taxi
com89
棒球比赛
阿普尔
杜隆坦
马庄村
伤心男孩
爱姐
妖魔
itin
凌辱
赫敏演员
杜心五
工人阶级是
名门暗战
我有喜欢的人
sarah
艾瑞丝
契约
林向宇
吴磊
yisan
何文茵
欢场
天道2
唐诗三百
萨德尔
阿黛拉
不要担心
胡猛
赘婿剧情
海底小纵队第六季
小矮星
糖衣
狮子王刀疤
israel
我是小四
中华弟子规
宁静电视剧
问题不大
阿利森
第三类
趴体
同性恋违法吗
蒙初之赐
200000
巴塔木中文儿歌
状元及第
几十
沸点
杨三郎
严君泽
淡蓝
doses
南极电影
程莉莎
最后还是分手
金光布袋戏
鹰眼演员
2521
张志平
历史性
我的姓氏
阿克曼
bouth
军武
追星女孩
鬼童
胡文倩
地平线3
所有人
疯狗
野地
田梅
松井大辅
浮世三千
成化
黑闪
高红
十四年
池江璃花子
李景浩
航拍中国西藏
交际花盛衰记
宗正寺
hawa
磊子
宽衣解带
阴部写真
聊色
中国革命
幕后揭秘
魔徒
出云
锣鼓巷
15号
以胖为美
larva动画
帝国的灭亡
惊爆游戏第二季
边程
鱼缸电影
梦幻水晶球
obscura
拓拓
入侵阿富汗
谢振
芽子
biomass
边巴
205
人体奥秘
思维宫殿
一郎
李汉
岩崎
安杰和江德福
人不在江湖
李珠贤
终于遇到
我的视野
闪婚电视剧
marie
获奖
鹰眼演员
——是什么符号
驱魔道长演员表
杜拉克
连我
爆头电影
政治斗争
我们的路
epic
法国爱情电影
formail
csi
很轻
施林
黄的
奈何情深缘浅
随他吧
勇往直前恋上你
红雪
切利比达克
洗脑术
莫问
烈阳天道
人间味道
日日日
康斯坦丁是谁
wisteria
王景明
帕德梅
ferguson
最终之战
永世难忘
很简单
莫扎特是什么之父
blaire
八仙过海传奇
puth
金泰山
王云辉
克罗泽
灭霸
中奖囧事
同学的后妈
foya
yykk
一片痴情
达利拉
萨马尔
我想要你了
回家的诱惑演员表
法勒
从农场到餐桌
落跑甜心姜潮
好多了
印度宝莱坞
cula
潘金莲就是我
严君泽
KAMP
何丽萍
夜市
孙力
topgun
castaway
阉割焦虑
光洙
卢彦西
儿女传奇
77mm
阿留
羞耻

合作伙伴

康养所

www.xys-piano.com
www.duoqv.cn
www.mgyxlm.com
www.ifaxing.net
dgsenshuo.com
www.bxdLk.com
www.tzystec.com
www.ylph.com
skbcnf.com
0710nk.com
suqinbeiye.com
www.bjtzgame.com
yxcandle.com
ylph.com
sdsrjt.com
yzsdyxh.com
lyxzbjy.com
hfgsdb.com
www.guimanchunjiu.com
www.suqinbeiye.com