感谢好兄弟Lane 提供讲解服务 此文章目的为复现。
目标:nonce=89BD9D8D-69A6-474E-8F46-7CC8796ED151×tamp=1731047246145&devicetoken=910D166A-736E-3231-8B21-8D12DFD75F16&sign=9A2DFE01283DF2583E06643E1AAB7216
这个中的sign值
一、unidbg搭架子
package com.fan.qing;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class qing2 extends AbstractJni implements IOResolver {
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("pathName:" + pathname);
if (pathname.equals("/proc/self/maps")) {
return FileResult.success(new SimpleFileIO(oflags, new File("apks/FCZ/maps"), pathname));
}
return null;
}
public static AndroidEmulator emulator;
public static Memory memory;
public static VM vm;
public static DalvikModule dm;
public static Module module;
public void traceCode(){
// 持久化写入 tracecode.txt
String traceFile = "apks/qing/tracecode.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();
}
}
public qing2() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("vz.com").build();
// 2.获取内存对象(可以操作内存)
memory = emulator.getMemory();
// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));
// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样)
vm = emulator.createDalvikVM(new File("apks/qing/xiaoshuo.apk"));
vm.setJni(this); // 后续设置补环境
vm.setVerbose(true); //是否展示调用过程的细节
emulator.getSyscallHandler().addIOResolver(this);
// 5.加载so文件
DalvikModule dm = vm.loadLibrary("sfdata", true);
dm.callJNI_OnLoad(emulator);
// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule();
// append 函数
emulator.attach().addBreakPoint(module.base + 0x7A980, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
Arm64RegisterContext rc = emulator.getContext();
System.out.println("[0x7A980] ->"+rc.getLRPointer());
// Arm64RegisterContext rc = emulator.getContext();
UnidbgPointer ptr1 = rc.getPointerArg(0);
UnidbgPointer ptr2 = rc.getPointerArg(1);
int len = rc.getIntArg(2);
byte[] sourcebarr = ptr2.getByteArray(0,len);
String source = new String(sourcebarr);
System.out.println("sss: " + Arrays.toString(sourcebarr));
System.out.println("ssss: " + source);
// traceCode();
return true;
}
});
emulator.attach().addBreakPoint(module.base + 0x166A0, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
Arm64RegisterContext rc = emulator.getContext();
// traceCode();
return true;
}
});
emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
System.out.println("memcp hit");
Arm64RegisterContext rc = emulator.getContext();
UnidbgPointer ptr1 = rc.getPointerArg(0);
UnidbgPointer ptr2 = rc.getPointerArg(1);
int len = rc.getIntArg(2);
byte[] sourcebarr = ptr2.getByteArray(0,len);
String source = new String(sourcebarr);
System.out.println("memcp: lr:" + rc.getLRPointer() + " 目标数组-> " + ptr1 + " 数据源->" + ptr2 + " len->" + len);
System.out.println("memcp: sourcebarr" + Arrays.toString(sourcebarr));
System.out.println("memcp: source =" + source);
if (len == 160){
System.out.println("start trace");
}
return true;
}
});
}
public static String stringToHexString(String s) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
int ch = s.charAt(i);
String s4 = Integer.toHexString(ch);
str.append(s4);
}
return str.toString();
}
public String getSFsecurity(){
// traceCode();// 0x3631323762616131
Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一个参数是env
list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到
Object custom = null;
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(custom);// context
list.add(vm.addLocalObject(context));
list.add(vm.addLocalObject(new StringObject(vm, "910D166A-736E-3231-8B21-8D12DFD75F16")));
int intValue = 0x3;
list.add(intValue);
Number number = module.callFunction(emulator, 0x2728C, list.toArray());
String result = vm.getObject(number.intValue()).getValue().toString();
Inspector.inspect(result.getBytes(),"aaa");
System.out.println(result);
return result;
}
public static void main(String[] args) {
qing2 result = new qing2();
result.getSFsecurity();
}
}
二、从trace还原算法
此样本中的ollvm混淆很恶心,所以才选择在汇编中还原算法,在做算法还原之前之前我们已经固定了输出结果。
首先我们上面提供了traceCode函数,我们可以去trace 得到一份汇编指令流程代码。并使用010去打开他。
接着我们可以观察我们的sign是32位 那么当然首选肯定是md5呀(猜测)。
1.初步分析
借用龙哥的文章的话:”在 trace 日志里搜索 K 的表的第一个数 0xd76aa478,对于 MD5,我们一般不通过 IV 来确认是否是它,因为 SHA1 等算法也沿用了 MD5 的 IV,不具备唯一标识性。“
我们看到了两组结果而且他们的位置很接近,我们可以想到是md5 将填充后的数据分成多个 512 位(64 字节)的块。对于 1024 比特的数据,共有 2 个数据块。
对每个数据块进行四轮计算,每轮包含 16 次操作,共 64 次操作。每次操作的形式为:
a = b + ((a + F(b, c, d) + X[k] + T[i]) <<< s)
md5算法部分源码:
FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */
#define FF(a,b,c,d,x,s,ac) \
{ \
a += F(b,c,d) + x + ac; \
a = ROTATE_LEFT(a,s); \
a += b; \
}
// 公式:
a += F(b,c,d) + x + ac;
a = a + F(b,c,d) + x + ac;
ac = 0xd76aa478
a = 0x67452301
F(b,c,d) = 0x98badcfe
a+F(b,c,d) = 0xffffffff
接下来 我们从trace中看到了
[libsfdata.so 0x173b4] [0801030b] 0x400173b4: "add w8, w8, w3" w8=0x50373735 w3=0xd76aa478 => w8=0x27a1dbad这条指令,所以 0x50373735 = a + F(b,c,d) + x , a = 0x67452301 ,F(b,c,d) = 0x98badcfe,所以x = 0x50373736;
提示:负数通过模 232 运算被映射到 0 到 232−1 的范围内,最终结果为 1346855990(即 0x50373736)。
接下来跟0x50373736 这个值:
是从这个0xbfffecc0 这里加载的 所以我们去看看这个0xbfffecc0是什么。
unidbg中下断点:
第一次
m0xbfffecc0
>-----------------------------------------------------------------------------<
[21:53:47 035]unidbg@0xbfffecc0, md5=80af3d1b53c5917b13544c40006ca36e, hex=363737505143385033365242333132373643383845365039365142373236364f4442364e32363034394e373436364e4f335131324d34374f423350355039313610f045400000000010000000000000000900000000000000010000000000000020f04540000000000000000000000000
size: 112
0000: 36 37 37 50 51 43 38 50 33 36 52 42 33 31 32 37 677PQC8P36RB3127
0010: 36 43 38 38 45 36 50 39 36 51 42 37 32 36 36 4F 6C88E6P96QB7266O
0020: 44 42 36 4E 32 36 30 34 39 4E 37 34 36 36 4E 4F DB6N26049N7466NO
0030: 33 51 31 32 4D 34 37 4F 42 33 50 35 50 39 31 36 3Q12M47OB3P5P916
0040: 10 F0 45 40 00 00 00 00 10 00 00 00 00 00 00 00 ..E@............
0050: 09 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
0060: 20 F0 45 40 00 00 00 00 00 00 00 00 00 00 00 00 .E@............
^-----------------------------------------------------------------------------^
第二次:
m0xbfffecc0
>-----------------------------------------------------------------------------<
[21:54:30 977]unidbg@0xbfffecc0, md5=2f359eff5924c3506707d2b015696619, hex=39344230374e4e4232524f33425044433736523350514a4a4d4b354f42485041485157515180000000000000000000000000000000000000280300000000000010f045400000000010000000000000000900000000000000010000000000000020f04540000000000000000000000000
size: 112
0000: 39 34 42 30 37 4E 4E 42 32 52 4F 33 42 50 44 43 94B07NNB2RO3BPDC
0010: 37 36 52 33 50 51 4A 4A 4D 4B 35 4F 42 48 50 41 76R3PQJJMK5OBHPA
0020: 48 51 57 51 51 80 00 00 00 00 00 00 00 00 00 00 HQWQQ...........
0030: 00 00 00 00 00 00 00 00 28 03 00 00 00 00 00 00 ........(.......
0040: 10 F0 45 40 00 00 00 00 10 00 00 00 00 00 00 00 ..E@............
0050: 09 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
0060: 20 F0 45 40 00 00 00 00 00 00 00 00 00 00 00 00 .E@............
^-----------------------------------------------------------------------------^
28 03就是数据的长度
根据 MD5 填充规则,数据的长度会被附加在填充后的数据的末尾,以 64 位(8 字节)表示。
28 03
是以小端序(Little-Endian)表示的长度值。将其转换为十进制:
0x0328 = 808
因此,数据的原始长度为 808 比特(即 101 字节)。
验证:677PQC8P36RB31276C88E6P96QB7266ODB6N26049N7466NO3Q12M47OB3P5P91694B07NNB2RO3BPDC76R3PQJJMK5OBHPAHQWQQ md5 后确实是sign 9A2DFE01283DF2583E06643E1AAB7216
2.继续分析
那么这一串值是怎么来的继续010走起:
现在我们是找到sign的入参了但是入参二点明文我们并不知道如何来的。
接下来我们可以使用龙哥的DataSearch( 内存数据搜索工具)去寻找
0000: 36 37 37 50 51 43 38 50 33 36 52 42 33 31 32 37 677PQC8P36RB3127 明文前16个字节第一次生成的位置:
匹配到
Search matches: RW@0x40453090
继续traceWrite emulator.traceWrite(0x40453090L,0x40453090L + 15);先看前16个字节是如何生成的~
[22:07:29 065] Memory WRITE at 0x40453090, data size = 1, data value = 0x36, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453091, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453092, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453093, data size = 1, data value = 0x3d, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453094, data size = 1, data value = 0x3e, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453095, data size = 1, data value = 0x43, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453096, data size = 1, data value = 0x38, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453097, data size = 1, data value = 0x3d, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453098, data size = 1, data value = 0x33, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453099, data size = 1, data value = 0x36, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309a, data size = 1, data value = 0x3f, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309b, data size = 1, data value = 0x42, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309c, data size = 1, data value = 0x33, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309d, data size = 1, data value = 0x31, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309e, data size = 1, data value = 0x32, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309f, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453093, data size = 1, data value = 0x50, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453094, data size = 1, data value = 0x51, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453097, data size = 1, data value = 0x50, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309a, data size = 1, data value = 0x52, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
前16个字节的位置在0x2b1cc这个地方,010看一下
恰好是101个字节也就是我们的md5明文的入参长度。
全部复制到cyberChef
是这么个玩意,也就是说我们的md5入参是由26049;7466;3QJJ:K5<BH=.HQW>>12:47<B3=5=91694/07;;/2?<3/=DC76?3=677=>C8=36?B31276C88E6=-6>B7266<D/6; 变化而来的。
我们现在的公式可以这样写:
另 x2 = 26049;7466;3QJJ:K5<BH=.HQW>>12:47<B3=5=91694/07;;/2?<3/=DC76?3=677=>C8=36?B31276C88E6=-6>B7266<D/6;
另 x1 = 677PQC8P36RB31276C88E6P96QB7266ODB6N26049N7466NO3Q12M47OB3P5P91694B07NNB2RO3BPDC76R3PQJJMK5OBHPAHQWQQ
sign = MD5(x1); x1 = 某算法1(x2);
那么这个x2是什么呢我们接着分析:
下篇文章接着分析ing