逃避 Linux 恶意软件静态分析的新方法

admin 2024年7月30日15:22:03评论9 views字数 15141阅读50分28秒阅读模式

本文章介绍了如何使用dl_iterate_phdr回调来混淆 Linux 符号。它代表了 的原创安全研究。

介绍

API 哈希长期以来一直用于整个 Windows 恶意软件开发。此技术有许多公开的概念验证 (PoC),它通过隐藏 Windows 上的导入地址表中的条目来帮助逃避静态分析。此技术是防止防御软件轻易识别潜在恶意应用程序的功能所必需的。例如,用于进程注入的已知 API 调用的存在将是恶意意图的一大危险信号。随着 EDR 继续增强这些平台上的检测能力,相同的检测逻辑现在也应用于基于 Linux 的系统。这篇博文和 PoC 旨在通过隐藏符号表中的条目来实现相同的结果,即模糊可能指示有效负载恶意功能的已使用符号,目的是绕过基于 Linux 的 EDR 使用的静态检测机制。

Windows于Liunx

几周前,我开始探索在 ELF 二进制文件中隐藏动态符号表 (.dynsym) 的不同方法。事实证明,这种方法与在 Windows 上所做的非常相似,只是略有不同。
1.获取我们感兴趣的函数所在库的基地址,例如 kernel32.dll。
2.找到 kernel32 导出地址表 (EAT)。
3.遍历 kernel32 模块导出的每个函数名称。
4.对于每个导出的函数名称,计算其哈希值并将其与我们自己的哈希值进行比较,如果匹配,则计算该函数的虚拟地址。
在 Linux 上,高级方法与 Windows 类似:
1.使用 dl_iterate_phdr 函数遍历当前进程中加载的所有共享对象。
2.对于每个共享对象(或仅 libc),找到其符号表(例如,ELF 文件中的动态符号表 (dynsym) 部分)。    
3.遍历共享对象的符号表中的每个符号名称。
4.对于每个导出的符号名称,使用我们的哈希函数计算其哈希值。
5.如果计算出的哈希值等于目标哈希值,则检索符号的地址。
您所见,Windows 和 Linux 上的流程非常相似。

问:什么是dl_iterate_phdr?

dl_iterate_phdr函数是 GNU C 库 (glibc) 的一部分,用于遍历进程中加载的共享对象列表。它会callback为每个对象调用一次该函数,直到所有对象都已处理完毕或返回callback非零值。使用此函数,我们可以检查每个共享对象并查看其内部结构,例如动态符号表,这正是我们在这里想要做的。
此函数采用以下两个参数:
callback它将为每个共享对象调用一个函数。
指向预定义用户数据的指针(void *data)
以下是 dl_iterate_phdr 手册页中的一段代码,演示了如何定义该函数:
int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size, void *data), void *data);
回调函数接收一个dl_phdr_info结构体,其中包含有关共享对象的信息。该结构体如下所示,这也是从手册页中截取的片段:
struct dl_phdr_info { ElfW(Addr) dlpi_addr; /* Base address of object */ const char *dlpi_name; /* (Null-terminated) name of object */ const ElfW(Phdr) *dlpi_phdr; /* Pointer to array of ELF program headers for this object */ ElfW(Half) dlpi_phnum; /* Number of items in dlpi_phdr */

/* The following fields were added in glibc 2.4, after the first version of this structure was available. Check the size argument passed to the dl_iterate_phdr callback to determine whether or not each later member is available. */

unsigned long long dlpi_adds; /* Incremented when a new object may have been added */ unsigned long long dlpi_subs; /* Incremented when an object may have been removed */ size_t dlpi_tls_modid; /* If there is a PT_TLS segment, its module ID as used in TLS relocations, else zero */ void *dlpi_tls_data; /* The address of the calling thread's instance of this module's PT_TLS segment, if it has one and it has been allocated in the calling thread, otherwise a null pointer */ };
从上面的结构中我们可以看出,它包含以下信息:
从上面的结构中我们可以看出,它包含以下信息:
共享对象的基地址
共享对象的名称
指向对象中的 ELF 程序头数组的指针
程序头的数量
此外,如手册页中所述,ElfW () 宏将其参数转换为适合主机硬件体系结构的 ELF 数据类型的名称。例如,在 32 位平台上,ElfW(Addr)解析为Elf32_Addr,在 64 位平台上解析为Elf64_Addr。这只是确保 dl_phdr_info 结构正确表示加载的共享对象的底层 ELF 格式,而不管主机体系结构如何。

使用dl_iterate_phdr

