这应该是一篇关于ios备份解密最全面的文章。

ios备份分为三个版本:低于3.03.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中获取到BackupKeyBagManifestKey的值,通过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解密所需要的密匙序号和加密密匙。

前四字节是序号,也就是说我们需要取出序号3WPKY 加密字段,和四字节后的部分算出文件解密密匙。

		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 类似,取出序号3WPKY 加密字段,和四字节后的部分算出文件解密密匙。 然后进行解密就可以了。


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注