技术.收藏喜欢

ios 签名本质

2021-07-22  本文已影响0人  刃之剑

签名的本质

是用于验证数据的合法性,确保被签名的数据来自特定的来源,并且未经篡改。它基于非对称加密,和哈希算法,研究签名之前需要对这两种算法有一定的了解。

为什么要签名?

数字签名其实跟我们手写的签名类似,
代表一个特定的主体(签名者)对特定内容(被签名数据)的署名和认可,
签名是对信息发送行为真实性的有效保障。
数字签名在很多领域都有应用,
iOS的代码签名正是其中最典型的一种,
我们可以先尝试分析一下iOS上代码签名的目的和好处。

A - 安全性

代码签名的首要任务是保证设备及系统的安全性,只有被苹果设备认可的证书签名的代码才能够被执行。
否则在安装或者运行时会因为无法通过内核的签名校验而失败。
iOS的系统中内置了来自苹果的CA证书,系统自身的代码都是被苹果”签名“过的, 而用户从AppStore下载的App也都已被苹果官方进行签名。签名机制可以有效地防止来自外部的攻击。
这里存在两种场景:
第一种是对系统本身的攻击,比如越狱,假如黑客发现了内核任意读写的漏洞,借此注入提权代码,但是这些代码会因为没有合法的签名而被系统拒绝运行,也就自然无法对系统造成实质性的破坏。
第二种是对设备或者用户的攻击,众所周知,提交到AppStore的应用代码都会经过苹果的审查,包含恶意代码的App是无法上架的。
此时,黑客可能会尝试先提交一个正常的App,通过各种技术手段躲避Apple的审查,上架后从网络上下载恶意代码并加载执行,但这种方式也会因为签名不合法而失败。

B - 垄断

代码签名还给苹果带来了一个巨大的好处:App分发的绝对控制权。
在iOS平台上(面向未越狱的用户)公开发行App的合法途径有且只有一种,就是上传到苹果官方的AppStore供用户下载。
苹果会对App进行严格的审查并签名,App的功能及支付渠道也因此可以受苹果的严格管制,这为苹果带来的经济效益不言而喻。

----以上两点参考别的博客----

C 签名的证书

image.png
我们的Mac电脑中包含了一对公私钥 ,公钥M,私钥M,
向苹果申请证书,我们首先要生成csr文件,csr文件中包括了公钥M。
在钥匙串->证书助理->从证书颁发机构请求证书, 
填写相关信息后会生成一个CertificateSigningRequest.certSigningRequest 证书,然后在开发者网站上上传这个文件,就可以生成这证书。
然后下载这个证书,双击安装到钥匙串。
这个证书中包含了公钥M,
还有用私钥A对公钥M签名文件进行RSA加密后的文件。

在开发这网站上选择bundleID生成相应的测试,上架描述文件,
当然,也可以在xcode中生成。然后项目中选择这个bundleID对应的描述文件进行运行。
描述文件中包含了苹果后端注册的设备列表,APPID,还有权限文件。

Xocde 在运行的过程中会使用电脑中的私钥(也可以是我们导出来的p12文件),
将描述文件和从开发者网站下载的证书一起打包进ipa文件中。
也是说ipa文件中包含了MachO文件,MachO文件的签名,
描述文件还有从苹果开发者网站上下载下来的证书。


一个ipa的包含结构是什么样的呢?

一个非砸壳的ipa里面可用有效文件也许就是图片资源了.
那么一个砸壳或者这个ipa没有加密他的内容可以怎么操作呢?

在编译iOS App时,Xcode在编译的打包的流程中会自动进行代码签名, 可以在编译日志界面找到一个Sign的步骤,内部是调用了codesign这个命令对app进行签名.

就像我们往常使用的自动打包上传脚本一样里面用的就是codesign

#使用方法 ./shell.sh

project_path=$(cd `dirname $0`; pwd)
if [ ! -d ./IPADir ];
then
mkdir -p IPADir;
fi

rm -rf /${project_path}/build

#工程绝对路径
ACCOUNT='开发者账号'
PASSWORD='专用密码'

#工程名 将XXX替换成自己的工程名
project_name='sheme'

#scheme名 将XXX替换成自己的sheme名
scheme_name='sheme'

#打包模式 Debug/Release
development_mode=Debug