正如我之前提到的,我们可以使用此函数循环遍历当前进程中加载的所有共享对象。这使我们能够获取这些共享对象的名称和基地址。以下是可为我们实现此目的的代码:
// callback function to be called for each shared object int callback(struct dl_phdr_info *info, size_t size, void *data) { if (info->dlpi_name && info->dlpi_name[0]) { printf("Shared object: %s @ %pn", info->dlpi_name, (void *)info->dlpi_addr); } else { printf("Shared object: (null)n"); } return 0; }

int main() { // iterate over the shared objects in the current process dl_iterate_phdr(callback, NULL); exit(EXIT_SUCCESS); }
dl_iterate_phdr函数遍历所有共享对象并为每个共享对象调用回调函数,如前所述,从下面运行程序可以观察到这一点:
$ ./main Shared object: (null) Shared object: linux-vdso.so.1 @ 0x7ffe6d5cc000 Shared object: /usr/lib/libc.so.6 @ 0x7f4c3cfe8000 Shared object: /lib64/ld-linux-x86-64.so.2 @ 0x7f4c3d1f8000
上面的输出显示了共享对象及其对应的基地址。空共享对象只是我们自己的程序,这是因为它始终是此列表中的第一个条目。但是,我们目前只对 libc.so.6 感兴趣,它为我们的程序提供了必要的功能。
现在我们有了这些信息,我们可以继续解析节头并在 libc 共享对象中定位动态符号表。

解析节头来定位符号表

为了找到符号表,我们将:
用于dl_iterate_phdr迭代当前进程中加载的所有共享对象
识别libc.so.6共享对象并将其映射到内存
解析 ELF 头和节头以lib.so.6定位 .dynsym
我们将从使用dl_iterate_phdr我们已经解释过的函数开始,因此我不会在这里详细介绍。下面是启动迭代并识别libc.so.6共享对象的代码:
void locate_symtable(const char *obj_path);

// callback function to be called for each shared object int callback(struct dl_phdr_info *info, size_t size, void *data) { if (info->dlpi_name && info->dlpi_name[0]) { printf("Shared object: %s @ %pn", info->dlpi_name, (void *)info->dlpi_addr); if (strstr(info->dlpi_name, "libc.so")) { locate_symtable(info->dlpi_name); } } else { printf("Shared object: (null)n"); } return 0; }
我们添加了一个函数,当识别出共享对象时,该函数会从该函数locate_symtable中调用。此函数会将 ELF 文件映射到内存中,并读取其标头以定位符号表。callback``libc.so
该函数如下locate_symtable
// function to locate the symbol table in the shared object void locate_symtable(const char *obj_path) { int fd = open(obj_path, O_RDONLY); if (fd < 0) { perror("open"); return; }

// map the ELF file into memory off_t file_size = lseek(fd, 0, SEEK_END); void *elf_base = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (elf_base == MAP_FAILED) { perror("mmap"); close(fd); return; }

// read ELF header ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)elf_base;

// read section headers ElfW(Shdr) *shdrs = (ElfW(Shdr) *)((char *)elf_base + ehdr->e_shoff); const char *shstrtab = (char *)elf_base + shdrs[ehdr->e_shstrndx].sh_offset;

// iterate over section headers to find the dynamic symbol table for (int i = 0; i < ehdr->e_shnum; i++) { if (shdrs[i].sh_type == SHT_DYNSYM) { const char *section_name = shstrtab + shdrs[i].sh_name; printf(" Found symbol table: %sn", section_name); } }

munmap(elf_base, file_size); close(fd); }
我们的定位_符号表函数执行以下操作:
1.以只读模式打开共享对象文件
2.将整个文件映射到内存中    
3.读取 ELF 头以获取诸如节头偏移量之类的信息
4.读取节头和节头字符串表以访问节名称及其属性
5.遍历所有节头以找到 SHT_DYNSYM 节
总而言之,这将使我们能够识别中的动态符号表libc.so.6,编译并运行时可以如下所示看到:
$ ./main                            Shared object: (null)              Shared object: linux-vdso.so.1 @ 0x7ffe6d5cc000              Shared object: /usr/lib/libc.so.6 @ 0x7f4c3cfe8000                Found symbol table: .dynsym <--- found dynamic symbol table              Shared object: /lib64/ld-linux-x86-64.so.2 @ 0x7f4c3d1f8000

解析并打印符号表中的所有符号

