【技术分享】Android应用逆向工程
华域联盟
登陆 / 注册 搜索

USERCENTER

SEARCHSITE

搜索

查看: 493|回复: 0

【技术分享】Android应用逆向工程

[复制链接]
发表于 2017-9-20 13:37:15 | 显示全部楼层 |阅读模式

【CHU】
信息来源: 华域联盟(www.cnhackhy.com)

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x

1 j- [# s2 z8 N  A+ o( G8 D5 @) d' Z7 q3 q/ i
t01ee71e888c7d378da.jpg
* p, ?# x, V4 t) R- U, V+ X
  . C% c3 F5 l3 d9 x4 a! \" H& ], z1 e
  阅读本文最好破费45Min~8 ^, ^* U6 a* u. o2 W' \! Q
  你经常在用 Burp 拦截信息的时分很迷茫么?你经常在剖析用加密的数据中止通讯的App,关于需求了解它的数据而疑惑么?在本文,我将会分享很多办法来用于逆向剖析APK。我们将会对目的APP采用动态和静态的剖析办法。4 E! G2 |% G; N4 O: q* C
  我创建了一个简单的APP作为剖析目的,它的功用只是单纯地对我们输入的数据中止考证,假定用户输入正确的话,将会在屏幕上显现“Congratulations“。7 T1 e$ X+ s0 F1 p: H* }5 |
  我们先看一下这个应用的源代码以便于我们一会儿能够将它与反编译后的APK代码中止比较。# Q% I  A4 [# Q/ D8 M- }
  package com.punsec.demo;
( K! u4 \/ u( |* r( }  import android.os.Bundle;
* ?* k) |; p" {" `4 z9 {  import android.support.v7.app.AppCompatActivity;# r8 ?) `! u3 p* I+ [
  import android.util.Base64;
  n4 X8 v2 N# ~0 N9 j  import android.view.View;
9 e* f' ?. T1 f  import android.widget.Button;
6 Y) x6 K4 K! x3 w: U  import android.widget.EditText;# \& _6 N' i) e& a
  import android.widget.TextView;
* Q; L$ T4 [( X  import javax.crypto.SecretKey;
( ?& g8 P, ~* T3 E# e/ t  public class MainActivity extends AppCompatActivity {
0 o( Z/ C9 t; k  TextView result;
" E, [, s6 M7 h0 c  EditText input;
6 J& g( n$ O# b* {8 H  Button button;
) B3 Y/ h, @, S8 @# I  @Override5 S% b/ X2 o2 L" A) B4 e/ J
  protected void onCreate(Bundle savedInstanceState) {
7 N  w" e+ _2 T( B- R- P5 H  super.onCreate(savedInstanceState);
. d0 r; j/ ~' P; I2 A4 R  setContentView(R.layout.activity_main);1 c7 y/ E) _5 q( s  w7 Y1 I
  input = (EditText) findViewById(R.id.input);' H# W+ p* e* ~3 n8 U2 L
  result = (TextView) findViewById(R.id.result);
6 m3 H* }( Z; I) S8 F  button = (Button) findViewById(R.id.ok);4 J( g* X/ y3 E& S; d$ g
  button.setOnClickListener(new View.OnClickListener() {
6 _& {- a$ l3 q- _  @Override
* ]4 e3 @* e8 E. e+ p, a  public void onClick(View v) {! b' }: q1 n9 B. n' w: G0 p
  String a = input.getText().toString();
* t' R' a( k. d/ z" y" z# h. h! ~3 k% [% ~  String b = getString(R.string.a);2 J7 n2 ^- d$ k* n! O, C
  try {
" P7 E+ C, T0 `  SecretKey secretKey = Util.a(new String(Base64.decode(getString(R.string.b), Base64.DEFAULT)));' S* X; Z8 B# ?; _: F( k3 f" V
  byte e[]
* B9 L( P( D. {  = Util.a(a, secretKey);
  g6 n$ J/ U' v. M6 y) y. i  String er = Base64.encodeToString(e, Base64.DEFAULT).trim();9 J3 u7 |. z6 }( G
  if(er.equals(b)) {
& P' ?1 \( S! X1 _+ K  result.setText(getString(R.string.d));
, H2 |1 O" B: O5 A) L4 P( v  }else {# C. o9 U0 H8 q* V% `) T
  result.setText(getString(R.string.e));0 ~) e7 ?7 n/ |3 ^/ W. U/ F
  }+ J" \0 P; M9 u+ W* u
  } catch (Exception e) {
2 k( H: b7 v9 Q" I  //                    Log.d(EXCEPTION:, e.getMessage());8 F8 O7 m( O& |* s
  }5 p, w# ?' Z1 a- C  y; S
  }
8 {% \9 F) I6 v9 M+ j8 U5 e' E2 m  });9 q* {5 M/ t8 ?1 c( @2 B& O8 k
  }
9 \' A" I+ A3 o2 \. S' g4 \  }
! b0 E- k3 M& E) ^  这个应用运用下面的这个辅助类来执行一些重要的操作:! G  w4 R/ F5 y
  package com.punsec.demo;
) g8 O- u7 L. ]2 U/ v' U6 E3 Y2 e  import java.io.UnsupportedEncodingException;
, D& W$ L& o$ u5 |1 D  import java.security.InvalidAlgorithmParameterException;
, ~4 A% r7 q( h$ w2 Y8 q  import java.security.InvalidKeyException;, c* z9 w9 O( q8 N8 ]
  import java.security.MessageDigest;% a7 o& `0 b$ v' n4 J1 d" y
  import java.security.NoSuchAlgorithmException;% F. D) v5 y& x5 P
  import java.security.spec.InvalidKeySpecException;9 t6 j$ J9 H% `8 M
  import java.security.spec.InvalidParameterSpecException;
/ }: B1 }9 J6 t- D  import javax.crypto.BadPaddingException;
; _/ c) w& V$ Q  import javax.crypto.Cipher;
+ a, n2 N; q1 Z4 f  import javax.crypto.IllegalBlockSizeException;( P" y; n5 \" Q7 ^5 c9 m8 |/ V# E
  import javax.crypto.NoSuchPaddingException;3 q9 j5 k0 x; K$ }1 |
  import javax.crypto.SecretKey;
% o2 Y! `6 t  K, u  import javax.crypto.spec.SecretKeySpec;8 n3 d  J& [- P. }
  class Util {6 C& a+ l+ d1 w3 i1 R1 M
  static SecretKey a(String secret)
" z: T5 e# U# C* T/ E, m+ d  throws NoSuchAlgorithmException, InvalidKeySpecException: [5 d: ^5 w/ R1 q$ w) b' h9 I
  {
9 X- L, R  `) n8 T1 x$ z7 ~  MessageDigest md = MessageDigest.getInstance(MD5);  p8 l- `4 `6 `
  md.update(secret.getBytes());
7 M7 M& j9 A8 |+ l  byte[]
0 @: X# _5 F; r- l" U& T  digest = md.digest();
3 ~' Z7 j2 ]3 e. G# ^( ]  StringBuilder sb = new StringBuilder();; h; }( t2 C1 c& S! B5 @
  for (byte b : digest) {
5 Y0 g! h+ {, e5 m9 x) ]  sb.append(String.format(%02x, (0xFF  b)));
  z$ _# Z% L' |6 ^: K  g& f  }
5 O$ V5 J% M1 e, H' V) x  return new SecretKeySpec(sb.toString().substring(0,16).getBytes(), AES);- X' }6 j! N1 h. Q1 G6 c
  }
1 {% Q3 O3 b, z" W' j' B  static byte[]
" N& O" N; T. W: z4 J4 T3 S! f  a(String message, SecretKey secret)
+ S$ \: ~7 s! W7 e% L. H% A4 _6 w  throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException0 {  [$ [/ O- R1 G  O
  {
9 R$ b0 `+ B/ O  N  Cipher cipher = null;8 R  z# E( X: F$ o+ P0 Y
  cipher = Cipher.getInstance(AES/ECB/PKCS5Padding);) n9 G: q( f- b. r! p' r* Z9 e
  cipher.init(Cipher.ENCRYPT_MODE, secret);; R) a) Y2 |2 C" V! N
  return cipher.doFinal(message.getBytes(UTF-8));7 `) x: {- j# O* i
  }: C/ h5 `- j% l. @7 v% a( V0 o
  static String a(byte[]
2 C7 K) M9 U5 y. e8 f# A  cipherText, SecretKey secret)7 h/ z% b  F  B! |$ g4 }3 A% n8 Z6 i
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException1 n/ }' |0 Y4 _1 j: X- {) Q
  {
# V. Q% Y! Z  }$ k  @6 w7 n8 {  Cipher cipher = null;* l' L" n. v/ S3 ?- g0 D0 Y
  cipher = Cipher.getInstance(AES/ECB/PKCS5Padding);/ X* z+ H; G/ `, X+ n
  cipher.init(Cipher.DECRYPT_MODE, secret);. ]5 F. F" C# D1 n9 e
  return new String(cipher.doFinal(cipherText), UTF-8);$ k/ L3 F% I' U# ?( c& @
  }5 w$ S  `' W# O1 O3 G  L2 _8 J
  }( }' h9 c  t5 r% D, h
  你能够下载曾经编译好的APK From - CrackMe
* A% y' W% G/ K9 O  在我们中止下一步的操作之前,先罗列剖析所需的背景学问:& Z7 C3 E( ?7 x2 M8 _& a
  一个曾经root的安卓设备或者虚拟机(固然并不是一切的剖析办法都需求root权限,但是有一个root的设备是不错的)。
9 N) e% c$ u" z3 ]6 n# J  Frida+ g& V  m+ u6 W$ M6 d2 W  t8 N5 M- M
  Python
1 C1 v3 [  i# r/ {" y  Inspeckage* T/ l7 O+ g5 d$ `9 A) n+ T
  Xposed Framework
- ]; V; c" C( \$ W# ~  APKTool
8 R1 F9 e# m6 A  APKStudio4 Q6 I  Z, F5 C: v
  ByteCodeViewer
; p) ~! ?& ^7 }% c, \* i  Dex2Jar8 G/ k. R0 X; y% b- c: p4 ]
  JarSigner(Java JDK)
