先分析个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 中的 map
和 python 中的 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。以上仅仅是我个人的分析,所以为了验证,直接调试。为了方便我直接把我以前写的调试教程贴上来了
点 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!=(__gnu_cxx::__normal_iterator const&,__gnu_cxx::__normal_iterator 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::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::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::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
::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=