接下来,我们将扩展我们的locate_symtable函数来打印在共享对象的 dynsym 中找到的所有符号libc.so.6
为了实现这个功能我们需要做一些改变:
1.读取符号表及其对应的字符串表。
2.确定表中的符号数量。
3.迭代符号表的条目并打印每个符号名称。
void locate_symtable(const char *obj_path) { int fd = open(obj_path, O_RDONLY); if (fd < 0) { perror("open"); return; }

// map the ELF file into memory off_t file_size = lseek(fd, 0, SEEK_END); void *elf_base = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (elf_base == MAP_FAILED) { perror("mmap"); close(fd); return; }

// read ELF header ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)elf_base;

// read section headers ElfW(Shdr) *shdrs = (ElfW(Shdr) *)((char *)elf_base + ehdr->e_shoff); const char *shstrtab = (char *)elf_base + shdrs[ehdr->e_shstrndx].sh_offset;

// iterate over section headers to find the dynamic symbol table for (int i = 0; i < ehdr->e_shnum; i++) { if (shdrs[i].sh_type == SHT_DYNSYM) { const char *section_name = shstrtab + shdrs[i].sh_name; printf(" Found symbol table: %sn", section_name);

// read the symbol table ElfW(Sym) *symtab = (ElfW(Sym) *)((char *)elf_base + shdrs[i].sh_offset); const char *strtab = (const char *)elf_base + shdrs[shdrs[i].sh_link].sh_offset; int num_symbols = shdrs[i].sh_size / shdrs[i].sh_entsize;

for (int j = 0; j < num_symbols; j++) { printf(" Symbol: %sn", strtab + symtab[j].st_name); } } }

munmap(elf_base, file_size); close(fd); }
在像我们之前所做的那样识别符号表部分之后,我们:
1.读取 处的符号表shdrs[i].sh_offset及其对应的 处的字符串表shdrs[shdrs[i].sh_link].sh_offset
2.通过使用 将节大小除以每个条目的大小来确定符号的数量shdrs[i].sh_size / shdrs[i].sh_entsize
3.遍历符号表条目并使用字符串表打印每个符号的名称,如下所示:
$ ./main Shared object: (null) Shared object: linux-vdso.so.1 @ 0x7ffe6d5cc000 Shared object: /usr/lib/libc.so.6 @ 0x7f4c3cfe8000 Found symbol table: .dynsym Symbol: Symbol: _dl_argv Symbol: _dl_find_dso_for_object Symbol: __libc_enable_secure Symbol: _dl_deallocate_tls Symbol: __tls_get_addr Symbol: __libc_stack_end Symbol: _rtld_global_ro Symbol: _dl_signal_error Symbol: _dl_signal_exception Symbol: _dl_audit_symbind_alt Symbol: __tunable_is_initialized Symbol: _dl_rtld_di_serinfo Symbol: _dl_allocate_tls Symbol: __tunable_get_val Symbol: _dl_catch_exception Symbol: _dl_allocate_tls_init Symbol: _rtld_global Symbol: __nptl_change_stack_perm Symbol: _dl_audit_preinit Symbol: fgetc Symbol: pthread_attr_setscope Symbol: pthread_attr_getstacksize Symbol: envz_strip Symbol: pthread_attr_getstacksize ....[snip]....
现在我们能够查看所有符号,这使我们更接近实现动态函数解析的符号散列的目标。

为系统函数生成哈希值

首先,我们需要system使用基本的 Djb2 哈希函数为该函数生成一个哈希值:
unsigned long HASH(unsigned char *str) { unsigned long hash = 6543; int c;

while ((c = *str++)) { hash = ((hash << 5) + hash) + c; }

return hash; }

int main() { unsigned char *function_name = (unsigned char *)"system"; unsigned long hash = HASH(function_name); printf("hash of %s: %un", function_name, hash);

return 0; } system`这将为我们提供与之对应的字符串的哈希值:`2227611796

实现哈希函数

现在,我们可以将此哈希函数实现到我们的代码中,并且如果它与哈希匹配,则解析该函数,我们将通过预定义我们先前通过HASH哈希函数获得的系统哈希来执行此操作,然后将所有符号与该哈希进行比较,如果匹配,我们就知道它是系统符号。
以下是我们更新后的代码,以反映这一点:
// djb2 hash function uint32_t HASH(const char *str) { uint32_t hash = 6543; int c;

while ((c = *str++)) { hash = ((hash << 5) + hash) + c; }

return hash; }

// function to locate the symbol table in the shared object and resolve function addresses void locate_symtable(const char *obj_path, void *base_addr) { int fd = open(obj_path, O_RDONLY); if (fd < 0) { perror("open"); return; }

