android dy 完结篇(0x3)

标签: 逆向分析  爬虫

正文

先分析个if块,对应的c++伪代码如下

  if ( paramsArray )                            // 如果String[]不为null
  {
    pArrayLength = _JNIEnv::GetArrayLength(env, paramsArray);// 获取String[]长度
    if ( !(pArrayLength & 1) )                  // 这里是判断String[]长度是不是2的倍数,目的应该是为了判断参数是否完整
    {
      for ( j = 0; j < pArrayLength; j += 2 )   // 以2为间隔遍历String[],其实就是为了获取key,因为key肯定在前面的,例如:0 2 4 6 8 10 都是key的索引
      {
        key = _JNIEnv::GetObjectArrayElement((int)env, (int)paramsArray, j);// GetObjectArrayElement这个方法是获取array元素,第一个参数是jnienv,参数二是array,参数三是索引
        key_char = (const char *)_JNIEnv::GetStringUTFChars((int)env, key, 0);// 将array元素转为字符串
        value = _JNIEnv::GetObjectArrayElement((int)env, (int)paramsArray, j + 1);// 这个就是获取value了
        v38_value = getUTF8(env, (int)jcls, value);// 转为字符串
        nullsub_1();
        sub_3FCC8(&v46, key_char, (int)&v45);   // 首先处理key,v45为key_char长度
        v18 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::operator[](
                (int)&v47,
                (int)&v46);                     // 将处理后的key添加到map
        sub_3F044(v18, v38_value, v19, v18);    // 接着处理value
        sub_3EAE8(&v46);
        nullsub_2();
        _JNIEnv::ReleaseStringUTFChars(env, key, key_char);
      }
    }
  }

注释因为是很久以前写的了,仅供参考。
map这玩意儿类似 Java 中的 mappython 中的 dict,不过底层是啥玩意儿我就不太清楚,没怎么学过c++。
这里的逻辑应该看得明白:

1、首先是判断paramsArray,也就是请求参数被分割成的字符串数组。这个字符串数据的**索引%2(0,2,...)**是key。所以这个array差不多是这个形式:[key,value,key,value,...]
2、然后就是 if 判断参数数组是否完整
3、接着就是遍历字符串数组,按键值对的形式保存到map对象里

挑几条代码分析一波

这里是调用了jni的GetObjectArrayElement方法获取数组元素,第一个参数是java接口指针,第二参数是jobjectArray数组,第三个参数是索引。分别获取key和value

key = _JNIEnv::GetObjectArrayElement((int)env, (int)paramsArray, j);
value = _JNIEnv::GetObjectArrayElement((int)env, (int)paramsArray, j + 1);

然后呢,v47是map类型的。首先是operator[]方法,这个方法返回到映射到等于 key 的关键的值的引用,若这种关键不存在则进行插入 ?,意思就是用key取value,就好比python里的dict取值一样:params['key']。至于3F044这个地址的方法是干嘛的我就懒得去了解了,只要大概知道做了些什么就行。

v18 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::operator[](
                (int)&v47,
                (int)&v46);
sub_3F044(v18, v38_value, v19, v18);

后面还有几行也一并分析了

v41 = &v47;
v45 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::begin(&v47);// 获取map首个iterate
v46 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::end(v41);// 获取map最后一个iterate
while ( std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator!=(&v45, &v46) )// 如果首尾不相等就一直循环
{
v42 = std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator*(&v45);
sub_3F36C((int)&v32, v42 + 4);              // 字符串拼接
std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator++(&v45);
}
v20 = sub_3EC48(&v32);
v21 = sub_3EC80(&v32);
sub_153C0(v20, v21, v6);
v22 = (char *)sub_3E4D4((int)&v32);

也是挑选几行分析

1、这两行是获取map的开头和结尾两个迭代器

v45 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::begin(&v47);
v46 = std::map<std::string,std::string,std::less<std::string>,std::allocator<std::pair<std::string const,std::string>>>::end(v41);

2、然后呢。先是一层while循环,条件是v45和v46不相等就一直循环

while ( std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator!=(&v45, &v46) )// 如果不相等就一直循环
{
v42 = std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator*(&v45);
sub_3F36C((int)&v32, v42 + 4);              // 字符串拼接
std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator++(&v45);
}

循环内部首先是从map开头迭代器中获取key的name,然后用获取到的name的地址偏移4个字节获取到下一个key的name。以上仅仅是我个人的分析,所以为了验证,直接调试。为了方便我直接把我以前写的调试教程贴上来了


IDA调试

点 File -> New instance(新建实例) 新打开一个程序,大概长这样

