ApkParser.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <?php
  2. /**
  3. * ****************************************************
  4. * Android APK File Parser
  5. * Author: Katana
  6. * Version: v0.1
  7. * Web: http://www.win-ing.cn
  8. * 功能:解析安卓apk包中的压缩XML文件,还原和读取XML内容
  9. * 依赖功能:需要PHP的ZIP包函数支持。
  10. * ****************************************************
  11. */
  12. class ApkParser {
  13. // ----------------------
  14. // 公共函数,供外部调用
  15. // ----------------------
  16. public function open($apk_file, $xml_file = 'AndroidManifest.xml') {
  17. $zip = new ZipArchive();
  18. if ($zip->open($apk_file) === true) {
  19. $xml = $zip->getFromName($xml_file);
  20. $zip->close();
  21. if ($xml) {
  22. try {
  23. return $this->parseString($xml);
  24. } catch (Exception $e) {
  25. }
  26. }
  27. }
  28. return false;
  29. }
  30. public function parseString($xml) {
  31. $this->xml = $xml;
  32. $this->length = strlen($xml);
  33. $this->root = $this->parseBlock(self::AXML_FILE);
  34. return true;
  35. }
  36. public function getXML($node = null, $lv = -1) {
  37. if ($lv == -1) {
  38. $node = $this->root;
  39. }
  40. if (!$node) {
  41. return '';
  42. }
  43. if ($node['type'] == self::END_TAG) {
  44. $lv--;
  45. }
  46. $xml = @($node['line'] == 0 || $node['line'] == $this->line) ? '' : "\n".str_repeat(' ', $lv);
  47. $xml .= $node['tag'];
  48. $this->line = @$node['line'];
  49. foreach ($node['child'] as $c) {
  50. $xml .= $this->getXML($c, $lv + 1);
  51. }
  52. return $xml;
  53. }
  54. public function getPackage() {
  55. return $this->getAttribute('manifest', 'package');
  56. }
  57. public function getVersionName() {
  58. return $this->getAttribute('manifest', 'android:versionName');
  59. }
  60. public function getVersionCode() {
  61. return $this->getAttribute('manifest', 'android:versionCode');
  62. }
  63. public function getAppName() {
  64. return $this->getAttribute('manifest/application', 'android:name');
  65. }
  66. public function getMainActivity() {
  67. for ($id = 0; true; $id++) {
  68. $act = $this->getAttribute("manifest/application/activity[{$id}]/intent-filter/action", 'android:name');
  69. if (!$act) {
  70. break;
  71. }
  72. if ($act == 'android.intent.action.MAIN') {
  73. return $this->getActivity($id);
  74. }
  75. }
  76. return null;
  77. }
  78. public function getActivity($idx = 0) {
  79. $idx = intval($idx);
  80. return $this->getAttribute("manifest/application/activity[{$idx}]", 'android:name');
  81. }
  82. public function getAttribute($path, $name) {
  83. $r = $this->getElement($path);
  84. if (is_null($r)) {
  85. return null;
  86. }
  87. if (isset($r['attrs'])) {
  88. foreach ($r['attrs'] as $a) {
  89. if ($a['ns_name'] == $name) {
  90. return $this->getAttributeValue($a);
  91. }
  92. }
  93. }
  94. return null;
  95. }
  96. // ----------------------
  97. // 类型常量定义
  98. // ----------------------
  99. const AXML_FILE = 0x00080003;
  100. const STRING_BLOCK = 0x001C0001;
  101. const RESOURCEIDS = 0x00080180;
  102. const START_NAMESPACE = 0x00100100;
  103. const END_NAMESPACE = 0x00100101;
  104. const START_TAG = 0x00100102;
  105. const END_TAG = 0x00100103;
  106. const TEXT = 0x00100104;
  107. const TYPE_NULL = 0;
  108. const TYPE_REFERENCE = 1;
  109. const TYPE_ATTRIBUTE = 2;
  110. const TYPE_STRING = 3;
  111. const TYPE_FLOAT = 4;
  112. const TYPE_DIMENSION = 5;
  113. const TYPE_FRACTION = 6;
  114. const TYPE_INT_DEC = 16;
  115. const TYPE_INT_HEX = 17;
  116. const TYPE_INT_BOOLEAN = 18;
  117. const TYPE_INT_COLOR_ARGB8 = 28;
  118. const TYPE_INT_COLOR_RGB8 = 29;
  119. const TYPE_INT_COLOR_ARGB4 = 30;
  120. const TYPE_INT_COLOR_RGB4 = 31;
  121. const UNIT_MASK = 15;
  122. private static $RADIX_MULTS
  123. = array(
  124. 0.00390625,
  125. 3.051758E-005,
  126. 1.192093E-007,
  127. 4.656613E-010
  128. );
  129. private static $DIMENSION_UNITS
  130. = array(
  131. "px",
  132. "dip",
  133. "sp",
  134. "pt",
  135. "in",
  136. "mm",
  137. "",
  138. ""
  139. );
  140. private static $FRACTION_UNITS
  141. = array(
  142. "%",
  143. "%p",
  144. "",
  145. "",
  146. "",
  147. "",
  148. "",
  149. ""
  150. );
  151. private $xml = '';
  152. private $length = 0;
  153. private $stringCount = 0;
  154. private $styleCount = 0;
  155. private $stringTab = array();
  156. private $styleTab = array();
  157. private $resourceIDs = array();
  158. private $ns = array();
  159. private $cur_ns = null;
  160. private $root = null;
  161. private $line = 0;
  162. // ----------------------
  163. // 内部私有函数
  164. // ----------------------
  165. private function getElement($path) {
  166. if (!$this->root) {
  167. return null;
  168. }
  169. $ps = explode('/', $path);
  170. $r = $this->root;
  171. foreach ($ps as $v) {
  172. if (preg_match('/([^\[]+)\[([0-9]+)\]$/', $v, $ms)) {
  173. $v = $ms[1];
  174. $off = $ms[2];
  175. } else {
  176. $off = 0;
  177. }
  178. foreach ($r['child'] as $c) {
  179. if ($c['type'] == self::START_TAG && $c['ns_name'] == $v) {
  180. if ($off == 0) {
  181. $r = $c;
  182. continue 2;
  183. } else {
  184. $off--;
  185. }
  186. }
  187. }
  188. // 没有找到节点
  189. return null;
  190. }
  191. return $r;
  192. }
  193. private function parseBlock($need = 0) {
  194. $o = 0;
  195. $type = $this->get32($o);
  196. if ($need && $type != $need) {
  197. throw new Exception('Block Type Error', 1);
  198. }
  199. $size = $this->get32($o);
  200. if ($size < 8 || $size > $this->length) {
  201. throw new Exception('Block Size Error', 2);
  202. }
  203. $left = $this->length - $size;
  204. $props = false;
  205. switch ($type) {
  206. case self::AXML_FILE :
  207. $props = array(
  208. 'line' => 0,
  209. 'tag' => '<?xml version="1.0" encoding="utf-8"?>'
  210. );
  211. break;
  212. case self::STRING_BLOCK :
  213. $this->stringCount = $this->get32($o);
  214. $this->styleCount = $this->get32($o);
  215. $o += 4;
  216. $strOffset = $this->get32($o);
  217. $styOffset = $this->get32($o);
  218. $strListOffset = $this->get32array($o, $this->stringCount);
  219. $styListOffset = $this->get32array($o, $this->styleCount);
  220. $this->stringTab = $this->stringCount > 0 ? $this->getStringTab($strOffset, $strListOffset) : array();
  221. $this->styleTab = $this->styleCount > 0 ? $this->getStringTab($styOffset, $styListOffset) : array();
  222. $o = $size;
  223. break;
  224. case self::RESOURCEIDS :
  225. $count = $size / 4 - 2;
  226. $this->resourceIDs = $this->get32array($o, $count);
  227. break;
  228. case self::START_NAMESPACE :
  229. $o += 8;
  230. $prefix = $this->get32($o);
  231. $uri = $this->get32($o);
  232. if (empty($this->cur_ns)) {
  233. $this->cur_ns = array();
  234. $this->ns[] = &$this->cur_ns;
  235. }
  236. $this->cur_ns[$uri] = $prefix;
  237. break;
  238. case self::END_NAMESPACE :
  239. $o += 8;
  240. $prefix = $this->get32($o);
  241. $uri = $this->get32($o);
  242. if (empty($this->cur_ns)) {
  243. break;
  244. }
  245. unset($this->cur_ns[$uri]);
  246. break;
  247. case self::START_TAG :
  248. $line = $this->get32($o);
  249. $o += 4;
  250. $attrs = array();
  251. $props = array(
  252. 'line' => $line,
  253. 'ns' => $this->getNameSpace($this->get32($o)),
  254. 'name' => $this->getString($this->get32($o)),
  255. 'flag' => $this->get32($o),
  256. 'count' => $this->get16($o),
  257. 'id' => $this->get16($o) - 1,
  258. 'class' => $this->get16($o) - 1,
  259. 'style' => $this->get16($o) - 1,
  260. 'attrs' => &$attrs
  261. );
  262. $props['ns_name'] = $props['ns'].$props['name'];
  263. for ($i = 0; $i < $props['count']; $i++) {
  264. $a = array(
  265. 'ns' => $this->getNameSpace($this->get32($o)),
  266. 'name' => $this->getString($this->get32($o)),
  267. 'val_str' => $this->get32($o),
  268. 'val_type' => $this->get32($o),
  269. 'val_data' => $this->get32($o)
  270. );
  271. $a['ns_name'] = $a['ns'].$a['name'];
  272. $a['val_type'] >>= 24;
  273. $attrs[] = $a;
  274. }
  275. // 处理TAG字符串
  276. $tag = "<{$props['ns_name']}";
  277. foreach ($this->cur_ns as $uri => $prefix) {
  278. $uri = $this->getString($uri);
  279. $prefix = $this->getString($prefix);
  280. $tag .= " xmlns:{$prefix}=\"{$uri}\"";
  281. }
  282. foreach ($props['attrs'] as $a) {
  283. $tag .= " {$a['ns_name']}=\"".$this->getAttributeValue($a).'"';
  284. }
  285. $tag .= '>';
  286. $props['tag'] = $tag;
  287. unset($this->cur_ns);
  288. $this->cur_ns = array();
  289. $this->ns[] = &$this->cur_ns;
  290. $left = -1;
  291. break;
  292. case self::END_TAG :
  293. $line = $this->get32($o);
  294. $o += 4;
  295. $props = array(
  296. 'line' => $line,
  297. 'ns' => $this->getNameSpace($this->get32($o)),
  298. 'name' => $this->getString($this->get32($o))
  299. );
  300. $props['ns_name'] = $props['ns'].$props['name'];
  301. $props['tag'] = "</{$props['ns_name']}>";
  302. if (count($this->ns) > 1) {
  303. array_pop($this->ns);
  304. unset($this->cur_ns);
  305. $this->cur_ns = array_pop($this->ns);
  306. $this->ns[] = &$this->cur_ns;
  307. }
  308. break;
  309. case self::TEXT :
  310. $o += 8;
  311. $props = array(
  312. 'tag' => $this->getString($this->get32($o))
  313. );
  314. $o += 8;
  315. break;
  316. default :
  317. throw new Exception('Block Type Error', 3);
  318. break;
  319. }
  320. $this->skip($o);
  321. $child = array();
  322. while ($this->length > $left) {
  323. $c = $this->parseBlock();
  324. if ($props && $c) {
  325. $child[] = $c;
  326. }
  327. if ($left == -1 && $c['type'] == self::END_TAG) {
  328. $left = $this->length;
  329. break;
  330. }
  331. }
  332. if ($this->length != $left) {
  333. throw new Exception('Block Overflow Error', 4);
  334. }
  335. if ($props) {
  336. $props['type'] = $type;
  337. $props['size'] = $size;
  338. $props['child'] = $child;
  339. return $props;
  340. } else {
  341. return false;
  342. }
  343. }
  344. private function getAttributeValue($a) {
  345. $type = &$a['val_type'];
  346. $data = &$a['val_data'];
  347. switch ($type) {
  348. case self::TYPE_STRING :
  349. return $this->getString($a['val_str']);
  350. case self::TYPE_ATTRIBUTE :
  351. return sprintf('?%s%08X', self::_getPackage($data), $data);
  352. case self::TYPE_REFERENCE :
  353. return sprintf('@%s%08X', self::_getPackage($data), $data);
  354. case self::TYPE_INT_HEX :
  355. return sprintf('0x%08X', $data);
  356. case self::TYPE_INT_BOOLEAN :
  357. return ($data != 0 ? 'true' : 'false');
  358. case self::TYPE_INT_COLOR_ARGB8 :
  359. case self::TYPE_INT_COLOR_RGB8 :
  360. case self::TYPE_INT_COLOR_ARGB4 :
  361. case self::TYPE_INT_COLOR_RGB4 :
  362. return sprintf('#%08X', $data);
  363. case self::TYPE_DIMENSION :
  364. return $this->_complexToFloat($data).self::$DIMENSION_UNITS[$data & self::UNIT_MASK];
  365. case self::TYPE_FRACTION :
  366. return $this->_complexToFloat($data).self::$FRACTION_UNITS[$data & self::UNIT_MASK];
  367. case self::TYPE_FLOAT :
  368. return $this->_int2float($data);
  369. }
  370. if ($type >= self::TYPE_INT_DEC && $type < self::TYPE_INT_COLOR_ARGB8) {
  371. return (string)$data;
  372. }
  373. return sprintf('<0x%X, type 0x%02X>', $data, $type);
  374. }
  375. private function _complexToFloat($data) {
  376. return (float)($data & 0xFFFFFF00) * self::$RADIX_MULTS[($data >> 4) & 3];
  377. }
  378. private function _int2float($v) {
  379. $x = ($v & ((1 << 23) - 1)) + (1 << 23) * ($v >> 31 | 1);
  380. $exp = ($v >> 23 & 0xFF) - 127;
  381. return $x * pow(2, $exp - 23);
  382. }
  383. private static function _getPackage($data) {
  384. return ($data >> 24 == 1) ? 'android:' : '';
  385. }
  386. private function getStringTab($base, $list) {
  387. $tab = array();
  388. foreach ($list as $off) {
  389. $off += $base;
  390. $len = $this->get16($off);
  391. $mask = ($len >> 0x8) & 0xFF;
  392. $len = $len & 0xFF;
  393. if ($len == $mask) {
  394. if ($off + $len > $this->length) {
  395. throw new Exception('String Table Overflow', 11);
  396. }
  397. $tab[] = substr($this->xml, $off, $len);
  398. } else {
  399. if ($off + $len * 2 > $this->length) {
  400. throw new Exception('String Table Overflow', 11);
  401. }
  402. $str = substr($this->xml, $off, $len * 2);
  403. $tab[] = mb_convert_encoding($str, 'UTF-8', 'UCS-2LE');
  404. }
  405. }
  406. return $tab;
  407. }
  408. private function getString($id) {
  409. if ($id > -1 && $id < $this->stringCount) {
  410. return $this->stringTab[$id];
  411. } else {
  412. return '';
  413. }
  414. }
  415. private function getNameSpace($uri) {
  416. for ($i = count($this->ns); $i > 0;) {
  417. $ns = $this->ns[--$i];
  418. if (isset($ns[$uri])) {
  419. $ns = $this->getString($ns[$uri]);
  420. if (!empty($ns)) {
  421. $ns .= ':';
  422. }
  423. return $ns;
  424. }
  425. }
  426. return '';
  427. }
  428. private function get32(&$off) {
  429. $int = unpack('V', substr($this->xml, $off, 4));
  430. $off += 4;
  431. return array_shift($int);
  432. }
  433. private function get32array(&$off, $size) {
  434. if ($size <= 0) {
  435. return null;
  436. }
  437. $arr = unpack('V*', substr($this->xml, $off, 4 * $size));
  438. if (count($arr) != $size) {
  439. throw new Exception('Array Size Error', 10);
  440. }
  441. $off += 4 * $size;
  442. return $arr;
  443. }
  444. private function get16(&$off) {
  445. $int = unpack('v', substr($this->xml, $off, 2));
  446. $off += 2;
  447. return array_shift($int);
  448. }
  449. private function skip($size) {
  450. $this->xml = substr($this->xml, $size);
  451. $this->length -= $size;
  452. }
  453. }
  454. ?>