当前位置 博文首页 > S1mba:Android第一代壳demo编写

    S1mba:Android第一代壳demo编写

    作者:S1mba 时间:2021-02-01 22:22

    Android第一代壳Demo编写

    前言

    这篇文章是对姜维大佬的这篇文章[Android中的Apk的加固(加壳)原理解析和实现]的补充。建议先看一编姜维大佬的这篇文章再看。

    姜维大佬写那篇文章的时间距今已久,如果要按照他文章中的思路编写你需要Android4.4以下的环境也就是Dalvik虚拟机的环境。而且我在用Dv虚拟机进行试验时也运行不了。不明原因,现在猜想可能是当时抄的脚本问题。

    这里讲一下我在Android5.0及以上环境下实现第一代壳demo过程中遇到的问题以及应对方法。

    后面将待加壳APK称为源APK,壳程序称为壳APK,加壳后的APK称为加壳APK。

    0x1 DEX的加载问题

    首先遇到的是DEX的加载问题。在某个和Android版本升级之后,DexClassLoader的代码发生了改变。已经不可以直接加载Dex文件了。需要将Dex转化为jar或者apk进行加载,否则加载过程中取出来的Dex文件加载进去也没用,文件还会被删除掉。在attachBasecontext中尝试加载MainActivity的时候会报错。

    • 解决方法

    将代码中的这句

    DexClassLoader dcl = new DexClassLoader(apkName,dexPath,libPath,oldCl);
    

    dcl的类型改为DexClassLoader的父类ClassLoader。

    并使用dex2jar将源APK的DEX文件转为jar包,再用dx工具转为Android可以加载的jar包。具体命令为

    dex2jar 源APK.apk -o target.jar -> 得到普通jar包
    dx --dex --output=target.jar target,jar  ->得到Android可加载的jar
    

    拼接时就用得到的jar包代替源APK的Dex拼接。

    0x2 资源加载问题

    加载完Dex后遇到的就是资源加载的问题。因为姜维大佬的方法是将源APK的DEX拼接到壳APK的DEX后,在运行时在取出来进行加载。那么这个APK运行时的布局等资源是没有被打包过去的,只有代码。当壳APK将DEX取出并动态加载运行到加载布局文件的代码时就会出错。

    img

    网上有些文章提供的解决办法是直接将源APK的资源文件复制到壳APK中。但是不同的项目布局文件对应的id是不一样的,需要重新将资源的id编辑过于麻烦。

    • 解决方法

    将拼接后的DEX文件放回源APK中就不用再自己动手去加载资源了。

    0x3 AppCompatActivity问题

    解决上面两个问题后加壳APK还是不能运行,报错中有对Android版本的描述如:

    Before Android4.1…………balalbala

    且报错中有Appcompat的字样

    查资料发现这个AppCompatActivity是更换art虚拟机之后才有的。与普通的Activity不同。AppCompat的布局文件会在上方有个标题栏which is Activity所没有的。

    • 解决方法

    目前只想到将源APK的Activity继承的父类全部改为Activity。毕竟这只是一个小demo,不是重点。以后找到方法再进行改进。

    0x4 Dex拼接后Dex头的修复问题

    若不修复Dex头,Dex文件是不会被正确识别的。之前拼接Dex所使用的脚本是拿网上的。

    image-20210201213549982

    这样子并不能修复SHA1字段和CheckSum字段,还会覆盖掉文件大小字段。因为这样子写实际写进去的是得到SHA1对应的ASCII码的十六进制数。得不到正确的Dex文件。

    • 解决方法

    这个是我修改过的脚本

    import binascii
    import os
    import hashlib
    import zlib
    import zipfile
    import shutil
    from xml.dom import minidom
    
    unshellpath = r"D:\CodeAS\shell1\app\build\outputs\apk\debug\app-debug.apk"
    srcapkpath = r"D:\CodeAS\srcapk\app\build\outputs\apk\debug\app-debug.apk"
    
    
    def fixCheckSum(shell):
        shell.seek(0x0C)
        data = shell.read()
        checksum = zlib.adler32(data)
        strchecksum = str(hex(checksum))
        strchecksum = strchecksum.replace("0x", "")
        if not len(strchecksum) % 2 == 0:
            strchecksum = strchecksum.zfill(len(strchecksum)+1)
        shell.seek(0x08)
        shell.write(bytearray.fromhex(strchecksum)[::-1])
        
    def fixSHA1(shell):
        shell.seek(0x20)
        signBytes = shell.read()
        sha1 = hashlib.sha1()
        sha1.update(signBytes)
        sign = sha1.hexdigest()
        b = bytes.fromhex(sign)
        shell.seek(0x0C)
        shell.write(b)
    
    
    def fixFileSize(shell, num):
        b = bytearray()
        for i in range(4):
            number = int(num % 256)
            b.append(number)
            num = num >> 8
        shell.seek(0x20)
        shell.write(b)
    
    def IntToHex(num):
        b = bytearray()
        for i in range(4):
            number = int(num % 256)
            b.append(number)
            num = num >> 8
        b.reverse()
        return b
    
    def fixXML():
        doc = minidom.parse(srcapkpath.replace('.apk', r'\AndroidManifest.xml'))
        root = doc.documentElement
        app_node = root.getElementsByTagName('application')[0]
        app_name = app_node.getAttribute('android:name')
        app_node.setAttribute('android:name', 'app.simba.shell1.ShellApplication')
        meta = doc.createElement('meta-data')
        meta.setAttribute('android:name', 'ApplicationName')
        meta.setAttribute('android:value', app_name)
        app_node.appendChild(meta)
    
        doc.writexml(open(srcapkpath.replace(
            '.apk', r'\AndroidManifest.xml'), 'w'), encoding='utf-8')
    
    
    def main():
        
        if os.path.exists(unshellpath.replace('.apk','')):
            shutil.rmtree(unshellpath.replace('.apk',''))
    
        apk = zipfile.ZipFile(unshellpath)
        for n in apk.namelist():
            apk.extract(n,unshellpath.replace('.apk',''))
    
        for root, dirs, files in os.walk(unshellpath.replace('.apk', '')):
            for f in files:
                if os.path.join(root, f).endswith('.dex'):
                    unshelldexpath = os.path.join(root, f)
        print('[*] 成功反编译shellAPK')
    
        os.system('apktool.bat -f -s d ' + srcapkpath +
                  ' -o ' + srcapkpath.replace('.apk', ''))
        srcjarpath = srcapkpath.replace('.apk','.jar')
        os.system('dex2jar.bat -f '+srcapkpath+' -o '+srcjarpath)
        os.system('java -jar dx.jar --dex --output='+srcjarpath+' '+srcjarpath)
        for root, dirs, files in os.walk(srcapkpath.replace('.apk', '')):
            for f in files:
                if os.path.join(root, f).endswith('.dex'):
                    os.remove(os.path.join(root, f))
        print('[*] 成功反编译待加壳APK')
    
        unshelldex = open(unshelldexpath, 'rb+')
        tmpshell = unshelldex.read()
        unshellArray = bytearray(tmpshell)
        unshelldex.close()
        print('[*] 成功读取脱壳DEX文件')
    
        tmp = open(srcjarpath,'rb+')
        srcjarArray = bytearray(tmp.read())
        tmp.close()
    
        tmpdex = unshellArray +srcjarArray+IntToHex(len(srcjarArray))
    
        f = open(srcapkpath.replace('.apk','/classes.dex'),'wb+')
        f.write(tmpdex)
        f.close()
    
        shell = open(srcapkpath.replace('.apk','/classes.dex'),'rb+')
        fixFileSize(shell, len(tmpdex))
        fixSHA1(shell)
        fixCheckSum(shell)
        shell.close()
    
        print('[*] DEX文件修复完毕')
    
        fixXML()
        print('[*] AndroidManifest文件修改完毕')
    
        outputpath = os.path.dirname(
            srcapkpath)+r'\shell_'+os.path.basename(srcapkpath)
        os.system('apktool.bat  b '+srcapkpath.replace('.apk', '')+' -o ' +
                  outputpath)
        print('[*] APK重打包完毕')
    
        os.system('jarsigner -keystore '+r'D:\CodeAS\mykey.jks'+' -signedjar ' +
                  outputpath.replace('.apk', '_signed.apk')+' '+outputpath+' mykey '+'-storepass 123456')
    
    
    if __name__ == "__main__":
        main()
    
    bk
    下一篇:没有了