' i! y) q; E( o: U2 G  JD-JUI6 m8 e+ k6 R: x% C8 z, N% v
  Brain! t) }1 e8 ~6 ?0 `0 k* \& Q
  我们将会运用的三种剖析办法:; T. U3 N8 ^3 `' B
  动态剖析和Hooking.( Q( s3 t( M$ b( C' l0 ?2 i
  二进制文件Patch(byte code修正).. }  G5 Q2 @0 Q% R) z- X* A
  静态剖析和代码复制.
* p: X. P$ r# ]3 J  动态/运转时环境 剖析和函数Hooking:
& w7 \2 P9 x1 E/ V; B- S  我们需求运用的剖析工具: Frida, dex2jar, JD-GUI.5 i: T. e4 U5 X! T. E7 v- i
  用 Frida剖析:& a) v6 A& ]* J, x) `/ }
  到底什么是Frida ?
% D+ {: I& }5 w  It's Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript or your own library into native apps on Windows, macOS, Linux, iOS, Android, and QNX. Frida also provides you with some simple tools built on top of the Frida API. These can be used as-is, tweaked to your needs, or serve as examples of how to use the API.
+ y( I& @/ }5 ^  用简单的术语来说,它能够被用来Hook函数调用,注入你自己的代码未来能够来修正应用自身的执行流程。我们将会运用它来经过检测和来辨认不同的变量。, p- q$ F! x% z2 F! T
  为了能够装置Frida,我们能够将手机开启USB调试之后用数据线衔接电脑,并且在电脑端运转
4 q+ M$ @$ U- ~- D0 Z' ?& W  # check adb devices whether connected or not
7 X0 a: L4 k2 E0 @% a' r  adb devices. }- j) v7 b% m# u
  # push/copy the latest frida server to phone
5 B; T/ H% O5 W) Z: r  adb push frida-server-10.4.0-android-arm /data/local/tmp/frida  Z* H' b7 c( \/ u
  # set permissions for frida, grant SU permissions if prompted
9 n7 u! q, ?& a7 J/ F* k9 m. [) U) W  adb shell su -c chmod 755 /data/local/tmp/frida$ R8 i$ U9 b  m+ l( I5 @
  # start frida server on android device5 a  P( T# T  K
  adb shell su -c ./data/local/tmp/frida
- t0 i0 ?& }; n/ a  # install frida-python on your Windows/Mac/Linux device& s" O2 R# |( r4 D8 p
  pip install --user frida+ P7 d% S5 @! I- }
  运转了上面的命令之后,我们的Frida Server就曾经运转在了我们的电脑上,让我们来检验一下,翻开终端,运转python:
! {( S! h! d2 k7 E3 M9 y0 {( ~  Python 2.7.10 (default, Feb  7 2017, 00:08:15)
2 N* C$ d; z4 K  Type help, copyright, credits or license for more information.; w6 |. T# h, s3 T( f4 M
  import frida
8 s5 D7 I2 _& r% y% Z8 u1 U  frida.get_usb_device()7 L9 _' g' `+ R6 K
  Device(id=802b7421, name=LG SCH-XXXX, type='tether')
