so加固之手动脱upx

ELF可执行文件脱壳

使用upx对ELF文件进行加壳,加壳后的文件不包含动态链接信息,也就是说加壳后的可执行文件是一个纯静态链接文件。对于纯静态链接的可执行文件而言,linux内核在将此elf文件加载到内存后会直接修改应用层入口为此elf文件的入口函数,不再需要动态链接器的参与。

加壳后的elf文件的壳入口就是upx的壳代码,这部分代码会申请内存并保存原elf文件压缩的数据和upx壳的解压缩代码。加壳elf文件的入口函数执行完之后就会跳转到upx壳的解压缩代码中,解压缩代码会将压缩的数据解密并map到默认映射地址空间中。紧接着壳代码还会加载原elf文件的链接器程序并设置好对应的参数跳转到链接器程序的入口地址,这部分工作本来是由linux内核代码完成的。

当运行到linker入口时就可以对so内存地址空间进行dump,因为此时linker程序还未运行,也就是原elf文件的重定位信息并没有被其修改,所以dump时不需要对重定位数据进行修复。对于elf文件而言section信息不会被加载到内存所以可以直接清空。(idc脚本)

//获取基地址
ImageBase = AskAddr(0, "请输入基地址");
Message("ImageBase is :%x\n", ImageBase);
auto filename = AskFile(1, "*", "dump文件另存为");
Message("dump file :%s\n", filename);
auto fd = fopen(filename,"wb");
if(fd != 0)
{
        e_phoff=ImageBase+Dword(ImageBase+0x1C);
        e_phnum=Word(ImageBase+0x2C);
               
	    //WriteFile PT_LOAD
        auto i;
        for(i = 0; i < e_phnum; i++)
        {
                if (Dword(e_phoff)==PT_LOAD )//PT_LOAD类型的会加载到内存中
                {
                        Program_Fileoffset=Dword(e_phoff+0x4);	                        //file offset
                        Program_start_address=ImageBase + Dword(e_phoff+0x8);	        //virtual address
                        Program_end_address=Program_start_address+Dword(e_phoff+0x14);	//结束地址                          
                        WriteFile(fd,Program_start_address,Program_end_address,Program_Fileoffset);            
                }
                e_phoff=e_phoff+0x20;
        }
        
        //将SHT(section header table)信息清除
        fseek(fd,0x30,0);
        fputc(0x00,fd);
        fputc(0x00,fd);
      
        fseek(fd,0x20,0);
        fputc(0x00,fd);
        fputc(0x00,fd);
        fputc(0x00,fd);
        fputc(0x00,fd);
        fclose(fd);
}

android so库文件脱壳

android so库文件也是标准的elf文件格式,但是与elf可执行文件的upx脱壳区别很大。因为so文件在被linker链接器加载后会被重定位,所以在dump时需要对R_ARM_RELATIVE类型重定位数据进行修复(R_ARM_GLOB_DAT,R_ARM_ABC32,R_ARM_JUMP_SLOT类型的重定位数据不用管,因为他们在文件中的值不影响ida分析,ida自己是会重新解析并对这些值进行修正的)

//修复R_ARM_RELATIVE类型的重定位信息。
currentaddr = rel_dyn_startaddr;
e_phoff = ImageBase+Dword(ImageBase+0x1C);
while(Dword(currentaddr + 4) == R_ARM_RELATIVE)
{
    e_phoff = ImageBase + Dword(ImageBase + 0x1C);
    Elf32_Rel_offset = Dword(currentaddr);
    Elf32_Rel_address = ImageBase + Dword(currentaddr);

    //获得对应的.got表项的文件偏移
    for (i = 0; i < e_phnum; i++) {
        if (Dword(e_phoff) == PT_LOAD)//PT_LOAD类型的会加载到内存中
        {
            Program_Fileoffset = Dword(e_phoff + 0x4);                            //file offset
            Program_start_address = ImageBase + Dword(e_phoff + 0x8);             //virtual address
            Program_end_address = Program_start_address + Dword(e_phoff + 0x14);  //结束地址
            if ((Elf32_Rel_address >= Program_start_address) && (Elf32_Rel_address <= Program_end_address))
            {
                Elf32_Rel_fileOffset = Program_Fileoffset + (Elf32_Rel_address - Program_start_address);
                //获得文件偏移后设置文件指针
                fseek(fd, Elf32_Rel_fileOffset, 0);
                Message(" Elf32_Rel_address= 0x%x\n", Elf32_Rel_address);
            }
        }
        e_phoff = e_phoff + 0x20;
    }


    //开始写入数据(修复.got表)
    writeByte = (Dword(Elf32_Rel_offset + ImageBase) - ImageBase) & 0x000000FF;
    fputc(writeByte, fd);
    Message("writeByte = 0x%x\n", writeByte);

    writeByte = ((Dword(Elf32_Rel_offset + ImageBase) - ImageBase) & 0x0000FF00) >> 8;
    fputc(writeByte, fd);
    Message("writeByte = 0x%x\n", writeByte);

    writeByte = ((Dword(Elf32_Rel_offset + ImageBase) - ImageBase) & 0x00FF0000) >> 16;
    fputc(writeByte, fd);
    Message("writeByte = 0x%x\n", writeByte);

    writeByte = ((Dword(Elf32_Rel_offset + ImageBase) - ImageBase) & 0xFF000000) >> 24;
    fputc(writeByte, fd);
    Message("writeByte = 0x%x\n", writeByte);

    currentaddr = currentaddr + Elf32_Rel_size;
}

同时因为android 7之后增加了ELF section header检查,所以在抹去了section后程序是无法正常运行的,但是就静态分析而言是完全没问题的。下图就是利用idc脚本手动脱壳并去除了section信息后的程序。

posted @ 2023-02-01 17:50  怎么可以吃突突  阅读(919)  评论(0编辑  收藏  举报