#build文件夹路径
build_path=${project_path}/build

#plist文件所在路径
exportOptionsPlistPath=${project_path}/ExportOptions.plist

#导出.ipa文件所在路径
exportIpaPath=${project_path}/IPADir


echo "Place enter the number you want to export ? [ 1:app-store 2:ad-hoc] "

##
read number
while([[ $number != 1 ]] && [[ $number != 2 ]])
do
echo "Error! Should enter 1 or 2"
echo "Place enter the number you want to export ? [ 1:app-store 2:ad-hoc] "
read number
done

if [ $number == 1 ];then
development_mode=Release
exportOptionsPlistPath=${project_path}/exportAppstore.plist
else
development_mode=Debug
exportOptionsPlistPath=${project_path}/ExportOptions.plist
fi


echo '///-----------'
echo '/// 正在清理工程'
echo '///-----------'
xcodebuild \
clean -configuration ${development_mode} -quiet  || exit


echo '///--------'
echo '/// 清理完成'
echo '///--------'
echo ''

echo '///-----------'
echo '/// 正在编译工程:'${development_mode}
echo '///-----------'
xcodebuild \
archive -workspace ${project_path}/${project_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${development_mode} \
-archivePath ${build_path}/${project_name}.xcarchive  -quiet  || exit

echo '///--------'
echo '/// 编译完成'
echo '///--------'
echo ''

echo '///----------'
echo '/// 开始ipa打包'
echo '///----------'
xcodebuild -exportArchive -archivePath ${build_path}/${project_name}.xcarchive \
-configuration ${development_mode} \
-exportPath ${exportIpaPath} \
-exportOptionsPlist ${exportOptionsPlistPath} \
-quiet || exit

if [ -e $exportIpaPath/$scheme_name.ipa ]; then
echo '///----------'
echo '/// ipa包已导出'
echo '///----------'
#open $exportIpaPath
else
echo '///-------------'
echo '/// ipa包导出失败 '
echo '///-------------'
fi
echo '///------------'
echo '/// 打包ipa完成  '
echo '///-----------='
echo ''

echo '///-------------'
echo '/// 开始发布ipa包 '
echo '///-------------'

if [ $number == 1 ];then

#验证并上传到App Store


xcrun altool --validate-app -f ${exportIpaPath}/${scheme_name}.ipa -u ${ACCOUNT} -p ${PASSWORD} -t ios --output-format normal
xcrun altool --upload-app -f ${exportIpaPath}/${scheme_name}.ipa -u ${ACCOUNT} -p ${PASSWORD} -t ios --output-format normal


rm -rf /${project_path}/build
rm -rf /${project_path}/IPADir
echo '/// ipa包已成功上传App Store'

else

curl -F "file=@$exportIpaPath/$scheme_name.ipa" \
-F "uKey=0a5202ce3899e429bc549221f471f3ae90" \
-F "_api_key=5e4f5bf63400bcd3f5c2d72b0a74337f99" \
'https://www.pgyer.com/apiv2/app/upload'
echo '/// ipa包已成功上传蒲公英'

open  https://www.pgyer.com/6Vrs


curl 'https://oapi.dingtalk.com/robot/send?access_token=19b93f88b2dc401e40791950cddd4cfbd79358f6941964f69cb83efb221aeb8b11' \
-H 'Content-Type: application/json' \
-d "
 {\"msgtype\": \"text\",
   \"text\": {
       \"content\": \"测试版下载链接\n\'https://www.pgyer.com/6Vrs'\"
    }
 }"

fi

exit 0

CodeSign的有用的几个命令

检测APP是否完整

$ codesign --verify Example.app

直接使用证书对app签名(这里的证书到钥匙串直接复制证书名称即可)

$ codesign -s 'iPhone Developer: 151245000@qq.com (4KPK36MFV)' Example.app

重签名命令 和上面的区别就是要带上-f

$ codesign -f -s 'iPhone Developer: 151245000@qq.com (4KPK36MFV)' Example.app

打印 entitlements

$ codesign -d --entitlements - Example.app 

相关的一个Mac 自带的打印签名进ipa里面的文件内容的命令

$ security cms -D -I example.mobileprovision

那么我们查看一下Ipa文件里面的签名文件的内容吧!

image.png

