main.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /*
  2. * Copyright (C) 2018 Hein-Pieter van Braam
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include <stdlib.h>
  17. #include <stdio.h>
  18. #include <stdarg.h>
  19. #include <stdint.h>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #include "minizip/mz.h"
  23. #include "minizip/mz_os.h"
  24. #include "minizip/mz_zip.h"
  25. #include "minizip/mz_strm.h"
  26. #include "minizip/mz_strm_mem.h"
  27. #include "ResStringPool.h"
  28. #include "android-xml.h"
  29. struct options_t {
  30. char* jarsigner = NULL;
  31. char* keystore = NULL;
  32. char* storepass = NULL;
  33. char* keyalias = NULL;
  34. } options;
  35. #ifndef __unix__
  36. #define popen _popen
  37. #endif
  38. void print_error(const char* format, ...) {
  39. va_list args;
  40. va_start(args, format);
  41. int message_size = vsnprintf(NULL, 0, format, args);
  42. va_end(args);
  43. char* message = (char*) malloc(message_size + 1);
  44. va_start(args, format);
  45. vsnprintf(message, message_size + 1, format, args);
  46. va_end(args);
  47. perror(message);
  48. free(message);
  49. exit(1);
  50. }
  51. char* find_jarsigner() {
  52. #ifdef __unix
  53. char output[512];
  54. const char* command = "/usr/bin/env which jarsigner";
  55. FILE* pipe = popen(command, "r");
  56. if (!pipe)
  57. print_error("/usr/bin/which not found");
  58. size_t size = fread(output, 1, 511, pipe);
  59. int retval = pclose(pipe);
  60. output[sizeof(output) - 1] = 0;
  61. if (retval == 0) {
  62. // strip newline
  63. output[strlen(output) - 1] = 0;
  64. return strdup(output);
  65. }
  66. #else
  67. FILE* temp = fopen("C:\\Program Files\\Android\\Android Studio\\jre\\bin\\jarsigner.exe", "r");
  68. if (temp) {
  69. fclose(temp);
  70. return strdup("C:\\Program Files\\Android\\Android Studio\\jre\\bin\\jarsigner.exe");
  71. }
  72. #endif
  73. printf("Could not locate jarsigner in $PATH\n");
  74. return NULL;
  75. }
  76. #ifdef __unix__
  77. #define CMD "\"%s\" -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore \"%s\" -storepass \"%s\" \"%s\" \"%s\""
  78. #else
  79. #define CMD "\"\"%s\" -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore \"%s\" -storepass \"%s\" \"%s\" \"%s\"\""
  80. #endif
  81. void resign_apk(const char* apk) {
  82. char output[512];
  83. char* command;
  84. uint32_t command_size = snprintf(NULL, 0,
  85. CMD, options.jarsigner, options.keystore, options.storepass, apk,
  86. options.keyalias);
  87. command = (char*) malloc(command_size + 1);
  88. if (!command)
  89. print_error("Out of memory");
  90. snprintf(command, command_size + 1,
  91. CMD, options.jarsigner, options.keystore, options.storepass, apk,
  92. options.keyalias);
  93. printf("Running %s\n", command);
  94. int retval = system(command);
  95. if (retval) {
  96. printf("jarsigner failed\n");
  97. exit(retval);
  98. }
  99. free(command);
  100. }
  101. void print_help(const char* progname) {
  102. printf(
  103. "Usage: %s -j </path/to/jarsigner> -k <keystore> -p <keystore password> -a <keystore alias> apkfile.apk\n",
  104. progname);
  105. printf("\n");
  106. printf(
  107. " example: %s -j /usr/bin/jarsigner -k ~/debug.keystore -p android -a androiddebugkey game.apk\n",
  108. progname);
  109. printf("\n");
  110. #if __unix__
  111. printf(
  112. " note that you don't have to supply jarsigner path if it is in $PATH\n");
  113. #endif
  114. exit(1);
  115. }
  116. void fix_manifest(uint8_t* manifest_data, uint32_t manifest_size,
  117. uint8_t** fixed_manifest, uint32_t* fixed_manifest_size) {
  118. uint32_t file_location = 0;
  119. uint32_t out_size = 0;
  120. uint8_t *out;
  121. ResChunk_header main_header;
  122. memcpy(&main_header, manifest_data, sizeof(ResChunk_header));
  123. if (main_header.type != RES_XML_TYPE) {
  124. fprintf(stderr, "Not an Android binary XML file\n");
  125. exit(1);
  126. }
  127. if (main_header.size != manifest_size) {
  128. fprintf(stderr, "Header size does not match filesize\n");
  129. exit(1);
  130. }
  131. out_size = sizeof(ResChunk_header);
  132. out = (uint8_t*) malloc(out_size);
  133. if (!out) {
  134. print_error("Out of memory");
  135. }
  136. file_location += sizeof(ResChunk_header);
  137. ResChunk_header cur_header;
  138. ResStringPool* stringpool = NULL;
  139. bool skip_end = false;
  140. while (file_location < manifest_size) {
  141. memcpy(&cur_header, manifest_data + file_location,
  142. sizeof(ResChunk_header));
  143. if (cur_header.type == RES_XML_START_ELEMENT_TYPE) {
  144. if (!stringpool) {
  145. fprintf(stderr,
  146. "StringPool not yet initialized, bailing out\n");
  147. exit(1);
  148. }
  149. bool skip = false;
  150. ResXMLTree_node xml_header;
  151. memcpy(&xml_header, manifest_data + file_location,
  152. sizeof(ResXMLTree_node));
  153. ResXMLTree_attrExt attrs;
  154. memcpy(&attrs,
  155. manifest_data + file_location
  156. + xml_header.header.headerSize,
  157. sizeof(ResXMLTree_attrExt));
  158. const char* name = stringpool->get(attrs.name.index);
  159. for (int i = 0; i < attrs.attributeCount; ++i) {
  160. ResXMLTree_attribute attribute;
  161. memcpy(&attribute,
  162. manifest_data + file_location
  163. + xml_header.header.headerSize
  164. + sizeof(ResXMLTree_attrExt)
  165. + (i * attrs.attributeSize),
  166. sizeof(ResXMLTree_attribute));
  167. if (strcmp(name, "uses-permission") == 0) {
  168. const char* value = stringpool->get(
  169. attribute.rawValue.index);
  170. if (!(strncmp(value, "android.", 7) == 0)
  171. || (strncmp(value, "com.android", 11) == 0)) {
  172. printf("Removing invalid permission '%s'\n", value);
  173. skip = true;
  174. }
  175. }
  176. }
  177. skip_end = skip;
  178. if (skip) {
  179. file_location += cur_header.size;
  180. continue;
  181. }
  182. }
  183. if (cur_header.type == RES_XML_END_ELEMENT_TYPE) {
  184. if (skip_end) {
  185. skip_end = false;
  186. file_location += cur_header.size;
  187. continue;
  188. }
  189. }
  190. if (cur_header.type == RES_STRING_POOL_TYPE) {
  191. stringpool = new ResStringPool(manifest_data + file_location);
  192. }
  193. out = (uint8_t*) realloc(out, out_size + cur_header.size);
  194. if (!out) {
  195. print_error("Out of memory");
  196. }
  197. memcpy(out + out_size, manifest_data + file_location, cur_header.size);
  198. out_size += cur_header.size;
  199. file_location += cur_header.size;
  200. }
  201. main_header.size = out_size;
  202. memcpy(out, &main_header, sizeof(ResChunk_header));
  203. *fixed_manifest = out;
  204. *fixed_manifest_size = out_size;
  205. delete stringpool;
  206. }
  207. int main(int argc, char* argv[]) {
  208. int c;
  209. while ((c = getopt(argc, argv, "j:k:p:a:")) != -1) {
  210. switch (c) {
  211. case 'j':
  212. options.jarsigner = strdup(optarg);
  213. break;
  214. case 'k':
  215. options.keystore = strdup(optarg);
  216. break;
  217. case 'p':
  218. options.storepass = strdup(optarg);
  219. break;
  220. case 'a':
  221. options.keyalias = strdup(optarg);
  222. break;
  223. case '?':
  224. print_help(argv[0]);
  225. break;
  226. default:
  227. print_help(argv[0]);
  228. }
  229. }
  230. const char* infile = NULL;
  231. for (int index = optind; index < argc; index++)
  232. infile = argv[index];
  233. if (!options.jarsigner) {
  234. options.jarsigner = find_jarsigner();
  235. }
  236. if (!options.jarsigner || !options.keyalias || !options.keystore
  237. || !options.storepass || !infile) {
  238. print_help(argv[0]);
  239. }
  240. FILE* in = fopen(infile, "rb");
  241. if (!in)
  242. print_error("Error opening %s", infile);
  243. if (fseek(in, 0, SEEK_END) == -1)
  244. print_error("Fseek failed on %s", infile);
  245. long filesize = ftell(in);
  246. rewind(in);
  247. uint8_t* apk_data = (uint8_t*) malloc(filesize);
  248. if (!apk_data)
  249. print_error("Out of memory");
  250. fread(apk_data, filesize, 1, in);
  251. if (ferror(in))
  252. print_error("Error reading from %s", infile);
  253. fclose(in);
  254. char* outfile;
  255. uint32_t infile_len = strlen(infile);
  256. uint32_t dot = 0;
  257. for (int i = infile_len; i >= 0; --i) {
  258. if (infile[i] == '.') {
  259. dot = i;
  260. break;
  261. }
  262. }
  263. if (dot == 0)
  264. dot = infile_len;
  265. outfile = (char*) calloc(1, infile_len + 8);
  266. if (!outfile)
  267. print_error("Out of memory");
  268. memcpy(outfile, infile, dot);
  269. memcpy(outfile + dot, "_fixed", 6);
  270. memcpy(outfile + dot + 6, infile + dot, infile_len - dot);
  271. void* mem_stream = NULL;
  272. uint32_t err;
  273. mz_stream_mem_create(&mem_stream);
  274. mz_stream_mem_set_buffer(mem_stream, apk_data, filesize);
  275. mz_stream_open(mem_stream, NULL, MZ_OPEN_MODE_READ);
  276. void* in_zip_handle = mz_zip_open(mem_stream, MZ_OPEN_MODE_READ);
  277. err = mz_zip_locate_entry(in_zip_handle, "AndroidManifest.xml", NULL);
  278. if (err != MZ_OK) {
  279. fprintf(stderr, "File AndroidManifest.xml not found in the zip file\n");
  280. return err;
  281. }
  282. mz_zip_file* manifest_file_info = NULL;
  283. err = mz_zip_entry_get_info(in_zip_handle, &manifest_file_info);
  284. if (err != MZ_OK) {
  285. fprintf(stderr, "Error %d getting entry info in zip file\n", err);
  286. return err;
  287. }
  288. err = mz_zip_entry_read_open(in_zip_handle, 0, NULL);
  289. if (err != MZ_OK) {
  290. printf("Error %d opening entry in zip file, password protected file?\n",
  291. err);
  292. return err;
  293. }
  294. uint32_t manifest_size = manifest_file_info->uncompressed_size;
  295. uint8_t* manifest_data = (uint8_t*) malloc(manifest_size);
  296. if (!manifest_data) {
  297. print_error("Out of memory");
  298. }
  299. void *manifest_stream = NULL;
  300. mz_stream_mem_create(&manifest_stream);
  301. mz_stream_mem_set_buffer(manifest_stream, manifest_data, manifest_size);
  302. mz_stream_open(manifest_stream, NULL, MZ_OPEN_MODE_WRITE);
  303. uint8_t buf[INT16_MAX];
  304. int32_t read = 0;
  305. int32_t written = 0;
  306. while (1) {
  307. read = mz_zip_entry_read(in_zip_handle, buf, sizeof(buf));
  308. if (read < 0) {
  309. err = read;
  310. printf("Error %d reading entry in zip file\n", err);
  311. break;
  312. }
  313. if (read == 0)
  314. break;
  315. written = mz_stream_mem_write(manifest_stream, buf, read);
  316. if (written != read) {
  317. err = mz_stream_mem_error(manifest_stream);
  318. printf("Error %d in writing extracted file\n", err);
  319. break;
  320. }
  321. }
  322. mz_zip_entry_close(in_zip_handle);
  323. if (err != MZ_OK) {
  324. fprintf(stderr, "Failed to extract AndroidManifest.xml\n");
  325. return err;
  326. }
  327. uint8_t* fixed_manifest;
  328. uint32_t fixed_manifest_size;
  329. fix_manifest(manifest_data, manifest_size, &fixed_manifest,
  330. &fixed_manifest_size);
  331. printf("Fixed AndroidManifest.xml, old size: %i, new size %i\n",
  332. manifest_size, fixed_manifest_size);
  333. printf("Writing new APK %s\n", outfile);
  334. void* out_stream;
  335. mz_stream_os_create(&out_stream);
  336. err = mz_stream_os_open(out_stream, outfile, MZ_OPEN_MODE_CREATE);
  337. if (err != MZ_OK) {
  338. printf("Error opening file %s\n", outfile);
  339. exit(1);
  340. }
  341. void* out_zip_handle = mz_zip_open(out_stream, MZ_OPEN_MODE_WRITE);
  342. mz_zip_set_version_madeby(out_zip_handle, MZ_VERSION_MADEBY);
  343. err = mz_zip_goto_first_entry(in_zip_handle);
  344. while (err == MZ_OK) {
  345. mz_zip_file *file_info = NULL;
  346. uint8_t buf[INT16_MAX];
  347. int32_t read = 0;
  348. int32_t written = 0;
  349. err = mz_zip_entry_get_info(in_zip_handle, &file_info);
  350. if (err != MZ_OK) {
  351. printf("Error %d getting entry info in zip file\n", err);
  352. return err;
  353. }
  354. if (strncmp("META-INF", file_info->filename, 8) == 0) {
  355. err = mz_zip_goto_next_entry(in_zip_handle);
  356. continue;
  357. }
  358. if (strcmp("AndroidManifest.xml", file_info->filename) == 0) {
  359. printf("Adding fixed AndroidManifest.xml\n");
  360. file_info->uncompressed_size = fixed_manifest_size;
  361. err = mz_zip_entry_write_open(out_zip_handle, file_info,
  362. MZ_COMPRESS_LEVEL_DEFAULT, NULL);
  363. if (err == MZ_OK) {
  364. written = mz_zip_entry_write(out_zip_handle, fixed_manifest,
  365. fixed_manifest_size);
  366. if (written != fixed_manifest_size) {
  367. printf("Error opening %s for writing in new apk\n",
  368. file_info->filename);
  369. }
  370. } else {
  371. printf("Error opening %s for writing in new apk\n",
  372. file_info->filename);
  373. }
  374. mz_zip_entry_close(out_zip_handle);
  375. err = mz_zip_goto_next_entry(in_zip_handle);
  376. continue;
  377. }
  378. err = mz_zip_entry_read_open(in_zip_handle, 0, NULL);
  379. if (err != MZ_OK) {
  380. printf("Error %d opening entry in zip file\n", err);
  381. return err;
  382. }
  383. printf("Adding %s (%i bytes)\n", file_info->filename,
  384. file_info->uncompressed_size);
  385. err = mz_zip_entry_write_open(out_zip_handle, file_info,
  386. MZ_COMPRESS_LEVEL_DEFAULT, NULL);
  387. if (err == MZ_OK) {
  388. while (1) {
  389. read = mz_zip_entry_read(in_zip_handle, buf, sizeof(buf));
  390. if (read < 0) {
  391. err = read;
  392. printf("Error %d reading entry in zip file\n", err);
  393. break;
  394. }
  395. if (read == 0)
  396. break;
  397. written = mz_zip_entry_write(out_zip_handle, buf, read);
  398. if (written != read) {
  399. printf("Error in writing extracted file %i != %i\n",
  400. written, read);
  401. break;
  402. }
  403. }
  404. } else {
  405. printf("Error opening %s for writing in new apk\n",
  406. file_info->filename);
  407. }
  408. mz_zip_entry_close(in_zip_handle);
  409. mz_zip_entry_close(out_zip_handle);
  410. err = mz_zip_goto_next_entry(in_zip_handle);
  411. }
  412. mz_zip_close(in_zip_handle);
  413. mz_zip_close(out_zip_handle);
  414. mz_stream_mem_delete(&manifest_stream);
  415. mz_stream_mem_delete(&mem_stream);
  416. mz_stream_os_close(out_stream);
  417. mz_stream_os_delete(&out_stream);
  418. printf("Resigning %s\n", outfile);
  419. resign_apk(outfile);
  420. free(apk_data);
  421. free(manifest_data);
  422. free(fixed_manifest);
  423. free(outfile);
  424. free(options.jarsigner);
  425. free(options.keyalias);
  426. free(options.keystore);
  427. free(options.storepass);
  428. }