[原创]某黑盒某请求参数分析
2022-10-28 10:59:32 Author: bbs.pediy.com(查看原文) 阅读量:17 收藏

前言

业余时间写的,纯兴趣更新! 做个记录。

测试环境

雷电模拟器 Android 7.1.2/pixexl2 Android 8.1

抓包测试

首先在apk中进行登录账户的时候进行抓包,获取登录凭证。

请求报文如下

1

2

3

4

5

6

7

GET /store/has_unfinished_game/?heybox_id=???&imei=???&os_type=Android&os_version=7.1.2&version=1.1.50&_time=1666165262&hkey=5656c95245635812219460113c5a14eb HTTP/1.1

Referer: http://api.maxjia.com/

User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0

Cookie: pkey=???

Host: api.xiaoheihe.cn

Connection: Keep-Alive

Accept-Encoding: gzip

在get请求中发现有传递以下参数

1

2

3

4

5

6

7

heybox_id      固定且唯一(对于每一个用户来说)

imei           移动通信国际识别码(每个移动设备具有唯一性)

os_type        系统类型

os_version     系统版本

version        apk的版本

_time          时间

hkey           时间key

以及cookie中的pkey

1

Cookie: pkey=???   固定且唯一(对于每一个用户登录后)

以上参数我们需要知道hkey是怎么来的

因为是请求报文,所以算法肯定在apk内部实现的。要么在应用层要么在so层

定位算法(v1.1.50)

将apk拖入到GDA中进字符串搜索"hkey"

发现在intercept方法中有存在这个字符串。双击进入

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

package com.max.xiaoheihe.network.b$1;

import okhttp3.w;

import com.max.xiaoheihe.network.b;

import java.lang.Object;

import okhttp3.w$a;

import okhttp3.ad;

import okhttp3.ab;

import com.max.xiaoheihe.app.HeyBoxApplication;

import com.max.xiaoheihe.bean.account.User;

import okhttp3.v;

import okhttp3.v$a;

import java.lang.StringBuilder;

import java.lang.System;

import java.lang.String;

import com.max.xiaoheihe.b.d;

import com.max.xiaoheihe.b.af;

import com.max.xiaoheihe.b.c;

import com.max.xiaoheihe.bean.account.AccountDetailObj;

import android.os.Build$VERSION;

import okhttp3.ab$a;

class b$1 implements w    // class@000e5c

{

    final b a;

    void b$1(b p0){

       this.a = p0;

       super();

    }

    public ad intercept(w$a p0){

       ab uoab = p0.request();

       User user = HeyBoxApplication.b();

       v$a uoa = uoab.a().v();

       StringBuilder str = (System.currentTimeMillis() / 1000)+""; //获取当前时间/1000

       String str1 = d.h(((d.h("xiaoheihe/_time="+str)).replaceAll("a", "app")).replaceAll("0", "app"));  //这里是关键代码

       if (c.b(af.b(uoab.a().toString(), "heybox_id"))) {

          String str2 = "heybox_id";

          String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1";

          uoa.b(str2, userid);

       }

       uoa.b("imei", d.g()).b("os_type", "Android").b("os_version", (Build$VERSION.RELEASE).trim()).b("version", d.h()).b("_time", str).b("hkey", str1);

       if (!c.b("")) {

          uoa.b("game_type", "");

       }

       v ov = uoa.c();

       str = "";

       String str3 = (c.b(user.getPkey()))? "": "pkey="+user.getPkey();

       str = str+str3;

       str3 = (c.b(uoab.a("Cookie")))? "": ";"+uoab.a("Cookie");

       return p0.proceed(uoab.f().b("Referer", "http://api.maxjia.com/").b("User-Agent", "Mozilla/5.0 AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0").a("Cookie", str+str3).a(ov).d());

    }

}

str是当前时间/1000

分析下str1是怎么赋值的

1

String str1 = d.h(((d.h("xiaoheihe/_time="+str)).replaceAll("a", "app")).replaceAll("0", "app"));

首先调用类d的h方法,参数为特定字符串"xiaoheihe/_time="和当前时间str进行拼接的结果

然后再次调用类d的h方法,参数为 将第一次调用的的返回值中的“a”和"0"给替换为"app"的结果