双击打开并复制里面的内容,vi 然后创建文件粘贴刚才的内容.:wq保存.


image.png

接下来我们看一下这个签名文件里面到底是什么:


image.png
image.png
image.png
files和files2分别是旧版本和新版本的文件列表,而rules与rules2分别是与之对应的规则说明,
里面描述了计算hash时需要被排除的文件以及每个文件的权重。
files中保存的是每个文件的sha1值,
而files2中同时保存了sha1和sha256,
因为sha1在计算机硬件高度发达的今天,已经相对没有那么安全了,因此最新的签名算法中,引入了sha256。
注意,这里的hash值都是base64编码的明文.

通过对file1文件的查看对应的value就是文件对应的hash值,对于苹果的算法,
众多大神都是无法参破的.否则带来的影响那是不言而喻的.
当我们对ipa进行签名的时候,完成了就会生成这个codeResource.


image.png

当我们点击app 的应用图标的时候,苹果拿embedded.mobileprovision里面的信息和coderesources里面的信息进行比对,如果你改了一个内容但是并没有重新签名那么这个ipa的完整性就被破坏了,如果你的证书过期了那么app就会闪退.或者说你的app安装不上.出现什么完整性的问题.

当然embedded.mobileprovision也是可以导出成plist.


image.png

而codesign签名则是对所有文件包括资源文件进行一个遍历和hash值的存储.

App的安装是由/usr/libexec/installd完成的,
installd会通过libmis.dylib校验ProvisioningProfile、Entitlements
及签名的合法性,并递归地校验签名时每一个步骤生成的哈希值:
CDHash, Code Directory, _CodeSignature/CodeResources。

接下来我们详细看一下ZSign的文件

image.png

ZSign 的.h文件

int zsign(int argc, char * argv[]);

ZSign 的.cpp文件签名命令

int zsign(int argc, char * argv[])
{
    ZTimer gtimer;
    
    bool bForce = true;
    bool bInstall = false;
    bool bWeakInject = false;
    uint32_t uZipLevel = 5;

    string strCertFile;
    string strPKeyFile;
    string strProvFile;
    string strPassword;
    string strBundleId;
    string strBundleVersion;
    string strDisplayName;
    string strEntitlementsFile;
    string strOutputFile;
    string fromIpaPath;

    for (int i = 0; i < argc; i += 2) {
        
        char* option = argv[i];
        if (strcmp(option, "-k") == 0) {
            
            strPKeyFile = argv[i+1];
            
        } else if (strcmp(option, "-c") == 0) {
            
            strCertFile = argv[i+1];
            
        } else if (strcmp(option, "-p") == 0) {
            
            strPassword = argv[i+1];
            
        } else if (strcmp(option, "-m") == 0) {
            
            strProvFile = argv[i+1];
            
        } else if (strcmp(option, "-o") == 0) {
            
            strOutputFile = argv[i+1];
            
        } else if (strcmp(option, "-v") == 0) {
            
            strBundleVersion = argv[i+1];
            
            
        } else if (strcmp(option, "-b") == 0) {
            
            strBundleId = argv[i+1];
            
            
        } else if (strcmp(option, "-n") == 0) {
            
            strDisplayName = argv[i+1];
            
            
        } else if (strcmp(option, "-z") == 0) {
            
            uZipLevel = atoi(argv[i+1]);
            
            
        } else if (strcmp(option, "-i") == 0) {
            
            fromIpaPath = argv[i+1];
        
        }

    }
    
    string strPath = fromIpaPath;
    
    if (!IsFileExists(strPath.c_str()))
    {
        ZLog::ErrorV(">>> Invalid Path! %s\n", strPath.c_str());
        return -1;
    }

    bool bZipFile = false;
    if (!IsFolder(strPath.c_str()))
    {
        bZipFile = IsZipFile(strPath.c_str());
        if (!bZipFile)
        { //macho file
            ZMachO macho;
            if (macho.Init(strPath.c_str()))
            {
                macho.Free();
            }
            return 0;
        }
    }

    ZTimer timer;
    ZSignAsset zSignAsset;
    if (!zSignAsset.Init(strCertFile, strPKeyFile, strProvFile, strEntitlementsFile, strPassword))
    {
        return -2;
    }


    MyCPPClass *temp = new MyCPPClass();
    temp->init();
    //temp->getAppCachePath((char* )fromIpaPath.c_str())
    char* appCachePath = (char* )fromIpaPath.c_str();
    
    //unzip
    bool bEnableCache = true;
    string strFolder = GetCanonicalizePath(appCachePath);
    if (bZipFile)
    { //ipa file
        bForce = true;
        bEnableCache = false;
        ZLog::PrintV(">>> Unzip:\t%s (%s) -> %s ... \n", strPath.c_str(), GetFileSizeString(strPath.c_str()).c_str(), strFolder.c_str());
        if (!temp->unzip((char*)strPath.c_str(), appCachePath))
        {
            ZLog::ErrorV(">>> Unzip Failed!\n");
            return -3;
        }
        
        timer.PrintResult(true, ">>> Unzip OK!");
    }
    
    //resign and inject libs
    timer.Reset();
    ZAppBundle bundle;
    bool bRet = bundle.SignFolder(&zSignAsset, strFolder, strBundleVersion, strBundleId, strDisplayName, bForce, bWeakInject, bEnableCache);
    timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed");
    if (bRet == false)
    {
        return -7;
    }


    if (!strOutputFile.empty())
    {
        timer.Reset();
        string strBaseFolder = bundle.m_strAppFolder;
        //move signd file to dir
        bool res = temp->moveFile((char *)strBaseFolder.c_str(), (char *)strOutputFile.c_str(), "");
        if (!res) {
            return -8;
        }
        timer.PrintResult(true, ">>> Archive OK! (%s)", GetFileSizeString(strOutputFile.c_str()).c_str());
    
    } else {
    
        return -10;
    }

    delete temp;
    gtimer.Print(">>> Done.");
    return 0;
}