然后点 GO,这个时候我们还需要做一件事,直接调试是不可能的,需要一个中介人一样的东西,和frida-server差不多的东西,一般都在ida安装文件夹里,我这里目前只有mac,所以只说mac,找到你的app
在这里插入图片描述
然后右键显示包内容,路径是 Contents/MacOS/dbgsrc/

这里面有两个 android_server 一个是32位一个是64位,这里用32位就行,把它复制到你手机根目录的文件夹里,但也别乱放,我放在data里,命令如下:

adb push android_server /data/ # 复制
adb shell # 进入交互
$ su # 拿个权限
# chmod +x /data/android_server # 给个执行权限
# /data/android_server # 启动
IDA Android 32-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017
Listening on 0.0.0.0:23946...
=========================================================
[1] Accepting connection from 127.0.0.1...
[1] Closing connection from 127.0.0.1...
=========================================================
[2] Accepting connection from 127.0.0.1...
[2] Closing connection from 127.0.0.1...

输出了大概这么些东西就差不多了,其中23946是端口,复制一下;然后新开一个terminal,转发一下端口:

adb forward tcp:23946 tcp:23946

接着回到ida,在菜单栏处点 Debugger -> attach 选择 Remote ARMLinux/Android Debugger 调试类型

在新弹出的窗口里点确定就行,如果你要改什么的话就点 options

不出意外的话你将会看到这个进程窗口,如果出意外的话我也没办法,大家都这么大了自己google

按 Ctrl-F mac也是这个快捷键,输入 com.ss 就会筛选处抖音进程,前提是你打开了抖音,出现这个窗口后你没打开,然后你打开了你就需要右键 Refresh 刷新再搜索,找到之后双击或者点 OK 开始debug,这里需要注意,如果你手机一直出现未响应弹窗记得要一直点等待,不然ida会崩,然后你就要重新来过

然后就进入了调试界面,这时候程序是卡着的,别乱点手机了

第一步先加载 libuserinfo.so 找到基址,按 Ctrl-S 打开选择段跳转窗口

还是 ctrl-f 搜索,start 是开始,也就是起始地址,这就是基址

接着回到之前分析的ida,复制那个方法地址 0x3E384,用基址加这个得到方法在内存中的地址 0xcef08384,按 G 跳转到这地方

在这方法开头处按 F2 打个断点,然后 F9 运行

这时候可以刷新一下让程序执行到这 … emm 接着我调试了一会儿后发现这玩意儿好像没什么用,权当讲了下怎么调试吧,接着分析


我在执行后将返回值复制到r3寄存器处和sub_3f36c函数调用结束后下一条指令处分别下了一个断点

v42 = std::_Rb_tree_iterator<std::pair<std::string const,std::string>>::operator*(&v45);

打好断点后,直接运行,运行到指令 ADD R2, SP, #0x90+var_78 处,此时的寄存器如下图

首先可以从上图看到,r3寄存器的地址正好是r0寄存器的地址偏移4个字节的地址,这两个地址的内容如下图


sub_3F36C((int)&v32, v42 + 4);

使用下面的frida hook代码可以得知该方法为字符串拼接

String.prototype.format = function () {
    var values = arguments;
    return this.replace(/\{(\d+)\}/g, function (match, index) {
        if (values.length > index) {
            return values[index];
        } else {
            return "";
        }
    });
}
// Memory.readUtf8String
var mru8s = function(addr) {return Memory.readUtf8String(addr)}
// Memory.readPointer
var mrp = function(addr) {return Memory.readPointer(addr)}
// Memory.allocUtf8String
var mau8s = function(addr) {return Memory.allocUtf8String(addr)}
// read process memory
var rpm = function(addr, size) {
    var buf = Memory.readByteArray(ptr('0x' + addr), size);
    console.log(hexdump(buf, {
    offset: 0,
        length: size,
        header: true,
        ansi: false
    }));
}

//==================================================================

var JNI_OnLoad;
var exports = Module.enumerateExportsSync("libuserinfo.so");
for (var i = 0; i < exports.length; i++) {
    var name = exports[i].name;
    var addr = exports[i].address;
    if (name == 'JNI_OnLoad') {
        JNI_OnLoad = addr;
    }
}

var BASE_ADDR = parseInt(JNI_OnLoad) - parseInt("0x14504");
var addr = '0x' + parseInt(BASE_ADDR + parseInt('0x3F36C')).toString(16);

var i = 0;

Interceptor.attach(new NativePointer(addr), {
    onEnter: function(args) {
        console.log('{0}======================='.format(addr));
        console.log('[{0}]({1})({2}) 参数1 > {3}\n'.format(args[0],
                                                    mrp(args[0]),
                                                    mrp(mrp(args[0])),
                                                    mru8s(mrp(args[0]))));
        console.log('[{0}]({1}) 参数2 > {2}\n'.format(args[1],
                                                      mrp(args[1]),
                                                      mru8s(mrp(args[1]))));
    },
    onLeave: function(retval) {
        console.log('retval > ', retval);
        console.log('\n');
    }
});

