吾爱破解 - LCG - LSG |安卓破解|病毒分析|破解软件|www.40881110.com

 找回密码
 注册[Register]

QQ登录

只需一步£¬快速开始

查看: 1613|回复: 11
上一主题 下一主题

[Android 原创] 论坛里的crackme 练手留念杂文2

[复制链接]
跳转到指定楼层
楼主
shuaiyue 发表于 2019-4-24 00:12 回帖奖励
本帖最后由 shuaiyue 于 2019-4-24 00:20 编辑

下载£ºhttp://www.40881110.com/forum.php ... peid%26typeid%3D262

双进程守护£¬无法反调试£¬肉疼£¬换了N多版本和真机无法运行¡£谁知道为啥的回复下£¬最后用夜神模拟器试试DDMS无法显示进程£¬各种肉疼£¬无奈索性静态分析吧¡£
?#20197;?#31967;的£¬凑合看吧¡£
流程£ºjadx加载dex  - > 分析入口函数



private native boolean check(String str);     //so


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        this.mEdit = (EditText) findViewById(R.id.mKeyEdit);
        this.mButton = (Button) findViewById(R.id.mCheckBtn);
        this.mButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                String flag = MainActivity.this.mEdit.getText().toString();
                if (MainActivity.this.check(flag)) {                   //JAVA层爆破
                    Toast.makeText(MainActivity.this, "great:flag{" + flag + "}", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "Error, try again", 0).show();
                }
            }
        });

在继续跟踪so在哪里加载的

//注意这里JAVA层运行先执行APP再到-¡·MainActivity
public class App extends Application {    //主要是做初始化用的
    private native int checkDebugger();         //动态函数


    static {
        System.loadLibrary("check");               
    }


    protected void attachBaseContext(Context arg1) {
        super.attachBaseContext(arg1);
    }


