1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(improper_ctypes)]
5
6#![allow(dead_code)]
7#![allow(unused_variables)]
8
9pub mod parser;
10pub mod disassembler;
11pub mod analysis;
12pub mod dvm_access_flags;
13
14use std::path::Path;
15use std::ffi::{ CStr, CString };
16
17use crate::parser::{
18 DvmHeader,
19 DvmMethod,
20 DvmClass
21};
22use crate::disassembler::DvmDisassembledMethod;
23use crate::analysis::{
24 DvmStringAnalysis,
25 DvmMethodAnalysis,
26 DvmClassAnalysis
27};
28
29mod shuriken {
30 #[cfg(not(docsrs))]
31 include!(concat!(env!("OUT_DIR"), "/shuriken_core.rs"));
32
33 #[cfg(docsrs)]
35 include!(".docs.rs/shuriken_core.rs");
36}
37
38#[derive(Debug)]
43pub struct DexContext {
44 ptr: shuriken::hDexContext
45}
46
47impl Drop for DexContext {
50 fn drop(&mut self) {
51 unsafe {
52 shuriken::destroy_dex(self.ptr);
53 }
54 }
55}
56
57impl DexContext {
58 pub fn parse_dex(filepath: &Path) -> Self {
63 let c_str = CString::new(filepath.to_path_buf().into_os_string().into_string().unwrap()).unwrap();
64 let c_world = c_str.as_ptr();
65
66 let ptr = unsafe { shuriken::parse_dex(c_world) };
67
68 Self { ptr }
69 }
70
71 pub fn get_number_of_strings(&self) -> usize {
73 unsafe {
74 shuriken::get_number_of_strings(self.ptr)
75 }
76 }
77
78 pub fn get_header(&self) -> Option<DvmHeader> {
80 let header_ptr = unsafe {
81 shuriken::get_header(self.ptr)
82 };
83
84 match header_ptr.is_null() {
85 true => None,
86 false => unsafe {
87 Some(DvmHeader::from_ptr(*header_ptr))
88 }
89 }
90 }
91
92 pub fn get_string_by_id(&self, string_id: usize) -> Option<String> {
94 unsafe {
95 let c_string = shuriken::get_string_by_id(self.ptr, string_id);
96 if let Ok(string) = CStr::from_ptr(c_string).to_str() {
97 Some(string.to_owned())
98 } else {
99 None
100 }
101 }
102 }
103
104 pub fn get_number_of_classes(&self) -> usize {
106 unsafe {
107 shuriken::get_number_of_classes(self.ptr).into()
108 }
109 }
110
111 pub fn get_class_by_id(&self, id: u16) -> Option<DvmClass> {
113 let dvm_class_ptr = unsafe { shuriken::get_class_by_id(self.ptr, id) };
114
115 if ! dvm_class_ptr.is_null() {
116 unsafe {
117 Some(DvmClass::from_ptr(*dvm_class_ptr))
118 }
119 } else {
120 None
121 }
122 }
123
124 pub fn get_class_by_name(&self, class_name: &str) -> Option<DvmClass> {
126 let c_str = CString::new(class_name)
127 .expect("CString::new failed");
128
129 let class_ptr = unsafe { shuriken::get_class_by_name(self.ptr, c_str.as_ptr()) };
130 if ! class_ptr.is_null() {
131 unsafe {
132 Some(DvmClass::from_ptr(*class_ptr))
133 }
134 } else {
135 None
136 }
137 }
138
139 pub fn get_method_by_name(&self, method_name: &str) -> Option<DvmMethod> {
141 let c_str = CString::new(method_name)
142 .expect("CString::new failed");
143
144 let method_ptr = unsafe { shuriken::get_method_by_name(self.ptr, c_str.as_ptr()) };
145 if ! method_ptr.is_null() {
146 unsafe {
147 Some(DvmMethod::from_ptr(*method_ptr))
148 }
149 } else {
150 None
151 }
152 }
153
154 pub fn disassemble_dex(&self) {
158 unsafe {
159 shuriken::disassemble_dex(self.ptr)
160 }
161 }
162
163 pub fn get_disassembled_method(&self, method_name: &str) -> Option<DvmDisassembledMethod> {
165 let c_str = CString::new(method_name)
166 .expect("CString::new failed");
167
168 let dvm_method = self.get_method_by_name(method_name)
169 .expect("Cannot find function");
170 let dvm_disas = unsafe { shuriken::get_disassembled_method(self.ptr, c_str.as_ptr()) };
171
172 if ! dvm_disas.is_null() {
173 Some(unsafe { DvmDisassembledMethod::from_dvmdisassembled_method_t(*dvm_disas, dvm_method) })
174 } else {
175 eprintln!("No disassembled method. Did you run `DexContext::disassemble_dex()`?");
176 None
177 }
178 }
179
180 pub fn create_dex_analysis(&self, create_xrefs: bool) {
187 let xrefs = if create_xrefs {
188 1
189 } else {
190 0
191 };
192
193 unsafe {
194 shuriken::create_dex_analysis(self.ptr, xrefs)
195 }
196 }
197
198 pub fn analyze_classes(&self) {
200 unsafe {
201 shuriken::analyze_classes(self.ptr)
202 }
203 }
204
205 pub fn get_analyzed_class_by_hdvmclass(&self, class: &DvmClass) -> Option<DvmClassAnalysis> {
207 self.get_analyzed_class(class.class_name())
208 }
209
210 pub fn get_analyzed_class(&self, class_name: &str) -> Option<DvmClassAnalysis> {
212 let c_str = CString::new(class_name)
213 .expect("CString::new failed");
214
215 let class_analysis_ptr = unsafe {
216 shuriken::get_analyzed_class(self.ptr, c_str.as_ptr())
217 };
218
219 match class_analysis_ptr.is_null() {
220 true => None,
221 false => {
222 let dvm_class_analysis = unsafe { DvmClassAnalysis::from_ptr(*class_analysis_ptr) };
223 Some(dvm_class_analysis)
224 }
225 }
226 }
227
228 pub fn get_analyzed_method_by_hdvmmethod(&self, method: &DvmMethod ) -> Option<DvmMethodAnalysis> {
230 self.get_analyzed_method(method.dalvik_name())
231 }
232
233 pub fn get_analyzed_method(&self, method_full_name: &str) -> Option<DvmMethodAnalysis> {
235 let c_str = CString::new(method_full_name)
236 .expect("CString::new failed");
237
238 let method_analysis_ptr = unsafe {
239 shuriken::get_analyzed_method(self.ptr, c_str.as_ptr())
240 };
241
242 match method_analysis_ptr.is_null() {
243 true => None,
244 false => {
245 let dvm_method_analysis = unsafe { DvmMethodAnalysis::from_ptr(*method_analysis_ptr) };
246 Some(dvm_method_analysis)
247 }
248 }
249 }
250}
251
252#[derive(Debug)]
257pub struct ApkContext {
258 ptr: shuriken::hApkContext
259}
260
261impl Drop for ApkContext {
262 fn drop(&mut self) {
264 unsafe {
265 shuriken::destroy_apk(self.ptr);
266 }
267 }
268}
269
270impl ApkContext {
271 pub fn parse_apk(filepath: &Path, create_xrefs: bool) -> Self {
273 let xrefs = if create_xrefs {
274 1
275 } else {
276 0
277 };
278
279 let c_str = CString::new(filepath.to_path_buf().into_os_string().into_string().unwrap()).unwrap();
280
281 let ptr = unsafe {
282 shuriken::parse_apk(c_str.as_ptr(), xrefs)
283 };
284
285 Self { ptr }
286 }
287
288 pub fn get_number_of_dex_files(&self) -> usize {
292 unsafe { shuriken::get_number_of_dex_files(self.ptr) as usize }
293 }
294
295 pub fn get_dex_file_by_index(&self, idx: usize) -> Option<String> {
297 let str_ptr = unsafe { shuriken::get_dex_file_by_index(self.ptr, idx as u32) };
298
299 match str_ptr.is_null() {
300 true => None,
301 false => if let Ok(string) = unsafe { CStr::from_ptr(str_ptr).to_str() } {
302 Some(string.to_owned())
303 } else {
304 None
305 }
306 }
307 }
308
309 pub fn get_number_of_classes_from_dex(&self, dex_file: &str) -> Option<usize> {
314 let dex_name = CString::new(dex_file)
315 .expect("CString::new() failed");
316
317 match unsafe { shuriken::get_number_of_classes_for_dex_file(self.ptr, dex_name.as_ptr()) } {
318 -1 => None,
319 nb => Some(nb as usize)
320 }
321 }
322
323 pub fn get_hdvmclass_from_dex_by_index(&self, dex_file: &str, idx: usize) -> Option<DvmClass> {
325 let dex_name = CString::new(dex_file)
326 .expect("CString::new() failed");
327
328 let ptr = unsafe {
329 shuriken::get_hdvmclass_from_dex_by_index(self.ptr, dex_name.as_ptr(), idx as u32)
330 };
331
332 match ptr.is_null() {
333 true => None,
334 false => unsafe {
335 Some(DvmClass::from_ptr(*ptr))
336 }
337 }
338 }
339
340 pub fn get_header_from_dex(&self, dex_file: &str) -> Option<DvmHeader> {
342 let dex_name = CString::new(dex_file)
343 .expect("CString::new() failed");
344
345 let header_ptr = unsafe {
346 shuriken::get_header_for_dex_file(self.ptr, dex_name.as_ptr())
347 };
348
349 match header_ptr.is_null() {
350 true => None,
351 false => unsafe {
352 Some(DvmHeader::from_ptr(*header_ptr))
353 }
354 }
355 }
356
357 pub fn get_number_of_strings_from_dex(&self, dex_file: &str) -> Option<usize> {
359 let dex_name = CString::new(dex_file)
360 .expect("CString::new() failed");
361
362 match unsafe { shuriken::get_number_of_strings_from_dex(self.ptr, dex_name.as_ptr()) } {
363 -1 => None,
364 nb => Some(nb as usize)
365 }
366 }
367
368 pub fn get_string_by_id_from_dex(&self, dex_file: &str, idx: usize) -> Option<String> {
370 let dex_name = CString::new(dex_file)
371 .expect("CString::new() failed");
372
373 let str_ptr = unsafe {
374 shuriken::get_string_by_id_from_dex(self.ptr, dex_name.as_ptr(), idx as u32)
375 };
376
377 match str_ptr.is_null() {
378 true => None,
379 false => if let Ok(string) = unsafe { CStr::from_ptr(str_ptr).to_str() } {
380 Some(string.to_owned())
381 } else {
382 None
383 }
384 }
385 }
386
387 pub fn get_disassembled_method_from_apk(&self, method_name: &str) -> Option<DvmDisassembledMethod> {
391 let method_name = CString::new(method_name)
392 .expect("CString::new() failed");
393
394 let method_ptr = unsafe {
395 shuriken::get_disassembled_method_from_apk(self.ptr, method_name.as_ptr())
396 };
397
398 match method_ptr.is_null() {
399 true => None,
400 false => unsafe {
401 Some(DvmDisassembledMethod::from_ptr(*method_ptr))
402 }
403 }
404 }
405
406 pub fn get_analyzed_class_by_hdvmclass_from_apk(&self, class: &DvmClass) -> Option<DvmClassAnalysis> {
410 self.get_analyzed_class_from_apk(class.class_name())
411 }
412
413 pub fn get_analyzed_class_from_apk(&self, class_name: &str) -> Option<DvmClassAnalysis> {
415 let class_name = CString::new(class_name)
416 .expect("CString::new() failed");
417
418 let class_ptr = unsafe {
419 shuriken::get_analyzed_class_from_apk(self.ptr, class_name.as_ptr())
420 };
421
422 match class_ptr.is_null() {
423 true => None,
424 false => unsafe {
425 Some(DvmClassAnalysis::from_ptr(*class_ptr))
426 }
427 }
428 }
429
430 pub fn get_analyzed_method_by_hdvmmethod_from_apk(&self, method: &DvmMethod) -> Option<DvmMethodAnalysis> {
432 self.get_analyzed_method_from_apk(method.dalvik_name())
433 }
434
435 pub fn get_analyzed_method_from_apk(&self, method_full_name: &str) -> Option<DvmMethodAnalysis> {
437 let method_name = CString::new(method_full_name)
438 .expect("CString::new() failed");
439
440 let method_ptr = unsafe {
441 shuriken::get_analyzed_method_from_apk(self.ptr, method_name.as_ptr())
442 };
443
444 match method_ptr.is_null() {
445 true => None,
446 false => unsafe {
447 Some(DvmMethodAnalysis::from_ptr(*method_ptr))
448 }
449 }
450 }
451
452 pub fn get_number_of_method_analysis_objects(&self) -> usize {
454 unsafe {
455 shuriken::get_number_of_methodanalysis_objects(self.ptr)
456 }
457 }
458
459 pub fn get_analyzed_method_by_idx(&self, idx: usize) -> Option<DvmMethodAnalysis> {
461 let method_ptr = unsafe {
462 shuriken::get_analyzed_method_by_idx(self.ptr, idx)
463 };
464
465 match method_ptr.is_null() {
466 true => None,
467 false => unsafe {
468 Some(DvmMethodAnalysis::from_ptr(*method_ptr))
469 }
470 }
471 }
472
473 pub fn get_analyzed_string_from_apk(&self, string: &str) -> Option<DvmStringAnalysis> {
475 let string = CString::new(string)
476 .expect("CString::new() failed");
477
478 let analysis_ptr = unsafe {
479 shuriken::get_analyzed_string_from_apk(self.ptr, string.as_ptr())
480 };
481
482 match analysis_ptr.is_null() {
483 true => None,
484 false => unsafe {
485 Some(DvmStringAnalysis::from_ptr(*analysis_ptr))
486 }
487 }
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 mod dex {
494 use super::super::*;
495
496 use std::fs;
497 use std::path::PathBuf;
498
499 use parser::*;
500 use dvm_access_flags::{ DvmAccessFlag, DvmAccessFlagType };
501
502 const TEST_FILES_PATH: &str = "test_files/";
503
504 #[test]
505 fn test_parse_dex() {
506 let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
507
508 for path in paths {
509 let path = path.unwrap().path();
510
511 if path.extension().unwrap() == "apk" {
513 continue;
514 }
515
516 let context = DexContext::parse_dex(&path);
517 }
518 }
519
520 #[test]
521 fn test_dex_header() {
522 let path = PathBuf::from("test_files/DexParserTest.dex");
523 let context = DexContext::parse_dex(&path);
524
525 let header = context.get_header();
526 assert!(header.is_some());
527 let header = header.unwrap();
528
529 assert_eq!(header.magic(), &[0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00]);
530 assert_eq!(header.checksum(), 0xe4eefae3);
531 assert_eq!(header.file_size(), 1624);
532 assert_eq!(header.header_size(), 112);
533
534 assert_eq!(header.link_size(), 0);
535 assert_eq!(header.link_off(), 0);
536 assert_eq!(header.string_ids_size(), 33);
537 assert_eq!(header.string_ids_off(), 112);
538 assert_eq!(header.type_ids_size(), 9);
539 assert_eq!(header.type_ids_off(), 244);
540 assert_eq!(header.proto_ids_size(), 7);
541 assert_eq!(header.proto_ids_off(), 280);
542 assert_eq!(header.field_ids_size(), 3);
543 assert_eq!(header.field_ids_off(), 364);
544 assert_eq!(header.method_ids_size(), 10);
545 assert_eq!(header.method_ids_off(), 388);
546 assert_eq!(header.class_defs_size(), 1);
547 assert_eq!(header.class_defs_off(), 468);
548 }
549
550 #[test]
551 fn test_nb_strings() {
552 use std::collections::HashMap;
553
554 let counts = HashMap::from([
555 ("test_files/_pi.dex", 32usize),
556 ("test_files/_null.dex", 23),
557 ("test_files/_float.dex", 71),
558 ("test_files/_test_lifter.dex", 16),
559 ("test_files/_long.dex", 28),
560 ("test_files/_double.dex", 71),
561 ("test_files/DexParserTest.dex", 33),
562 ("test_files/_exception.dex", 31),
563 ("test_files/_cast.dex", 29),
564 ("test_files/test_zip.apk", 0),
565 ("test_files/_loop.dex", 23),
566 ("test_files/TestFieldsLifter.dex", 44),
567 ("test_files/_instance.dex", 28),
568 ("test_files/_switch.dex", 33),
569 ("test_files/_int.dex", 27)
570 ]);
571
572 let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
573
574 for path in paths {
575 let path = path.unwrap().path();
576
577 if path.extension().unwrap() == "apk" {
579 continue;
580 }
581
582 let context = DexContext::parse_dex(&path);
583 let count = context.get_number_of_strings();
584
585 assert_eq!(count, *counts.get(&path.to_str().unwrap()).unwrap());
586 }
587 }
588
589 #[test]
590 fn test_get_string() {
591 let strings = vec![
592 " and ",
593 " is: ",
594 "<init>",
595 "DexParserTest.java",
596 "Field 1: ",
597 "Field 2: ",
598 "Hello, Dex Parser!",
599 "I",
600 "III",
601 "L",
602 "LDexParserTest;",
603 "LI",
604 "LL",
605 "Ljava/io/PrintStream;",
606 "Ljava/lang/Object;",
607 "Ljava/lang/String;",
608 "Ljava/lang/StringBuilder;",
609 "Ljava/lang/System;",
610 "Sum of ",
611 "This is a test message printed from DexParserTest class.",
612 "V",
613 "VL",
614 "[Ljava/lang/String;",
615 "append",
616 "calculateSum",
617 "field1",
618 "field2",
619 "main",
620 "out",
621 "printMessage",
622 "println",
623 "toString",
624 "~~D8{\"backend\":\"dex\",\"compilation-mode\":\"debug\",\"has-checksums\":false,\"min-api\":1,\"version\":\"3.3.20-dev+aosp5\"}"
625 ];
626
627 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
628
629 assert_eq!(context.get_number_of_strings(), 33);
630
631 for idx in 0..context.get_number_of_strings() {
632 let string = context.get_string_by_id(idx);
633 assert!(string.is_some());
634 assert_eq!(string.unwrap(), strings[idx]);
635 }
636 }
637
638 #[test]
639 fn test_fields() {
640 use std::collections::HashMap;
641
642 let fields = [HashMap::from([
643 ("name", "field1"),
644 ("flags", "2"),
645 ("type", "I")
646 ]),
647 HashMap::from([
648 ("name", "field2"),
649 ("flags", "2"),
650 ("type", "Ljava/lang/String;"),
651 ])];
652
653 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
654 let class = context.get_class_by_id(0);
655
656 assert!(class.is_some());
657 let class = class.unwrap();
658
659 assert_eq!(class.class_name(), "DexParserTest");
660 assert_eq!(class.super_class(), "java.lang.Object");
661 assert_eq!(class.source_file(), "DexParserTest.java");
662
663 assert_eq!(class.access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
664 assert_eq!(class.instance_fields_size(), 2);
665 assert_eq!(class.static_fields_size(), 0);
666
667 let class_descriptor = String::from("LDexParserTest;");
668 let access_flags = [DvmAccessFlag::ACC_PUBLIC];
669
670 for (idx, field) in class.instance_fields().iter().enumerate() {
671 let access_flags = DvmAccessFlag::parse(
672 fields[idx]["flags"].parse::<u32>().unwrap(),
673 DvmAccessFlagType::Field
674 );
675
676 assert_eq!(field.class_name(), class_descriptor);
677 assert_eq!(field.name(), fields[idx]["name"]);
678 assert_eq!(field.access_flags(), access_flags);
679
680 if fields[idx]["type"].starts_with("L") {
681 assert_eq!(field.field_type(), DexTypes::Class);
682 assert_eq!(field.fundamental_value(), DexBasicTypes::FundamentalNone);
683 assert_eq!(field.type_value(), "Ljava/lang/String;");
684 } else {
685 assert_eq!(field.field_type(), DexTypes::Fundamental);
686 assert_eq!(field.fundamental_value(), DexBasicTypes::Int);
687 assert_eq!(field.type_value(), "I");
688 }
689 }
690 }
691
692 #[test]
693 fn test_methods() {
694 use std::collections::HashMap;
695
696 let methods = [HashMap::from([
697 ("dalvik_name", "LDexParserTest;-><init>()V"),
698 ("flags", "1"),
699 ]),
700 HashMap::from([
701 ("dalvik_name", "LDexParserTest;->calculateSum(II)I"),
702 ("flags", "2"),
703 ]),
704 HashMap::from([
705 ("dalvik_name", "LDexParserTest;->main([Ljava/lang/String;)V"),
706 ("flags", "9"),
707 ]),
708 HashMap::from([
709 ("dalvik_name", "LDexParserTest;->printMessage()V"),
710 ("flags", "2"),
711 ])];
712
713 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
714 let class = context.get_class_by_id(0);
715
716 assert!(class.is_some());
717 let class = class.unwrap();
718
719 assert_eq!(class.class_name(), "DexParserTest");
720 assert_eq!(class.super_class(), "java.lang.Object");
721 assert_eq!(class.source_file(), "DexParserTest.java");
722
723 assert_eq!(class.access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
724 assert_eq!(class.direct_methods_size(), 4);
725 assert_eq!(class.virtual_methods_size(), 0);
726
727 let class_descriptor = String::from("LDexParserTest;");
728 let access_flags = [DvmAccessFlag::ACC_PUBLIC];
729
730 for (idx, method) in class.direct_methods().iter().enumerate() {
731 let access_flags = DvmAccessFlag::parse(
732 methods[idx]["flags"].parse::<u32>().unwrap(),
733 DvmAccessFlagType::Method
734 );
735
736 assert_eq!(method.class_name(), class_descriptor);
737 assert_eq!(method.dalvik_name(), methods[idx]["dalvik_name"]);
738 assert_eq!(method.access_flags(), access_flags);
739 }
740 }
741
742 #[test]
743 fn test_get_class_by_name() {
744 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
745 let class = context.get_class_by_name("DexParserTest");
746
747 assert!(class.is_some());
748 assert_eq!(class.as_ref().unwrap().class_name(), "DexParserTest");
749 assert_eq!(class.as_ref().unwrap().super_class(), "java.lang.Object");
750 assert_eq!(class.as_ref().unwrap().source_file(), "DexParserTest.java");
751 assert_eq!(class.as_ref().unwrap().access_flags(), vec![DvmAccessFlag::ACC_PUBLIC]);
752 }
753
754 #[test]
755 fn test_get_method_by_name() {
756 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
757 let method = context.get_method_by_name("LDexParserTest;->printMessage()V");
758
759 assert!(method.is_some());
760 assert_eq!(method.as_ref().unwrap().method_name(), "printMessage");
761 assert_eq!(method.as_ref().unwrap().class_name(), "LDexParserTest;");
762 assert_eq!(method.as_ref().unwrap().prototype(), "()V");
763 assert_eq!(method.as_ref().unwrap().access_flags(), vec![DvmAccessFlag::ACC_PRIVATE]);
764 }
765
766 #[test]
767 fn test_disassemble_dex() {
768 let paths = fs::read_dir(TEST_FILES_PATH).unwrap();
769
770 for path in paths {
771 let path = path.unwrap().path();
772
773 if path.extension().unwrap() == "apk" {
775 continue;
776 }
777
778 let context = DexContext::parse_dex(&path);
779 context.disassemble_dex();
780 }
781 }
782
783 #[test]
784 fn test_get_disassembled_method() {
785 use std::collections::HashMap;
786
787 let methods = HashMap::from([
788 (
789 String::from("LDexParserTest;-><init>()V"),
790 vec![
791 ".method constructor public LDexParserTest;-><init>()V",
792 ".registers 2",
793 "00000000 invoke-direct {v1}, Ljava/lang/Object;-><init>()V // method@5",
794 "00000006 const/16 v0, 42",
795 "0000000a iput v0, v1, DexParserTest->field1 int // field@0",
796 "0000000e const-string v0, \"Hello, Dex Parser!\" // string@6",
797 "00000012 iput-object v0, v1, DexParserTest->field2 java.lang.String // field@1",
798 "00000016 return-void",
799 ".end method"
800 ]
801 ),
802 (
803 String::from("LDexParserTest;->calculateSum(II)I"),
804 vec![
805 ".method private LDexParserTest;->calculateSum(II)I",
806 ".registers 7",
807 "00000000 add-int v0, v5, v6",
808 "00000004 sget-object v1, java.lang.System->out java.io.PrintStream // field@2",
809 "00000008 new-instance v2, Ljava/lang/StringBuilder; // type@5",
810 "0000000c invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V // method@6",
811 "00000012 const-string v3, \"Sum of \" // string@18",
812 "00000016 invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
813 "0000001c move-result-object v2",
814 "0000001e invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
815 "00000024 move-result-object v5",
816 "00000026 const-string v2, \" and \" // string@0",
817 "0000002a invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
818 "00000030 move-result-object v5",
819 "00000032 invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
820 "00000038 move-result-object v5",
821 "0000003a const-string v6, \" is: \" // string@1",
822 "0000003e invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
823 "00000044 move-result-object v5",
824 "00000046 invoke-virtual {v5, v0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
825 "0000004c move-result-object v5",
826 "0000004e invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
827 "00000054 move-result-object v5",
828 "00000056 invoke-virtual {v1, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
829 "0000005c return v0",
830 ".end method"
831 ]
832 ),
833 (
834 String::from("LDexParserTest;->main([Ljava/lang/String;)V"),
835 vec![
836 ".method public static LDexParserTest;->main([Ljava/lang/String;)V",
837 ".registers 3",
838 "00000000 new-instance v2, LDexParserTest; // type@1",
839 "00000004 invoke-direct {v2}, LDexParserTest;-><init>()V // method@0",
840 "0000000a invoke-direct {v2}, LDexParserTest;->printMessage()V // method@3",
841 "00000010 const/16 v0, 10",
842 "00000014 const/16 v1, 20",
843 "00000018 invoke-direct {v2, v0, v1}, LDexParserTest;->calculateSum(II)I // method@1",
844 "0000001e return-void",
845 ".end method"
846 ]
847 ),
848 (
849 String::from("LDexParserTest;->printMessage()V"),
850 vec![
851 ".method private LDexParserTest;->printMessage()V",
852 ".registers 4",
853 "00000000 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
854 "00000004 new-instance v1, Ljava/lang/StringBuilder; // type@5",
855 "00000008 invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
856 "0000000e const-string v2, \"Field 1: \" // string@4",
857 "00000012 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
858 "00000018 move-result-object v1",
859 "0000001a iget v2, v3, DexParserTest->field1 int // field@0",
860 "0000001e invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
861 "00000024 move-result-object v1",
862 "00000026 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
863 "0000002c move-result-object v1",
864 "0000002e invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
865 "00000034 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
866 "00000038 new-instance v1, Ljava/lang/StringBuilder; // type@5",
867 "0000003c invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
868 "00000042 const-string v2, \"Field 2: \" // string@5",
869 "00000046 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
870 "0000004c move-result-object v1",
871 "0000004e iget-object v2, v3, DexParserTest->field2 java.lang.String // field@1",
872 "00000052 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
873 "00000058 move-result-object v1",
874 "0000005a invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
875 "00000060 move-result-object v1",
876 "00000062 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
877 "00000068 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
878 "0000006c const-string v1, \"This is a test message printed from DexParserTest class.\" // string@19",
879 "00000070 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
880 "00000076 return-void",
881 ".end method"
882 ]
883 )
884 ]);
885
886 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
887
888 let dvm_method = context.get_disassembled_method(
890 "LDexParserTest;->printMessage()V"
891 );
892 assert!(dvm_method.is_none());
893
894 context.disassemble_dex();
895
896 for (method, code) in methods.iter() {
897 let dvm_method = context.get_disassembled_method(method);
898 assert!(dvm_method.is_some());
899
900 let dvm_method = dvm_method.unwrap();
901 assert_eq!(dvm_method.method_string()
902 .split("\n")
903 .zip(code)
904 .filter(|&(a, b)| a != *b)
905 .map(|x| println!("{x:?}"))
906 .count(),
907 0
908 );
909 }
910 }
911
912 #[test]
913 fn test_dvm_basic_block() {
914 use std::collections::HashMap;
915
916 let methods = HashMap::from([
917 (
918 String::from("LDexParserTest;->printMessage()V"),
919 vec![
920 "BB.0-120",
921 "00000000 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
922 "00000004 new-instance v1, Ljava/lang/StringBuilder; // type@5",
923 "00000008 invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
924 "0000000e const-string v2, \"Field 1: \" // string@4",
925 "00000012 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
926 "00000018 move-result-object v1",
927 "0000001a iget v2, v3, DexParserTest->field1 int // field@0",
928 "0000001e invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
929 "00000024 move-result-object v1",
930 "00000026 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
931 "0000002c move-result-object v1",
932 "0000002e invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
933 "00000034 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
934 "00000038 new-instance v1, Ljava/lang/StringBuilder; // type@5",
935 "0000003c invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // method@6",
936 "00000042 const-string v2, \"Field 2: \" // string@5",
937 "00000046 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
938 "0000004c move-result-object v1",
939 "0000004e iget-object v2, v3, DexParserTest->field2 java.lang.String // field@1",
940 "00000052 invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
941 "00000058 move-result-object v1",
942 "0000005a invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
943 "00000060 move-result-object v1",
944 "00000062 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
945 "00000068 sget-object v0, java.lang.System->out java.io.PrintStream // field@2",
946 "0000006c const-string v1, \"This is a test message printed from DexParserTest class.\" // string@19",
947 "00000070 invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
948 "00000076 return-void",
949 ]
950 ),
951
952 (
953 String::from("LDexParserTest;->main([Ljava/lang/String;)V"),
954 vec![
955 "BB.0-32",
956 "00000000 new-instance v2, LDexParserTest; // type@1",
957 "00000004 invoke-direct {v2}, LDexParserTest;-><init>()V // method@0",
958 "0000000a invoke-direct {v2}, LDexParserTest;->printMessage()V // method@3",
959 "00000010 const/16 v0, 10",
960 "00000014 const/16 v1, 20",
961 "00000018 invoke-direct {v2, v0, v1}, LDexParserTest;->calculateSum(II)I // method@1",
962 "0000001e return-void",
963 ]
964 ),
965
966 (
967 String::from("LDexParserTest;->calculateSum(II)I"),
968 vec![
969 "BB.0-94",
970 "00000000 add-int v0, v5, v6",
971 "00000004 sget-object v1, java.lang.System->out java.io.PrintStream // field@2",
972 "00000008 new-instance v2, Ljava/lang/StringBuilder; // type@5",
973 "0000000c invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V // method@6",
974 "00000012 const-string v3, \"Sum of \" // string@18",
975 "00000016 invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
976 "0000001c move-result-object v2",
977 "0000001e invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
978 "00000024 move-result-object v5",
979 "00000026 const-string v2, \" and \" // string@0",
980 "0000002a invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
981 "00000030 move-result-object v5",
982 "00000032 invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
983 "00000038 move-result-object v5",
984 "0000003a const-string v6, \" is: \" // string@1",
985 "0000003e invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@8",
986 "00000044 move-result-object v5",
987 "00000046 invoke-virtual {v5, v0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; // method@7",
988 "0000004c move-result-object v5",
989 "0000004e invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // method@9",
990 "00000054 move-result-object v5",
991 "00000056 invoke-virtual {v1, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // method@4",
992 "0000005c return v0",
993 ]
994 ),
995
996 (
997 String::from("LDexParserTest;-><init>()V"),
998 vec![
999 "BB.0-24",
1000 "00000000 invoke-direct {v1}, Ljava/lang/Object;-><init>()V // method@5",
1001 "00000006 const/16 v0, 42",
1002 "0000000a iput v0, v1, DexParserTest->field1 int // field@0",
1003 "0000000e const-string v0, \"Hello, Dex Parser!\" // string@6",
1004 "00000012 iput-object v0, v1, DexParserTest->field2 java.lang.String // field@1",
1005 "00000016 return-void",
1006 ]
1007 ),
1008 ]);
1009
1010
1011 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1012 context.disassemble_dex();
1013 context.create_dex_analysis(true);
1014 context.analyze_classes();
1015
1016 for idx in 0..context.get_number_of_classes() {
1017 let class = context.get_class_by_id(idx as u16);
1018 assert!(class.is_some());
1019 let class = class.unwrap();
1020
1021 let class_name = class.class_name();
1022 assert_eq!(class_name, "DexParserTest");
1023
1024 let class_analysis = context.get_analyzed_class(class_name);
1025 assert!(class_analysis.is_some());
1026 let class_analysis = class_analysis.unwrap();
1027
1028 for method_analysis in class_analysis.methods() {
1029 let basic_blocks = method_analysis.basic_blocks();
1030
1031 for (block_idx, block) in basic_blocks.blocks().iter().enumerate() {
1032 let data = methods.get(method_analysis.full_name());
1033 assert!(data.is_some());
1034
1035 let data = data.unwrap();
1036 assert_eq!(block.block_string()
1037 .split("\n")
1038 .zip(data)
1039 .filter(|&(a, b)| a != *b)
1040 .map(|x| println!("{x:?}"))
1041 .count(),
1042 0
1043 );
1044 }
1045 }
1046 }
1047 }
1048
1049 #[test]
1050 fn test_get_analyzed_class() {
1051 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1052 context.disassemble_dex();
1053 context.create_dex_analysis(true);
1054 context.analyze_classes();
1055
1056 assert_eq!(context.get_number_of_classes(), 1);
1057
1058 let class_analysis = context.get_analyzed_class("DexParserTest");
1059 assert!(class_analysis.is_some());
1060 let class_analysis = class_analysis.unwrap();
1061
1062 let dvm_class = context.get_class_by_name("DexParserTest");
1063 assert!(dvm_class.is_some());
1064 let dvm_class = dvm_class.unwrap();
1065
1066 let class_analysis_by_hdvmclass = context.get_analyzed_class_by_hdvmclass(&dvm_class);
1067 assert!(class_analysis_by_hdvmclass.is_some());
1068 let class_analysis_by_hdvmclass = class_analysis_by_hdvmclass.unwrap();
1069
1070 assert_eq!(class_analysis.is_external(), class_analysis_by_hdvmclass.is_external());
1071 assert_eq!(class_analysis.extends(), class_analysis_by_hdvmclass.extends());
1072 assert_eq!(class_analysis.name(), class_analysis_by_hdvmclass.name());
1073 assert_eq!(class_analysis.n_of_methods(), class_analysis_by_hdvmclass.n_of_methods());
1074 assert_eq!(class_analysis.methods(), class_analysis_by_hdvmclass.methods());
1075 assert_eq!(class_analysis.n_of_fields(), class_analysis_by_hdvmclass.n_of_fields());
1076 assert_eq!(class_analysis.fields(), class_analysis_by_hdvmclass.fields());
1077 assert_eq!(class_analysis.n_of_xrefnewinstance(), class_analysis_by_hdvmclass.n_of_xrefnewinstance());
1078 assert_eq!(class_analysis.xrefnewinstance(), class_analysis_by_hdvmclass.xrefnewinstance());
1079 assert_eq!(class_analysis.n_of_xrefconstclass(), class_analysis_by_hdvmclass.n_of_xrefconstclass());
1080 assert_eq!(class_analysis.xrefconstclass(), class_analysis_by_hdvmclass.xrefconstclass());
1081 assert_eq!(class_analysis.n_of_xrefto(), class_analysis_by_hdvmclass.n_of_xrefto());
1082 assert_eq!(class_analysis.xrefto(), class_analysis_by_hdvmclass.xrefto());
1083 assert_eq!(class_analysis.n_of_xreffrom(), class_analysis_by_hdvmclass.n_of_xreffrom());
1084 assert_eq!(class_analysis.xreffrom(), class_analysis_by_hdvmclass.xreffrom());
1085 }
1086
1087 #[test]
1088 fn test_get_analyzed_method() {
1089 let context = DexContext::parse_dex(&PathBuf::from("test_files/DexParserTest.dex"));
1090 context.disassemble_dex();
1091 context.create_dex_analysis(true);
1092 context.analyze_classes();
1093
1094 let dvm_method = context.get_method_by_name("LDexParserTest;->printMessage()V");
1095 assert!(dvm_method.is_some());
1096 let dvm_method = dvm_method.unwrap();
1097
1098 let method_analysis = context.get_analyzed_method(dvm_method.dalvik_name());
1099 assert!(method_analysis.is_some());
1100 let method_analysis = method_analysis.unwrap();
1101
1102 let method_analysis_by_hdvmmethod = context.get_analyzed_method_by_hdvmmethod(&dvm_method);
1103 assert!(method_analysis_by_hdvmmethod.is_some());
1104 let method_analysis_by_hdvmmethod = method_analysis_by_hdvmmethod.unwrap();
1105
1106 assert_eq!(method_analysis.name(), method_analysis_by_hdvmmethod.name());
1107 assert_eq!(method_analysis.descriptor(), method_analysis_by_hdvmmethod.descriptor());
1108 assert_eq!(method_analysis.full_name(), method_analysis_by_hdvmmethod.full_name());
1109 assert_eq!(method_analysis.external(), method_analysis_by_hdvmmethod.external());
1110 assert_eq!(method_analysis.is_android_api(), method_analysis_by_hdvmmethod.is_android_api());
1111 assert_eq!(method_analysis.access_flags(), method_analysis_by_hdvmmethod.access_flags());
1112 assert_eq!(method_analysis.class_name(), method_analysis_by_hdvmmethod.class_name());
1113 assert_eq!(method_analysis.basic_blocks(), method_analysis_by_hdvmmethod.basic_blocks());
1114 assert_eq!(method_analysis.n_of_xrefread(), method_analysis_by_hdvmmethod.n_of_xrefread());
1115 assert_eq!(method_analysis.xrefread(), method_analysis_by_hdvmmethod.xrefread());
1116 assert_eq!(method_analysis.n_of_xrefwrite(), method_analysis_by_hdvmmethod.n_of_xrefwrite());
1117 assert_eq!(method_analysis.xrefwrite(), method_analysis_by_hdvmmethod.xrefwrite());
1118 assert_eq!(method_analysis.n_of_xrefto(), method_analysis_by_hdvmmethod.n_of_xrefto());
1119 assert_eq!(method_analysis.xrefto(), method_analysis_by_hdvmmethod.xrefto());
1120 assert_eq!(method_analysis.n_of_xreffrom(), method_analysis_by_hdvmmethod.n_of_xreffrom());
1121 assert_eq!(method_analysis.xreffrom(), method_analysis_by_hdvmmethod.xreffrom());
1122 assert_eq!(method_analysis.n_of_xrefnewinstance(), method_analysis_by_hdvmmethod.n_of_xrefnewinstance());
1123 assert_eq!(method_analysis.xrefnewinstance(), method_analysis_by_hdvmmethod.xrefnewinstance());
1124 assert_eq!(method_analysis.n_of_xrefconstclass(), method_analysis_by_hdvmmethod.n_of_xrefconstclass());
1125 assert_eq!(method_analysis.xrefconstclass(), method_analysis_by_hdvmmethod.xrefconstclass());
1126 assert_eq!(method_analysis.method_string(), method_analysis_by_hdvmmethod.method_string());
1127 }
1128 }
1129}