然后是空格和’+'字符替换操作

  v20 = sub_3EC48(&v32);
  v21 = sub_3EC80(&v32);
  sub_153C0(v20, v21, v6);

主要看 153C0 这个方法,v20是拼接后的字符串,v21正常情况下为空,v6这个是一个char,没用到。
下面是 153C0 的伪代码

int __fastcall sub_153C0(int a1, int a2, char a3)
{
  int v3; // r4
  _BYTE *v4; // r0

  while ( __gnu_cxx::operator!=<char *,std::string>((int)&a1, (int)&a2) )
  {

    v4 = (_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&a1);
    sub_13EE8((int)&a3, v4);                    // 将字符串中的空格和+替换为字符a
    __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&a1);
  }
  sub_153B4();
  return v3;
}

operator!=那里可以这样写:a1 != a2;然后循环体首先取a1的第一个字符给v4,接着调用sub_13EE8,就是替换’ ’ 和 ‘+’ 字符为字符 ‘a’

void __fastcall sub_13EE8(int a1, _BYTE *a2)
{
  if ( *a2 == 32 || *a2 == 43 )                 // 如果a2等于' '或'+',那么返回a
    *a2 = 97;
}

从伪代码中看不到返回值是什么,切换到汇编

.text:000153C0 10 B5       PUSH            {R4,LR}
.text:000153C2 84 B0       SUB             SP, SP, #0x10
.text:000153C4 03 90       STR             R0, [SP,#0x18+var_C]
.text:000153C6 02 91       STR             R1, [SP,#0x18+var_10]
.text:000153C8 8D F8 04 20 STRB.W          R2, [SP,#0x18+var_14]
.text:000153CC 0D E0       B               loc_153EA

push,把R4寄存器和LR寄存器放到堆栈里,其中R4为方法3EC48的返回值
然后把参数1、2、3也依次放到堆栈里

接着是while循环体。

.text:000153EA             loc_153EA
.text:000153EA 03 AA       ADD             R2, SP, #0x18+var_C
.text:000153EC 02 AB       ADD             R3, SP, #0x18+var_10
.text:000153EE 10 46       MOV             R0, R2
.text:000153F0 19 46       MOV             R1, R3
.text:000153F2 00 F0 7D F9 BL              _ZN9__gnu_cxxneIPcSsEEbRKNS_17__normal_iteratorIT_T0_EES7_ ; __gnu_cxx::operator!=<char *,std::string>(__gnu_cxx::__normal_iterator<char *,std::string> const&,__gnu_cxx::__normal_iterator<char *,std::string> const&)
.text:000153F6 03 46       MOV             R3, R0
.text:000153F8 00 2B       CMP             R3, #0
.text:000153FA E8 D1       BNE             loc_153CE

000153EA> 首先是从堆栈中取出参数1放到R2寄存器;然后取出参数2放到寄存器R3

000153EE> 然后把R2复制到R0,R3复制到R1

000153F2> 接着比较这两个寄存器中地址指向的内容是否相等,如果相等返回1,不想等返回0

000153F6> 结果复制到R3寄存器

000153F8> 然后比较如果等于0跳转到loc_153CE

看true执行块

0153CE             loc_153CE
.text:000153CE 03 AB       ADD             R3, SP, #0x18+var_C
.text:000153D0 18 46       MOV             R0, R3
.text:000153D2 00 F0 AF F9 BL              _ZNK9__gnu_cxx17__normal_iteratorIPcSsEdeEv ; __gnu_cxx::__normal_iterator<char *,std::string>::operator*(void)
.text:000153D6             ; 9:     sub_13EE8((int)&a3, v4);                    // 将字符串中的空格和+替换为字符a
.text:000153D6 03 46       MOV             R3, R0
.text:000153D8 01 AA       ADD             R2, SP, #0x18+var_14
.text:000153DA 10 46       MOV             R0, R2
.text:000153DC 19 46       MOV             R1, R3
.text:000153DE FE F7 83 FD BL              sub_13EE8
.text:000153E2             ; 10:     __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&a1);
.text:000153E2 03 AB       ADD             R3, SP, #0x18+var_C
.text:000153E4 18 46       MOV             R0, R3
.text:000153E6 00 F0 99 F9 BL              _ZN9__gnu_cxx17__normal_iteratorIPcSsEppEv ; __gnu_cxx::__normal_iterator<char *,std::string>::operator++

000153CE> 从堆栈取参数1到R3寄存器,然后复制到R0寄存器

000153D2> 然后是operator*方法,这个方法可以详细分析下它的汇编来搞懂是干嘛用的

这是对应的汇编代码

.text:00015734             var_4= -4
.text:00015734
.text:00015734 82 B0       SUB             SP, SP, #8
.text:00015736 01 90       STR             R0, [SP,#8+var_4]
.text:00015738 01 9B       LDR             R3, [SP,#8+var_4]
.text:0001573A 1B 68       LDR             R3, [R3]
.text:0001573C 18 46       MOV             R0, R3
.text:0001573E 02 B0       ADD             SP, SP, #8
.text:00015740 70 47       BX              LR
.text:00015740             ; End of function __gnu_cxx::__normal_iterator<char *,std::string>::operator*(void)

为了更好的示范,我获取了该方法在内存中的地址0xd5aab735;并在内存中创建了一个字符串 “hello”,地址为 0x9ef86a88
首先是在堆栈申请内存,然后将参数1放到刚刚申请的堆栈里,我画了个图

接着从 SP+4 处取出参数1的地址到R3,然后再从该地址取出存放的内容(hello)

然后将R3复制到R0,之后算是释放刚刚申请的堆栈内存,然后回到调用方法

000153D6> 返回值放到R3寄存器

000153D8> 然后从堆栈取出第三个参数,放到R0寄存器;顺便把operator*的返回值放到R1寄存器,接着调用 sub_13EE8 方法,其中R0、R1为该方法的参数1和参数2;
sub_13EE8 这个方法是替换’ ’ 和 ‘+’ 为字符 ‘a’,就不分析了,看看伪代码就能懂

void __fastcall sub_13EE8(int a1, _BYTE *a2)
{
  if ( *a2 == 32 || *a2 == 43 )                 // 如果a2等于' '或'+',那么返回a
    *a2 = 97;
}

000153E2> 这三条指令主要分析下_ZN9__gnu_cxx17__normal_iteratorIPcSsEppEv
假设R0是字符串“Hello”

.text:0001571C 82 B0       SUB             SP, SP, #8
.text:0001571E 01 90       STR             R0, [SP,#8+var_4]
.text:00015720 01 9B       LDR             R3, [SP,#8+var_4]
.text:00015722 1B 68       LDR             R3, [R3]
.text:00015724 5A 1C       ADDS            R2, R3, #1
.text:00015726 01 9B       LDR             R3, [SP,#8+var_4]
.text:00015728 1A 60       STR             R2, [R3]
.text:0001572A 01 9B       LDR             R3, [SP,#8+var_4]
.text:0001572C 18 46       MOV             R0, R3
.text:0001572E 02 B0       ADD             SP, SP, #8
.text:00015730 70 47       BX              LR

先是在堆栈分配内存
然后将R0放到堆栈
从堆栈取出字符串地址到R3
取出字符串到R3,此时R3的内容是十六进制格式的字符串Hell,也就是0x6c6c6548
然后将R3 + 1后放到R2寄存器,所以R2为0x6c6c6549
然后从堆栈重新取一次原字符串地址,将R2也就是+1后的字符串0x6c6c6549放到原字符串地址,然后复制到R0,接着就是释放堆栈并返回

.text:000153FC 01 AB       ADD             R3, SP, #0x18+var_14
.text:000153FE 18 46       MOV             R0, R3
.text:00015400 FF F7 D8 FF BL              sub_153B4
.text:00015404 23 46       MOV             R3, R4
.text:00015406 18 46       MOV             R0, R3
.text:00015408 04 B0       ADD             SP, SP, #0x10
.text:0001540A 10 BD       POP             {R4,PC}

000153FC> 取参数3到R3,然后复制到R0作为参数传入 sub_153B4,这个方法自己看看吧

00015404> 然后将R4复制到R3,又将R3复制到R0当作返回值,直接结束了
这个R4寄存器全程只见过三次,第一次是将它放到堆栈,第二次是复制到R3寄存器,第三次是pop。这个寄存器其实和这个方法的第一个参数也就是寄存器R0指向同一个地址,它是方法 sub_3ec48 的返回值,详细可以看到地址 0001421C

.text:00014218 2A F0 16 FD BL              sub_3EC48
.text:0001421C 04 46       MOV             R4, R0
.text:0001421E 06 AB       ADD             R3, SP, #0x90+var_78
.text:00014220 18 46       MOV             R0, R3
.text:00014222 2A F0 2D FD BL              sub_3EC80
.text:00014226 03 46       MOV             R3, R0
.text:00014228 20 46       MOV             R0, R4
.text:0001422A 19 46       MOV             R1, R3
.text:0001422C 2A 46       MOV             R2, R5
.text:0001422E 01 F0 C7 F8 BL              sub_153C0 ; 这里的r4是方法3ec48的返回值,与R0指向同一个地址

总的来说这个方法就是替换空字符和字符’+‘为字符’a’,其他的操作没看到有啥太大的影响,具体可以自行调试一行一行的分析
接着是最后一点了

v22 = (char *)sub_3E4D4((int)&v32);
v43 = getName(serverTime, v22, (char *)&unk_598B4);// v22是拼接后的所有value

这里的v32和上面说的R4、R0的地址是同一个,所以这里的v32就是替换过空字符和’+'的最后的params,然后sub_3E4D4这个方法相当于从v32指向的地址取出字符串,然后就是调用getName加密


先前说了这个参数在添加到map后是排序了的,按字母表顺序,比如如下处理后的参数

0xc930f36d=======================
[0xc82ff108](0xc9339acc)(0x0) 参数1 >

[0xc4770604](0xc0a2e96c) 参数2 > 1575269103371

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xc1cf2a2c)(0x35373531) 参数1 > 1575269103371

[0xc4770034](0xc4770044) 参数2 > wifi

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0d8e2e4)(0x35373531) 参数1 > 1575269103371wifi

[0xc47700c4](0xc47700d4) 参数2 > 1128

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0d8e2e4)(0x35373531) 参数1 > 1575269103371wifi1128

[0xc477010c](0xc47700ec) 参数2 > aweme

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0d8e2e4)(0x35373531) 参数1 > 1575269103371wifi1128aweme

[0xc477007c](0xc477005c) 参数2 > wandoujia

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xc0aff4cc)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia

[0xc476ff2c](0xc2fbe14c) 参数2 > 6

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xc0aff4cc)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6

[0xc477028c](0xc477026c) 参数2 > Xiaomi

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xc0aff4cc)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi

[0xc477001c](0xc476fffc) 参数2 > 69143529399

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399

[0xc47701cc](0xc477017c) 参数2 > android

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399android

[0xc477025c](0xc477023c) 参数2 > MI MAX 3

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 3

[0xc47705bc](0xc2fbe2dc) 参数2 > 440

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 3440

[0xc476ffd4](0xc476ffb4) 参数2 > 88263783316

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316

[0xc47702a4](0xc2fbe1bc) 参数2 > zh

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh

[0xc477055c](0xc2fbe24c) 参数2 > 166

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh166

[0xc476fee4](0xc2fbe12c) 参数2 > 0

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh1660

[0xc476ff14](0xc2fbe13c) 参数2 > 0

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dec18c)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600

