吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7503|回复: 59
收起左侧

[Android ReverseMe] 萌新发个有奖安卓cm

[复制链接]
赤座灯里 发表于 2020-5-31 12:55
CM是什么?Crackme是什么?这是什么东西?楼主发的什么?
他们都是一些公开给别人尝试破解的小程序,制作 Crackme 的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位 Cracker,想挑战一下其它 Cracker 的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破解,KeyGenMe是要求别人做出它的 keygen (序号产生器), ReverseMe 要求别人把它的算法做出逆向分析, UnpackMe 是要求别人把它成功脱壳,本版块禁止回复非技术无关水贴。

本帖最后由 赤座灯里 于 2020-6-12 02:06 编辑

鉴于追码cm玩的人少,大佬们不愿玩,发个口令红包调动下积极性

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 4吾爱币 +8 热心值 +3 收起 理由
一块砖头 + 1 我很赞同!
evildoer7758 + 1 我很赞同!
CrazyNut + 6 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
KM920201 + 1 + 1 我很赞同!

查看全部评分

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

 楼主| 赤座灯里 发表于 2020-6-3 11:53
本帖最后由 赤座灯里 于 2020-6-3 12:44 编辑

WP

红包时间过了,我就发下设计思路吧,供其他同学学习:

jadx打开,onClick判断eq方法是否为666,是的话用输入RC4解密内置字符串得到红包码:

public class MainActivity extends Activity {
    public native int eq(String str);

    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.loadLibrary("native-lib");
        setContentView(R.layout.activity_main);
        final Button btn = (Button) findViewById(R.id.check);
        btn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                TextView tv = (TextView) MainActivity.this.findViewById(R.id.input);
                String input = tv.getText().toString();
                if (MainActivity.this.eq(input) != 666) {
                    Toast.makeText(MainActivity.this, "不对哦,再试试叭~", 0).show();
                    return;
                }
                tv.setText("厉害啦~你成功了✧*。(ˊᗜˋ*)✧*");
                tv.setTextColor(MainActivity.this.getResources().getColor(R.color.colorAccent));
                tv.setEnabled(false);
                btn.setEnabled(false);
                ((TextView) MainActivity.this.findViewById(R.id.code)).setText("支付宝红包口令:" + MainActivity.this.getCode(input));
            }
        });
    }

    public String getCode(String input) {
        try {
            return RC4Util.decryRC4("79adb30330b50997ba81e07e4636e13d1544fed7763315224c70", input, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }
}

IDA打开libnative-lib.so,在JNI_OnLoad的RegisterNatives中找到对应的函数:

int __fastcall sub_84C4(_JNIEnv *a1)
{
  int x; // r5
  const char *v2; // r4
  int v3; // r1
  int result; // r0
  size_t v5; // r0
  _BYTE *ret; // r5
  unsigned int i; // r4
  unsigned int v8; // r1
  int v9; // r4
  unsigned __int8 input; // [sp+4h] [bp-34h]
  unsigned int v11; // [sp+8h] [bp-30h]
  int isdbg[4]; // [sp+10h] [bp-28h]

  x = 0;
  v2 = (const char *)((int (*)(void))a1->functions->GetStringUTFChars)();
  isdbg[0] = dbg1();
  isdbg[1] = dbg2();
  isdbg[2] = dbg3();
  isdbg[3] = dbg4();
  while ( x != 4 )
  {
    v3 = isdbg[x++];
    if ( v3 )
      return anti();
  }
  v5 = strlen(v2);
  ret = malloc(v5 + 1);
  std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string<decltype(nullptr)>(
    &input,
    v2);
  for ( i = 0; ; ++i )
  {
    v8 = v11;
    if ( !(input << 31) )
      v8 = (unsigned int)input >> 1;
    if ( i >= v8 )
      break;
    ret[i] = *(_BYTE *)std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::at(
                         &input,
                         i) ^ 0xA;
  }
  v9 = memcmp(ret, &unk_16C75, 0x1Bu);
  std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&input);
  result = 0;
  if ( !v9 )
    result = 666;
  return result;
}

