使用blutter分析闲鱼的libapp.so时出现段错误(SIGSEGV)。我很早就发现有这个问题了,一直在等作者修复,但发现这个项目的更新频率较低,有一大堆issues和PR都没处理,于是就尝试用claude分析这个问题的原因,还算比较简单,cluade很快发现了问题并修复成功(https://github.com/xjohjrdy/blutter)。以下是我让cluade写的文档:
Blutter SIGSEGV Bug 修复报告
问题概述
Blutter 在分析特定的 ARM64 Flutter 应用 (libapp.so) 时崩溃,错误信息如下:
| subprocess.CalledProcessError: Command '['.../bin/blutter_dartvm2.19.6_android_arm64', '-i', '.../libapp.so', '-o', '.../out']' died with <Signals.SIGSEGV: 11>.
|
Bug 定位过程
第一步:添加调试日志
为了定位崩溃位置,在关键代码路径添加了调试日志:
blutter/src/DartLoader.cpp - Dart VM 初始化相关代码
blutter/src/DartApp.cpp - 应用加载和类表处理代码
blutter/src/ElfHelper.cpp - ELF 文件解析代码
第二步:获取崩溃堆栈
使用 gdb 运行程序获取崩溃时的堆栈跟踪:
| gdb -batch -ex "run -i libapp.so -o out" -ex "bt full" ./blutter_dartvm2.19.6_android_arm64
|
堆栈跟踪结果:
| Program received signal SIGSEGV, Segmentation fault. 0x00005555557a6457 in dart::BSS::Initialize(dart::Thread*, unsigned long*, bool) () #0 0x00005555557a6457 in dart::BSS::Initialize(dart::Thread*, unsigned long*, bool) () #1 0x00005555557a59a2 in dart::FullSnapshotReader::ReadProgramSnapshot() () #2 0x00005555557b14a3 in dart::Dart::InitIsolateFromSnapshot(...) () #3 0x00005555557b273b in dart::Dart::InitializeIsolate(...) () #4 0x000055555578f9ca in dart::CreateIsolate(...) () #5 0x000055555578ff0e in Dart_CreateIsolateGroup() () #6 0x000055555562be80 in load_isolate(...) () ...
|
崩溃发生在 dart::BSS::Initialize 函数中。
第三步:分析 ELF 文件结构
检查测试样本的 ELF 文件结构:
关键发现:
| LOAD Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000003021f80 0x0000000003021f80 R E 0x10000 LOAD 0x0000000003022000 0x0000000003032000 0x0000000003032000 0x00000000000000d0 0x00000000000000e8 RW 0x10000
|
注意到第二个 LOAD 段:
- FileSiz (文件大小): 0xd0
- MemSiz (内存大小): 0xe8
两者的差值 0xe8 - 0xd0 = 0x18 (24字节) 就是 BSS 段的大小。
第四步:分析原始代码
原始的 load_map_file 函数(Linux 版本):
| static void* load_map_file(const char* path) { int fd = open(path, O_RDONLY); struct stat st;
fstat(fd, &st); void* mem = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd); return mem; }
|
问题:只映射了文件大小(st.st_size)的内存,没有为 BSS 段分配额外空间。
Bug 原因分析
ELF 文件中的 BSS 段
BSS(Block Started by Symbol)段是 ELF 文件中用于存储未初始化全局变量和静态变量的区域:
- BSS 段在文件中不占用实际空间(类型为
NOBITS)
- 程序加载时,BSS 段需要分配内存并初始化为零
- BSS 段的内存大小(MemSiz)通常大于其在文件中的大小(FileSiz)
崩溃原因
- 原始代码只映射了文件大小的内存
- Dart VM 初始化时调用
dart::BSS::Initialize 尝试写入 BSS 区域
- BSS 区域位于映射内存之外,导致访问非法内存
- 触发 SIGSEGV(段错误)
具体来说:
- 文件映射大小:0x30220d0 字节
- BSS 段虚拟地址:0x30320d0(相对于映射基址偏移约 0x10000)
- 访问 BSS 区域时超出映射范围,导致崩溃
修复方案
修改 blutter/src/ElfHelper.cpp
Linux 平台
添加 get_elf_mem_size 函数计算实际需要的内存大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| static size_t get_elf_mem_size(int fd, size_t file_size) { uint8_t ehdr[64]; if (read(fd, ehdr, 64) != 64) return file_size + 0x1000;
if (memcmp(ehdr, "\x7f" "ELF", 4) != 0) return file_size + 0x1000;
uint64_t phoff = *(uint64_t*)(ehdr + 32); uint16_t phnum = *(uint16_t*)(ehdr + 56); uint16_t phentsize = *(uint16_t*)(ehdr + 54);
lseek(fd, phoff, SEEK_SET); std::vector<uint8_t> phdrs(phnum * phentsize); if (read(fd, phdrs.data(), phnum * phentsize) != phnum * phentsize) return file_size + 0x1000;
uint64_t max_vaddr = 0; uint64_t min_vaddr = UINT64_MAX;
for (int i = 0; i < phnum; i++) { uint8_t* phdr = phdrs.data() + i * phentsize; uint32_t p_type = *(uint32_t*)(phdr + 0);
if (p_type == 1) { uint64_t p_vaddr = *(uint64_t*)(phdr + 16); uint64_t p_memsz = *(uint64_t*)(phdr + 40);
if (p_vaddr + p_memsz > max_vaddr) max_vaddr = p_vaddr + p_memsz; if (p_vaddr < min_vaddr) min_vaddr = p_vaddr; } }
size_t mem_size = max_vaddr - min_vaddr; lseek(fd, 0, SEEK_SET);
if (mem_size < file_size) mem_size = file_size + 0x1000;
mem_size = (mem_size + 0xfff) & ~0xfffull;
return mem_size; }
|
修改 load_map_file 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| static void* load_map_file(const char* path) { int fd = open(path, O_RDONLY); struct stat st;
fstat(fd, &st);
size_t memSize = get_elf_mem_size(fd, st.st_size);
void* mem = mmap(NULL, memSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) { close(fd); return NULL; }
ssize_t bytesRead = read(fd, mem, st.st_size); close(fd);
if (bytesRead != (ssize_t)st.st_size) { munmap(mem, memSize); return NULL; }
memset((char*)mem + st.st_size, 0, memSize - st.st_size);
return mem; }
|
Windows 平台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| static void* load_map_file(const char* path) { HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("\nCannot find %s\n", path); return NULL; }
DWORD fileSize = GetFileSize(hFile, NULL); const DWORD extraBssSize = 0x1000;
HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fileSize + extraBssSize, NULL); if (hMapFile == INVALID_HANDLE_VALUE) return NULL;
void* mem = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); CloseHandle(hMapFile); CloseHandle(hFile);
return mem; }
|
验证结果
修复后重新编译并运行:
| rm -rf build/blutter_dartvm2.19.6_android_arm64 python blutter.py --rebuild test_app/arm64-v8a/ test_app/out
|
程序成功执行并生成输出文件:
| libapp is loaded at 0x7e2f0b1cd000 Dart heap at 0x7e2e00000000 Analyzing the application Dumping Object Pool Generating application assemblies Generating Frida script Dart version: 2.19.6, Snapshot: adb4292f3ec25074ca70abcd2d5c7251, Target: android arm64 flags: product no-code_comments dwarf_stack_traces_mode no-lazy_dispatchers dedup_instructions no-asserts arm64 android compressed-pointers no-null-safety
|
输出文件:
| test_app/out/ ├── asm/ ├── blutter_frida.js ├── ida_script/ ├── objs.txt └── pp.txt
|
经验总结
ELF 文件映射不仅要考虑文件大小,还要考虑内存大小:BSS 段在文件中不占空间,但在内存中需要分配。
调试技巧:通过添加日志和使用 gdb 可以快速定位崩溃位置。
程序头分析:readelf -l 命令可以查看 ELF 程序头,其中 FileSiz 和 MemSiz 的差异揭示了 BSS 段的存在。
正确的内存映射方式:对于需要写入 BSS 区域的程序,应使用匿名映射 + 文件读取的方式,而不是直接映射文件。