[0xc4770544](0xc1c9bd6c) 参数2 > ffa82bd3b1106594

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b1106594

[0xc476fe54](0xc2fbe22c) 参数2 > 28

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b110659428

[0xc4770304](0xc2fbe23c) 参数2 > 9

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b1106594289

[0xc47705a4](0xc4770584) 参数2 > 1080*2029

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029

[0xc476ff8c](0xc476ff6c) 参数2 > no_retry

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retry

[0xc4770664](0xc4770674) 参数2 > efc84c17

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17

[0xc4770214](0xc2fbe1ac) 参数2 > a

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a

[0xc4770634](0xc4770614) 参数2 > 1575269102

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a1575269102

[0xc476fe9c](0xc2fbe11c) 参数2 > 0

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a15752691020

[0xc47705d4](0xc476fe7c) 参数2 > 1662

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a157526910201662

[0xc4770514](0xc1c9bd8c) 参数2 > 868695035559432

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a157526910201662868695035559432

[0xc4770124](0xc2fbe19c) 参数2 > 166

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a157526910201662868695035559432166

[0xc477016c](0xc477014c) 参数2 > 1.6.6

retval >  0xc82ff108


0xc930f36d=======================
[0xc82ff108](0xe0dba9ac)(0x35373531) 参数1 > 1575269103371wifi1128awemewandoujia6Xiaomi69143529399androidMI MAX 344088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a1575269102016628686950355594321661.6.6

