吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1742|回复: 10
收起左侧

[Android 原创] 安卓某注入类外挂脱壳和功能分析—上

[复制链接]
jbczzz 发表于 2024-8-6 16:56
本帖最后由 jbczzz 于 2024-8-6 16:58 编辑

0x0 前言
今天看到了一个某手游的注入类外挂样本,目标是脱壳加密流程的分析,以及分析出外挂实现功能修改的地方,此篇仅作为学习记录。
0x1 GZIP加密
首先,这个样本本体是一个500kb的sh文件,拖到IDA里提示是二进制文件。于是用everedit打开这个文件看一眼,发现开头有这个自解压脚本
[Asm] 纯文本查看 复制代码
#!/data/data/com.termux/files/usr/bin/bash
skip=50
set -e

tab='        '
nl='
'
IFS=" $tab$nl"

umask=`umask`
umask 77

gztmpdir=
trap 'res=$?
  test -n "$gztmpdir" && rm -fr "$gztmpdir"
  (exit $res); exit $res
' 0 1 2 3 5 10 13 15

case $TMPDIR in
  / | /*/) ;;
  /*) TMPDIR=$TMPDIR/;;
  *) TMPDIR=/data/data/com.termux/files/usr/tmp/;;
esac
if type mktemp >/dev/null 2>&1; then
  gztmpdir=`mktemp -d "${TMPDIR}gztmpXXXXXXXXX"`
else
  gztmpdir=${TMPDIR}gztmp$$; mkdir $gztmpdir
fi || { (exit 127); exit 127; }

gztmp=$gztmpdir/$0
case $0 in
-* | */*'
') mkdir -p "$gztmp" && rm -r "$gztmp";;
*/*) gztmp=$gztmpdir/`basename "$0"`;;
esac || { (exit 127); exit 127; }

case `printf 'X\n' | tail -n +1 2>/dev/null` in
X) tail_n=-n;;
*) tail_n=;;
esac
if tail $tail_n +$skip <"$0" | gzip -cd > "$gztmp"; then
  umask $umask
  chmod 700 "$gztmp"
  (sleep 5; rm -fr "$gztmpdir") 2>/dev/null &
  "$gztmp" ${1+"$@"}; res=$?
else
  printf >&2 '%s\n' "Cannot decompress $0"
  (exit 127); res=127
fi; exit $res

解这个方式很简单,把shell脚本的部分删掉,然后用gzip解压就行了,解压出来后把文件拖进ida看,发现有壳和混淆

0x2 upx+xor+ollvm加密
然后本来以为接下来就是unidbg还原ollvm环节,但是开始还原之后发现这玩意的解密流程很像之前看到过的一个加密样本。
那个加密样本的加密流程是:
1.读取需要加密的文件,先用upx压缩
2.破坏segment信息:修改(RW_)Loadable Segment:p_addr_VIRTUAL_address(0x000f8)的字节,先取低四位,然后将高四位写为1001,修改(RW_)Loadable Segment:p_memsz_SEGMENT_RAM_LENGTH(0x110) 位置的三个字节为 FF FF FF,修改(R_X)Loadable Segment(0x000B0 ~ 0x000E8)的所有字节为0x0A
3.找到这段字串
[Asm] 纯文本查看 复制代码
"UPX!",        "$Info: This file is packed with the UPX executable packer http://upx.sf.net $",
        "$Id: UPX 4.22 Copyright (C) 1996-2024 the UPX Team. All Rights Reserved. $",
        "PROT_EXEC|PROT_WRITE failed."};

将这段字串的字符替换成\x0A
4.将整个文件xor加密,加密后的数据头部接一个字串特征,把这段数据拼接到一个已经编译好的壳的后边
5.写回文件完成加密

在运行的时候那个壳文件会通过字串特征定位到xor加密数据,然后通过key把加密的数据段解密,解密之后通过魔数判断文件类型用对应方法执行。
这一段就是它的解密过程(无混淆):
[Asm] 纯文本查看 复制代码
 void __noreturn sub_202DC()
{
  v49 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  memset(v46, 0, sizeof(v46));
  filebuffer = fopen((const char *)v46 + 1, "rb");
  if ( !filebuffer )
    ((void (__fastcall __noreturn *)(__int64 *, const char *, __int64))loc_22200)(
      &qword_63B78,
      "Failed to open file.",
      20LL);
  v1 = filebuffer;
  fseek(filebuffer, 0LL, 2);
  filesize = ftell(v1);
  rewind(v1);
  filebufferTMP = (void *)sub_404E0(filesize);
  v4 = fread(filebufferTMP, 1uLL, filesize, v1);
  fclose(v1);
  if ( v4 != filesize )
    ((void (__fastcall __noreturn *)(__int64 *, const char *, __int64))loc_22200)(
      &qword_63B78,
      "Failed to read file.",
      20LL);
  if ( filesize >= 0xFFFFFFFFFFFFFFF0LL )
    sub_40684(&v43);
  if ( filesize >= 0x17 )
  {
    v5 = (char *)sub_4047C((filesize + 16) & 0xFFFFFFFFFFFFFFF0LL);
    v44 = filesize;
    ptr = v5;
    v43 = (filesize + 16) & 0xFFFFFFFFFFFFFFF0LL | 1;
  }
  else
  {
    v5 = (char *)&v43 + 1;
    LOBYTE(v43) = 2 * filesize;
    if ( !filesize )
    {
LABEL_11:
      v5[filesize] = 0;
      j_j__free(filebufferTMP);
      v6 = sub_4047C(32LL);
      v7 = (unsigned __int8)v43;
      *(_QWORD *)(v6 + 16) = 0xA3A7E8B4A0E7BF8BLL;
      v8 = (_QWORD *)v6;
      if ( (v7 & 1) != 0 )
        v9 = (char *)ptr;
      else
        v9 = (char *)&v43 + 1;
      v10 = v7 >> 1;
      if ( (v7 & 1) != 0 )
        v10 = v44;
      *(_OWORD *)v6 = xmmword_DC73;
      *(_BYTE *)(v6 + 24) = 0;
      if ( v10 >= 24 )
      {
        v11 = &v9[v10];
        v12 = v9;
        while ( 1 )
        {
          if ( v10 == 23 )
            goto LABEL_17;
          v13 = (char *)memchr(v12, 232, v10 - 23);
          if ( !v13 )
            goto LABEL_17;
          if ( !(*(_QWORD *)v13 ^ *v8 | *((_QWORD *)v13 + 1) ^ v8[1] | *((_QWORD *)v13 + 2) ^ v8[2]) )
            break;
          v12 = v13 + 1;
          v10 = v11 - (_BYTE *)v12;
          if ( v11 - (_BYTE *)v12 < 24 )
            goto LABEL_17;
        }
        if ( v13 != v11 && v13 - v9 != -1 )
        {
          sub_40C0C(&v40, &v43, v13 - v9 + 24, -1LL, &v43);
          v47 = 6;
          v48 = 6518904;
          sub_40830(&v37, &v40);
          for ( i = 0LL; ; ++i )
          {
            if ( (v40 & 1) != 0 )
            {
              if ( i >= *(_QWORD *)&v41[7] )
              {
LABEL_35:
                generateRandomString_1FB78(64LL);
                generateRandomString_1FB78(64LL);
                sub_41620("mkdir -p /data/data/", v36);
                if ( (v30 & 1) != 0 )
                  mkdirCommand = (const char *)v32;
                else
                  mkdirCommand = v31;
                system(mkdirCommand);
                sub_41620("/data/data/", v36);
                v19 = sub_40F1C((int)&v25, "/");
                v20 = *(_OWORD *)v19;
                v27 = *(void **)(v19 + 16);
                v26 = v20;
                *(_QWORD *)(v19 + 8) = 0LL;
                *(_QWORD *)(v19 + 16) = 0LL;
                *(_QWORD *)v19 = 0LL;
                if ( (v33 & 1) != 0 )
                  v21 = v35;
                else
                  v21 = v34;
                if ( (v33 & 1) != 0 )
                  v22 = *(_QWORD *)&v34[7];
                else
                  v22 = (unsigned __int64)v33 >> 1;
                v23 = sub_40B48((int)&v26, v21, v22);
                v24 = *(_OWORD *)v23;
                v29 = *(void **)(v23 + 16);
                v28 = v24;
                *(_QWORD *)(v23 + 8) = 0LL;
                *(_QWORD *)(v23 + 16) = 0LL;
                *(_QWORD *)v23 = 0LL;
                sub_21180(&v47, &v28, 16LL);
              }
              v15 = v42;
            }
            else
            {
              v15 = v41;
              if ( i >= (unsigned __int64)v40 >> 1 )
                goto LABEL_35;
            }
            v16 = v15[i];
            if ( (v37 & 1) != 0 )
              v17 = v39;
            else
              v17 = v38;
            v17[i] = *((_BYTE *)&v48 + i + -3 * (i / 3)) ^ v16;
          }
        }
      }
LABEL_17:
      ((void (__fastcall __noreturn *)(__int64 *, void *, __int64))loc_22200)(&qword_63B78, &unk_D808, 27LL);
    }
  }
  memcpy(v5, filebufferTMP, filesize);
  goto LABEL_11;}

于是可以直接在壳文件中找到xor的key,把加密数据取出来,按照壳的解密流程去还原一次就得到了原elf文件,打开看了之后发现这只是一个下载器,并不是外挂文件,他的功能是用curl下载注入器和动态库,并把这两个东西放到/data/app/~~xxxxxxxxxxxxxxx==/com.xxxx.xxxx.xxxx-xxxxxxxxxxxxxxxxx==/lib/arm64/这个文件夹下执行注入,直接在这个文件夹就能获取到文件。
后面想了想,感觉有挺多可以偷懒不用分析这个下载器就能获取注入器和动态库的方法,例如去ebpf监控他的写入文件的系统调用、或者通过charles抓包定位到他的文件下载网址,他注入的动态库也没有隐藏,可以直接去看/proc/maps/下加载的动态库文件路径等等。

0x4 小结
至此获取到了外挂的动态库文件,接下来就是分析这个动态库里的功能。

免费评分

参与人数 8威望 +1 吾爱币 +26 热心值 +7 收起 理由
allspark + 1 + 1 用心讨论,共获提升!
jaffa + 1 谢谢@Thanks!
zwbvip123 + 1 + 1 谢谢@Thanks!
janken + 1 + 1 热心回复!
fengbolee + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
GHFXX + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
solitary369 + 1 谢谢@Thanks!

查看全部评分

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

清与啊 发表于 2024-8-6 17:07
是的,但是我认为前面分析都还是必要的,毕竟一开始谁也没办法知道它就是个下载器嘛
hanghaidongchen 发表于 2024-8-6 17:36
有些外挂是几mb的,也是这样的自解压脚本,那种可能有真正的外挂数据
Triple.J 发表于 2024-8-6 21:06
厉害。不过作为菜鸟还是有点看不懂,有大佬能再简化一下吗?
ytianfeng1983 发表于 2024-8-6 22:06
        用心讨论,共获提升!
adevour 发表于 2024-8-7 01:59
看着挺有难度
kevinZ0 发表于 2024-8-7 08:37
看起来有难度,学到了
yooyyo 发表于 2024-8-7 14:14
学到了,谢谢佬!
风生·水起 发表于 2024-8-7 15:53
下载的可以自动更新
7001 发表于 2024-8-8 08:33
确实,思路很重要。学习了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-24 11:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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