上面的argv[] 是从外界传过来的参数数组.这里面从里面读取了参数并赋值到当前的变量.

我们看到这个里面初始化了一个ZSignAsset::Init
这个是在openssl.cpp里面定义的一个方法

bool ZSignAsset::Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword)
{
    ReadFile(strProvisionFile.c_str(), m_strProvisionData);
    ReadFile(strEntitlementsFile.c_str(), m_strEntitlementsData);
    if (m_strProvisionData.empty())
    {
        ZLog::Error(">>> Can't Find Provision File!\n");
        return false;
    }

    JValue jvProv;
    string strProvContent;
    if (GetCMSContent(m_strProvisionData, strProvContent))
    {
        if (jvProv.readPList(strProvContent))
        {
            m_strTeamId = jvProv["TeamIdentifier"][0].asCString();
            if (m_strEntitlementsData.empty())
            {
                jvProv["Entitlements"].writePList(m_strEntitlementsData);
            }
        }
    }

    if (m_strTeamId.empty())
    {
        ZLog::Error(">>> Can't Find TeamId!\n");
        return false;
    }

    X509 *x509Cert = NULL;
    EVP_PKEY *evpPkey = NULL;
    BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r");
    if (NULL != bioPKey)
    {
        evpPkey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str());
        if (NULL == evpPkey)
        {
            BIO_reset(bioPKey);
            evpPkey = d2i_PrivateKey_bio(bioPKey, NULL);
            if (NULL == evpPkey)
            {
                BIO_reset(bioPKey);
                PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL);
                if (NULL != p12)
                {
                    if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPkey, &x509Cert, NULL))
                    {
                        CMSError();
                    }
                    PKCS12_free(p12);
                }
            }
        }
        
        BIO_free(bioPKey);
    }

    if (NULL == evpPkey)
    {
        ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n");
        return false;
    }

    if (NULL == x509Cert && !strSignerCertFile.empty())
    {
        BIO *bioCert = BIO_new_file(strSignerCertFile.c_str(), "r");
        if (NULL != bioCert)
        {
            x509Cert = PEM_read_bio_X509(bioCert, NULL, 0, NULL);
            if (NULL == x509Cert)
            {
                BIO_reset(bioCert);
                x509Cert = d2i_X509_bio(bioCert, NULL);
            }
            BIO_free(bioCert);
        }
    }

    if (NULL != x509Cert)
    {
        if (!X509_check_private_key(x509Cert, evpPkey))
        {
            X509_free(x509Cert);
            x509Cert = NULL;
        }
    }

    if (NULL == x509Cert)
    {
        for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++)
        {
            string strCertData = jvProv["DeveloperCertificates"][i].asData();
            BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), strCertData.size());
            if (NULL != bioCert)
            {
                x509Cert = d2i_X509_bio(bioCert, NULL);
                if (NULL != x509Cert)
                {
                    if (X509_check_private_key(x509Cert, evpPkey))
                    {
                        break;
                    }
                    X509_free(x509Cert);
                    x509Cert = NULL;
                }
                BIO_free(bioCert);
            }
        }
    }

    if (NULL == x509Cert)
    {
        ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n");
        return false;
    }

    if (!GetCertSubjectCN(x509Cert, m_strSubjectCN))
    {
        ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n");
        return false;
    }

    m_evpPkey = evpPkey;
    m_x509Cert = x509Cert;
    return true;
}