最后就是对str1进行赋值了,将第二次调用d的h方法的返回值.

后面其实就是一个拼接请求头的操作,不需要关注。

所以这里我们看下类d的h方法的怎么实现的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public static String h(String p0){

       if (TextUtils.isEmpty(p0)) {

          return "";

       }

       try{

          byte[] uobyteArray = MessageDigest.getInstance("MD5").digest(p0.getBytes());

          String str = "";

          int len = uobyteArray.length;

          for (int i = 0; i < len; i = i + 1) {

             int i1 = uobyteArray[i] & 0x00ff;

             String str1 = Integer.toHexString(i1);

             if (str1.length() == 1) {

                str1 = "0"+str1;

             }

             str = str+str1;

          }

          return str;

       }catch(java.security.NoSuchAlgorithmException e6){

          e6.printStackTrace();

          return "";

       }

    }

发现就是个md5.

我们可以得知请求报文中的_time和hkey字段是关联的。

1

2

3

4

5

6

7

GET /store/has_unfinished_game/?heybox_id=46447531&imei=863342023667829&os_type=Android&os_version=7.1.2&version=1.1.50&_time=1666165262&hkey=5656c95245635812219460113c5a14eb HTTP/1.1

Referer: http://api.maxjia.com/

User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0

Cookie: pkey=MTY2NjE2NTIyNC45M180NjQ0NzUzMWhyd2JubG9ud2NndHFubmI__

Host: api.xiaoheihe.cn

Connection: Keep-Alive

Accept-Encoding: gzip

模拟算法(v1.1.50)

用脚本去验证下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

import hashlib

import datetime

def md5(test_str: str, key=None):

    print('MD5加密前为 :' + test_str)

    if key is None:

        m = hashlib.md5()

    else:

        m = hashlib.md5(key.encode('utf-8'))

    m.update(test_str.encode())

    res = m.hexdigest()

    print('MD5加密后为 :' + res)

    return res

def gethkey():

    _time = "1666165262"

    str=md5(_time)

    str1=md5(str.replace("a","app").replace("0","app"))

if __name__ == '__main__':

    gethkey()

定位算法(v.1.3.92)

下载了小黑盒 v.1.3.92版本。

同样在登录的时候进行抓包。

1

2

3

4

5

6

7

GET /bbs/app/api/user/permission?heybox_id=???&imei=???&os_type=Android&os_version=8.1.0&version=1.3.92&_time=1666259837&hkey=0cf9be6f88 HTTP/1.1

Referer: http://api.maxjia.com/

User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0

Cookie: pkey=???

Host: api.xiaoheihe.cn

Connection: Keep-Alive

Accept-Encoding: gzip

这里imei变化了是因为换了台手机。

我们先不逆向

用上面的gethkey.py跑一下看看hkey和请求报文中的是否一致。如果不一致就说明,加密算法变化了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

import hashlib

import datetime

def md5(test_str: str, key=None):

    print('MD5加密前为 :' + test_str)

    if key is None:

        m = hashlib.md5()

    else:

        m = hashlib.md5(key.encode('utf-8'))

    m.update(test_str.encode())

    res = m.hexdigest()

    print('MD5加密后为 :' + res)

    return res

def gethkey():

    _time = "xiaoheihe/_time="+"1666165262"   

    _time = "xiaoheihe/_time="+"1666259837"   

    str=md5(_time)

    str1=md5(str.replace("a","app").replace("0","app"))

if __name__ == '__main__':

    gethkey()

发现不一致,所以这个版本的getkey的算法发生了变化。

额,脑子坏掉了,请求报文中的hkey的长度只有10位的长度,肯定加密算法变了。

同样的操作定位到hkey的位置

发现新版本有三处地址含有该字符串。

先看第一个

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