// map the ELF file into memory off_t file_size = lseek(fd, 0, SEEK_END); void *elf_base = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (elf_base == MAP_FAILED) { perror("mmap"); close(fd); return; }

// read ELF header ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)elf_base;

// read section headers ElfW(Shdr) *shdrs = (ElfW(Shdr) *)((char *)elf_base + ehdr->e_shoff); const char *shstrtab = (char *)elf_base + shdrs[ehdr->e_shstrndx].sh_offset;

// system hash calculated from other snippet uint32_t target_hash = 2227611796;

// iterate over section headers to find the dynamic symbol table for (int i = 0; i < ehdr->e_shnum; i++) { if (shdrs[i].sh_type == SHT_DYNSYM) { const char *section_name = shstrtab + shdrs[i].sh_name; printf(" Found symbol table: %sn", section_name);

// read symbol table ElfW(Sym) *symtab = (ElfW(Sym) *)((char *)elf_base + shdrs[i].sh_offset); const char *strtab = (const char *)elf_base + shdrs[shdrs[i].sh_link].sh_offset; int num_symbols = shdrs[i].sh_size / shdrs[i].sh_entsize;

// iterate each symbol to check for a matching hash with system for (int j = 0; j < num_symbols; j++) { const char *sym_name = strtab + symtab[j].st_name; uint32_t sym_hash = HASH(sym_name); if (sym_hash == target_hash) { printf(" Found target symbol: %sn", sym_name); // calculate the functions address (need to calculate this by also adding the base addr) void *func_addr = (void *)((char *)base_addr + symtab[j].st_value); printf(" Address of system: %pn", func_addr); } } } }

munmap(elf_base, file_size); close(fd); }

// callback function to be called for each shared object int callback(struct dl_phdr_info *info, size_t size, void *data) { if (info->dlpi_name && info->dlpi_name[0]) { printf("Shared object: %s @ %pn", info->dlpi_name, (void *)info->dlpi_addr); if (strstr(info->dlpi_name, "libc.so")) { locate_symtable(info->dlpi_name, (void *)info->dlpi_addr); } } else { printf("Shared object: (null)n"); } return 0; }

