赤座灯里 发表于 2020-5-31 12:55

萌新发个有奖安卓cm

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

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

赤座灯里 发表于 2020-6-3 11:53

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

## WP

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

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

```java
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中找到对应的函数:

```c
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; //
unsigned int v11; //
int isdbg; //

x = 0;
v2 = (const char *)((int (*)(void))a1->functions->GetStringUTFChars)();
isdbg = dbg1();
isdbg = dbg2();
isdbg = dbg3();
isdbg = dbg4();
while ( x != 4 )
{
    v3 = isdbg;
    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 = *(_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做比较,解密算法:

```python
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函数:

```c
int anti()
{
return 0;
}
```

看下这四个函数是什么:

```c
signed int dbg1()
{
int v0; // r11
FILE *v1; // r4
int v3; //
int v4; //

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; //
char v5; //

_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; //
__int16 v4; //
char v5; //

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; //
int v7; //

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 - 48) < 0xAu )
      ++v3;
}
result = 0;
if ( v3 > 0xA )
    result = 1;
return result;
}
```

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

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

注册的函数源码如下:
```c
static jint func(JNIEnv *env, jobject, jstring n) {
    const char *input = env->GetStringUTFChars(n, 0);
    int dbg;
    dbg = CheckPort23946ByTcp();
    dbg = SearchObjProcess();
    dbg = readStatus();
    dbg = 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) 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 = 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的处理,鉴于存在结构体和花指令,这里放源码:

```c
    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设置为一个空函数,从而改变返回值:

```c
    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 <= str3以限制多解,接着进行大数运算,将str1、str2、str3三次方,记为a、b、c,记d = b + c,将a和d右对齐,记e = d - a,最后判断e的前49位为'0',以及第50位为'4'、51位为'2',结果正确将r2赋值为666:

```c
static void check() {
    char *input;
    int ret = 0;
    __asm __volatile(
    "mov    %0, r1\n"
    :"=r" (input): :"r1");
    char str, a, b, c, d, e, f = {0};
    int i, j, k;
    for (i = 0; i < _strlen(input); i++) str = input - 20;
    char *str1 = substring(str, 0, 17);
    char *str2 = substring(str, 17, 17);
    char *str3 = substring(str, 34, 17);
    if (str2 > str3) {
      goto end;
    }
    pow3(str1, a);
    pow3(str2, b);
    pow3(str3, c);
    add(b, c, d);
    _strcpy(e, a);
    for (i = 0; d; i++);
    for (j = 0; e; j++);
    for (k = M - 2; i > 0; k--, i--) {
      d = d;
      d = 0;
    }
    for (k = M - 2; j > 0; k--, j--) {
      e = e;
      e = 0;
    }
    sub(d, e, f);
    for (i = 0; !f; i++);
    _strcpy(e, &f);
    for (i = 0; i < _strlen(e); i++) {
      if (i <= 48) {
            if (e != 48) {
                goto end;
            }
      } else {
            if (e != (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月开始学安卓,由于本人懒得要死,也是想到什么就学什么,所以一直没有长进,大一快结束了,希望大二有所收获吧。

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的时候等式成立。
所以你能看到这神奇的一幕。

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

shaoge 发表于 2020-6-1 14:53

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

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

赤座灯里 发表于 2020-5-31 15:23
修复了,你再试试

{:1_907:} 1天后,可以公布下解密思路么?搞不懂{:1_889:}

赤座灯里 发表于 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,很想学习{:301_975:}

赤座灯里 发表于 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,期待高手了!{:1_909:}

F111 发表于 2020-5-31 14:19

哇哦,看大佬表演

天然大咪咪 发表于 2020-5-31 14:37

等我打完游戏。。。
粗略看了下,破解so文件的吧?
其实对Android来说,hook textview的settext就可以了吧?

雨落惊鸿, 发表于 2020-5-31 14:40

楼主 这个软件是不是输入错误就会闪退为什么我又看见提示错误的字符串不知道是软件的问题还是我手机的问题
https://i.loli.net/2020/05/31/lDLQVFr5h21tib7.jpg

赤座灯里 发表于 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啊。然后输错了还是闪退啊。新下载的也闪退。
页: [1] 2 3 4 5 6
查看完整版本: 萌新发个有奖安卓cm