    public void onCreate() {
        super.onCreate();
        if (checkDebugger() != 0) {                         //第一次执行
        }
    }


接着到IDA 分析so 先看看checkDebugger£¨£©函数 代码很长简化下
int checkDebugger()
{
  signed int mark; // r7
  int v1; // r9
  __pid_t v2; // r0
  _BOOL4 v3; // r3
  int cid; // r0
  int v5; // r1
  int v6; // r2
  int v7; // r4
  int v8; // r0
  __pid_t fpid; // [sp+Ch] [bp-44h]
  char v11; // [sp+10h] [bp-40h]
  int v12; // [sp+14h] [bp-3Ch]
  char v13; // [sp+18h] [bp-38h]
  char v14; // [sp+19h] [bp-37h]
  char v15; // [sp+1Ah] [bp-36h]
  char v16; // [sp+1Bh] [bp-35h]
  char v17; // [sp+1Ch] [bp-34h]
  char v18; // [sp+1Dh] [bp-33h]
  char v19; // [sp+1Eh] [bp-32h]
  char v20; // [sp+1Fh] [bp-31h]
  char v21; // [sp+20h] [bp-30h]
  char v22; // [sp+21h] [bp-2Fh]
  char v23; // [sp+22h] [bp-2Eh]
  char v24; // [sp+23h] [bp-2Dh]
  char v25; // [sp+24h] [bp-2Ch]
  char v26; // [sp+25h] [bp-2Bh]
  char v27; // [sp+26h] [bp-2Ah]
  char v28; // [sp+27h] [bp-29h]


  mark = 6;
LABEL_2:
  if ( --mark && !pipe(&gpipe) )                // ?#26434;?#25972;数 ,当n为0时,转换为布尔值就是假,此时 !n 就是 真 进入if后面的语句块
                                                // fd[0]指向管道的读端£¬fd[1]指向管道的写端
  {
    fpid = fork();                              // 系统先给新的进程分配?#35797;´_简单理解克隆自己创建一个新的进程
    prctl(4, 1);                                // 获取子进程?#37038;?#22120;
    v1 = fpid;
    if ( !fpid )                                // 为真子进程干活了¡£
    {
      cid = getppid();                          // 子进程ID
      safe_attach(cid, v5, v6);                 // 安全附加
      close(gpipe);                             // 关?#23637;?#36947;
      v7 = dword_3034;
      memset(&v13, v1, 0x11u);                  // 初始化数组
      v13 = 74;
      v14 = 117;
      v15 = 115;
      v16 = 116;
      v17 = 72;
      v18 = 97;
      v19 = 118;
      v20 = 101;
      v21 = 65;
      v22 = 84;
      v23 = 114;
      v24 = 121;
      v25 = 33;
      v26 = 33;
      v27 = 33;
      v28 = 33;
      write(dword_3034, &v13, 0x10u);           // 子进程向管道里写数据
                                                //  arrys[] = {74,117,115,116,72,97,118,101,65,84,114,121,33,33,33,33}
      v8 = close(v7);                           // 成功执行后会返回0£¬否则返回-1
      handle_events(v8);                        // 处理?#37038;?#21040;的连接
      exit(1);
    }
    close(dword_3034);
    pthread_create(&v11, 0, parent_read_thread, &fpid);// 父进程--线程管道读取
    do
    {
      v2 = waitpid(fpid, &v12, 1);
      if ( v2 > 0 )
        v3 = (v12 - 1) <= 0;                    // 正常
      else
        v3 = v2 == 0;                           // 检测到子进程在玩火
      if ( !v3 )
      {
        kill(fpid, 9);                          // 强制杀死子进程
        goto LABEL_2;                           // 在去制造个儿子
      }
    }
    while ( !sflag[6] );                        // 0循?#32602;?#38750;0退出   £¬一直在循环
    pthread_create(&v11, 0, child_attach_thread, &fpid);// 监控子线程
  }
  return 0;
}



safe_attach  重点注释

if ( ptrace(PTRACE_ATTACH, cid) < 0 )         // ptrace系统调用提供了一种方法£¬这个方法可以让一个进程监视
                                                // 控制另一个进程的执行£¬并且可以查看和更改被追踪进程的内存和寄存器¡£
                                                // 通常用来下断点和调试¡£
                                                // 子进程调用PTRACE_TRACEME£¬表明这个进程由它的父进程来跟踪
                                                // 任何发给这个进程的信号signal£¨除了SIGKILL£©将导致该进程停止运行
                                                // 而它的父进程会通过wait()获得通知¡£另外£¬该进程之后所有对exec()的调用都将使操作系统产生一个SIGTRAP信号发送给它£¬这让父进程有机会在新程序开始执行之前获得对子进程的控制权¡£
                                                // 成功返回0¡£错误返回-1¡£errno被设置
    goto Fail_Code;                             // GOTO执行失败
  v8 = 0;                                       // 返回子进程结束状态值¡£ 子进程的结束状态值会由?#38382;?status 返回
  while ( 1 )                                   // 循环作用主要是子进程退出?#24065;?#24178;的事情
  {                                             // #define __WALL 0x40000000 等待所有类型的子进程£¬包括"clone"和"non-clone"
    result = waitpid(v3, &v8, 0x40000000);      // 如果在调用waitpid()函数时£¬当指定等待的子进程已经停止运行或结束了£¬则waitpid()会立即返回£»但是如果子进程还没有停止运行或结束£¬则调用waitpid()函数的父进程则会被阻塞£¬暂停运行¡£
                                                // 这调试£¡
                                                // 如果不关心子进程为什么推出的话£¬也可以传入空指针
    if ( result != -1 )                         // 失败
      break;
    if ( *_errno(-1) != 4 )
      goto Fail_Code;
  }

handle_events 函数的分析

// //事件处理函数
__pid_t __fastcall handle_events(int a1)
{
  __pid_t result; // r0
  __pid_t v2; // r5
  const char *v3; // r0
  int v4; // r4
  signed int v5; // r3
  int v6; // [sp+0h] [bp-18h]
  int v7; // [sp+4h] [bp-14h]

  v6 = a1;                                      // 成功执行时£¬返回状态改变的子进程标识
  v7 = 0;
  do
  {                                             // 监控自己子进程
    while ( 1 )
    {
      result = waitpid(-1, &v7, 0x40000000);    // ?#38382;?1 = 等待任一个子进程
      v2 = result;
      if ( result != -1 )
        break;
      if ( *_errno(-1) != 4 )
        goto LABEL_4;
    }
    if ( result < 0 )
    {
LABEL_4:
      v3 = "waitpid";
LABEL_13:
      perror(v3);
      exit(1);
    }
    if ( (v7 & 0x7F) == 127 && ((v7 + 1) & 0x7F) <= 1 )//     先来对wait status做个整体总结£¬一般我们通过wait status可以判定子进程发生了以下事件£º
                                                //
                                                //    £¨1£©子进程通过传递一个整形?#38382;?#32473;exit£¨或者_exit£©而正常退出
                                                //
                                                //    £¨2£©子进程被一个信号终止
                                                //
                                                //    £¨3£©子进程被一个信号暂停£¨调用waitpid时需指定WUNTRACED标志£©
                                                //
                                                //    £¨4£©暂停的子进程被信号SIGCONT恢复£¨调用waitpid时需指定WCONTINUED标志£©
                                                //
    {
      v4 = v7 >> 8;
      v5 = may_cause_group_stop(v4) ? 0 : v4;
      result = ptrace(PTRACE_CONT, v2, 0, v5, v6);// 简单理解只要子程序被调试中断将被退出
      if ( result < 0 )
      {
        v3 = "PTRACE_CONT";
        goto LABEL_13;
      }
    }
  }
  while ( v7 << 25 && ((v7 + 1) & 0x7F) <= 1 ); // 成功执行时£¬返回状态改变的子进程标识
  return result;
}


分析完看到了SFLAG 数组£¬用于后面后面 check(JNIEnv *a1, int a2, char *getText) 做亦或运算用¡£
接着来看看check 函数

bool __fastcall check(JNIEnv *a1, int a2, char *getText)
{
  JNIEnv *v3; // r4
  char *strpass; // r7
  const void *v5; // r8
  int v6; // r2
  _BOOL4 v7; // r5
  char v9; // [sp+0h] [bp-38h]
  char v10; // [sp+1h] [bp-37h]
  char v11; // [sp+2h] [bp-36h]  //所以第?#22856;?#26159;0
  char v12; // [sp+4h] [bp-34h]  //注意这里和上面少了一位
  char v13; // [sp+5h] [bp-33h]
  char v14; // [sp+6h] [bp-32h]
  char v15; // [sp+7h] [bp-31h]
  char v16; // [sp+8h] [bp-30h]
  char v17; // [sp+9h] [bp-2Fh]
  char v18; // [sp+Ah] [bp-2Eh]
  char v19; // [sp+Bh] [bp-2Dh]
  char v20; // [sp+Ch] [bp-2Ch]
  char v21; // [sp+Dh] [bp-2Bh]
  char v22; // [sp+Eh] [bp-2Ah]
  char v23; // [sp+Fh] [bp-29h] 最后一位是0

  v3 = a1;
  strpass = getText;
  if ( ((*a1)->GetStringLength)() != 16 )
    return 0;
  v5 = ((*v3)->GetStringUTFChars)(v3, strpass, 0);
  memset(&v9, 0, 0x11u);
  v12 = 1;
  v6 = 0;
  v19 = 0x11;
  v9 = 30;
  v10 = 29;
  v11 = 18;
  v13 = 18;
  v14 = 51;
  v15 = 11;
  v16 = 37;
  v17 = 120;
  v18 = 38;
  v20 = 64;
  v21 = 79;
  v22 = 74;
  v23 = 82;
  do
  {
    *(&v9 + v6) ^= sflag[v6];                   // FLAG是上面checkdebugger 函数动态生成的¡£
    ++v6;
  }
  while ( v6 != 16 );
  v7 = memcmp(v5, &v9, 16u) == 0;               // 爆破关键位置
  ((*v3)->ReleaseStringUTFChars)(v3, strpass, v5);// 释放
  return v7;
}

这里注意£¬就是V9 ?#22336;?#20018;的问题£¬上面我注释了¡£第?#22856;?#21644;最后一位¡£


开VS2017

int main()
{
        char sflag[] = { "F8LEFT" };
        char str[] = { 30,29,18,0,1,18,51,11,37,120,38,17,64,79,74,82,0 };
        
        char flag[] = { 74,117,115,116,72,97,118,101,65,84,114,121,33,33,33,33 };
        char str2[17] = { 0 };
        for (size_t i = 0; i < 16; i++)
        {
                str2= str ^ flag;
        }

        cout << str2 << endl;
£ý

最终密码=ThatIsEnd,Thanks

免费评分

参与人数 5威望 +1 吾爱币 +12 热心值 +4 收起 理由
qtfreet00 + 1 + 9 + 1 ?#34892;?#21457;布原创作?#32602;?#21566;爱破解论坛因你更精彩£¡
封帆帆 + 1 我很赞同£¡
劣酒先生 + 1 + 1 热心回复£¡
fei8255 + 1 + 1 用心讨论£¬?#19981;?#25552;升£¡
耳食之辈 + 1 用心讨论£¬?#19981;?#25552;升£¡

查看全部评分

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

推荐
 楼主| shuaiyue 发表于 2019-4-24 17:40 |楼主
pk8900 发表于 2019-4-24 07:45
for (size_t i = 0; i < 16; i++)
        {
                str2= str ^ flag;

估计是缓存造成的£¬编辑里都是正确的£¬显示就是错的¡£ str2=str ^ flag
推荐
pk8900 发表于 2019-4-24 18:33
本帖最后由 pk8900 于 2019-4-24 18:35 编辑
shuaiyue 发表于 2019-4-24 17:40
估计是缓存造成的£¬编辑里都是正确的£¬显示就是错的¡£ str2=str ^ flag
str2[i]= str[i] ^ flag[i];

用插入代码功能就好了£¬你分析的没错£¬我研究了一下£¬确实是这个flag
沙发
wang19940311 发表于 2019-4-24 06:19
3#
yuedingc 发表于 2019-4-24 07:38
没有看懂是什么
4#
pk8900 发表于 2019-4-24 07:45
for (size_t i = 0; i < 16; i++)
        {
                str2= str ^ flag;
        }
能?#26032;·w?/td>
5#
zcgts 发表于 2019-4-24 11:06
看不懂呀£¬我的天
8#
 楼主| shuaiyue 发表于 2019-4-24 19:46 |楼主

谢谢提醒£¬没注意看还有这功能¡£
9#
血色天空 发表于 2019-4-24 20:13
想楼主学习£¬高手
10#
cdevil 发表于 2019-4-24 20:37
太厉害了£¬向楼主学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告£º禁止回复与主题无关内容£¬违者重罚£¡

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

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

GMT+8, 2019-5-22 11:19

Powered by Discuz!

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表
Çò̽ÍøÀºÇòÖ¸Êý