private void i(){

   L l = new L$a().c(0, TimeUnit.MILLISECONDS).b(0, TimeUnit.MILLISECONDS).a();

   String str = this.h();

   T.a("zzzzconntest", "url=="+str);

   HashMap hashMap = new HashMap(16);

   User user = HeyBoxApplication.k();

   String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1";

   hashMap.put("userid", userid);

   hashMap.put("appid", "heybox");

   hashMap.put("pkey", user.getPkey());

   hashMap.put("imei", Q.d());

   hashMap.put("os_type", "Android");

   hashMap.put("os_version", (Build$VERSION.RELEASE).trim());

   hashMap.put("version", Q.g());

   hashMap.put("hkey", Q.b(((Q.b("xiaoheihe/_time="+(System.currentTimeMillis() / 1000)+"")).replaceAll("a", "app")).replaceAll("0", "app")));//这里其实就是v1.1.50版本gethkey是一样的

   l.a(new N$a().b(Ab.a(str, hashMap)).a(), new l$c(this));

   l.h().b().shutdown();

   return;

}

类Q下的b方法,就是md5加密

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public static String b(String p0){

       if (TextUtils.isEmpty(p0)) {

          return "";

       }

       try{

          byte[] uobyteArray = MessageDigest.getInstance("MD5").digest(p0.getBytes());

          int len = uobyteArray.length;

          String str = "";

          for (int i = 0; i < len; i = i + 1) {

             int i1 = uobyteArray[i] & 0x00ff;

             String str1 = Integer.toHexString(i1);

             if (str1.length() == 1) {

                str1 = "0"+str1;

             }

             str = str+str1;

          }

          return str;

       }catch(java.security.NoSuchAlgorithmException e7){

          e7.printStackTrace();

          return "";

       }

    }

第二个是就是这个版本使用的接口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

package com.max.xiaoheihe.network.c;

import okhttp3.H;

import com.max.xiaoheihe.network.d;

import java.lang.Object;

import okhttp3.H$a;

import okhttp3.T;

import okhttp3.N;

import com.max.xiaoheihe.app.HeyBoxApplication;

import com.max.xiaoheihe.bean.account.User;

import okhttp3.G;

import okhttp3.G$a;

import java.lang.StringBuilder;

import java.lang.System;

import java.lang.String;

import com.max.xiaoheihe.utils.NDKTools;

import com.max.xiaoheihe.utils.Q;

import com.max.xiaoheihe.utils.Ab;

import com.max.xiaoheihe.utils.M;

import com.max.xiaoheihe.bean.account.AccountDetailObj;

import android.os.Build$VERSION;

import okhttp3.N$a;

class c implements H    // class@00135d

{

    final d a;

    void c(d p0){

       this.a = p0;

       super();

    }

    public T intercept(H$a p0){

       N n = p0.request();

       User user = HeyBoxApplication.k();

       G$a uoa = n.h().j();

       String str = "";

       StringBuilder str1 = (System.currentTimeMillis() / 1000)+str;

       String str2 = n.h().c();

       int i = 0;

       if (str2.endsWith("/")) {

          str2 = str2.substring(i, (str2.length() - 1));

       }

       String str3 = "app";

       str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll("a", str3)).replaceAll("0", str3))).substring(i, 10);

       String str4 = "heybox_id";

       if (M.d(Ab.b(n.h().toString(), str4))) {

          String userid = (user.isLoginFlag())? user.getAccount_detail().getUserid(): "-1";

          uoa.a(str4, userid);

       }

       uoa.a("imei", Q.d()).a("os_type", "Android").a("os_version", (Build$VERSION.RELEASE).trim()).a("version", Q.g()).a("_time", str1).a("hkey", str2);

       if (Q.h()) {

          uoa.a(str3, "concept");

       }

       G g = uoa.a();

       str1 = "";

       String str5 = (M.d(user.getPkey()))? str: "pkey="+user.getPkey();

       str1 = str1+str5;

       if (!M.d(n.a("Cookie"))) {

          str = ";"+n.a("Cookie");

       }

       return p0.proceed(n.f().a("Referer", "http://api.maxjia.com/").a("User-Agent", "Mozilla/5.0 AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0").b("Cookie", str1+str).a(g).a());

    }

}

关键代码

1

uoa.a("imei", Q.d()).a("os_type", "Android").a("os_version", (Build$VERSION.RELEASE).trim()).a("version", Q.g()).a("_time", str1).a("hkey", str2);

看下str2是怎么获得的

1

2

3

4

5

6

7

8

9

String str = "";

StringBuilder str1 = (System.currentTimeMillis() / 1000)+str;

String str2 = n.h().c();

int i = 0;

