futurerestore/futurerestore/futurerestore.cpp
tihmstar 818f5a3ea2 m
2016-11-28 11:51:05 +01:00

684 lines
23 KiB
C++

//
// futurerestore.cpp
// futurerestore
//
// Created by tihmstar on 14.09.16.
// Copyright © 2016 tihmstar. All rights reserved.
//
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include "futurerestore.hpp"
#include "common.h"
#include "all_tsschecker.h"
#include "../external/img4tool/img4tool/img4.h"
#include "img4tool.h"
#include "normal.h"
#include "recovery.h"
#include "ipsw.h"
#include "locking.h"
#include "restore.h"
#include "tsschecker.h"
#ifdef WIN32
#include <windows.h>
#define __mkdir(path, mode) mkdir(path)
#else
#include <sys/stat.h>
#define __mkdir(path, mode) mkdir(path, mode)
#endif
#define NONCESIZE 20
#define USEC_PER_SEC 1000000
#define TMP_PATH "/tmp"
#define FUTURERESTORE_TMP_PATH TMP_PATH"/futurerestore"
#define BASEBAND_TMP_PATH FUTURERESTORE_TMP_PATH"/baseband.bbfw"
#define BASEBAND_MANIFEST_TMP_PATH FUTURERESTORE_TMP_PATH"/basebandManifest.plist"
#define SEP_TMP_PATH FUTURERESTORE_TMP_PATH"/sep.im4p"
#define SEP_MANIFEST_TMP_PATH FUTURERESTORE_TMP_PATH"/sepManifest.plist"
#define reterror(code,msg ...) error(msg),throw int(code)
#define safeFree(buf) if (buf) free(buf), buf = NULL
#define safePlistFree(buf) if (buf) plist_free(buf), buf = NULL
futurerestore::futurerestore(){
_client = idevicerestore_client_new();
if (_client == NULL) throw std::string("could not create idevicerestore client\n");
struct stat st{0};
if (stat(FUTURERESTORE_TMP_PATH, &st) == -1) __mkdir(FUTURERESTORE_TMP_PATH, 0755);
//tsschecker nocache
nocache = 1;
}
bool futurerestore::init(){
if (_didInit) return _didInit;
return _didInit = (check_mode(_client) != MODE_UNKNOWN);
}
uint64_t futurerestore::getDeviceEcid(){
if (!_didInit) reterror(-1, "did not init\n");
uint64_t ecid;
get_ecid(_client, &ecid);
return ecid;
}
int futurerestore::getDeviceMode(bool reRequest){
if (!_didInit) reterror(-1, "did not init\n");
if (!reRequest && _client->mode->index != MODE_UNKNOWN) {
return _client->mode->index;
}else{
normal_client_free(_client);
recovery_client_free(_client);
return check_mode(_client);
}
}
void futurerestore::putDeviceIntoRecovery(){
if (!_didInit) reterror(-1, "did not init\n");
getDeviceMode(false);
info("Found device in %s mode\n", _client->mode->string);
if (_client->mode->index == MODE_NORMAL) {
info("Entering recovery mode...\n");
if (normal_enter_recovery(_client) < 0) {
reterror(-2,"Unable to place device into recovery mode from %s mode\n", _client->mode->string);
}
}else if (_client->mode->index == MODE_RECOVERY){
info("Device already in Recovery mode\n");
}else{
reterror(-3, "unsupported devicemode, please put device in recovery mode or normal mode\n");
}
//only needs to be freed manually when function did't throw exception
safeFree(_client->udid);
//these get also freed by destructor
normal_client_free(_client);
recovery_client_free(_client);
}
void futurerestore::setAutoboot(bool val){
if (!_didInit) reterror(-1, "did not init\n");
if (getDeviceMode(false) != MODE_RECOVERY){
reterror(-2, "can't set autoboot, when device isn't in recovery mode\n");
}
if (_client->recovery || recovery_client_new(_client) == 0) {
if (recovery_set_autoboot(_client, val)){
reterror(-3,"Setting auto-boot failed?!\n");
}
} else {
reterror(-4,"Could not connect to device in recovery mode.\n");
}
}
bool futurerestore::nonceMatchesApTicket(){
if (!_didInit) reterror(-1, "did not init\n");
if (getDeviceMode(true) != MODE_RECOVERY) reterror(-10, "Device not in recovery mode, can't check apnonce\n");
unsigned char* realnonce;
int realNonceSize = 0;
recovery_get_ap_nonce(_client, &realnonce, &realNonceSize);
return memcmp(realnonce, (unsigned const char*)getNonceFromIM4M(_im4m), realNonceSize) == 0;
}
void futurerestore::waitForNonce(const char *nonce){
if (!_didInit) reterror(-1, "did not init\n");
setAutoboot(false);
unsigned char* realnonce;
int realNonceSize = 0;
info("waiting for nonce: ");
int i = 0;
for (i = 0; i < NONCESIZE; i++) {
info("%02x ", ((unsigned char *)nonce)[i]);
}
info("\n");
do {
if (realNonceSize){
recovery_send_reset(_client);
recovery_client_free(_client);
usleep(1*USEC_PER_SEC);
}
while (getDeviceMode(true) != MODE_RECOVERY) usleep(USEC_PER_SEC*0.5);
if (recovery_client_new(_client)) reterror(-4,"Could not connect to device in recovery mode.\n");
recovery_get_ap_nonce(_client, &realnonce, &realNonceSize);
info("Got ApNonce from device: ");
int i = 0;
for (i = 0; i < realNonceSize; i++) {
info("%02x ", realnonce[i]);
}
info("\n");
} while (memcmp(realnonce, (unsigned const char*)nonce, realNonceSize) != 0);
info("Device has requested ApNonce now\n");
setAutoboot(true);
}
void futurerestore::waitForNonce(){
if (!_im4m) reterror(-1, "No IM4M loaded\n");
waitForNonce(getNonceFromIM4M(_im4m));
}
void futurerestore::loadAPTicket(const char *apticketPath){
if (_apticket) plist_free(_apticket), _apticket = NULL;
FILE *f = fopen(apticketPath,"rb");
if (!f) reterror(-9, "failed to load apticket at %s\n",apticketPath);
fseek(f, 0, SEEK_END);
size_t fSize = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = (char*)malloc(fSize);
fread(buf, fSize, 1, f);
fclose(f);
if (memcmp(buf, "bplist00", 8) == 0)
plist_from_bin(buf, (uint32_t)fSize, &_apticket);
else
plist_from_xml(buf, (uint32_t)fSize, &_apticket);
safeFree(_im4m);
plist_t ticket = plist_dict_get_item(_apticket, "ApImg4Ticket");
uint64_t im4msize=0;
plist_get_data_val(ticket, &_im4m, &im4msize);
}
void futurerestore::loadAPTicket(string apticketPath){
loadAPTicket(apticketPath.c_str());
}
int futurerestore::doRestore(const char *ipsw, bool noerase){
int err = 0;
//some memory might not get freed if this function throws an exception, but you probably don't want to catch that anyway.
struct idevicerestore_client_t* client = _client;
int unused;
int result = 0;
int delete_fs = 0;
char* filesystem = NULL;
plist_t buildmanifest = NULL;
plist_t sepbuildmanifest = NULL;
plist_t basebandbuildmanifest = NULL;
plist_t build_identity = NULL;
plist_t sep_build_identity = NULL;
plist_t bb_build_identity = NULL;
client->ipsw = strdup(ipsw);
if (!noerase) client->flags |= FLAG_ERASE;
getDeviceMode(true);
info("Found device in %s mode\n", client->mode->string);
if (client->mode->index != MODE_RECOVERY) reterror(-6, "device not in recovery mode\n");
// discover the device type
if (check_hardware_model(client) == NULL || client->device == NULL) {
reterror(-2,"ERROR: Unable to discover device model\n");
}
info("Identified device as %s, %s\n", client->device->hardware_model, client->device->product_type);
if (!nonceMatchesApTicket()) reterror(-20, "Devicenonce does not match APTicket nonce\n");
// verify if ipsw file exists
if (access(client->ipsw, F_OK) < 0) {
error("ERROR: Firmware file %s does not exist.\n", client->ipsw);
return -1;
}
info("Extracting BuildManifest from IPSW\n");
if (ipsw_extract_build_manifest(client->ipsw, &buildmanifest, &unused) < 0) {
reterror(-3,"ERROR: Unable to extract BuildManifest from %s. Firmware file might be corrupt.\n", client->ipsw);
}
/* check if device type is supported by the given build manifest */
if (build_manifest_check_compatibility(buildmanifest, client->device->product_type) < 0) {
reterror(-4,"ERROR: Could not make sure this firmware is suitable for the current device. Refusing to continue.\n");
}
/* print iOS information from the manifest */
build_manifest_get_version_information(buildmanifest, client);
info("Product Version: %s\n", client->version);
info("Product Build: %s Major: %d\n", client->build, client->build_major);
client->image4supported = is_image4_supported(client);
info("Device supports Image4: %s\n", (client->image4supported) ? "true" : "false");
client->tss = _apticket;
plist_dict_remove_item(client->tss, "BBTicket");
plist_dict_remove_item(client->tss, "BasebandFirmware");
if (noerase) {
build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(buildmanifest, client->device->hardware_model, "Update");
if (!build_identity) {
build_identity = build_manifest_get_build_identity_for_model(buildmanifest, client->device->hardware_model);
}
}else{
build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(buildmanifest, client->device->hardware_model, "Erase");
if (build_identity == NULL) {
reterror(-5,"ERROR: Unable to find any build identities\n");
}
}
sepbuildmanifest = loadPlistFromFile(_sepManifestPath);
sep_build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(sepbuildmanifest, client->device->hardware_model, "Erase");
if (!sep_build_identity) sep_build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(sepbuildmanifest, client->device->hardware_model, "Update");
if (sep_build_identity == NULL) {
reterror(-5,"ERROR: Unable to find any build identities for sep\n");
}
basebandbuildmanifest = loadPlistFromFile(_basebandManifestPath);
bb_build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(basebandbuildmanifest, client->device->hardware_model, "Erase");
if (!bb_build_identity) bb_build_identity = build_manifest_get_build_identity_for_model_with_restore_behavior(basebandbuildmanifest, client->device->hardware_model, "Update");
if (bb_build_identity == NULL) {
reterror(-5,"ERROR: Unable to find any build identities for baseband\n");
}
plist_t bb_manifest = plist_dict_get_item(bb_build_identity, "Manifest");
plist_t bb_baseband = plist_copy(plist_dict_get_item(bb_manifest, "BasebandFirmware"));
plist_t sep_manifest = plist_dict_get_item(sep_build_identity, "Manifest");
plist_t sep_sep = plist_copy(plist_dict_get_item(sep_manifest, "SEP"));
plist_t manifest = plist_dict_get_item(build_identity, "Manifest");
plist_dict_set_item(manifest, "BasebandFirmware", bb_baseband);
plist_dict_set_item(manifest, "SEP", sep_sep);
client->bbfwtmp = (char*)_basebandPath;
/* print information about current build identity */
build_identity_print_information(build_identity);
// Get filesystem name from build identity
char* fsname = NULL;
if (build_identity_get_component_path(build_identity, "OS", &fsname) < 0) {
error("ERROR: Unable get path for filesystem component\n");
return -1;
}
// check if we already have an extracted filesystem
struct stat st;
memset(&st, '\0', sizeof(struct stat));
char tmpf[1024];
if (client->cache_dir) {
if (stat(client->cache_dir, &st) < 0) {
mkdir_with_parents(client->cache_dir, 0755);
}
strcpy(tmpf, client->cache_dir);
strcat(tmpf, "/");
char *ipswtmp = strdup(client->ipsw);
strcat(tmpf, basename(ipswtmp));
free(ipswtmp);
} else {
strcpy(tmpf, client->ipsw);
}
char* p = strrchr((const char*)tmpf, '.');
if (p) {
*p = '\0';
}
if (stat(tmpf, &st) < 0) {
__mkdir(tmpf, 0755);
}
strcat(tmpf, "/");
strcat(tmpf, fsname);
memset(&st, '\0', sizeof(struct stat));
if (stat(tmpf, &st) == 0) {
off_t fssize = 0;
ipsw_get_file_size(client->ipsw, fsname, &fssize);
if ((fssize > 0) && (st.st_size == fssize)) {
info("Using cached filesystem from '%s'\n", tmpf);
filesystem = strdup(tmpf);
}
}
if (!filesystem) {
char extfn[1024];
strcpy(extfn, tmpf);
strcat(extfn, ".extract");
char lockfn[1024];
strcpy(lockfn, tmpf);
strcat(lockfn, ".lock");
lock_info_t li;
lock_file(lockfn, &li);
FILE* extf = NULL;
if (access(extfn, F_OK) != 0) {
extf = fopen(extfn, "w");
}
unlock_file(&li);
if (!extf) {
// use temp filename
filesystem = tempnam(NULL, "ipsw_");
if (!filesystem) {
error("WARNING: Could not get temporary filename, using '%s' in current directory\n", fsname);
filesystem = strdup(fsname);
}
delete_fs = 1;
} else {
// use <fsname>.extract as filename
filesystem = strdup(extfn);
fclose(extf);
}
remove(lockfn);
// Extract filesystem from IPSW
info("Extracting filesystem from IPSW\n");
if (ipsw_extract_to_file_with_progress(client->ipsw, fsname, filesystem, 1) < 0) {
reterror(-7,"ERROR: Unable to extract filesystem from IPSW\n");
}
if (strstr(filesystem, ".extract")) {
// rename <fsname>.extract to <fsname>
remove(tmpf);
rename(filesystem, tmpf);
free(filesystem);
filesystem = strdup(tmpf);
}
}
if ((client->build_major > 8)) {
if (!client->image4supported) {
/* send ApTicket */
if (recovery_send_ticket(client) < 0) {
error("WARNING: Unable to send APTicket\n");
}
}
}
/* now we load the iBEC */
if (recovery_send_ibec(client, build_identity) < 0) {
reterror(-8,"ERROR: Unable to send iBEC\n");
}
recovery_client_free(client);
/* this must be long enough to allow the device to run the iBEC */
/* FIXME: Probably better to detect if the device is back then */
sleep(7);
check_mode(client);
//do magic
get_sep_nonce(client, &client->sepnonce, &client->sepnonce_size);
get_ap_nonce(client, &client->nonce, &client->nonce_size);
get_ecid(client, &client->ecid);
if (client->mode->index == MODE_RECOVERY) {
if (client->srnm == NULL) {
reterror(-9,"ERROR: could not retrieve device serial number. Can't continue.\n");
}
if (recovery_enter_restore(client, build_identity) < 0) {
reterror(-10,"ERROR: Unable to place device into restore mode\n");
}
recovery_client_free(client);
}
if (get_tss_response(client, sep_build_identity, &client->septss) < 0) {
reterror(-11,"ERROR: Unable to get SHSH blobs for SEP\n");
}
FILE *fsep = fopen(_sepPath, "r");
fseek(fsep, 0, SEEK_END);
client->sepfwdatasize = ftell(fsep);
fseek(fsep, 0, SEEK_SET);
client->sepfwdata = (char*)malloc(client->sepfwdatasize);
fread(client->sepfwdata, 1, client->sepfwdatasize, fsep);
fclose(fsep);
if (client->mode->index == MODE_RESTORE) {
info("About to restore device... \n");
result = restore_device(client, build_identity, filesystem);
if (result < 0) {
reterror(-11,"ERROR: Unable to restore device\n");
}
}
info("Cleaning up...\n");
error:
safeFree(client->sepfwdata);
safePlistFree(buildmanifest);
safePlistFree(sepbuildmanifest);
safePlistFree(basebandbuildmanifest);
safePlistFree(build_identity);
safePlistFree(sep_build_identity);
safePlistFree(bb_build_identity);
if (delete_fs && filesystem) unlink(filesystem);
if (!result && !err) info("DONE\n");
return result ? abs(result) : err;
}
futurerestore::~futurerestore(){
normal_client_free(_client);
recovery_client_free(_client);
idevicerestore_client_free(_client);
safeFree(_im4m);
safeFree(_firmwareJson);
safeFree(_firmwareTokens);
safeFree(__latestManifest);
safeFree(__latestFirmwareUrl);
safePlistFree(_apticket);
}
void futurerestore::loadFirmwareTokens(){
if (!_firmwareTokens){
if (!_firmwareJson) _firmwareJson = getFirmwareJson();
if (!_firmwareJson) reterror(-6,"[TSSC] could not get firmware.json\n");
int cnt = parseTokens(_firmwareJson, &_firmwareTokens);
if (cnt < 1) reterror(-2,"[TSSC] parsing %s.json failed\n",(0) ? "ota" : "firmware");
}
}
const char *futurerestore::getConnectedDeviceModel(){
if (!_client->device || !_client->device->product_type){
int mode = getDeviceMode(true);
if (mode != MODE_NORMAL && mode != MODE_RECOVERY)
reterror(-20, "unexpected device mode=%d\n",mode);
if (check_hardware_model(_client) == NULL || _client->device == NULL)
reterror(-2,"ERROR: Unable to discover device model\n");
}
return _client->device->product_type;
}
char *futurerestore::getLatestManifest(){
if (!__latestManifest){
loadFirmwareTokens();
const char *device = getConnectedDeviceModel();
t_iosVersion versVals;
memset(&versVals, 0, sizeof(versVals));
int versionCnt = 0;
int i = 0;
char **versions = getListOfiOSForDevice(_firmwareJson, _firmwareTokens, device, 0, &versionCnt);
if (!versionCnt) reterror(-8, "[TSSC] failed finding latest iOS\n");
char *bpos = NULL;
while((bpos = strstr(versVals.version = strdup(versions[i++]),"[B]")) != 0){
free((char*)versVals.version);
if (--versionCnt == 0) reterror(-9, "[TSSC] automatic iOS selection couldn't find non-beta iOS\n");
}
info("[TSSC] selecting latest iOS: %s\n",versVals.version);
if (bpos) *bpos= '\0';
if (versions) free(versions[versionCnt-1]),free(versions);
//make sure it get's freed after function finishes execution by either reaching end or throwing exception
ptr_smart<const char*>autofree(versVals.version);
__latestFirmwareUrl = getFirmwareUrl(device, versVals, _firmwareJson, _firmwareTokens);
if (!__latestFirmwareUrl) reterror(-21, "could not find url of latest firmware\n");
__latestManifest = getBuildManifest(__latestFirmwareUrl, device, versVals.version, 0);
if (!__latestManifest) reterror(-22, "could not get buildmanifest of latest firmware\n");
}
return __latestManifest;
}
char *futurerestore::getLatestFirmwareUrl(){
return getLatestManifest(),__latestFirmwareUrl;
}
void futurerestore::loadLatestBaseband(){
char * manifeststr = getLatestManifest();
char *pathStr = getPathOfElementInManifest("BasebandFirmware", manifeststr);
info("downloading Baseband\n\n");
if (downloadPartialzip(getLatestFirmwareUrl(), pathStr, _basebandPath = BASEBAND_TMP_PATH))
reterror(-32, "could not download baseband\n");
saveStringToFile(manifeststr, _basebandManifestPath = BASEBAND_MANIFEST_TMP_PATH);
}
void futurerestore::loadLatestSep(){
char * manifeststr = getLatestManifest();
char *pathStr = getPathOfElementInManifest("SEP", manifeststr);
info("downloading SEP\n\n");
if (downloadPartialzip(getLatestFirmwareUrl(), pathStr, _sepPath = SEP_TMP_PATH))
reterror(-33, "could not download SEP\n");
saveStringToFile(manifeststr, _sepManifestPath = SEP_MANIFEST_TMP_PATH);
}
#pragma mark static methods
inline void futurerestore::saveStringToFile(const char *str, const char *path){
FILE *f = fopen(path, "w");
if (!f) reterror(-41,"can't save file at %s\n",path);
else{
size_t len = strlen(str);
size_t wlen = fwrite(str, 1, len, f);
fclose(f);
if (len != wlen) reterror(-42, "saving file failed, wrote=%zu actual=%zu\n",wlen,len);
}
}
char *futurerestore::getNonceFromIM4M(const char* im4m){
char *ret = NULL;
t_asn1Tag *mainSet = NULL;
t_asn1Tag *manbSet = NULL;
t_asn1Tag *manpSet = NULL;
char *nonceOctet = NULL;
char *bnch = NULL;
char *manb = NULL;
char *manp = NULL;
if (!im4m) reterror(-15, "Got empty IM4M\n");
if (asn1ElementsInObject(im4m)< 3){
error("unexpected number of Elements in IM4M sequence\n");
goto error;
}
mainSet = asn1ElementAtIndex(im4m, 2);
manb = getValueForTagInSet((char*)mainSet, 0x4d414e42); //MANB priv Tag
if (asn1ElementsInObject(manb)< 2){
error("unexpected number of Elements in MANB sequence\n");
goto error;
}
manbSet = asn1ElementAtIndex(manb, 1);
manp = getValueForTagInSet((char*)manbSet, 0x4d414e50); //MANP priv Tag
if (asn1ElementsInObject(manp)< 2){
error("unexpected number of Elements in MANP sequence\n");
goto error;
}
manpSet = asn1ElementAtIndex(manp, 1);
bnch = getValueForTagInSet((char*)manpSet, 0x424e4348); //BNCH priv Tag
if (asn1ElementsInObject(bnch)< 2){
error("unexpected number of Elements in BNCH sequence\n");
goto error;
}
nonceOctet = (char*)asn1ElementAtIndex(bnch, 1);
ret = (char*)malloc(NONCESIZE);
if (ret){
nonceOctet++;
memcpy(ret, nonceOctet + asn1Len(nonceOctet).sizeBytes, NONCESIZE);
}
error:
return ret;
}
char *futurerestore::getNonceFromAPTicket(const char* apticketPath){
char *ret = NULL;
if (char *im4m = im4mFormShshFile(apticketPath)){
ret = getNonceFromIM4M(im4m);
free(im4m);
}
return ret;
}
plist_t futurerestore::loadPlistFromFile(const char *path){
plist_t ret = NULL;
FILE *f = fopen(path,"rb");
if (!f){
error("could not open file %s\n",path);
return NULL;
}
fseek(f, 0, SEEK_END);
size_t bufSize = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = (char*)malloc(bufSize);
if (!buf){
error("failed to alloc memory\n");
return NULL;
}
fread(buf, 1, bufSize, f);
fclose(f);
if (memcmp(buf, "bplist00", 8) == 0)
plist_from_bin(buf, (uint32_t)bufSize, &ret);
else
plist_from_xml(buf, (uint32_t)bufSize, &ret);
free(buf);
return ret;
}
char *futurerestore::getPathOfElementInManifest(const char *element, const char *manifeststr){
char *pathStr = NULL;
ptr_smart<plist_t> buildmanifest(NULL,plist_free);
plist_from_xml(manifeststr, (uint32_t)strlen(manifeststr), &buildmanifest);
if (plist_t buildidentities = plist_dict_get_item(buildmanifest._p, "BuildIdentities"))
if (plist_t firstIdentitie = plist_array_get_item(buildidentities, 0))
if (plist_t manifest = plist_dict_get_item(firstIdentitie, "Manifest"))
if (plist_t elem = plist_dict_get_item(manifest, element))
if (plist_t info = plist_dict_get_item(elem, "Info"))
if (plist_t path = plist_dict_get_item(info, "Path"))
if (plist_get_string_val(path, &pathStr), pathStr)
goto noerror;
reterror(-31, "could not get %s path\n",element);
noerror:
return pathStr;
}