[0xc476fddc](0xc2fbe15c) 参数2 > 0.0

retval >  0xc82ff108

可以得到如下排序

_rticket
ac
aid
app_name
channel
count
device_brand
device_id
device_platform
device_type
dpi
iid
language
manifest_version_code
max_cursor
min_cursor
openudid
os_api
os_version
openudid
retry_type
rstr
ssmix
ts
type
update_version_code
uuid
version_code
version_name
volume

然后上全部代码

import requests
import hashlib
import time


def shuffle(result, array):
    ret = ['0', '0', '0', '0', '0', '0', '0', '0']
    ret[2] = result[ord(array[2]) - 49]
    ret[4] = result[ord(array[4]) - 49]
    ret[6] = result[ord(array[6]) - 49]
    ret[5] = result[ord(array[5]) - 49]
    ret[3] = result[ord(array[3]) - 49]
    ret[1] = result[ord(array[1]) - 49]
    ret[7] = result[ord(array[7]) - 49]
    ret[0] = result[ord(array[0]) - 49]
    return ''.join(ret)


def get_name(server_time, params):
    _as = ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']
    _cp = ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']
    shuffle1 = hex(server_time)[2:]
    shuffle2 = hex(server_time)[2:]
    shuffle1 = shuffle(shuffle1, '57218436')
    shuffle2 = shuffle(shuffle2, '15387264')
    params_md5 = hashlib.md5(params.encode('utf-8')).hexdigest()
    if server_time & 1:
        params_md5 = hashlib.md5(params_md5.encode('utf-8')).hexdigest()
    _as[0] = chr(97)
    _as[1] = chr(49)
    for i in range(8):
        _as[2*(i+1)] = params_md5[i]

    for j in range(8):
        _as[2*j+3] = shuffle2[j]

    for k in range(8):
        _cp[2*k] = shuffle1[k]

    for l in range(8):
        _cp[2*l+1] = params_md5[l+24]
    
    _cp[16] = chr(101)
    _cp[17] = chr(49)

    return ''.join(_as + _cp)


