这应该是一篇关于ios备份解密最全面的文章。
ios备份分为三个版本:低于3.0、3.0、高于3.0。这从逆向结果中可以看到体现。版本信息在Status.plist中:
几个版本的解密文件部分算法其实都是一致的,区别在于解密密匙的获取方式。
这里是复现出的解密文件函数:
int backup::Decrypt_Write(const wchar_t* topath, const wchar_t* frompath, byte* key, int key_len)
{
if (!key || !frompath || !topath || !key_len)
return 1;
EVP_CIPHER* v7;
std::string DstBuf;
DstBuf.resize(0x1000);
std::string Str;
Str.resize(0x1000);
switch (8 * key_len)
{
case 128:
v7 = (EVP_CIPHER*)EVP_aes_128_cbc();
break;
case 192:
v7 = (EVP_CIPHER*)EVP_aes_192_cbc();
break;
case 256:
v7 = (EVP_CIPHER*)EVP_aes_256_cbc();
break;
default:
return 1;
}
FILE* ffrom = _wfopen(frompath, L"rb");
if (!ffrom)
return 10;
FILE* fto = _wfopen(topath, L"wb+");
if (!fto)
{
fclose(ffrom);
return 11;
}
EVP_CIPHER_CTX* v10 = EVP_CIPHER_CTX_new();
if (!EVP_DecryptInit_ex(v10, v7, 0, key, 0))
goto LABEL_20;
size_t readlen = fread(&DstBuf[0], 1u, 0x1000u, ffrom);
if (readlen > 0)
{
int count = 0;
while (EVP_DecryptUpdate(v10, (byte*)&Str[0], &count, (byte*)&DstBuf[0], readlen))
{
fwrite(&Str[0], 1u, count, fto);
readlen = fread(&DstBuf[0], 1u, 0x1000u, ffrom);
if (readlen <= 0)
goto LABEL_19;
}
goto LABEL_20;
}
LABEL_19:
int finallen = 0;
if (!EVP_DecryptFinal_ex(v10, (byte*)&Str[0], &finallen))
{
LABEL_20:
EVP_CIPHER_CTX_free(v10);
fclose(ffrom);
fclose(fto);
return 2;
}
fwrite(&Str[0], 1u, finallen, fto);
EVP_CIPHER_CTX_free(v10);
fclose(ffrom);
fclose(fto);
return 0;
}
3.0以上:
1.首先需要从Manifest.plist中获取到BackupKeyBag和ManifestKey的值,通过base64解密后获得如下结果:
BackupKeyBag:
ManifestKey
首先我们需要储存 BackupKeyBag 中的内容:
中间密匙需要带入备份密码计算两次,GetValueByKey是将对应字段从 BackupKeyBag 中取出:
{
std::string DPSL;
std::string DPIC;
GetValueByKey("DPSL", DPSL);
GetValueByKey("DPIC", DPIC);
EVP_MD* es256 = (EVP_MD*)EVP_sha256();
int iter = ntohl(*(DWORD*)&DPIC[0]);
PKCS5_PBKDF2_HMAC(&password[0], password.size(), (byte*)&DPSL[0], 20, iter, es256, 32, pw);
}
std::string SALT;
std::string ITER;
EVP_MD* es1 = (EVP_MD*)EVP_sha1();
GetValueByKey("SALT", SALT);
GetValueByKey("ITER", ITER);
int iter = ntohl(*(DWORD*)&ITER[0]);
PKCS5_PBKDF2_HMAC((char*)pw, 32, (byte*)&SALT[0], 20, iter, es1, 32, pw);
然后我们需要将 BackupKeyBag 中所有的WPKY字段的值加密。加密方式未知,函数是对照调试手动实现的。
int Unknown_AesCrypt(DWORD len, byte* key, std::string& indata, byte* outdata, int& outlen)
{
int* v8;
int v19;
int v20 = 5;
DWORD v7 = 6 * ((DWORD)(len - 8) >> 3);
DWORD v15 = (DWORD)(len - 8) >> 3;
int v21[4] = { *(DWORD*)&indata[0],*(DWORD*)(&indata[0] + 4),0,0 };
memcpy(outdata, &indata[0] + 8, len - 8);
int* v18 = (int*)((char*)outdata + len - 16);
while (true)
{
v8 = v18;
v19 = v15;
if (v15 >= 1)
break;
LABEL_10:
if (--v20 < 0)
{
outlen = len - 8;
return 3;
}
}
while (true)
{
int v9 = 7;
int v10 = v7;
do
{
if (!v10)
break;
*((BYTE*)&v21 + v9) ^= v10;
v10 >>= 8;
--v9;
} while (v9 >= 0);
v21[2] = *v8;
v21[3] = v8[1];
if (sub_3010((byte*)v21, key, (byte*)v21))
return 2;
*v8 = v21[2];
v8[1] = v21[3];
--v7;
v8 -= 2;
if (--v19 < 1)
goto LABEL_10;
}
}
ManifestKey中包含Manifest.db解密所需要的密匙序号和加密密匙。
前四字节是序号,也就是说我们需要取出序号3的 WPKY 加密字段,和四字节后的部分算出文件解密密匙。
int index = *(int*)&ManifestKey[0];
int outlen = 0;
std::string Mkey;
Mkey.resize(0x28);
memcpy(&Mkey[0], (byte*)&ManifestKey[0] + 4, 0x28);
Unknown_AesCrypt(40, (byte*)&maplist[index]["WPKYEncrypt"][0], Mkey, dbdecodekey, outlen);
DecryptandWrite((localpath + L"/newManifest.db").c_str(), (localpath + L"/Manifest.db").c_str(), (byte*)dbdecodekey, outlen);
很显然数据库解密成功了
使用工具打开后很明显file字段中的blob是个plist。
在逆向分析中,我们可知plist中的NS.data字段存有对应文件解密的中间密匙。所以我们取出后进行base64解密:
和 ManifestKey解密Manifest.db 类似,取出序号3的 WPKY 加密字段,和四字节后的部分算出文件解密密匙。 然后进行解密就可以了。
0 条评论