这个Init方法将.mobleprovison和Entitlements权限文件,读取到data,

ReadFile(strProvisionFile.c_str(), m_strProvisionData);
ReadFile(strEntitlementsFile.c_str(), m_strEntitlementsData);
jvProv["Entitlements"].writePList(m_strEntitlementsData);

随后用P12密码和P12文件进行了证书的校验
X509 *x509Cert = NULL;
X509 *x509Cert = NULL;
EVP_PKEY *evpPkey = NULL;
BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r");
if (NULL != bioPKey)
{
evpPkey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str());
if (NULL == evpPkey)
{
BIO_reset(bioPKey);
evpPkey = d2i_PrivateKey_bio(bioPKey, NULL);
if (NULL == evpPkey)
{
BIO_reset(bioPKey);
PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL);
if (NULL != p12)
{
if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPkey, &x509Cert, NULL))
{
CMSError();
}
PKCS12_free(p12);
}
}
}

    BIO_free(bioPKey);
}

if (NULL == evpPkey)
{
    ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n");
    return false;

}
读取到的密钥信息存储到了全局变量:
m_evpPkey = evpPkey;
m_x509Cert = x509Cert;
在签名的时候是要用到的.

随后读取了IPA文件
char* appCachePath = (char* )fromIpaPath.c_str();

//unzip
bool bEnableCache = true;
string strFolder = GetCanonicalizePath(appCachePath);
接下来是签名和注入动态库
resign and inject libs

ZAppBundle bundle;

bool bRet = bundle.SignFolder(&zSignAsset, strFolder, strBundleVersion, strBundleId, strDisplayName, bForce, bWeakInject, bEnableCache);
timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed");

然后是打包文件
if (!strOutputFile.empty())
{
timer.Reset();
string strBaseFolder = bundle.m_strAppFolder;
//move signd file to dir
bool res = temp->moveFile((char *)strBaseFolder.c_str(), (char *)strOutputFile.c_str(), "");
if (!res) {
return -8;
}
timer.PrintResult(true, ">>> Archive OK! (%s)", GetFileSizeString(strOutputFile.c_str()).c_str());
}

base64,common 则是encode文件要用到的.json文件是用的比较广的,用于解析值.

archo读取的是我们的mach-o文件

bool ZArchO::BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput)

上面这个方法则是创建签名文件