v2是输入,乍一看前面的几行没什么用,从v5 = strlen(v2);开始同v2有关。算法是将输入异或0xA后,同unk_16C75做比较,解密算法:

for i in [0x5E, 0x62, 0x63, 0x79, 0x55, 0x43, 0x79, 0x55, 0x43, 0x64, 0x69, 0x65, 0x78, 0x78, 0x6F, 0x69, 0x7E, 0x55,
          0x5E, 0x78, 0x73, 0x55, 0x4B, 0x6D, 0x6B, 0x63, 0x64]:
    print(chr(i ^ 0xA), end="")

得到flag:This_Is_Incorrect_Try_Again,结束,输入拿红包。

不对?什么姿势错了?
回头看下前面的几行,执行四个函数后将结果赋值给isdbg数组,循环内遍历数组结果,结果非0则返回anti函数:

int anti()
{
  return 0;
}

看下这四个函数是什么:

signed int dbg1()
{
  int v0; // r11
  FILE *v1; // r4
  int v3; // [sp+0h] [bp-1018h]
  int v4; // [sp+1008h] [bp-10h]

  v4 = v0;
  _aeabi_memclr8(&v3, 4096);
  v1 = popen("cat /proc/net/tcp |grep :5D8A", "r");
  if ( fgets((char *)&v3, 4096, v1) )
    return 1;
  pclose(v1);
  return 0;
}
signed int dbg2()
{
  char *v0; // r6
  char *v1; // r5
  char *v2; // r1
  signed int result; // r0
  FILE *stream; // [sp+4h] [bp-102Ch]
  char v5; // [sp+8h] [bp-1028h]

  _aeabi_memclr8(&v5, 4096);
  stream = popen("ps", "r");
  while ( fgets(&v5, 4096, stream) )
  {
    v0 = strstr(&v5, "android_server");
    v1 = strstr(&v5, "frida");
    v2 = strstr(&v5, "gdb");
    result = 1;
    if ( v0 || v1 || v2 )
      return result;
  }
  pclose(stream);
  return 0;
}
signed int dbg3()
{
  FILE *v0; // r4
  int v1; // r5
  char s; // [sp+4h] [bp-11Ch]
  __int16 v4; // [sp+Eh] [bp-112h]
  char v5; // [sp+84h] [bp-9Ch]

  syscall(20);
  sub_884C(&v5);
  v0 = fopen(&v5, "r");
  while ( fgets(&s, 128, v0) )
  {
    if ( !memcmp(&s, "TracerPid", 9u) )
    {
      v1 = atoi((const char *)&v4);
      fclose(v0);
      if ( v1 )
        return 1;
    }
  }
  return 0;
}
signed int dbg4()
{
  int v0; // r11
  DIR *v1; // r0
  DIR *v2; // r4
  unsigned int v3; // r6
  struct dirent *v4; // r0
  signed int result; // r0
  char v6; // [sp+8h] [bp-118h]
  int v7; // [sp+110h] [bp-10h]

  v7 = v0;
  _aeabi_memclr8(&v6, 256);
  getpid();
  sub_8890(&v6);
  v1 = opendir(&v6);
  if ( !v1 )
    return 0;
  v2 = v1;
  v3 = 0;
  while ( 1 )
  {
    v4 = readdir(v2);
    if ( !v4 )
      break;
    if ( (unsigned __int8)(v4->d_name[8] - 48) < 0xAu )
      ++v3;
  }
  result = 0;
  if ( v3 > 0xA )
    result = 1;
  return result;
}

平凡无奇的反调试函数,检测被调试就不向下执行,直接返回0,那就不用看了。

那就没思路了。是这样的嘛?

注册的函数源码如下:

static jint func(JNIEnv *env, jobject, jstring n) {
    const char *input = env->GetStringUTFChars(n, 0);
    int dbg[4];
    dbg[0] = CheckPort23946ByTcp();
    dbg[1] = SearchObjProcess();
    dbg[2] = readStatus();
    dbg[3] = CheckTaskCount();
    for (int i = 0; i < 4; i++) {
        __asm __volatile (
        "mov    r7, %0\n"
        "mov    r2, %1\n"
        ::"r" (i), "r" (input)
        :"r7", "r2");
        if (dbg[i]) return anti();
    }
    unsigned char *a = static_cast<unsigned char *>(malloc(strlen(input) + 1));
    std::string str(input);
    for (int i = 0; i < str.length(); i++) a[i] = str.at(i) ^ 0xA;
    unsigned char data[] = {94, 98, 99, 121, 85, 67, 121, 85, 67, 100, 105, 101, 120, 120, 111, 105, 126, 85, 94, 120, 115, 85, 75, 109, 107, 99, 100};
    return !memcmp(a, data, sizeof(data)) ? 666 : 0;
}

为了防frida或自写so来hook API,所以将输入的指针直接传递给了anti函数。
调试起来就会发现,anti函数会被patch成一条未知指令。

在.init_array可以找到对anti的处理,鉴于存在结构体和花指令,这里放源码:

    struct sigmux_registration *reg;
    reg = sigmux_init();
    uintptr_t addr = (uintptr_t) reg->handle - (uintptr_t) _; //anti
    sysc(52, 73, ~(PAGE_SIZE - 1) & ((uintptr_t) _ & 0xFFFFFFFE), PAGE_SIZE * 2, 7, 0, 0, 0); //mprotect
    if (addr & 1) { //thumb mode
        unsigned char break_insns[] = {0x01, 0xde};
        memcpy(reinterpret_cast<void *>((addr & ~1)), break_insns, 2);
    } else {
        unsigned char break_insns[] = {0xf0, 0x01, 0xf0, 0xe7};
        memcpy(reinterpret_cast<void *>(addr), break_insns, 4);
    }
    sysc(0x78001, 0x88003, addr, addr, 0, 0, 0, 0); //__clear_cache

处理了SIGTRAP信号,也就是断点指令,本意是让IDA运行到anti后死循环,结果发现IDA能识别非自己设置的断点并传递给程序,实为本人疏忽。处理部分若isdbg的前三个结果为1,则将anti函数修复,pc设为anti继续执行,否则从r2得到输入传给r1进行校验,然后将r0赋值为r2,pc设置为一个空函数,从而改变返回值:

    if (sc->arm_pc == addr - (addr & 1)) {
        if (sc->arm_r7 != 3) {
            unsigned char origin_insns[] = {0x00, 0x20, 0x70, 0x47};
            memcpy((void *) (addr & 1 ? ((uintptr_t) addr & ~1) : addr), origin_insns, 4);
            sysc(0x3C000, 0xCC002, addr, addr, 0, 0, 0, 0); //__clear_cache
            sc->arm_pc = addr;
        } else {
            unsigned long ret;
            __asm __volatile (
            "mov    r1, %0\n"
            ::"r" (sc->arm_r2)
            :"r1");
            check();
            __asm __volatile (
            "mov    %0, r2\n"
            :"=r" (ret):
            :"r2");
            sc->arm_r0 = ret > 65535 ? 0 : ret;
            sc->arm_pc = (unsigned long) callback;
        }
    }

check函数从r1得到输入,接着将每个字符减去20,拆分为各17字节长的三部分str1、str2、str3,并要求str2[0] <= str3[0]以限制多解,接着进行大数运算,将str1、str2、str3三次方,记为a、b、c,记d = b + c,将a和d右对齐,记e = d - a,最后判断e的前49位为'0',以及第50位为'4'、51位为'2',结果正确将r2赋值为666:

static void check() {
    char *input;
    int ret = 0;
    __asm __volatile(
    "mov    %0, r1\n"
    :"=r" (input): :"r1");
    char str[M], a[M], b[M], c[M], d[M], e[M], f[M] = {0};
    int i, j, k;
    for (i = 0; i < _strlen(input); i++) str[i] = input[i] - 20;
    char *str1 = substring(str, 0, 17);
    char *str2 = substring(str, 17, 17);
    char *str3 = substring(str, 34, 17);
    if (str2[0] > str3[0]) {
        goto end;
    }
    pow3(str1, a);
    pow3(str2, b);
    pow3(str3, c);
    add(b, c, d);
    _strcpy(e, a);
    for (i = 0; d[i]; i++);
    for (j = 0; e[j]; j++);
    for (k = M - 2; i > 0; k--, i--) {
        d[k] = d[i - 1];
        d[i - 1] = 0;
    }
    for (k = M - 2; j > 0; k--, j--) {
        e[k] = e[j - 1];
        e[j - 1] = 0;
    }
    sub(d, e, f);
    for (i = 0; !f[i]; i++);
    _strcpy(e, &f[i]);
    for (i = 0; i < _strlen(e); i++) {
        if (i <= 48) {
            if (e[i] != 48) {
                goto end;
            }
        } else {
            if (e[i] != (51 - i) * 2 + 48) {
                goto end;
            }
        }
    }
    ret = 666;
    end:
    __asm __volatile (
    "mov    r2, %0\n"::"r" (ret):"r2");
}

综上,这是一个丢番图方程,即求b^3+c^3-a^3==42,百度一下即可得解,随便贴个链接:http://www.360doc.com/content/19/0910/09/7442640_860164846.shtml,倒推回去得到flag:LDIGLKGLLEFDKIMKHEFJDFEFGFMKGGIJGELDHGIKILEHILEKIEI

sysc函数是自写的syscall,在其上面有一个函数会导致IDA指令识别错误:

修复方法Alt+G切换为thumb指令即可:

从去年12月开始学安卓,由于本人懒得要死,也是想到什么就学什么,所以一直没有长进,大一快结束了,希望大二有所收获吧。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 3吾爱币 +3 热心值 +3 收起 理由
jmzqwh + 1 + 1 用心讨论,共获提升!
vLove0 + 1 + 1 大佬牛逼~
Li1y + 1 + 1 我很赞同!

查看全部评分

jmzqwh 发表于 2020-6-13 14:54
动态修复代码真是厉害,IDA还是不太会用,跟着大佬的答案跑最后也没有找到check()函数位置,代码内部嵌入汇编代码这就是大佬么
梦游枪手 发表于 2020-6-3 15:55
本帖最后由 梦游枪手 于 2020-6-3 16:17 编辑

最后的方程判断被优化成了这个样子。

没看懂这是个判断。。。
不过,无视掉v38,方程可以写成a^3==b^3+c^3,这个方程没有整数解,但是 a b c都为0的时候等式成立。
所以你能看到这神奇的一幕。

也可能有其他的多解?我也懒得找了,反正正解其实就一个。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x
shaoge 发表于 2020-6-1 14:53
1、4个反调试函数;
2、隐藏真正算法于迷惑算法中.
3、突破点:0x29A =666就是突破点;
4、剩下来的就是体力劳动:开头是每个字母-20,然后分成17字节长的3段,比较第35字节是否大于18字节,然后再分段继续。。。体力劳动。。。算法实在拖拉。。。
5、贴点汇编:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
CrazyNut + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

vLove0 发表于 2020-5-31 21:32

1天后,可以公布下解密思路么?搞不懂
 楼主| 赤座灯里 发表于 2020-6-1 16:39
本帖最后由 赤座灯里 于 2020-6-1 17:47 编辑
shaoge 发表于 2020-6-1 14:53
1、4个反调试函数;
2、隐藏真正算法于迷惑算法中.
3、突破点:0x29A =666就是突破点;

是调试找到的还是一个个翻到的。理解你的意思像是翻到的,因为我并没有把算法隐藏在迷惑算法里,也并不是4个反调试函数。算法也并不拖拉,调试一下很容易就能知道在做什么,patch一处花指令后F5静态看也显而易见。如果是翻到的,校验函数写分散的话,或者换个不显眼的结果,就又遗漏了。翻到很显然并不是我想要的结果。故意把校验算法写在同一个函数里并留下666的提示,函数也没做任何处理,我更想知道别人是如何理解运行流程的
Li1y 发表于 2020-6-1 19:49
希望楼主能出一个WP,很想学习
 楼主| 赤座灯里 发表于 2020-6-1 22:33