$ t- j0 E4 i& d; ^) O# u! D& Z  为了便当以后的剖析,往常让我们创建一个python脚本:
2 @2 t' e! j5 F; I# Z  import frida, sys, time  D! q( Q/ C/ k% y5 ^. y
  encrypted = None1 _7 Q4 ^3 v; {
  def on_message(message, data):
( m0 L6 I1 G$ P! |. d  global encrypted
# s8 a, Y1 ?& P. l- [8 X- a% X  try:
6 C' A1 f$ a# [" |0 J- l  if not encrypted:0 x3 T5 b+ R: {6 z; J2 X
  encrypted = message['payload']['encrypted']
" v3 a- S% ]+ b$ u! i; C  print('[+]
" Q( m, L+ ^0 F8 o; A  T( y; V  Received str : ' + encrypted)
/ D- P9 Q9 e2 C# q8 ?  return+ K7 c. s5 v/ P; o0 u" _2 F+ ^
  except:( I- N4 U: e2 e2 R4 L
  pass   
. r# X1 j6 }8 K, x  if message['type']2 x1 B$ Y1 b1 X8 E4 x$ S
  == 'send':" b$ t  j# [& r# X8 `* k9 I# ~
  print('0 [3 B4 g" {" G1 P/ D0 U$ ^
  {0}'.format(message['payload']))( T, U3 D# t4 D1 l- ^( F% J
  elif message['type']& n: z& O+ G) S" H. s
  == 'error':
- `/ o8 t" K# T3 k# ^& H+ J8 f  if 'encrypted' undefined in message['description']:
# W+ y- S4 C6 U1 \( c  print('[!]9 `: d+ b6 ?8 ~' f. H- S$ j
  Encrypted value not updated yet, try to rotate the device')9 Y: l* M1 i, t0 P! F
  else:- x7 w: L5 n/ z$ W. _( X
  print('[!]
0 R" x& T# `; X  ' + message['description'])& c% I# \0 m% N2 W3 y7 i5 E
  else:( G) v# R, m& y# ?8 n
  print message
& {  f* Z1 ~+ K+ c& Z% o5 z& f  jscode = open('punsec.js').read()
; \3 q+ f. e) F& A, V( c4 U  print('[+]; O! L( D$ x5 o* o& x0 a: Q, z& g
  Running')) l& V. [7 V1 O5 O$ Q3 [1 w
  process_name = 'com.punsec.demo'
, r) E$ G# i0 ]5 |  device = frida.get_usb_device()
5 s/ u. G8 r! w# p  Y  D  try:
: K2 v3 ~) J/ O* {* p  pid = device.get_process(process_name).pid
& ]; C+ s$ d/ J6 ?" U& k% ~  print('[+]
" B  c3 ~0 r5 `1 q) S, v* O2 i$ \  Process found')
4 J4 i& w" e" O) R  except frida.ProcessNotFoundError:/ m- m2 {) }+ y+ u
  print('[+]
$ U# T( k" @% e' K  Starting process')/ R: h) X. l: w* `+ T& H
  pid = device.spawn([process_name])/ O* n% l: z' v1 Q, ?
  device.resume(pid)
0 e6 j) v. _( L5 U! ~0 r  time.sleep(1)
& l. i  n' q$ z4 u6 [  process = device.attach(pid)
' j, {4 e6 M# v( x% {  script = process.create_script(jscode)
9 E+ I8 I3 b6 J) v8 `& O% K  script.on('message', on_message)! n2 ?& H* }+ r9 P, S
  script.load()
3 l$ e, q% O. W7 p2 ^  while True:
. s  P" O" p: I  time.sleep(1)
4 e/ l, p  r* c. p2 i: g/ n  if encrypted:
. ~- u. \# F6 \$ H2 z, J  script.post({'type':'encrypted','value':encrypted})
" G6 K2 P6 g; }" b  break+ p# c; G0 I- |& \* Q3 ]. Y' Q+ e
  sys.stdin.read(): L! w3 R0 u( @! v
  让我们慢慢讲一些这些代码:
/ O3 p3 X4 x% X5 s( A  这个Encrypted变量最初是一个无类型的对象,它不久之后就会被脚原本将它更新为一个加密的值。这个 on_message 函数是一个回调函数能够被 frida 的 javascript 代码来应用,在javascript代码之中,我们将注入到我们程序的进程中来回调我们的python代码。这个回调函数能够被经过在javascript代码中的send() 函数来执行。下一个变量是jscode, 它能够将我们的js代码注入到程序的进程中。为了更便当我们阅读,js代码被写到另一个文件中。Process_name变量是我们的进程名字。我们能够经过在adb shell中运转 ps 命令 pm list packages 命令得到我们应用的进程名字。
. [3 C, ]& K& i( Y5 U  这个 device 变量是来衔接我们的USB设备(手机)的。Try except 用于处置异常(万一目的程序还没有在我们的设备上运转的话,就会产生异常)。在知道了运转程序的UID后,我们能够挂接到目的程序上,并且在目的进程上注入jscode。经过运用js的 send() 函数,脚本就会开端注册我们的回调函数。下面是 while 循环,能够看到frida理论上是有多么的强大,在这个循环中,我们检测能否encrypted变量它的类型曾经不是None了,假定它的类型发作了改动,脚本的post()函数将会发送一个信息将我们的js代码注入到目的进程,并且信息将会被在js代码中的recv() 函数所处置。
; R$ x0 M" O2 @# F  在开端下一步的操作之前,我们需求对目的apk中止静态剖析。我们首先要反编译apk并且将java bytecode转换为.java格式的代码来阅读。在这里,我们运用的剖析工具是dex2jar。
3 p# a) ~6 V0 B  B& Y8 ^  $ ./d2j-dex2jar.sh CrackMe.apk' x4 S. M2 c: M/ F3 V# V! m# E6 i
  dex2jar CrackMe.apk - ./CrackMe-dex2jar.jar6 k7 Y$ l" P- w/ }  }- n$ @$ l
  往常让我们经过JD-GUI来剖析刚才生成的CrackMe-dex2jar.jar文件3 O- b% N6 A9 v4 ?% Y+ f2 l8 t
& r' B" ?, L* j: ?
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p3.qhimg.com/t017af7f50a7c320927.png" border="0" alt="" />- N! T: _# u' }# ]9 _' t3 e
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

: x" }$ z/ m9 m7 D* H! ]: W+ T0 H, u, [" x
  能够看到反编译后的代码与原始的java代码还是有很大的不同的。我们来剖析一下不同的中央:首先能够很明显的看到资源 id由原来的R.x.x变换称为了数字格式的。. `0 ~) f2 N4 l; }; H# C8 m
  正如我们上面看到的,MainActivity只包含一个 onCreate() 函数。我们首先来看一下android应用的生命周期:
1 C: T  H( o* c& X; X& C
9 ]# M; y( t; ^+ z3 w6 c
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p9.qhimg.com/t01ac9fce0966ee766f.png" border="0" alt="" />0 t: q* l2 ^) l
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
/ E' M9 A" y6 c1 E& S

2 Y2 [% F! X" F  X  能够看到: onCreate() 函数在app启动之后就运转。为了坚持应用的理论功用,我们往常就在hook这个函数,来执行对原始函数的调用,能够获取到目前activity的上下文来得到一些字符串的值,就像下面这一行一样:/ w) }4 B- C' I/ m- g
  String str = MainActivity.this.getString(2131099669);; R+ W/ S) l0 c7 k( B
  往常让我们创建punsec.js文件,来得到这些值。/ i/ N% [# d1 a5 r. @0 ~. d+ |
  Java.perform(function () {/ H% I1 P' ?% M7 [+ L" X
  var MainActivity = Java.use('com.punsec.demo.MainActivity');8 c4 @) Z" }! p; p: y' z  u2 ?
  MainActivity.onCreate.implementation = function(a) {, Q) V% x7 Z" {/ ^! y
  this.onCreate(a);
8 B! I) I* w5 M+ Z! K  send({encrypted:this.getString(2131099669)});8 g6 U* o6 Q, z. m
  };
/ y' d3 T3 [( ^! t1 V5 U4 f  });1 T9 T9 S8 |7 L" S  Q/ B$ m' J1 C
  Java.perform() 是 frida 定义的,它的功用是:通知frida server来运转曾经包装好的js代码。Java.use() 是一个包装器为了能够动态的加载packages到我们的目的进程中。为了下一步的需求,我们将会运用send() 回调函数来发送数据到我们的python程序中。往常运转着的python脚本给我们返回了这样的信息:
, S, n2 Q4 u3 p$ f% V  $ python punsec.py
$ Y& ~' s3 E) P. f$ u+ _  [+]
; I" ]1 Y% s* c  Running
6 h2 P, D1 n6 u5 z4 b8 R  J: m, ]  [+], e- |8 @& \( W5 m' s
  Starting process
+ @0 v$ \* Q9 K7 z+ R- v% n  [+]
  \$ D+ _* s+ \* _  Received str : vXrMphqS3bWfIGT811/V2Q==
' I1 |9 J+ d- i& s0 m0 Q8 L  要记住,要想onCreate() 函数触发,必需求执行回调函数,也就是在启动进程之后,必需求让它在后台运转后再翻开程序,请参考上面的Activity生命周期。
! I1 \2 F  n: A6 C3 t  我们也看到了代码中有几个调用来执行 Base64.decode()2 N/ l" U1 g! J0 F- u% w# \
  和经过数字id来得到string, 我们可能也会需求这些值,所以让我们来修正一下我们的代码
9 k* V# s0 ^; Y* d  Java.perform(function () {
: m2 Y' n8 k% J  var MainActivity = Java.use('com.punsec.demo.MainActivity');9 I9 D5 D: B) e
  MainActivity.onCreate.implementation = function(a) {
  k% ?" X4 P6 d# k/ g7 e  this.onCreate(a);; n8 L- M3 p/ h$ @
  send({'encrypted':this.getString(2131099669)});
9 t! P9 h" [  S: _  };7 N# X4 o# w5 M
  var base64 = Java.use('android.util.Base64');' d9 u7 ?6 [( ]% K$ U0 _( h
  base64.decode.overload('java.lang.String', 'int').implementation = function(x, y) {
6 |: c7 p0 U* B+ K3 k, l  send('Base64 Encoded : ' + x);3 R) y$ V& I, f, m8 ~6 V9 n2 G
  var buf = new Buffer(base64.decode(x, y));' C1 V" ^7 x6 }! {& G  ^7 f4 }
  send('Base64 Decoded : ' + buf.toString());) ?4 z9 ]* I! r* K
  return base64.decode(x, y);
; U5 y+ p! A7 T$ `  }6 P4 E" [4 Z, R
  });, L* a  v4 l" K; k4 \
  再一次运转我们的python程序将会得到下面的输出:
/ O: z! J$ H7 _8 A  $ python punsec.py
7 a% \( X1 z% L3 H2 h& i5 i  [+]
% `$ L4 E. M6 e% `( C! H5 w% `* B4 m# Q  @  Running; l3 F6 l( M" J
  [+]
. l8 X# n$ w" m! M2 B  Process found* v+ R& e, V+ K6 p6 F
 
' r7 ]7 x. X/ ^, h( W  Base64 Encoded : TXlTdXBlclNlY3JldEwzM3RQYXNzdzByZA==
& t1 d8 N; B8 D4 F % W8 m7 u& l% i5 M0 ?2 t/ K, O
  Base64 Decoded : MySuperSecretL33tPassw0rd
5 _0 x  G; L- a% k8 E  Hmm, 似乎我们曾经胜利了。不要着急,往常让我们再来认真看一下我们的反编译代码:. T2 U9 `3 }! P
  if (Base64.encodeToString(+ F0 \+ l* E. s
  Util.a(paramAnonymousView,
  O/ a9 Z% l% w  y3 q) |$ r  Util.a(new String(
! z/ N' F" y" c  Base64.decode(MainActivity.this.getString(2131099683), 0)
% S4 w& Q* p  U/ P  )$ y" Y1 K7 ?+ Y  m1 D
  )
' m0 t. ^0 n- B' x  ), 2 b% m, K2 v" o4 e/ @
  0).trim().equals(str))- Q) d5 l. M' k( {
  在上面的代码中,有两次对Util.a7 Q9 [, ]; E: r: t: O2 u0 Q+ X
  函数的调用但是都采用的不同的参数类型,我们曾经hook了 Base64.decode()
2 G4 f1 {9 @9 I1 a  函数,所以往常让我们用下面的代码对Util.a() 创建一个 hook :, T; T, X! }% R/ r2 S+ I! G
  Java.perform(function () {: I( P2 @1 E$ A) T2 l* M
  var MainActivity = Java.use('com.punsec.demo.MainActivity');
9 X6 @) N7 p6 k% ?  MainActivity.onCreate.implementation = function(a) {
# P% q; P+ X7 I  this.onCreate(a);% W$ q( h: D) I8 _4 F( E  q
  send({'encrypted':this.getString(2131099669)});
! x6 E$ S. S9 {3 c1 Z  };
1 j8 ^% G9 P% a+ C& @% _% H% C  var base64 = Java.use('android.util.Base64');
* H# y1 {* K; C) Q0 c  base64.decode.overload('java.lang.String', 'int').implementation = function(x, y) {
. X0 ^3 A( `8 G( u! u) m1 j  send('Base64 Encoded : ' + x);5 `* r6 N6 q6 I* x5 e/ v7 ?9 k
  var buf = new Buffer(base64.decode(x, y));" Q: `: A4 p$ ]3 Q( d8 u0 X3 s3 \& c
  send('Base64 Decoded : ' + buf.toString());
, x: I0 ~/ D7 T5 i! L. D8 Y6 s6 R  return base64.decode(x, y);" O* [. J2 i0 w4 x+ Q: ~  [
  }