if (str2.endsWith("/")) {

    str2 = str2.substring(i, (str2.length() - 1));

    }

String str3 = "app";

str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll("a", str3)).replaceAll("0", str3))).substring(i, 10);

发现调用了类NDKTools下的encode方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package com.max.xiaoheihe.utils.NDKTools;

import java.lang.System;

import java.lang.String;

import java.lang.Object;

public class NDKTools    // class@00138f

{

    static {

       System.loadLibrary("native-lib");

    }

    public void NDKTools(){

       super();

    }

    public static native int checkSignature(Object p0);

    public static native synchronized String encode(Object p0,String p1,String p2);

    public static native String getrsakey(Object p0,String p1);

}

encode方法在native-lib的so中进行实现的。

hook&疑问

本地环境

手机端运行frida的服务端

1

./frida-server-15.2.2-android-arm64 &

电脑端查看进程

run.py

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

import sys

import frida

process_name = 'com.max.xiaoheihe'

def on_message(message, data):

    if message['type'] == 'send':

        print(f"[*] {message['payload']}")

    else:

        print(message)

if __name__ == '__main__':

    try:

        device = frida.get_usb_device(timeout=1000)

        print("* get usb device成功")

    except:

        device = frida.get_remote_device(timeout=1000)

        print("* get remote device成功")

    if not device:

        print("* 连接到Frida Server失败")

    else:

        process = device.attach(process_name)

        js = open('hook.js', encoding='utf-8').read()

        print(js)

        script = process.create_script(js)

        script.on('message', on_message)

        script.load()

        input()

        script.unload()

hook.js

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

console.log("脚本载入成功");

Java.perform(function () {

    var encodeAddr = Module.findExportByName("libnative-lib.so", "encode");

    console.log(encodeAddr);

    if (encodeAddr != null) {

        Interceptor.attach(encodeAddr, {

            onEnter: function (args) {

                //args参数数组

                console.log('encode-Enter')

                console.log(args[0], Memory.readCString(args[0]));

                console.log(args[1], Memory.readCString(args[1]));

                console.log(args[2], Memory.readCString(args[2]));

                console.log(args[3], Memory.readCString(args[3]));

                console.log(args[4], Memory.readCString(args[4]));

            },

            onLeave: function (retval) {

                //retval函数返回值

                console.log('encode-Leave');

                console.log(retval.toString());

                console.log('======');

            }

        });

    }

})

发现可以成功运行起来

0xc9e90b01是encode函数的地址,当打开app后,这个地址是不变的。

这里我运行这个脚本只输出这些内容,但并没有打印出来参数和返回地址。

先静态分析吧。解包获得libnative-lib.so并拖入ida中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

int __fastcall encode(_JNIEnv *env, int a2, int a3, int can_str2, int can_str1)

{

  const char *v5; // r10

  _JNIEnv *v6; // r4

  int v7; // r6

  int v8; // r8

  const char *str2; // r11

  int str1; // r0

  bool v11; // zf

  size_t v12; // r5

  char *v13; // r5

  char *v14; // r0

  size_t v15; // r0

  char *v16; // r0

  int v17; // r5

  int result; // r0

  int v19; // [sp+0h] [bp-30h]

  int *v20; // [sp+4h] [bp-2Ch]

  int v21; // [sp+8h] [bp-28h]

  int v22; // [sp+Ch] [bp-24h]

  v19 = can_str2;

  v6 = env;

  v7 = can_str2;

  if ( j_check_signature(env) == 1 )

  {

    v8 = 0;

    str2 = (const char *)((int (__fastcall *)(_JNIEnv *, int, _DWORD))v6->functions->GetStringUTFChars)(v6, v7, 0);

    str1 = ((int (__fastcall *)(_JNIEnv *, int, _DWORD))v6->functions->GetStringUTFChars)(v6, can_str1, 0);// 时间戳

    v11 = str2 == 0;

    if ( str2 )

    {

      v5 = (const char *)str1;

      v11 = str1 == 0;

    }

    if ( !v11 )                                 // str2不为空进入if

    {

      v21 = can_str1;

      v12 = strlen(str2);

      v20 = &v19;

      v13 = (char *)&v19 - ((strlen(v5) + v12 + 21) & 0xFFFFFFF8);

      v14 = strcpy(v13, str2);

      v15 = strlen(v14);

      _aeabi_memcpy(&v13[v15], "/bfhdkud_time=", 15);// v13为str2后面拼接上"/bfhdkud_time="

      v16 = strcat(v13, v5);                    // v16为v13后面拼接str1 即时间戳

      v17 = j_MDString(v16);                    // 对v16进行md5加密赋值给v17

      ((void (__fastcall *)(_JNIEnv *, int, const char *))v6->functions->ReleaseStringUTFChars)(v6, v7, str2);

      ((void (__fastcall *)(_JNIEnv *, int, const char *))v6->functions->ReleaseStringUTFChars)(v6, v21, v5);

      v8 = j_charToJstring(v6, v17);            // md5加密的结果赋值给v8

    }

    if ( _stack_chk_guard == v22 )

      return v8;                                // 返回

  }

  result = _stack_chk_guard - v22;

  if ( _stack_chk_guard == v22 )

    result = j_j_charToJstring(v6, UNSIGNATURE[0]);

  return result;

}