def params_replace(params):
    ret = ''
    for i in list(params):
        if ord(i) == 32 or ord(i) == 43:
            ret += chr(97)
            continue
        ret += i 
    return ret


def p2d(p):
    _ = p.split('&')
    r = {}
    for i in _:
        __ = i.split('=')
        r[__[0]] = __[1]
    r['rstr'] = 'efc84c17'
    return r


def params_process(params: dict):
    ret = {}
    for k,v in params.items():
        ret[k] = v
    # params = p2d(params)
    params['rstr'] = 'efc84c17'
    for i in ['as', 'cp']:
        if i in params:
            del params[i]
    st = int(params['_rticket'])
    sorted_k = sorted(params)
    pars = ''
    for k in sorted_k:
        pars += params[k]
    print(pars)
    encrypt = get_name(st, params_replace(pars))
    _as = encrypt[:len(encrypt)//2]
    _cp = encrypt[len(encrypt)//2:]
    ret.update({'as': _as, 'cp': _cp})
    return ret
    

def main():
    params = {
        'type': '0',  # 不知道啥玩意儿
        'max_cursor': '0', # 不知道啥玩意儿
        'min_cursor': '0', # 不知道啥玩意儿
        'count': '6', # 个数
        'volume': '0.0',  # 不知道啥玩意儿
        'retry_type': 'no_retry',  # 不知道啥玩意儿
        'iid': '88263783316',  # install id,固定即可
        'device_id': '69143529399',  # 设备id,固定即可
        'ac': 'wifi',  # 网络类型
        'channel': 'wandoujia',  # 安装渠道吧
        'aid': '1128',  # 不知道啥玩意儿
        'app_name': 'aweme',  # appname
        'version_code': '166',  # 版本号
        'version_name': '1.6.6',  # 版本
        'device_platform': 'android',  # 设备平台
        'ssmix': 'a',  # 不知道啥玩意儿
        'device_type': 'MI+8+UD',  # 设备类型
        'device_brand': 'Xiaomi',  # 设备品牌
        'language': 'zh',  # 系统语言
        'os_api': '28',  # android 版本号
        'os_version': '9',  # android 版本
        'uuid': '868695035559432',  # 多半和登陆账号有关
        'openudid': 'ffa82bd3b1106594',  # 不知道啥玩意儿
        'manifest_version_code': '166',  # androidmanifest中的版本号
        'resolution': '1080*2029',  # 分辨率
        'dpi': '440',  # dpi
        'update_version_code': '1662',  # 更新版本号
        '_rticket': str(int(time.time()*1000)),  # 时间戳
        'ts': str(int(time.time())),  # 时间戳
        'as': 'a185bba996d57d23ba',  # 不知道啥玩意儿
        'cp': 'b357d1586da49f39e1'  # 不知道啥玩意儿
    }
    params = params_process(params)
    headers = {
        'User-Agent': 'okhttp/3.8.1',
        'Host': 'aweme-eagle.snssdk.com',
    }
    resp = requests.get('https://aweme-eagle.snssdk.com/aweme/v1/feed/', params=params, headers=headers)
    print(resp.url)
    print(resp.json())


if __name__ == "__main__":
    main()

然后运行

❯ python3 crawl.py
1575271139258wifi1128awemewandoujia6Xiaomi69143529399androidMI+8+UD44088263783316zh16600ffa82bd3b11065942891080*2029no_retryefc84c17a1575271139016628686950355594321661.6.60.0
https://aweme-eagle.snssdk.com/aweme/v1/feed/?type=0&max_cursor=0&min_cursor=0&count=6&volume=0.0&retry_type=no_retry&iid=88263783316&device_id=69143529399&ac=wifi&channel=wandoujia&aid=1128&app_name=aweme&version_code=166&version_name=1.6.6&device_platform=and
roid&ssmix=a&device_type=MI%2B8%2BUD&device_brand=Xiaomi&language=zh&os_api=28&os_version=9&uuid=868695035559432&openudid=ffa82bd3b1106594&manifest_version_code=166&resolution=1080%2A2029&dpi=440&update_version_code=1662&_rticket=1575271139258&ts=1575271139&as=
a1e1757e60dae617ec&cp=5ea262180cc3e37be1
{'status_code': 2154, 'aweme_list': [], 'has_more': 1, 'min_cursor': 0, 'max_cursor': 0}

心里是不是跟吃了shit一样难受;为了搞清楚为什么返回2154,我hook了下java层调用部分来对比:

String.prototype.format = function () {
    var values = arguments;
    return this.replace(/\{(\d+)\}/g, function (match, index) {
        if (values.length > index) {
            return values[index];
        } else {
            return "";
        }
    });
}
// Memory.readUtf8String
var mru8s = function(addr) {return Memory.readUtf8String(addr)}
// Memory.readPointer
var mrp = function(addr) {return Memory.readPointer(addr)}
// Memory.allocUtf8String
var mau8s = function(addr) {return Memory.allocUtf8String(addr)}
// read process memory
var rpm = function(addr, size) {
    var buf = Memory.readByteArray(ptr('0x' + addr), size);
    console.log(hexdump(buf, {
    offset: 0,
        length: size,
        header: true,
        ansi: false
    }));
}

//==================================================================

var JNI_OnLoad;
var exports = Module.enumerateExportsSync("libuserinfo.so");
for (var i = 0; i < exports.length; i++) {
    var name = exports[i].name;
    var addr = exports[i].address;
    if (name == 'JNI_OnLoad') {
        JNI_OnLoad = addr;
    }
}

var BASE_ADDR = parseInt(JNI_OnLoad) - parseInt("0x14504");
var addr = '0x' + parseInt(BASE_ADDR + parseInt('0x11828')).toString(16);

var i = 0;

Interceptor.attach(new NativePointer(addr), {
    onEnter: function(args) {
        console.log('{0}======================='.format(addr));
        console.log('[{0}] 参数1 > {1}\n'.format(args[0], args[0].toInt32()));
        console.log('[{0}] 参数2 > {1}\n'.format(args[1], mru8s(args[1])));
    },
    onLeave: function(retval) {
        console.log('retval > ', mru8s(retval));
        console.log('\n');
    }
});

Java.perform(function() {
    var UserInfo = Java.use("com.ss.android.common.applog.UserInfo");
    UserInfo.getUserInfo.implementation = function(arg0, arg1, arg2) {
        console.log('===================================');
        console.log("arg0 > ", arg0);
        console.log("arg1 > ", arg1);
        console.log("arg2 > ", arg2);
        var retval = this.getUserInfo(arg0, arg1, arg2);
        console.log("retval > ", retval);
        console.log('\n');
        return retval;
    }
});

期间分析了很多地方,然后我突然想通了,我干嘛瞎分析这些玩意儿,直接抓包hook套用不就完事了,只需要改时间戳就行了,所以有了下面的代码;(其实上面没有返回数据是因为params和加密用到的参数不一致

import requests
import hashlib
import time


def shuffle(result, array):
    ret = ['0', '0', '0', '0', '0', '0', '0', '0']
    ret[2] = result[ord(array[2]) - 49]
    ret[4] = result[ord(array[4]) - 49]
    ret[6] = result[ord(array[6]) - 49]
    ret[5] = result[ord(array[5]) - 49]
    ret[3] = result[ord(array[3]) - 49]
    ret[1] = result[ord(array[1]) - 49]
    ret[7] = result[ord(array[7]) - 49]
    ret[0] = result[ord(array[0]) - 49]
    return ''.join(ret)


def get_name(server_time, params):
    _as = ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']
    _cp = ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']
    shuffle1 = hex(server_time)[2:]
    shuffle2 = hex(server_time)[2:]
    shuffle1 = shuffle(shuffle1, '57218436')
    shuffle2 = shuffle(shuffle2, '15387264')
    params_md5 = hashlib.md5(params.encode('utf-8')).hexdigest()
    if server_time & 1:
        params_md5 = hashlib.md5(params_md5.encode('utf-8')).hexdigest()
    _as[0] = chr(97)
    _as[1] = chr(49)
    for i in range(8):
        _as[2*(i+1)] = params_md5[i]

    for j in range(8):
        _as[2*j+3] = shuffle2[j]

    for k in range(8):
        _cp[2*k] = shuffle1[k]

    for l in range(8):
        _cp[2*l+1] = params_md5[l+24]
    
    _cp[16] = chr(101)
    _cp[17] = chr(49)

    return ''.join(_as + _cp)


def params_replace(params):
    ret = ''
    for i in list(params):
        if ord(i) == 32 or ord(i) == 43:
            ret += chr(97)
            continue
        ret += i 
    return ret


def p2d(p):
    _ = p.split('&')
    r = {}
    for i in _:
        __ = i.split('=')
        r[__[0]] = __[1]
    r['rstr'] = 'efc84c17'
    return r


def params_process(p1):
    ts = p1['ts']
    ps = [p1['_rticket'], 'wifi','1128','aweme','wandoujia','6','unknown','69143529399','android','unknown','440','88263783316','zh','166','0','0','ffa82bd3b1106594','28','9','1080*2029','no_retry','efc84c17','a',ts,'0','1662','166','1.6.6','0.13333333333333333']
    encrypt = get_name(int(ts), ''.join(ps))
    _as = encrypt[:len(encrypt)//2]
    _cp = encrypt[len(encrypt)//2:]
    p1.update({'as': _as, 'cp': _cp})
    print(encrypt)
    print(p1)
    

def main():
    _ts = str(int(time.time()))
    _rticket = str(int(time.time()*1000))
    
    p1 = {'type': '0',
        'max_cursor': '0',
        'min_cursor': '0',
        'count': '6',
        'volume': '0.13333333333333333',
        'retry_type': 'no_retry',
        'iid': '88263783316',
        'device_id': '69143529399',
        'ac': 'wifi',
        'channel': 'wandoujia',
        'aid': '1128',
        'app_name': 'aweme',
        'version_code': '166',
        'version_name': '1.6.6',
        'device_platform': 'android',
        'ssmix': 'a',
        'device_type': 'unknown',
        'device_brand': 'unknown',
        'language': 'zh',
        'os_api': '28',
        'os_version': '9',
        'openudid': 'ffa82bd3b1106594',
        'manifest_version_code': '166',
        'resolution': '1080*2029',
        'dpi': '440',
        'update_version_code': '1662',
        '_rticket': _rticket,
        'ts': _ts}
    params_process(p1)
    headers = {
        'User-Agent': 'okhttp/3.8.1',
        'Host': 'aweme-eagle.snssdk.com',
    }
    resp = requests.get('https://aweme-eagle.snssdk.com/aweme/v1/feed/', params=p1, headers=headers)
    print(resp.url)
    print(resp.json())


if __name__ == "__main__":
    main()

然后运行,输出,完美

❯ python3 crawl.py
a195132ebd4f4da9453cfbda55d05de699e1
{'type': '0', 'max_cursor': '0', 'min_cursor': '0', 'count': '6', 'volume': '0.13333333333333333', 'retry_type': 'no_retry', 'iid': '88263783316', 'device_id': '69143529399', 'ac': 'wifi', 'channel': 'wandoujia', 'aid': '1128', 'app_name': 'aweme', 'version_code': '166', 'version_name': '1.6.6', 'device_platform': 'android', 'ssmix': 'a', 'device_type': 'unknown', 'device_brand': 'unknown', 'language': 'zh', 'os_api': '28', 'os_version': '9', 'openudid': 'ffa82bd3b1106594', 'manifest_version_code': '166', 'resolution': '1080*2029', 'dpi': '440', 'update_version_code': '1662', '_rticket': '1575303677770', 'ts': '1575303677', 'as': 'a195132ebd4f4da945', 'cp': '3cfbda55d05de699e1'}
https://aweme-eagle.snssdk.com/aweme/v1/feed/?type=0&max_cursor=0&min_cursor=0&count=6&volume=0.13333333333333333&retry_type=no_retry&iid=88263783316&device_id=69143529399&ac=wifi&channel=wandoujia&aid=1128&app_name=aweme&version_code=166&version_name=1.6.6&device_platform=android&ssmix=a&device_type=unknown&device_brand=unknown&language=zh&os_api=28&os_version=9&openudid=ffa82bd3b1106594&manifest_version_code=166&resolution=1080%2A2029&dpi=440&update_version_code=1662&_rticket=1575303677770&ts=1575303677&as=a195132ebd4f4da945&cp=3cfbda55d05de699e1
{'status_code': 0, 'min_cursor': 0, 'max_cursor': 0, 'has_more': 1, 'aweme_list': [{'aweme_id': '6765856409071521032', 'desc': '#德阳#特警#在行动', 'create_time': 1575298748, 'author': {'uid': '1780829966313438', 'short_id': '2172980371', 'nickname': '德阳晚报', 'gender': 1, 'signature': '让新闻触动心灵', ...

最后

所有脚本、ida文件、jeb文件,以及最后的爬虫脚本都在这:5a6J5Y2T5oqW6Z+z55+t6KeG6aKRdjE=

版权声明:本文为qq_21692945原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_21692945/article/details/103424288