bool ZArchO::Init(uint8_t *pBase, uint32_t uLength)
{
    if (NULL == pBase || uLength <= 0)
    {
        return false;
    }
    
    m_pBase = pBase;
    m_uLength = uLength;
    m_uCodeLength = (uLength % 16 == 0) ? uLength : uLength + 16 - (uLength % 16);
    m_pHeader = (mach_header *)m_pBase;
    m_b64 = (MH_MAGIC_64 == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false;
    m_bBigEndian = (MH_CIGAM == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false;
    m_uHeaderSize = m_b64 ? sizeof(mach_header_64) : sizeof(mach_header);

    uint8_t *pLoadCommand = m_pBase + m_uHeaderSize;
    for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++)
    {
        load_command *plc = (load_command *)pLoadCommand;
        switch (BO(plc->cmd))
        {
        case LC_SEGMENT:
        {
            segment_command *seglc = (segment_command *)pLoadCommand;
            if (0 == strcmp("__TEXT", seglc->segname))
            {
                execSegLimit = seglc->vmsize;
                for (uint32_t j = 0; j < BO(seglc->nsects); j++)
                {
                    section *sect = (section *)((pLoadCommand + sizeof(segment_command)) + sizeof(section) * j);
                    if (0 == strcmp("__text", sect->sectname))
                    {
                        if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize))
                        {
                            m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize;
                        }
                    }
                    else if (0 == strcmp("__info_plist", sect->sectname))
                    {
                        m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO(sect->size));
                    }
                }
            }
            else if (0 == strcmp("__LINKEDIT", seglc->segname))
            {
                m_pLinkEditSegment = pLoadCommand;
            }
        }
        break;
        case LC_SEGMENT_64:
        {
            segment_command_64 *seglc = (segment_command_64 *)pLoadCommand;
            if (0 == strcmp("__TEXT", seglc->segname))
            {
                execSegLimit = seglc->vmsize;
                for (uint32_t j = 0; j < BO(seglc->nsects); j++)
                {
                    section_64 *sect = (section_64 *)((pLoadCommand + sizeof(segment_command_64)) + sizeof(section_64) * j);
                    if (0 == strcmp("__text", sect->sectname))
                    {
                        if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize))
                        {
                            m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize;
                        }
                    }
                    else if (0 == strcmp("__info_plist", sect->sectname))
                    {
                        m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO(sect->size));
                    }
                }
            }
            else if (0 == strcmp("__LINKEDIT", seglc->segname))
            {
                m_pLinkEditSegment = pLoadCommand;
            }
        }
        break;
        case LC_ENCRYPTION_INFO:
        case LC_ENCRYPTION_INFO_64:
        {
            encryption_info_command *crypt_cmd = (encryption_info_command *)pLoadCommand;
            if (BO(crypt_cmd->cryptid) >= 1)
            {
                m_bEncrypted = true;
            }
        }
        break;
        case LC_CODE_SIGNATURE:
        {
            codesignature_command *pcslc = (codesignature_command *)pLoadCommand;
            m_pCodeSignSegment = pLoadCommand;
            m_uCodeLength = BO(pcslc->dataoff);
            m_pSignBase = m_pBase + m_uCodeLength;
            m_uSignLength = GetCodeSignatureLength(m_pSignBase);
        }
        break;
        }

        pLoadCommand += BO(plc->cmdsize);
    }

    return true;
}

bool ZArchO::Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData)
这个方法则是对mach-o文件进行签名.

还有注入动态库的

bool ZArchO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate)

在macho.cpp文件可以看到对文件遍历的签名

bool ZMachO::Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData)
{
    if (NULL == m_pBase || m_arrArchOes.empty())
    {
        return false;
    }

    for (size_t i = 0; i < m_arrArchOes.size(); i++)
    {
        ZArchO *archo = m_arrArchOes[i];
        if (strBundleId.empty())
        {
            JValue jvInfo;
            jvInfo.readPList(archo->m_strInfoPlist);
            strBundleId = jvInfo["CFBundleIdentifier"].asCString();
            if (strBundleId.empty())
            {
                strBundleId = basename((char *)m_strFile.c_str());
            }
        }

        if (strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty())
        {
            if(archo->m_strInfoPlist.empty())
            {
                strInfoPlistSHA1.append(20, 0);
                strInfoPlistSHA256.append(32, 0);
            }
            else
            {
                SHASum(archo->m_strInfoPlist, strInfoPlistSHA1, strInfoPlistSHA256);
            }
        }

        if (!archo->Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData))
        {
            if (!archo->m_bEnoughSpace && !m_bCSRealloced)
            {
                m_bCSRealloced = true;
                if (ReallocCodeSignSpace())
                {
                    return Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData);
                }
            }
            return false;
        }
    }

    return CloseFile();
}

签名的 InjectDyLib

bool ZMachO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate)

signing文件则是对签名的解析和创建权限文件的处理,创建签名目录

bool ParseCodeSignature(uint8_t *pCSBase);
uint32_t GetCodeSignatureLength(uint8_t *pCSBase);
bool GetCodeSignatureCodeSlotsData(uint8_t *pCSBase, uint8_t *&pCodeSlots1, uint32_t &uCodeSlots1Length, uint8_t *&pCodeSlots256, uint32_t &uCodeSlots256Length);
bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput);
bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput);

未完待续

上一篇 下一篇

猜你喜欢

热点阅读