一下子就2025年了, 我们继续来战虾皮.
[17:18:57 881]memcpy:RW@0x406db220, md5=069ef430a7469dd9343c385c5513c11f, hex=7a6c75672b5846324e77766a792f76656e514a436c5159456d62343d
size: 28
0000: 7A 6C 75 67 2B 58 46 32 4E 77 76 6A 79 2F 76 65 zlug+XF2Nwvjy/ve
0010: 6E 51 4A 43 6C 51 59 45 6D 62 34 3D nQJClQYEmb4=
我决定对trace做一个限定, 当memcpy出现我们要的值的时候,停止trace. 方便分析.
emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext =emulator.getContext();
UnidbgPointer src = registerContext.getPointerArg(1);
int length = registerContext.getIntArg(2);
Inspector.inspect(src.getByteArray(0, length), "memcpy:"+src.toString());
emulator.getUnwinder().unwind();
String value = new String(src.getByteArray(0, length));
System.out.println(value);
if (value.equals("zlug+XF2Nwvjy/venQJClQYEmb4=")){
// 停止trace
System.out.println("找到目标"); // 这里下断点, 走到这里就手动结束程序
}
return true;
}
});
先来猜猜看
zlug+XF2Nwvjy/venQJClQYEmb4= 非常像base64加密.
我们来学习一下base64的编码流程.
3.4.1 base64编码
假设我们的输入为 A , base64之后的结果为QQ==, 我们来手动运算一下.
编码规则:
- 把3个字节变成4个字节。
- 每76个字符加一个换行符。
- 结束符也要进行处理。
编码步骤:
(1)对原始数据,分字节,求二进制字符串;
(2)对二进制串,6位为一组进行分组,不足末尾补0;
(3)在各分组前面补2个0成8位,求各分组的十进制值,根据十进制对应上表找编码字符;
(4)字节数对3取余数,余数为0,不加等号;余数为1,在上述编码符号串的末尾加2个等号;余数为2,末尾加1个等号。
编码流程:
# 1. 转换为二进制
A -> 01000001
# 2. 6个一组, 不足6位补0
010000 010000
# 3. 前面补0
00010000 00010000
# 4. 求10进制
16 16
# 5. 找编码表
Q Q
# 6. 原始数据字节数对3取余数,余数为0,不加等号;余数为1,在上述编码符号串的末尾加2个等号;余数为2,末尾加1个等号。
1%3 = 1 所以加2个等号
=>
QQ==
解码流程:
(1)先去掉等号;
(2)再根据编码表,找编码字符对应的编码值;
(3)取各编码值的8位二进制值,去掉每个二进制的前2位的0值,然后连接形成二进制串;
(4)对上述二进制串,从前到后,每8位构成一个字节的数据;多余的末尾0值去掉;
(5)此时得到的就是原始数据的二进制编码;再根据编码方式(例如 utf-8)等进行解码。
# 1. 去除等号
QQ
# 2.根据编码表,找编码字符对应的编码值
16 16
# 3.转二进制
00010000 00010000
# 4. 去除前面2个0
010000 010000
# 5. 从前到后 每8位是一个字节, 多余的0删除
01000001
# 6. 转成文本
A (from binary)
3.4.2 base64编码 C语言实现
/*
base64.cpp and base64.h
base64 encoding and decoding with C++.
More information at
https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
Version: 2.rc.09 (release candidate)
Copyright (C) 2004-2017, 2020-2022 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/
#include "base64.h"
#include <algorithm>
#include <stdexcept>
//
// Depending on the url parameter in base64_chars, one of
// two sets of base64 characters needs to be chosen.
// They differ in their last two characters.
//
static const char* base64_chars[2] = {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"+/",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"-_"};
static unsigned int pos_of_char(const unsigned char chr) {
//
// Return the position of chr within base64_encode()
//
if (chr >= 'A' && chr <= 'Z') return chr - 'A';
else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1;
else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters (
else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_'
else
//
// 2020-10-23: Throw std::exception rather than const char*
//(Pablo Martin-Gomez, https://github.com/Bouska)
//
throw std::runtime_error("Input is not valid base64-encoded data.");
}
static std::string insert_linebreaks(std::string str, size_t distance) {
//
// Provided by https://github.com/JomaCorpFX, adapted by me.
//
if (!str.length()) {
return "";
}
size_t pos = distance;
while (pos < str.size()) {
str.insert(pos, "\n");
pos += distance + 1;
}
return str;
}
template <typename String, unsigned int line_length>
static std::string encode_with_line_breaks(String s) {
return insert_linebreaks(base64_encode(s, false), line_length);
}
template <typename String>
static std::string encode_pem(String s) {
return encode_with_line_breaks<String, 64>(s);
}
template <typename String>
static std::string encode_mime(String s) {
return encode_with_line_breaks<String, 76>(s);
}
template <typename String>
static std::string encode(String s, bool url) {
return base64_encode(reinterpret_cast<const unsigned char*>(s.data()), s.length(), url);
}
std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) {
size_t len_encoded = (in_len +2) / 3 * 4;
unsigned char trailing_char = url ? '.' : '=';
//
// Choose set of base64 characters. They differ
// for the last two positions, depending on the url
// parameter.
// A bool (as is the parameter url) is guaranteed
// to evaluate to either 0 or 1 in C++ therefore,
// the correct character set is chosen by subscripting
// base64_chars with url.
//
const char* base64_chars_ = base64_chars[url];
std::string ret;
ret.reserve(len_encoded);
unsigned int pos = 0;
while (pos < in_len) {
ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]);
if (pos+1 < in_len) {
ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]);
if (pos+2 < in_len) {
ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]);
ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]);
}
else {
ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]);
ret.push_back(trailing_char);
}
}
else {
ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]);
ret.push_back(trailing_char);
ret.push_back(trailing_char);
}
pos += 3;
}
return ret;
}
template <typename String>
static std::string decode(String const& encoded_string, bool remove_linebreaks) {
//
// decode(…) is templated so that it can be used with String = const std::string&
// or std::string_view (requires at least C++17)
//
if (encoded_string.empty()) return std::string();
if (remove_linebreaks) {
std::string copy(encoded_string);
copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end());
return base64_decode(copy, false);
}
size_t length_of_string = encoded_string.length();
size_t pos = 0;
//
// The approximate length (bytes) of the decoded string might be one or
// two bytes smaller, depending on the amount of trailing equal signs
// in the encoded string. This approximation is needed to reserve
// enough space in the string to be returned.
//
size_t approx_length_of_decoded_string = length_of_string / 4 * 3;
std::string ret;
ret.reserve(approx_length_of_decoded_string);
while (pos < length_of_string) {
//
// Iterate over encoded input string in chunks. The size of all
// chunks except the last one is 4 bytes.
//
// The last chunk might be padded with equal signs or dots
// in order to make it 4 bytes in size as well, but this
// is not required as per RFC 2045.
//
// All chunks except the last one produce three output bytes.
//
// The last chunk produces at least one and up to three bytes.
//
size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) );
//
// Emit the first output byte that is produced in each chunk:
//
ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4)));
if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045)
encoded_string.at(pos+2) != '=' &&
encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also.
)
{
//
// Emit a chunk's second byte (which might not be produced in the last chunk).
//
unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) );
ret.push_back(static_cast<std::string::value_type>( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2)));
if ( ( pos + 3 < length_of_string ) &&
encoded_string.at(pos+3) != '=' &&
encoded_string.at(pos+3) != '.'
)
{
//
// Emit a chunk's third byte (which might not be produced in the last chunk).
//
ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) ));
}
}
pos += 4;
}
return ret;
}
std::string base64_decode(std::string const& s, bool remove_linebreaks) {
return decode(s, remove_linebreaks);
}
std::string base64_encode(std::string const& s, bool url) {
return encode(s, url);
}
std::string base64_encode_pem (std::string const& s) {
return encode_pem(s);
}
std::string base64_encode_mime(std::string const& s) {
return encode_mime(s);
}
#if __cplusplus >= 201703L
//
// Interface with std::string_view rather than const std::string&
// Requires C++17
// Provided by Yannic Bonenberger (https://github.com/Yannic)
//
std::string base64_encode(std::string_view s, bool url) {
return encode(s, url);
}
std::string base64_encode_pem(std::string_view s) {
return encode_pem(s);
}
std::string base64_encode_mime(std::string_view s) {
return encode_mime(s);
}
std::string base64_decode(std::string_view s, bool remove_linebreaks) {
return decode(s, remove_linebreaks);
}
#endif // __cplusplus >= 201703L
#include <jni.h>
#include <string>
#include "base64.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_xiaopang_iaa_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "A";
std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(hello.c_str()), hello.length());
return env->NewStringUTF(encoded.c_str());
}
3.4.2 汇编中的表现
3.4.3 traceWrite跟踪分析
根据traceWrite的日志, 可以确定,写入我们的值写入位置在0x1d2ccc
[15:08:52 685] Memory WRITE at 0x406db220, data size = 1, data value = 0x7a, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 686] Memory WRITE at 0x406db221, data size = 1, data value = 0x6c, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 686] Memory WRITE at 0x406db222, data size = 1, data value = 0x75, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db223, data size = 1, data value = 0x67, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db224, data size = 1, data value = 0x2b, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db225, data size = 1, data value = 0x58, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db226, data size = 1, data value = 0x46, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db227, data size = 1, data value = 0x32, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db228, data size = 1, data value = 0x4e, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db229, data size = 1, data value = 0x77, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22a, data size = 1, data value = 0x76, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22b, data size = 1, data value = 0x6a, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22c, data size = 1, data value = 0x79, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22d, data size = 1, data value = 0x2f, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22e, data size = 1, data value = 0x76, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db22f, data size = 1, data value = 0x65, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db230, data size = 1, data value = 0x6e, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db231, data size = 1, data value = 0x51, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db232, data size = 1, data value = 0x4a, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db233, data size = 1, data value = 0x43, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db234, data size = 1, data value = 0x6c, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db235, data size = 1, data value = 0x51, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db236, data size = 1, data value = 0x59, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 687] Memory WRITE at 0x406db237, data size = 1, data value = 0x45, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 688] Memory WRITE at 0x406db238, data size = 1, data value = 0x6d, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 689] Memory WRITE at 0x406db239, data size = 1, data value = 0x62, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 689] Memory WRITE at 0x406db23a, data size = 1, data value = 0x34, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
[15:08:52 689] Memory WRITE at 0x406db23b, data size = 1, data value = 0x3d, PC=RX@0x401d2ccc[libshpssdk.so]0x1d2ccc, LR=unidbg@0x5b4d
根据下面这张图, 我们可以发现一个非常有趣的点
0000: 7A 6C 75 67 2B 58 46 32 4E 77 76 6A 79 2F 76 65 zlug+XF2Nwvjy/ve
0010: 6E 51 4A 43 6C 51 59 45 6D 62 34 3D nQJClQYEmb4=
7A 0x1
2B 0x5
4E 0x9
79 0xD
6E 0x11
6C 0x15
我们的结果的一部分是由0x1d23d4
写入的
0x1d2ccc
和0x1d23d4
包了所有的字节写入. 这两个地址都位于sub_1D1B18
函数中.
把伪C代码丢入GPT, 我怀疑它是Base64.
trace sub_1D1B18
emulator.traceCode(module.base + 0x1D1B18, module.base + 0x1D2D48 ).setRedirect(traceStream);
[15:42:07 236][libshpssdk.so 0x1d23d0] [29014039] 0x401d23d0: "ldrb w9, [x9]" x9=0x402dd8e9 => w9=0x7a // w9 = *(uint8_t *)x9; 从地址x9(0x2dd8e9)读取一个字节.
[15:42:07 236][libshpssdk.so 0x1d23d4] [696a2a38] 0x401d23d4: "strb w9, [x19, x10]" w9=0x7a x19=0x406ee000 x10=0x108 => w9=0x7a // 第一个字节 追w9
进入IDA看0x2dd8e9
再把眼睛放灵光点,上面看看 0x3F长度的数据
按下A键.
A键: 数据转换成ASCII码字符串
shopEeSHOPDFTACGkrIJ45KLBM+NQcdRU1VW89XYwxZabfgijlmntquvyz02/376
非常棒的长度.
所以我们可以尝试用它给我们的目标数据解码
https://gchq.github.io/CyberChef/#recipe=From_Base64('shopEeSHOPDFTACGkrIJ45KLBM%2BNQcdRU1VW89XYwxZabfgijlmntquvyz02/376',true,false)To_Hexdump(16,false,false,false)&input=emx1ZytYRjJOd3ZqeS92ZW5RSkNsUVlFbWI0PQ
解码后的数据如下:
00000000 e7 1d ae 6a 62 fb 6e 8d f0 e3 cd c5 cd c4 ce c5 |ç.®jbûn.ðãÍÅÍÄÎÅ|
00000010 c9 c4 ca c5 |ÉÄÊÅ|
至此, 我们的目标就是把从追
0000: 7A 6C 75 67 2B 58 46 32 4E 77 76 6A 79 2F 76 65 zlug+XF2Nwvjy/ve
0010: 6E 51 4A 43 6C 51 59 45 6D 62 34 3D
变成了追踪
00000000 e7 1d ae 6a 62 fb 6e 8d f0 e3 cd c5 cd c4 ce c5 |ç.®jbûn.ðãÍÅÍÄÎÅ|
00000010 c9 c4 ca c5 |ÉÄÊÅ|
再用MemoryScan扫一下, 可以看到第一次内存写入位于0x1d23d0
3.4.4 RC4加密算法
3.4.4.1 初始化状态向量S(256个字节,用来作为密钥流生成种子1)
按照升序,给每个字节赋值0,1,2,3,4,5,6.....,254,255
3.4.4.2 初始化密钥, 用户输入,长度任意
假设我们输入长度小于256个字节,则进行轮询,直到填满256个字节.
假设, 我们输入的密钥是 x,i,a,o,p,a,n,g(xiaopang) . 那么它会把密钥不断填充到256个字节
x,i,a,o,p,a,n,g,x,i,a,o,p,a,n,g......
由上述轮转过程得到256个字节的向量T(用来作为密钥流生成的种子2)
在次, 我们可以得出一个结论
- 密钥为xiaopang和密钥为xiaopangxiaopang加密的结果是一致的. 因为它可以把xiaopang 8长度整除. 填充后的密钥是一致的
3.4.4.3 开始对状态向量S进行置换操作(用来打乱初始种子1)
按照下列规则进行
从第零个字节开始,执行256次,保证每个字节都得到处理
j = 0;
// s 为 0-255
// T 为 256长度的密钥
// mod
for (i = 0 ; i < 256 ; i++){
j = (j + S[i] + T[i]) mod 256; // 等价于 j = (j + S[i] + T[i]) & 0xFF;
swap(S[i] , S[j]); // tmp = S[i], S[i] = S[j], S[j]=tmp
}
这样处理后的状态向量S几乎是带有一定的随机性了
3.4.4.4 最后是秘钥流的生成与加密
假设我的明文字节数是datalength=1024个字节(当然可以是任意个字节)
i=0;
j=0;
while(datalength--){//相当于执行1024次,这样生成的秘钥流也是1024个字节
i = (i + 1) mod 256; // i = (i + 1) & 0xff
j = (j + S[i]) mod 256; // j = (j + S[i]) & 0xff
swap(S[i] , S[j]);
t = (S[i] + S[j]) mod 256; // t = (S[i] + S[j]) & 0xFF
k = S[t]; // 这里的K就是当前生成的一个秘钥流中的一位
//可以直接在这里进行加密,当然也可以将密钥流保存在数组中,最后进行异或就ok
data[]=data[]^k; //进行加密,"^"是异或运算符
}
3.4.5 继续分析
Hook 0x1D1B18函数
emulator.attach().addBreakPoint(module.base + 0x1C736C, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
// 获取 LR 寄存器值
long lr = emulator.getContext().getLR();
System.out.println("LR:" + lr);
// 添加一个断点在返回地址
emulator.attach().addBreakPoint(lr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
return false;
}
});
return false;
}
});
0xbffff4bc 写入监控
[16:35:02 493] Memory WRITE at 0xbffff4bc, data size = 1, data value = 0xe7, PC=RX@0x401c75bc[libshpssdk.so]0x1c75bc, LR=unidbg@0x6bcc
我Hook了0x1C736C的进入和返回值, 在进入的时候看看x0和0xbffff4bc. 可以看到, 当前的数据暂时还不是我们要的
走到函数执行完成, 此时我们的数据就变了.
得出结论:
- 经过0x1C736C方法, 我们的数据加密成了我们要的目标数据
所以我们对0x1C736C进行trace
emulator.attach().addBreakPoint(module.base + 0x1C736C, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
String traceFile = "unidbg-android/src/test/java/com/shopee/shpssdk/tracecode_1C736C.txt";
PrintStream traceStream;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 获取 LR 寄存器值
long lr = emulator.getContext().getLR();
System.out.println("LR:" + lr);
// 添加一个断点在返回地址
emulator.attach().addBreakPoint(lr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
return false;
}
});
return false;
}
});
一万多行的trace, 非常舒服.
随便搜索搜索, 猜猜大概可以看出,如下的逻辑
我们还知道该函数的入参为
0000: 22 3F B3 C4 08 99 95 E3 7D 13 2E 08 08 09 0A 0B "?......}.......
0010: 0C 0D 0E 0F
也能和下图的数据中的w5对应起来
那么我们这个函数未知的东西就剩下w6了. 看汇编.
[17:11:35 510][libshpssdk.so 0x1c74e4] [066866b8] 0x401c74e4: "ldr w6, [x0, x6]" x0=0x40713000 x6=0x8c => w6=0x22 // w6 = load(0x40713000 + 0x8c) = load(0x4071308c) = 0x22
[17:11:35 510][libshpssdk.so 0x1c74e8] [c500054a] 0x401c74e8: "eor w5, w6, w5" w6=0x22 w5=0x3f => w5=0x1d // 需要追w6
确定数据来自0x4071308c后, 就搜索不到了. traceWrite
emulator.traceWrite(0x4071308cL,0x4071308cL);
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x22, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x1d, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xae, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x6a, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x62, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xfb, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x6e, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0x8d, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xf0, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xe3, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xcd, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xc5, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xcd, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xc4, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xce, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 678] Memory WRITE at 0x4071308c, data size = 4, data value = 0xc5, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 679] Memory WRITE at 0x4071308c, data size = 4, data value = 0xc9, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 679] Memory WRITE at 0x4071308c, data size = 4, data value = 0xc4, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
[17:30:37 679] Memory WRITE at 0x4071308c, data size = 4, data value = 0xca, PC=RX@0x401c7854[libshpssdk.so]0x1c7854, LR=unidbg@0x6bcc
搜索0x1c7854,找到我们要的行, 继续分析汇编
确定要去找0x40713088.
上面的行数不多了, 向上看看吧
确定数据来自0xbffff4bc
那么这个0xbffff4bc
是什么呢? 是我们的这个方法的传入参数. (不记得它吭哧吭哧翻了好久,发现不对劲)
赶紧回去缕一缕. 拍大腿了.
看出来了吗?
假设输入是:[0x01, 0x02, 0x03]
步骤1: tmp = 0x01
步骤2: tmp = 0x01 ^ 0x02 = 0x03
步骤3: tmp = 0x03 ^ 0x03 = 0x00
步骤4: first = 0x01 ^ 0x00 = 0x01
最终结果:[0x01, 0x03, 0x00]
def func_1C736C(input):
tmp = input[0]
result = bytearray()
for i in range(0,len(input) - 1):
tmp = tmp ^ input[i + 1]
result.append(tmp)
first = input[0] ^ tmp
result = bytearray([first]) + result
return result.hex()
if __name__ == '__main__':
input = bytes.fromhex("223fb3c4089995e37d132e0808090a0b0c0d0e0f")
output = func_1C736C(input)
print(output)
3.4.6 阶段小结
我们前面还没写自定义base64的代码. 把代码都补上吧.
import base64
def func_1C736C(input):
tmp = input[0]
result = bytearray()
for i in range(0,len(input) - 1):
tmp = tmp ^ input[i + 1]
result.append(tmp)
first = input[0] ^ tmp
result = bytearray([first]) + result
return result.hex()
def custom_base64_encode(data):
# 自定义的base64字符集
custom_alphabet = "shopEeSHOPDFTACGkrIJ45KLBM+NQcdRU1VW89XYwxZabfgijlmntquvyz02/376"
# 标准base64字符集
standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# 创建转换表
trans_table = str.maketrans(standard_alphabet, custom_alphabet)
# 使用标准base64编码
import base64
standard_b64 = base64.b64encode(data).decode()
# 转换为自定义字符集
custom_b64 = standard_b64.translate(trans_table)
return custom_b64
def custom_base64_decode(encoded_data):
# 自定义的base64字符集
custom_alphabet = "shopEeSHOPDFTACGkrIJ45KLBM+NQcdRU1VW89XYwxZabfgijlmntquvyz02/376"
# 标准base64字符集
standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# 创建转换表
trans_table = str.maketrans(custom_alphabet, standard_alphabet)
# 转换回标准字符集
standard_b64 = encoded_data.translate(trans_table)
# 使用标准base64解码
decoded = base64.b64decode(standard_b64)
return decoded
if __name__ == '__main__':
input = bytes.fromhex("223fb3c4089995e37d132e0808090a0b0c0d0e0f")
output = func_1C736C(input)
# 自定义base64编码
print("func_1C736C",output)
# byte
print("base64",custom_base64_encode(bytes.fromhex(output)))
接下来的日子, 我们就要和223fb3c4089995e37d132e0808090a0b0c0d0e0f
进行绝斗了.