% V2 x  H4 [0 C/ ^0 C( R3 i  var Util = Java.use('com.punsec.demo.Util');
1 a5 Z+ _' S) E. Y8 z  Util.a.implementation;0 o8 v7 K/ _& f* }" v. v7 P
  });
$ e" s8 }1 R- g; r# C( A  运转我们的python代码,然后能够得到以下的输出:* f3 q: B6 U% v; M$ e9 t5 p" h
  $ python punsec.py' ^8 {  e6 i% q
  [+]( P: y3 O" z/ k  Z1 S0 v1 _
  Running$ k. D6 b9 N2 T2 U3 F
  [+]
% I4 h3 J0 G& ]- J& U) {  Process found2 f( F& W* K/ k- N4 E
  [!]4 u( G+ `2 d: r  j+ j: x
  Error: a(): has more than one overload, use .overload(signature) to choose from:
! R. C3 q9 w6 @4 @3 D- F  .overload('java.lang.String')
) l4 F9 Q" E1 F/ a% v# T  .overload('java.lang.String', 'javax.crypto.SecretKey')9 ~' B. V7 [; w* e& N. b
  .overload('[B', 'javax.crypto.SecretKey')# Q$ r! m  ^+ c
  这似乎呈现了一点错误。看起来是我们的Util类中有函数重载(有相同的办法称号但是具有不同的参数)。为了抑止这个问题, frida提供给我们额外的办法 overload(),经过这个办法,我们能够显式地设置哪个办法来 override/hook。我们将会 hook Util.a(String, SecretKey)函数(由于它是一个担任加密的函数)来为了中止下一步剖析:/ r. c) g9 M+ m( h' N6 }( n
7 B5 ~& Z3 {- x0 k/ ?+ G
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p8.qhimg.com/t01b905ec629ffd448b.png" border="0" alt="" /># I; |/ ?, I2 D7 b
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
  t5 n' K7 t5 ^: u2 |. x/ {
) y# h. {3 M# u2 n" S
  但是我们怎样样才干辨认出这是一个加密函数的呢?首先能够看到这个函数的返回类型是byte,很显然意味着并没有返回一个string类型,同时,本地密码初始化为1来作为第一个参数传送:) J! e+ ~0 D0 _, M

+ Y2 L9 e  X6 `
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p1.qhimg.com/t0190069cd4e662c0b0.png" border="0" alt="" />
# y$ N) F' Z6 l$ x; e3 D- j& `5 b                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

! i+ V: R- g* T7 n+ [5 n* e+ ^5 t& Q$ d. {$ _( s% _
  往常,让我们来修正我们的js代码为了能够合理地hook这个函数:: g* D6 h* n! D
  Java.perform(function () {/ j! q& |  i4 U" Y' ?
  var MainActivity = Java.use('com.punsec.demo.MainActivity');
! H8 ^: ?& X* X8 ^  MainActivity.onCreate.implementation = function(a) {
) I% q; K. t; |1 H  this.onCreate(a);
9 {8 i) x4 t. C  N# U( I( [  send({'encrypted':this.getString(2131099669)});
% S% \, A. I' m+ K, e6 O  };
9 H- y5 Y6 C! n  var base64 = Java.use('android.util.Base64');! x; C% b; I* V, g
  base64.decode.overload('java.lang.String', 'int').implementation = function(x, y) {3 C) |# O* V* ~# I
  send('Base64 Encoded : ' + x);6 i- P& i% j+ D: \$ o
  var buf = new Buffer(base64.decode(x, y));
2 N4 P9 a' ^& F+ F8 b% p9 X; B  send('Base64 Decoded : ' + buf.toString());* U3 I/ O  I* D2 x9 f6 F% \  g
  return base64.decode(x, y);
  s: \. ]" |1 j9 l% m  }
% x7 w" ?+ k: M$ |# [  var Util = Java.use('com.punsec.demo.Util');) Z1 O7 ]0 U/ w. V! D
  Util.a.overload('java.lang.String', 'javax.crypto.SecretKey').implementation = function(x, y) {
2 @, m5 M1 H7 Y9 |( ?  send('UserInput : ' + x);5 Q" U4 g% @- v: M8 s( _+ L
  return this.a(x,y);: ^- i; \3 _; j4 w; Q
  }
% A2 t5 e1 l2 x  });  D! e: @# y9 p2 a# j- M0 M9 Z- U
  再次运转我们的python程序,察看输出有哪些改动:  @1 N! V9 u! n: w! A+ o- c
  $ python punsec.py3 s2 |; a& E. }5 `% n2 ?5 S. j
  [+]
: D( r, a; |. M  v; T. K  Running
& o) t+ v% W& Y& b7 H6 }  [+], Y% s3 X7 k) ]- r/ o* w+ D. ?/ d
  Process found8 s6 n* l+ j0 ~4 L9 E" ~6 o
 
% c' ]# Z& ^. N! R" }  Base64 Encoded : TXlTdXBlclNlY3JldEwzM3RQYXNzdzByZA==3 F6 s- x9 k6 Q, ]7 ~
 ( g: @+ o$ `+ S" d$ C# c
  Base64 Decoded : MySuperSecretL33tPassw0rd8 ]" O0 x9 ^' \% w
 
4 s/ i7 i3 e1 d* e, M0 m( g- w  UserInput : wrongSecretTest
) n4 e2 u: t- K! V  |3 t  极好的,我们往常能够拦截我们的输出了。往常我们能够发现 Util 类还有一个函数Util.a(byte, SecretKey)
0 Z$ I9 d/ O& N: {$ ^' S; q1 B: Y  不时没有在app中运用,经过剖析能够看到这是一个解密函数。所以往常我们该如何做呢?
: F2 T' }; ?) W  加密函数曾经接纳到了密钥,所以我们能够在解密函数中应用,但是我们还需求第一个参数。第一个参数是一个 base64 解密的string 变量。所以让我们来修正我们的代码,为了能够在我们的 js中收到这个参数,并且过掉这个解密函数,这样的话,我们就能解密最终的Key来完成这次应战。往常最后一次修正我们的js代码:
! `2 i, d1 ^. I+ D2 j' }  Java.perform(function () {! u( [- F+ N' R! H, L
  var MainActivity = Java.use('com.punsec.demo.MainActivity');
1 Q7 W0 P% Z' r% Q6 W  v6 U9 W  MainActivity.onCreate.implementation = function(a) {" M/ U8 W9 ~9 r2 K4 a+ s% X
  this.onCreate(a);
( }' m5 G7 a" }4 o$ D  send({'encrypted':this.getString(2131099669)});" V! ?7 j: K4 P! H! j1 p* G7 w
  };
4 ]  z4 v; {( y  T7 [! R! x2 c/ s  var base64 = Java.use('android.util.Base64');
" z" Q+ ?6 J+ D* B& L' ?* P: L  base64.decode.overload('java.lang.String', 'int').implementation = function(x, y) {7 Z$ Q: r( O& z) i+ [0 W
  // send('Base64 Encoded : ' + x);
  S  p! P% A8 ?  // var buf = new Buffer(base64.decode(x, y));
9 k$ N. K( D1 f' J2 g, A  // send('Base64 Decoded : ' + buf.toString());
( g+ u/ v) O; ?7 a0 }  return base64.decode(x, y);
' Q$ G# a" B5 g) l3 x% G& a% u  }5 |, n5 c! G) }/ t5 j. x
  var Util = Java.use('com.punsec.demo.Util');
+ D4 X% j( M1 ]1 }$ a/ D6 u6 E  Util.a.overload('java.lang.String', 'javax.crypto.SecretKey').implementation = function(x, y) {
  X! \1 {5 K9 @( Y  recv('encrypted', function onMessage(payload) {
- p8 _2 r" y/ ^; c  encrypted = payload['value'];
* j: s. k" l: S+ H. f% ^. k# Q" _1 H  });! J. y' s) D, U: D" V, u+ ~
  cipher = base64.decode(encrypted, 0); // call the above base64 method
# J) u% s5 p9 s/ g; A0 j- {  secret = this.a(cipher, y); // call decrypt method( j0 }! v  j9 e  e  h7 E& ?: _
  send('Decrypted : ' + secret)/ ]& L1 m0 t# ~8 z7 Z+ Z
  return this.a(secret,y);
+ q- k0 a/ H& [  }
; c- A, S, [: a8 A* E  });2 @/ ]# Y6 w6 Z* E* K1 K
  我们把一个 recv() 调用放在了函数中以便于能够收到我们写的python程序中曾经存储的加密string。往常解密这个曾经被加密过的base64密钥并且和密钥一同发送到解密函数中。往常让我们再一次运转我们的python程序:5 b- E0 Z9 i% H+ G: w
  $ python punsec.py+ o/ w2 |6 `8 s
  [+]+ Y) {2 y/ H0 F6 X: ?2 o
  Running
# a8 G" l* |  e  [+]
9 A0 w7 D7 V2 n9 m  Process found8 w8 Y) p' p" `! \* F
  [!]: A. i$ ^3 O  O9 y# t' e8 T
  Encrypted value not updated yet, try to rotate the device% f8 g" f( A- E* {
  [+]
4 ^+ R. V, x/ I6 ?& M/ r9 G  Received str : vXrMphqS3bWfIGT811/V2Q==* n2 }) L# W/ \
 5 B! v) [0 o0 m4 A2 f9 f
  Decrypted : knb*AS234bnm*0* D$ W' ?3 A: }! ^* a" f
  woah, 我们得到了key。这也会掩盖掉任何的用户输入并将其交流为解密的string, 所以往常每一个用户输入都是起作用的:0 n2 n* Q4 c- m/ i
6 p, g; v# k+ ?$ u6 M+ K
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p8.qhimg.com/t019c8ffb907a10c4f2.png" border="0" alt="" />
2 Q8 Y+ E1 C1 I7 P' \                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

. J4 G7 z  z/ x1 O4 E0 Q, z+ f5 w5 L7 U# L) g  [6 K- {
  往常我们不只用理论的secret掩盖了用户输入,而且还掩盖了理论的secret phrase为了经过这个应战。
7 S3 S: u% R7 M9 D: v1 o' X  倘若我们的apk应用中没有解密函数,我们该怎样办呢?4 }. ^6 T9 _- \! C% r( m" j
  不用担忧,我们能巧妙的将js代码插入到package中来执行解密操作并且用必要的参数掩盖这个办法,或者我们还能够用下面的python代码来解密:
% e9 k: o; |% T) h( h- L9 D  import frida, sys, time, md5
; w" d: Y9 `6 E  S0 x$ a" G# f  from Crypto.Cipher import AES
% d5 \; i; J9 s9 V; @# ^  encrypted = None( D: }# e# C9 B- Y
  secretKey = None( Y9 o4 m/ i! w. }$ J6 ^/ w
  def decrypt(encrypted, key):/ J) @' ?# T9 H+ c% K3 T# x, E1 I
  key = md5.new(key).hexdigest()[:16]
9 u7 M( G8 X/ k1 C: h6 o  cipher = AES.new(key)/ h' @& i1 q! Y1 O
  decrypted = cipher.decrypt(encrypted.decode('base64'))[:14]
9 F- o! R5 h* V# B  for i in range(1,len(encrypted.decode('base64'))/16):
' \1 h) [5 U& C1 M# P; `) j  cipher = AES.new(key, AES, encodedEncrypted.decode('base64')[(i-1)*16:i*16])' d) o3 @  W/ `
  decrypted += cipher.decrypt(encodedEncrypted.decode('base64')[i*16:])[:16]6 h1 K" v' k- p
  return decrypted.strip()
9 T" ~1 ?- B' v* U8 G  def on_message(message, data):- h" b0 n8 _: ?! C
  global encrypted, secretKey
( b2 w; p8 s2 H; T- A; L  try:9 z$ i; h9 l5 X& _+ G! @' _
  if not encrypted:0 A3 H, m$ k& z  X2 f" ~' W
  encrypted = message['payload']['encrypted']
9 g8 o$ U* z. o" d  if not secretKey:
6 T0 F$ \6 W1 ]2 O  secretKey = message['payload']['secretKey']& U3 z1 [) L: I2 j" J- P& @
  except:, T* J9 o. \5 m  `! w
  pass$ t& W6 t  ?% E3 ^8 t
  if message['type']
6 R& T* }" M" e( L/ O& o4 c, z  == 'send':& `4 A( P" i& m9 \# d1 }; T
  print('# P& T( K' z6 T- l5 q3 ?" N' S
  {0}'.format(message['payload']))
9 Z3 g. O9 m/ n" u- l. q  elif message['type']
" M9 T6 E$ T- k( z  == 'error':! o" \  ]0 @8 t7 Q+ c. N, x
  if 'ReferenceError' in message['description']:
6 ?- Y2 q7 ^8 c: X  print('[!]6 |9 Y. K$ `0 D' @! z
  Rotate the device')
% o* `4 k5 ]: G. x/ d  else:
+ ~8 {# }" a' U" e6 M5 M  print('[!]+ k  W. [$ p# {2 J7 G& {
  ' + message['description'])5 ~- b, c" k0 H, y2 W/ b' ~; q
  else:9 a) e0 e7 O  v* u
  print message# K8 R& U9 K! o: _
  jscode = open('punsec.js').read(): p  @# D1 X4 m+ Q# ^+ s: S! a
  print('[+]6 r% i- ]. P' ^: ^. a+ o: r& `
  Running')
1 H, c( x9 N6 u4 Z5 r: u  process_name = 'com.punsec.demo'
* Y: z/ q6 _4 a% W# ?  device = frida.get_usb_device()* r. u  P" |; p0 |
  try:
; {# g( n4 {/ d% l9 O  pid = device.get_process(process_name).pid
- k/ W) A8 Q& s( _8 i  print('[+]
- k' w7 h- s3 S1 ]. O) @8 `  Process found')4 ^0 O3 y6 Y) {" `2 E4 R$ r
  except frida.ProcessNotFoundError:
# ]  g8 T: n$ m/ x# Z  print('[+]$ H( Q) H: W, g9 j3 Q
  Starting process')$ z% U) j; E0 {' e5 s
  pid = device.spawn([process_name])
, O( d0 l' a( v3 R8 Z5 R  device.resume(pid); |  ^0 v& u, Y
  time.sleep(1)! j7 S7 I6 h* G! r" u3 R% T: R6 E
  process = device.attach(pid)
( H( l9 ?9 ~' t# Z$ a* u( |  q  script = process.create_script(jscode)
; Z/ p2 ]6 o( w  K- H  script.on('message', on_message); X  T# u$ P& O0 a$ V0 K9 @
  script.load()
4 k, b/ f; d- g+ U! o4 \  while True:
; G' Q8 Z: o- K) r: G7 g  time.sleep(0.2)! ^! [/ L8 g( h& \
  if encrypted and secretKey:
4 F2 d( R" |$ w3 {  script.post({'type':'encrypted','value':decrypt(encrypted, secretKey)})
" Z' w" R* E4 S1 y0 n  break
) l* o& A: Z1 }# v, X* _! O  sys.stdin.read()  |+ ]* V" c8 \9 D, ^
  我们更新后的js代码:/ ~5 f& t1 p* ?& j4 S( T
  Java.perform(function () {
, F1 \# f" L5 f0 C* I4 ?* K  var MainActivity = Java.use('com.punsec.demo.MainActivity');
, e3 T. t5 ]8 O+ t* V0 [  MainActivity.onCreate.implementation = function(a) {
$ c6 C# C. s" T' Z  this.onCreate(a);' U$ C6 H. r" i5 }& ]" |5 d
  send({'encrypted':this.getString(2131099669)});
! O( ]8 u( l. S3 X' z/ y; A+ c  };4 Q+ |" L+ y7 {6 K/ W
  var base64 = Java.use('android.util.Base64');
% ^, e+ \* V% {9 ?# f+ m, J) C9 Z  base64.decode.overload('java.lang.String', 'int').implementation = function(x, y) {
3 w' C0 R  `7 C8 |* A  var buf = new Buffer(base64.decode(x, y));
' Q- |* |+ R7 R. D  send({'secretKey': buf.toString()});
/ `: o! r0 n9 K; ^# a9 _  return base64.decode(x, y);
! ~3 L  G/ B6 T  }$ t, K5 F* a; N+ j
  var Util = Java.use('com.punsec.demo.Util');7 k% k# S+ Z0 `  {+ C* s  e, M
  Util.a.overload('java.lang.String', 'javax.crypto.SecretKey').implementation = function(x, y) {
( u7 u/ a$ v( x4 p6 l/ s5 T  recv('encrypted', function onMessage(payload) {
4 z* m# N1 g+ B  `" v  secret = payload['value'];
# J# b% f8 T- Q" E9 S# m; k" x( d; `  });- P. @: F6 G) C4 \
  send('Decrypted : ' + secret)1 `% a3 z, o! D3 E% F
  return this.a(secret,y);
: {3 T+ {; q3 ^  }
9 t% E$ o" m% p+ u6 {  });! z) d2 r% H/ @+ N; |, \, q
  往常运转我们的python程序:8 Q' c' O8 @2 j% G% D+ u' n
  $ python punsec.py
* L) H' ]4 n& I9 B  [+]
; h% T7 [, r7 _: o( v; K: `5 w  Running
0 [  b6 A+ D1 y5 }  [+]
5 W2 n7 O" `2 h  Process found+ o, D3 K9 ~1 [1 F* f
 % E# x3 X" ~5 X- H. u
  {u'secretKey': u'MySuperSecretL33tPassw0rd'}+ H2 t# T: t& l
  [!]3 T( N- o: Y2 l7 a! b
  Rotate the device
% B; v1 F4 A4 T: l! H7 \- s * l. H/ t1 [( ]7 F; z
  {u'encrypted': u'vXrMphqS3bWfIGT811/V2Q=='}
; p6 D0 @& B. J# g. j 8 @( |5 R" q* `) d' C. W. T
  {u'secretKey': u'MySuperSecretL33tPassw0rd'}' j5 v1 y; a$ _: P2 ^# q5 }
 5 I, k3 O# _2 l  |# p% g9 G
  Decrypted : knb*AS234bnm*0
' o# u1 c9 N/ s7 H0 `/ d: _  用 Inspeckage 来剖析4 J! \+ Z: x- ]" ^# Q
  我们将会运用到Inspeckage, Xposed Framework 和 ApkStudio/ByteCodeViewer.
2 ]6 q3 O- U- M5 i) F/ x  Inspeckage – Android Package Inspector
1 U3 ?  h4 |5 B& Z  Inspeckage is a tool developed to offer dynamic analysis of Android applications. By applying hooks to functions of the Android API, Inspeckage will help you understand what an Android application is doing at runtime.9 L) a3 a) K: Z3 V4 u* S
  Inspeckage能够让你来用简单的web接口中止剖析。Inspeckage需求你装置Inspeckage Xposed module并且在 Xpose 框架中激活它。在你的android设备上启动Inspeckage App并且选择我们的目的应用并且在Inspeckage Webserver中阅读。# u/ z( C0 B/ Y7 N

2 s3 q2 q' M( Y/ s
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p4.qhimg.com/t016152e0013935706d.png" border="0" alt="" />" X! u! p4 S/ }8 }* {, y
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

# L( R$ R6 ~8 d  B" L5 f6 X, L
: v3 V3 d+ k" {! s3 I  o3 E! B9 f0 e/ w% E
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p6.qhimg.com/t016fc220b3b7bb543d.png" border="0" alt="" />: v0 ?. V. N9 w, Y  N7 O& ?# U* X
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

9 W! F( s1 S' B- |1 Y* n- p+ E% L/ [8 ~* o/ q7 m
  翻开自动刷新开关,点击在webserver上的设置按钮并且关闭一些Actvity检测就像下面这张图一样,最后点击 start App 并且刷新页面。4 @( c2 G/ C# S

! M8 q/ @4 T) c. A
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p9.qhimg.com/t01c243279b03fcd668.png" border="0" alt="" />
' h, G' p% A* \3 q. s/ E, A  G+ H                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

8 o/ ^6 l0 T0 \* w# ~
+ V6 _2 v/ V. s  一旦我们的App在手机上运转,就在App上输入测试的数据并点击ok按钮,然后察看Inspeckage webserver上的通知(留意要开启自动刷新):: c0 m- `* H% Z& O
, n4 {" X( ^+ w) H8 ]
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p2.qhimg.com/t01dea274f9a7233536.png" border="0" alt="" />
' p6 ]& M+ f4 s/ w7 i, k. j                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
1 j, B# v# s3 a. p) Y: o

6 l6 K; k1 \* {! D# y
% G$ b2 w' }# E/ ~+ z
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p7.qhimg.com/t01212b8a79fb4928c3.png" border="0" alt="" />
' y- n# K) F5 ~+ v; A2 O/ b$ o                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
: ^" u, R  |8 F6 d
4 N% i% w" |. }( H/ c# D; x
  这两张截图都显现出了我们运用了frida办法。用 Inspeckage剖析是相当简单的,你能够检测app执行的文件系统Activities, SQL队列操作,在这背后运用的是和我们运用frida办法相同的概念: 在加密,文件系统,hash等操作函数上中止hook,但是在这里,我们能够执行函数hook吗? 当然了,正如你在最后一个标签上看到的,它提供了一个hook选项。但是随之而来的问题是:它不像frida那样,Inseckage没有提供对重载的办法的掩盖,往常点击hook标签并且创建一个hook来考证我们的想 法:
! D. g7 {0 Q# T' ]0 L8 i! @( @2 P. \
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p7.qhimg.com/t01fd1266c050933682.png" border="0" alt="" />
" G3 x+ S+ A. S                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

6 k6 L# R. A" G, v  A$ Q7 g. R' c+ j) I) a/ j7 k
  所以往常为了能够创建一个有效的hook,我们将会运用 ByteCodeViewer 或者 APKStudio 来修正apk中的 bytecode(字节码)。下面这是我们对字节码的patch:
& u; K( q- P) j& b  i8 F9 h6 S' e9 T& @
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p1.qhimg.com/t010d616872d99b3e2a.png" border="0" alt="" />
7 c+ P8 Q, q0 J% y2 Z$ I8 l                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
: B$ }2 ^! `( c* D
  K7 Q6 G- M& ~5 Y  n2 N
  (留意:当翻开apk的时分,取消选择Decode Resource,否则你将会遇到下面这些问题)% e9 P3 G3 n$ L
  ERROR: 9-patch image C:\Users\labuser\Desktop\CrackMe\res\drawable-mdpi-v4\abc_list_divider_mtrl_alpha.9.png malformed.( ]* @6 U* {' s  k; }" @. U
  Must have one-pixel frame that is either transparent or white.
' i9 N) U0 }0 r5 |/ `8 N& Y8 Y9 o  ERROR: Failure processing PNG image C:\Users\labuser\Desktop\CrackMe\res\drawable-mdpi-v4\abc_list_divider_mtrl_alpha.9.png
+ M5 w+ o% s" T1 A  ERROR: 9-patch image C:\Users\labuser\Desktop\CrackMe\res\drawable-hdpi-v4\abc_list_divider_mtrl_alpha.9.png malformed.' Q( }$ V1 t7 n9 [: r  d! w
  Must have one-pixel frame that is either transparent or white.
- E+ M* @' x; Y9 H3 W. X& u2 B% [4 D  ERROR: Failure processing PNG image C:\Users\labuser\Desktop\CrackMe\res\drawable-hdpi-v4\abc_list_divider_mtrl_alpha.9.png
" q+ l$ O9 @. M6 \/ B  ERROR: 9-patch image C:\Users\labuser\Desktop\CrackMe\res\drawable-xhdpi-v4\abc_list_divider_mtrl_alpha.9.png malformed.7 |" K( Q! I6 s! Z5 W
  Must have one-pixel frame that is either transparent or white.) e0 X& B0 x: F. K# ~
  ERROR: Failure processing PNG image C:\Users\labuser\Desktop\CrackMe\res\drawable-xhdpi-v4\abc_list_divider_mtrl_alpha.9.png
2 b8 }, z7 P# Q3 w( U: P3 Q  ERROR: 9-patch image C:\Users\labuser\Desktop\CrackMe\res\drawable-xxhdpi-v4\abc_list_divider_mtrl_alpha.9.png malformed.
) z* U+ |, }9 B) G- f6 |5 t% W) A  Must have one-pixel frame that is either transparent or white.. b( \! N9 i+ F2 {' b4 ^' A
  ERROR: Failure processing PNG image C:\Users\labuser\Desktop\CrackMe\res\drawable-xxhdpi-v4\abc_list_divider_mtrl_alpha.9.png# }0 S# E) [, Y: J
  在上面那副截图中,能够看到第168行,我们经过辨认第168行的参数类型和返回值,胜利的辨认出了这就是加密函数,在第197行,这个被赋值为1的变量也是我们之前看到的。我们曾经把这个函数的名字改成了b ,并且解密函数称号改为c。往常为了保证我们的app能够正常运转,我们需求在MainActivity的字节码上做出相同的更新:
6 s) C( \( n0 ?- h/ J. z, J* d/ P8 _1 K8 J, s, m/ P, L8 C
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p4.qhimg.com/t01187d48b490c8a9c3.png" border="0" alt="" />
* |3 {+ o- G3 d- C* i' }                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
0 v- k, r1 z3 d7 r4 E2 q! M  b
% |: \: ], ?9 p  @: w; \* d, U. ?
  往常我们的任务曾经完成了,能够创建一个keystore来对我们的apk中止签名。- G7 @# w9 P* n0 U5 H$ D
  C:\Program Files\Java\jdk1.8.0_144\binkeytool -genkey -v -keystore C:%users\labuser\Desktop\my.keystore -alias alias_na0 y5 j2 e  e  m$ c, o  _
  me -keyalg RSA -keysize 2048 -validity 10000
; |. t; T: C  f* n  Enter keystore password:
+ F9 y' C9 A  A& j, p0 {8 d: u  Re-enter new password:1 ^% @* Y5 [5 _: f3 j* Q  J
  What is your first and last name?! Y6 Z! S, R" _9 }, @" A+ T! q0 j
  [Unknown]:& e. S" F( ?1 x. r" N9 \
  What is the name of your organizational unit?
  @& }, O+ P# f" Y3 S( y8 p2 r  [Unknown]:
; ^; _1 i1 v7 V& z5 `  What is the name of your organization?# @/ L! m9 A; _2 B& e" c0 a5 j
  [Unknown]:
. _# g& ~" B, }0 ]  What is the name of your City or Locality?
7 Z) L" s$ X& g% f) \/ Q# K  [Unknown]:
( R/ q( s. v5 @  What is the name of your State or Province?2 |; F0 r7 |* f4 q" |. D3 x& v
  [Unknown]:2 r8 L; }) L2 U; Q" T% V# _
  What is the two-letter country code for this unit?7 D, E1 n1 e( b3 T: v0 ?1 R
  [Unknown]:& V9 q9 W  g! g
  Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
0 L: l# Y5 H6 S' b: N' q" k: R3 z  [no]:  yes
. o8 [( q" z; p* ^5 D& ~0 f+ E  Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days' H  ?5 |# M/ {, I$ m& f
  for: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
& u7 t; D' M7 A' H' v, s% e  Enter key password for alias_name
/ p$ x  [5 E6 A; D  b: v4 k7 z4 B4 d  (RETURN if same as keystore password):
, D4 j- }& `8 \* S  [Storing C:%users\labuser\Desktop\my.keystore]
0 D3 H& {" ]6 M- j3 l  C:\Program Files\Java\jdk1.8.0_144\binjarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore C:%users\labuser\Desktop\my.keystore C:%users\labuser\Desktop\CrackMe.apk alias_name
; I; m5 X' |2 K7 Q! c  将曾经签名的apk装置到设备上。重启Inspeckage,开端hook来考证能否我们的修正曾经起作用了。
# X0 u/ z2 O+ d
2 g6 u. s) h! N  p5 X. v
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p7.qhimg.com/t01f474be241cc56d34.png" border="0" alt="" />
* P" S) \+ A+ M8 t. n  k                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
& x! a( s* I: H1 s6 T5 q$ l4 l

. U* G% [/ E1 A# k0 H  极好地,我们的修正是圆满的,往常我们能够对目的函数Util.b() 下hook。选择这个函数并且点击 Add hook 按钮。往常让我们点击ok按钮并且察看Inspeckage Server的通知。
9 x1 v% m) [4 ]# P* U  l8 r, R" ^  s- G% g: t0 }
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p5.qhimg.com/t019845f392b8416f56.png" border="0" alt="" />4 @3 d0 i; L" x5 i* [6 R# a: |
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

+ \( R' n7 l$ v! C% h$ U! J' Z- _
  我们能够看到Inspeckage曾经胜利地从曾经hook的函数中截取到数据并且提供给我们了函数的参数和返回值。往常点击 Replace 按钮并且配置如下的选项。
/ O$ h- A6 [- Z6 z6 z8 d2 y  b) f
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p4.qhimg.com/t017b0658e42a1681f6.png" border="0" alt="" />8 Z' k- ^& f8 e" N- d
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
2 k/ q- J; j9 t! m% l3 t* I

7 a+ k2 C5 o% j: g1 F- Z, ?3 G  在这里我们将第一个参数传送给了我们的加密函数,这个函数具有我们曾经用frida辨认出来的秘密值。无论什么时分中止输入测试(大小写敏感),Hook都会交流数据并且传送我们提供的值,然后将Congratulations再一次显往常我们的屏幕上。( a1 T9 n! c: }8 l4 ^
* o& F+ o/ x( M
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p8.qhimg.com/t013e82b79efa49c4f3.png" border="0" alt="" />
6 B/ m, Q0 w: y+ U                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

7 y- Z. t2 F7 ?5 I( y* y, C" u6 K0 D. L5 E  ]
  二进制补丁(字节码修正)
4 l5 e3 g0 P# Q" M! t! }  在这个办法中,我们将会运用ApkStudioJarsigner+ q2 l  n3 N4 E4 \9 z( p- g
  我们将会经过修正反编译的Apk,之后重新编译它来修正程序的逻辑。启动 ApkStudio并且再次加载文件( 记住要取消选择Decode Resources复选框),之后在MainActivity$1.smali中定位到程序代码中中止比较的位置
! y5 V# `$ d$ s3 O7 w) e1 l6 s$ K
5 ^' Y; @. v; Q% I) P0 S6 t; ~
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p3.qhimg.com/t01a441e52a3a16852f.png" border="0" alt="" />
# M# {, h( c6 M4 R" S                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

1 M' ~6 J) |6 }8 b2 n# U9 C. i2 E, h
  我们能够在第113行看到程序会比较两个不同的值来执行检测,假定比较失败了,会显现Umm, Try Again。但是假定程序总是将两个相同的值中止比较会怎样样呢?在这种状况下,程序将会跳过else条件直接返回true。所以往常让我们将代码修正后重新编译并对我们的Apk中止签名,然后做测试。
- y+ O$ e% b( r- f9 R/ m0 D) L6 s% l; W- E  W( |/ j
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p4.qhimg.com/t01bf2e4f8b4b680ceb.png" border="0" alt="" />7 {6 m& G1 {5 _4 _, j# M% @* H6 G
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
( w4 N/ l9 r5 g( F* ^8 i

& Z0 C- U0 @- G# u8 s  再一次运转应用考证能否程序能否经过了原来的程序逻辑。. E% r% D8 q2 U3 e, i- S
  静态剖析和代码复制& G; H1 t- D6 c) t" J2 M4 j" N
  在这个办法中,我们将会运用Android Studio/IntelliJ ByteCodeViewer来中止静态代码剖析。
: N& O, W: _: |* V" s9 e8 v( j  Static analysis" Q" d. c4 z+ M4 F' p- l7 l2 F
  Also called static code analysis, is a method of computer program debugging that is done by examining the code without executing the program. The process provides an understanding of the code structure, and can help to ensure that the code adheres to industry standards.
; B7 e" J0 i% `% N; j: U  启动 ByteCodeViewer(BCV) 并且等候它来装置依赖项。一旦装置好了之后,我们将能够直接在它里面翻开apk文件。在BCV中,点击File-Add 并且选择 CrackMe.apk,然后让它完成加载这个文件。点击View-Pane1-Procyon-java: ]& |2 {5 K9 O6 D" U" i- l9 z0 x+ x
  和View-Pane2-Smali/Dex-Samli/Dex
8 z; R$ ^8 p) H. d' k  。你的界面将会看起来和下面的一样+ [& |( [( I: [( T7 z

/ f* \* \: i* ]
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p3.qhimg.com/t0169b5db07390c7fe9.png" border="0" alt="" />
( E3 N( j5 u  P4 n. R                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

: r" U9 ?! W5 y0 m" R5 \
6 A8 O3 I( ~' n8 F' l; n6 L  在第9行,我们能够看到final String string2 = this.this$0.getString(2131099669);。在当前活动上下文的getString()办法,能够运用this,MainActivity.this 或者getApplicationContext()  经过一个整数值来得到资源值。这些数字id的索引在R类中被创建,所以我们将会在R$string.class 中寻觅资源id,BCV能够将内容辨认为xml 文件格式。
+ @2 I$ ]6 M: Y+ t0 N
0 D5 [2 I7 L; y! |0 E. p
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p0.qhimg.com/t0127a50619a607dcac.png" border="0" alt="" />8 L# u6 J& }9 _1 @5 B6 d
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

, d& ~- P% g) j9 C0 G# |6 K4 \7 ?% n; z/ s+ t
  我们能够看到这个整数值被分配给a,往常我们不得不对a在strings.xml中做一个查找,你能够在BCV中经过展开CrackMe.apk-Decoded Resources-res-values-strings.xml
( b* @; @( a5 Y& B* V- x0 y- T  R" Y) ^+ w7 c: g$ u
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p2.qhimg.com/t01e55406c413cd4794.png" border="0" alt="" />: X/ P. f) H8 }: H9 K9 N
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图
; Y2 L6 B1 j1 p0 ^. c% E6 ]

' h, I3 c$ B& m( c, N; _  有时分BCV翻开文件会呈现出二进制方式而不是xml格式,关于这种状况,我们能够点击File-Save As Zip ,然后解压zip并且在编辑器中翻开strings.xml。: C" x# @# V( B  t! s* ^
" e0 b" j, t* X6 a& c( g
php?mod=logging&action=login'+'&referer='+encodeURIComponent(location))" src="http://p0.qhimg.com/t015973be323327c6bd.png" border="0" alt="" />, N5 Y  D/ c/ n+ H% h
                               
php?mod=logging&action=login" onclick="showWindow('login', this.href+'&referer='+encodeURIComponent(location));">登录/注册后可看大图

2 ~) E. L) v% e) a5 N5 t. r) ]" c  Y* W9 f# {2 j
  极好的,我们曾经找到了这个字符串。我们将会用这个办法恢复一切的字符串并且保管它们。( s( ?: t" r# R* c
  2131099669 - a - vXrMphqS3bWfIGT811/V2Q==
7 h8 q1 L! ~2 Z: ^: ^  2131099683 - b - TXlTdXBlclNlY3JldEwzM3RQYXNzdzByZA==$ I* g6 ]% Z5 i
  2131099685 - d - Congratulations0 y( g5 `: s! A' t$ L' q
  2131099686 - e - Umm, Try again
  S- L# ?: b1 w5 h! A. M  我们将会运用IntelliJ来写我们的代码来试图完成逆向原始函数的功用,经过从BCV反编译后的文件中复制代码。
& K; l+ _  |/ f0 Y8 G) B  当一切的代码让在一块的时分,它将会看起来像下面的代码
. r9 L$ D% l: m' G) Z. [, O/ q  import javax.crypto.Cipher;0 L4 Z# b+ K1 O" Y8 m5 W
  import javax.crypto.SecretKey;
" O4 g" G! P# a3 x& D  g3 w  import javax.crypto.spec.SecretKeySpec;+ h7 J0 f& _. F* i# E5 E
  import java.security.MessageDigest;: @2 F0 B5 G/ k
  import java.util.Base64;; w' H1 W$ V7 z% s
  class Decrypt {
1 D6 ~6 w6 N; P1 N5 i  public static void main(String args[]) {
$ P7 z% a5 V. u9 |9 L$ t  String a = vXrMphqS3bWfIGT811/V2Q==;
" l, a+ H* C3 X1 S  String b = TXlTdXBlclNlY3JldEwzM3RQYXNzdzByZA==;; y% Z; E  \) z9 c; A) ^
  String new_b = new String(Base64.getDecoder().decode(b));
9 q9 t8 U/ t- j' V4 [  byte[], M+ C, s+ g2 u8 g& |
  array = Base64.getDecoder().decode(a);
  U0 S  N/ a& c  H; y( F  String decoded = decrypt(array, getKey(new_b));
4 j5 O* b2 Y' h, f  System.out.println(Decoded :  + decoded);* a& {) i  f1 v- c$ k; g
  }3 o9 P# M  o' Y$ D- @2 P4 C
  private static String decrypt(byte[]
3 `1 C' V( \7 p1 U" X+ M  array, SecretKey secretKey) {
' ~. m3 [4 b$ x6 R  String decoded = null;  s; o+ Y' z) H, A3 @/ v
  try {
3 D9 Y8 `! }" b  Cipher instance = Cipher.getInstance(AES/ECB/PKCS5Padding);
; H# [3 |5 [. w6 }- m( {; n  instance.init(2, secretKey);9 p8 u! n. \; H6 E# W
  decoded = new String(instance.doFinal(array), UTF-8);9 U/ G  m9 X' X6 M+ c- L7 q% {
  }catch (Exception e) {: Z5 ^9 d4 V4 S+ X# \& W
  // do something' _. g  [9 ?8 x* y
  }3 b+ \1 y5 g9 @6 D4 J2 L- x8 a
  return decoded;. l4 n5 Q; B9 o, d& W
  }: X7 O3 g- A; b% u2 m3 _2 F
  private static SecretKey getKey(String s) {
( a2 b. d3 _' @2 ]( b: a  n' B  SecretKeySpec secretKeySpec = null;- N1 u" P6 H( t! t! e
  try {
4 s& v) u$ c% }! R. E/ Q0 i% F  MessageDigest instance = MessageDigest.getInstance(MD5);
. u' N2 z- n; Q! g  instance.update(s.getBytes());
* v3 `4 O; c4 s4 I' v4 c6 ^/ i  byte[]
0 u3 d6 e8 @. B/ {2 L  digest = instance.digest();
* F- I7 F5 v  [6 i/ l' T  StringBuilder sb = new StringBuilder();
1 a+ x' S/ H! ~4 R  for (int length = digest.length, i = 0; i  length; ++i) {" }6 i; Q, E, l/ I7 X
  sb.append(String.format(%02x, digest" l9 A- W( x8 k/ \5 `
  0xFF));1 y% r, K' c2 \, u" ^5 R" J& C
  }3 ~: \3 _- d7 \2 q* x" c7 [
  secretKeySpec = new SecretKeySpec(sb.toString().substring(0, 16).getBytes(), AES);
$ \& `8 \' ~9 B9 ]( R' |0 E  } catch (Exception e) {
9 R+ e  i9 O: H- n* Q7 [; C  // do something
$ e5 r% f7 W; X: j4 ^: m4 h, K8 x( x( Y  }9 p/ e6 V2 L; J0 d: J+ n! }8 w
  return secretKeySpec;9 K* f. e4 r# q5 a
  }. n6 ]7 G' `; r$ d9 u
  }7 \! @: H) n+ i6 l
  将文件命名为Decrypt.java
9 w8 g& v  ^. T9 W: L# S5 O  并保管文件。我们需求编译这个文件,然后运转它来检测我们的代码能否起作用了。1 l/ ?4 i4 Z# ?8 [& T3 x2 k* w
  // create new file
% z5 X. w( I) t( j  $ nano Decrypt.java3 A' s6 M" t" t/ M1 f! P5 B7 f
  // compile file* Z& o/ F# U9 s4 ^
  $ javac Decrypt.java
3 u$ ?( S$ s# t1 T. s  // run file
" R( _9 y8 ~7 J4 g; }0 q! @  $ java Decrypt1 U2 R* Q7 M# t7 n% F: C) H! u. e
  Decoded : knb*AS234bnm*0
" D4 X: s# i2 E+ B  我们能够在python代码中做同样的事情,就像先前frida那样,但是有时分复制代码是更简单的,由于只需求做很小的修正就能够使它运转。
- }) t1 R( m7 d% Q. a# a  我们曾经描画了所提到的一切工具和办法,往常是时分喝杯咖啡了。4 Z0 r) F6 }, ?: M/ u

' I) Z* l  @% D4 P
文章来源:华域联盟

 

                                                  




上一篇:华域联盟 中国黑客联盟 免费黑客入门 学习 2017年9月19日签到记录帖
下一篇:最新点播钻代码2017刷钻代码大全

帖子的最近访客

回复 百度谷歌雅虎搜狗搜搜有道360奇虎 天涯海角搜一下: 百度 谷歌 360 搜狗 搜搜 有道 谷粉 雅虎 必应 即刻

使用道具 举报

GMT+8, 2017-11-22 19:14 , Processed in 0.328125 second(s), 41 queries .

© 2020 华域联盟 | 蒙公网安备 15062202000105号 版权删帖举报人口

备案号: 蒙ICP备17000689号-2                                                                                                                                                                    

快速回复 返回列表