shaoge 发表于 2020-6-1 22:31
你看那些多加固,哪个不是用了相当于smc或复杂变换,还不是被分析的赤果果。
数字,梆梆,百度,乐固,爱 ...

发这个cm上来就是求虐,怎么可能有破不掉的cm?我只想让别人知道:还有这种方法,这样才是互相学习。投机取巧的话虽然也能成功,但是并没有增长什么知识呀。
 楼主| 赤座灯里 发表于 2020-6-1 22:29
本帖最后由 赤座灯里 于 2020-6-1 22:31 编辑
shaoge 发表于 2020-6-1 22:26
校验函数如果smc或者简单变换,那等你做出成cm,自然有人会告诉用什么手段;
不过赏金不是3位数以上?{:1_ ...

校验函数没有smc就是提示,但是我的初衷是想让人理解运行流程,毕竟也不是什么ctf比赛,就故意设计了这处破绽。找到结果后溯源当然容易,但这和我的想法不同
 楼主| 赤座灯里 发表于 2020-6-1 21:46
本帖最后由 赤座灯里 于 2020-6-1 22:05 编辑
shaoge 发表于 2020-6-1 20:01
一、四个反调试函数:确切的截图在这,ida中明摆着。。。为什么说不是呢?
1、cat /proc/net/tcp |grep :5 ...

四个反调试函数中的第四个是必定返回为1的,另外第二个函数确切的说是无效的,ps在安卓高版本并不能返回进程。另外你能搜到0x29A,我就可以让你搜不到。既然你是纯静态分析,那就很有办法克制。校验函数如果加了smc,什么也分析不到。显然你并没有体会到玩法。之所以没做这些就是为了给提示,不让分析变得有难度,如果不是调试,那所做的就没有意义,完全有办法反制。我写这个的目的就是为了想看看是如何调试的,纯静态就不好玩了
PS:如果结果是返回1,也是搜索立即数1嘛
PPS:还是感谢有人愿意玩
 楼主| 赤座灯里 发表于 2020-5-31 13:01
本帖最后由 赤座灯里 于 2020-6-1 03:04 编辑

口令红包的时效性设为3天,过期了就当普通reverseme玩叭
weikun444 发表于 2020-5-31 14:16
eq函数是个什么鬼,还要等于666,期待高手了!
F111 发表于 2020-5-31 14:19
哇哦,看大佬表演
天然大咪咪 发表于 2020-5-31 14:37
等我打完游戏。。。
粗略看了下,破解so文件的吧?
其实对Android来说,hook textview的settext就可以了吧?
雨落惊鸿, 发表于 2020-5-31 14:40
楼主 这个软件是不是输入错误就会闪退  为什么我又看见提示错误的字符串  不知道是软件的问题还是我手机的问题
 楼主| 赤座灯里 发表于 2020-5-31 14:46
天然大咪咪 发表于 2020-5-31 14:37
等我打完游戏。。。
粗略看了下,破解so文件的吧?
其实对Android来说,hook textview的settext就可以了 ...

是要追码哦,不然解密出来的口令也是不正确的
 楼主| 赤座灯里 发表于 2020-5-31 14:53
weikun444 发表于 2020-5-31 14:16
eq函数是个什么鬼,还要等于666,期待高手了!

稍等有点问题,我改下
 楼主| 赤座灯里 发表于 2020-5-31 15:23
雨落惊鸿, 发表于 2020-5-31 14:40
楼主 这个软件是不是输入错误就会闪退  为什么我又看见提示错误的字符串  不知道是软件的问题还是我手机的 ...

修复了,你再试试
vLove0 发表于 2020-5-31 15:41
找到了地方,但是好像算出的结果不对。看不懂IDA啊。然后输错了还是闪退啊。新下载的也闪退。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止回复与主题无关非技术内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-26 01:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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