分析得知encode函数是将(str2+"/bfhdkud_time="+str1)进行md5加密,将加密的结果吗作为返回值

回到接口类中

1

2

String str3 = "app";

str2 = (Q.b(((NDKTools.encode(HeyBoxApplication.g(), str2, str1)).replaceAll("a", str3)).replaceAll("0", str3))).substring(i, 10);

发现只取md5返回值然后将其中的"a"和"0"都替换成str3即"app"

然后又调用了Q类下的b(String)方法,发现同样是md5算法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public static String b(String p0){

   if (TextUtils.isEmpty(p0)) {

      return "";

   }

   try{

      byte[] uobyteArray = MessageDigest.getInstance("MD5").digest(p0.getBytes());

      int len = uobyteArray.length;

      String str = "";

      for (int i = 0; i < len; i = i + 1) {

         int i1 = uobyteArray[i] & 0x00ff;

         String str1 = Integer.toHexString(i1);

         if (str1.length() == 1) {

            str1 = "0"+str1;

         }

         str = str+str1;

      }

      return str;

   }catch(java.security.NoSuchAlgorithmException e7){

      e7.printStackTrace();

      return "";

   }

}

然后再将其返回值取前10位赋值给str2,即后面的hkey

模拟算法(v.1.3.92)

写成python实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

import hashlib

import datetime

def md5(test_str: str, key=None):

    print('MD5加密前为 :' + test_str)

    if key is None:

        m = hashlib.md5()

    else:

        m = hashlib.md5(key.encode('utf-8'))

    m.update(test_str.encode())

    res = m.hexdigest()

    print('MD5加密后为 :' + res)

    return res

def gethkey():

    str1="1666285158"

    str1="1666259837"

    str2="/bbs/app/api/user/permission/"

    if str2[-1]=="/":

        str2=str2[:-1]

    zuhe_str = str2+"/bfhdkud_time="+str1

    ret=md5(zuhe_str).replace("a","app").replace("0","app")

    hkey=md5(ret)[0:10]

    print(hkey)

if __name__ == '__main__':

    gethkey()

对比下请求报文,发现key值是一样的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

GET /bbs/app/api/user/permission?heybox_id=???&imei=???&os_type=Android&os_version=8.1.0&version=1.3.92&_time=1666259837&hkey=0cf9be6f88 HTTP/1.1

Referer: http://api.maxjia.com/

User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0

Cookie: ???

Host: api.xiaoheihe.cn

Connection: Keep-Alive

Accept-Encoding: gzip

GET /account/get_ads_info/?heybox_id=???&imei=???&os_type=Android&os_version=8.1.0&version=1.3.92&_time=1666285158&hkey=d630c904c2 HTTP/1.1

Referer: http://api.maxjia.com/

User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 ApiMaxJia/1.0

Cookie: ????

Host: api.xiaoheihe.cn

Connection: Keep-Alive

Accept-Encoding: gzip

参考

小黑盒逆向分析笔记(二) - Chr_小屋 (chrxw.com)

看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)


文章来源: https://bbs.pediy.com/thread-274903.htm
如有侵权请联系:admin#unsafe.sh