int main() { // iterate over the shared objects in the current process dl_iterate_phdr(callback, NULL); return 0; }
到目前为止我们所做的是:
1.添加了 Djb2 哈希函数来计算每个符号的哈希值    
2.添加了从我们的 Djb2 哈希计算器计算的预定义系统哈希
3.修改了我们的符号表解析功能,将哈希值与系统哈希值进行比较,如果哈希值匹配,则解析该函数。

执行已解析的函数

一旦我们运行更新后的代码,我们就可以看到它找到了系统地址。这也可以通过 GDB 确认:
gef➤ b *main+30 Breakpoint 1 at 0x15ef: file main.c, line 98. gef➤ r

........

Shared object: (null) Shared object: linux-vdso.so.1 @ 0x7ffdf0fe9000 Shared object: /usr/lib/libc.so.6 @ 0x75d6d33a3000 Found symbol table: .dynsym Found target symbol: system Address of system: 0x75d6d33f3f10 <--- system addr Shared object: /lib64/ld-linux-x86-64.so.2 @ 0x75d6d35b3000

Breakpoint 1, 0x00005555555555ef in main () at main.c:98



gef➤ x/x 0x75d6d33f3f10 0x75d6d33f3f10: 0xfa1e0ff3
正如 GDB 的输出所示,我们得到了 system() 的地址!现在,我们确实已经设置好了一切,可以使用这个解析函数来执行命令,我们可以简单地编辑代码来执行此操作。我们将通过系统执行 uname 命令,需要注意的是,这个字符串将出现在二进制文件中,但有办法解决这个问题。
首先,我们定义一个与系统具有相同函数签名的函数指针:
typedef int (*system_func)(const char *);
然后在函数内部locate_symtable,一旦我们找到了的地址system,我们就会进行转换函数地址到定义的函数指针类型,在我们的例子中它是system_func
system_func _system = (system_func)func_addr;
最后我们使用函数指针调用系统函数执行uname命令:
_system("uname");
总的来说,它看起来像这样:
if (sym_hash == target_hash) { printf(" Found target symbol: %sn", sym_name); // calculate the functions address (need to calculate this by also adding the base addr) void *func_addr = (void *)((char *)base_addr + symtab[j].st_value); printf(" Address of system: %pn", func_addr);

// define a function pointer type for system typedef int (*system_func)(const char *); system_func _system = (system_func)func_addr;

// execute uname using the resolved system function _system("uname"); }
一旦执行,我们应该输出来自以下的值uname
$ ./main

Shared object: (null) Shared object: linux-vdso.so.1 @ 0x7ffe1f9d3000 Shared object: /usr/lib/libc.so.6 @ 0x7028ce28e000 Found symbol table: .dynsym Found target symbol: system Address of system: 0x7028ce2def10 Linux <--- uname executed Shared object: /lib64/ld-linux-x86-64.so.2 @ 0x7028ce49e000
现在,我们还可以检查该二进制文件的符号,以表明 system() 实际上并不存在。该二进制文件也是使用符号编译的:
$ readelf -Ws ./main

Symbol table '.dynsym' contains 16 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dl_iterate_phdr@GLIBC_2.2.5 (2) 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.34 (3) 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mmap@GLIBC_2.2.5 (2) 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lseek@GLIBC_2.2.5 (2) 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND close@GLIBC_2.2.5 (2) 9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND munmap@GLIBC_2.2.5 (2) 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@GLIBC_2.2.5 (2) 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.2.5 (2) 13: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 14: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strstr@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 36 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c 2: 0000000000000000 0 FILE LOCAL DEFAULT ABS 3: 0000000000003de0 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC 4: 00000000000020ac 0 NOTYPE LOCAL DEFAULT 17 __GNU_EH_FRAME_HDR 5: 0000000000003fe8 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_ 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dl_iterate_phdr@GLIBC_2.2.5 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.34 8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 9: 0000000000004050 0 NOTYPE WEAK DEFAULT 24 data_start 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 11: 0000000000004060 0 NOTYPE GLOBAL DEFAULT 24 _edata 12: 0000000000001608 0 FUNC GLOBAL HIDDEN 15 _fini 13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mmap@GLIBC_2.2.5 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lseek@GLIBC_2.2.5 16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND close@GLIBC_2.2.5 17: 0000000000004050 0 NOTYPE GLOBAL DEFAULT 24 __data_start 18: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 19: 0000000000004058 0 OBJECT GLOBAL HIDDEN 24 __dso_handle 20: 0000000000002000 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used 21: 0000000000004068 0 NOTYPE GLOBAL DEFAULT 25 _end 22: 00000000000010d0 38 FUNC GLOBAL DEFAULT 14 _start 23: 000000000000120f 793 FUNC GLOBAL DEFAULT 14 locate_symtable 24: 0000000000004060 0 NOTYPE GLOBAL DEFAULT 25 __bss_start 25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND munmap@GLIBC_2.2.5 26: 00000000000015e9 31 FUNC GLOBAL DEFAULT 14 main 27: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@GLIBC_2.2.5 28: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.2.5 29: 0000000000004060 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__ 30: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 31: 0000000000001528 193 FUNC GLOBAL DEFAULT 14 callback 32: 00000000000011c9 70 FUNC GLOBAL DEFAULT 14 HASH 33: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 34: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init 35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strstr@GLIBC_2.2.5
正如上面 readelf 的输出所示,不存在 system() 符号。在对二进制文件运行字符串时也可以看到这一点。找到的唯一条目与 printf() 语句有关。
$ strings ./main | grep -i sys             Address of system: %p

注意事项

如果二进制文件是静态编译的,则此方法不起作用。dl_iterate_phdr 函数用于遍历动态加载到可执行文件中的共享对象列表,由于静态链接程序不会加载libc共享对象,因此没有要dl_iterate_phdr迭代的内容。

结论

通过遵循本文章上面的代码片段,您应该能够了解如何为基于 Linux 的有效负载实现简单的符号哈希处理。本博客中使用的代码仅用于演示目的,以帮助理解概念以及演练。可以在此处找到与本博客配套的更完整的概念证明希望本文章能够证明是有用的,并强调了一种可能熟悉的 Linux 符号哈希处理方法。随着 EDR 和预防系统在 Linux 和 MacOS 等其他平台上不断发展,我们认为花时间开发针对不同平台上的有效负载开发现有问题的解决方案非常重要。

参考链接

https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.htmlhttps://linux.die.net/man/5/elfhttps://blogs.oracle.com/solaris/post/inside-elf-symbol-tableshttps://stackoverflow.com/a/15779309

打个广子

我们拥有专业的团队,可承接渗透测试,攻防演练,应急响应、钓鱼演练、ctf培训等比赛项目    
 

原文始发于微信公众号(影域实验室):重生之我在干免杀-逃避 Linux 恶意软件静态分析的新方法

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月30日15:22:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   逃避 Linux 恶意软件静态分析的新方法http://cn-sec.com/archives/3014833.html

发表评论

匿名网友 填写信息