首页
论坛
课程
招聘
[调试逆向] [病毒木马] [原创] 小试牛刀·手动构建HelloWorld弹窗可执行文件
5天前 1597

[调试逆向] [病毒木马] [原创] 小试牛刀·手动构建HelloWorld弹窗可执行文件

5天前
1597

工具说明

参照书籍:《Windows PE权威指南》;
文件:附件中(文件密码:apebro);
使用工具:WinHex、CFF、StudyPE+、OD、IDA、MSDN;


摘要

1、有时候会需要干净的小exe文件进行魔改;

2、无关紧要的地方就用CC(int3)或者90(nop)的地方覆盖了;

3、关键位置我都用红框给框起来了;

开始

使用WinHex手动构建一个PE;

 

image-20210406005843355image-20210406005912096

  • 新建一个空白txt文件,然后改exe后缀,直接拖进WinHex

image-20210406010248744

 

因为是手动构造,所以就舍弃了DOS头下面的一堆垃圾数据了;

  • 构造NT Headers

image-20210406010505752

  • 构造File Header

image-20210406011433020

  • 构造Optional Header

image-20210406011722167

  • 构造一个必要的代码页节表,两行半;

image-20210406012009509

 

最重要的四个参数标注出来,这四个数都是有来头的,并不是瞎设的;

  • 我们先来查看Optional Header中的三个重要参数;

image-20210406012338302

 

这分别是:

1
2
3
1. `镜像基址`:`0x00400000`;
2. `镜像对齐`:`0x00001000`;
3. `文件对齐`:`0x00000200`;
  • 所以我们就要从节表处对齐到0x000001F0,对标文件对齐;

image-20210406012855971

 

这时候就可以明白代码页节表的文件起始地址就是从0x00000200开始的;

  • 这个时候我们需要构建我们的导入表了,因为.text节区空间很大,所以也放里面了;

  • 首先在导入表节表目录处设置一下。

image-20210406025507546

 

image-20210406013634693

 

这里解释一下:

  • 导入符号表为零,系统就会直接引用IAT表,这是系统特性;

  • 然后看导入链接库名(RVA:0x00001030,FOA:0x00000230),这是一个C语言类型的字符串,\0结尾,得到导入的动态库名为user32.dll

  • 再看IAT表间接地址(RVA:0x00001050,FOA:0x00000250),得到IAT表地址(RVA:0x00001040,FOA:0x00000240);

  • 这里就是一个重点了,函数名字符串并不是从0x00000240开始的,而是从0x00000243开始的。因为这里的结构类型如下:

    1
    2
    3
    4
    IMAGE_IMPORT_BY_NAME struct{
        word Hint;        // 函数编号;
        dword Name;        // 函数名字符串;
    }

    所以得到函数名MessageBoxA

  • 这时候就用到另外一个神器OD了;
  • 用OD随便打开一个非我们正在构建的可执行文件,因为我们手动构建的PE文件有些字段被设置,所以OD并不能正常打开,除非有插件;

  • 然后准备进行以下结构的汇编代码插入;

1
2
3
4
5
6
push    0              
push    offset Caption
push    offset Text    
push    0              
call    MessageBoxA
retn

这里的CaptionText两个字符串我们需要先找个地方存放;

 

image-20210406020738686

 

然后根据约定,补齐0x200的倍数;

 

这里我们补齐到0x3FF;

 

image-20210406030317339

 

然后选定代码存放位置;

 

image-20210406030449216

 

这里的0x00000280FOA,我们需要换算成RVA,也就是0x00001080;

 

Optional Header中找到OEP,这里的OEP是字段,并不是脱壳时说的原始入口点;

  • 修改OEP字段;

image-20210406030922046

 

保存后,执行文件;

  • 没错,肯定是失败的;

image-20210406031049278

 

但是问题不大,我们要的就是如此,别退掉这个窗口;

  • 重新用WinHex打开内存,找到test程序,进入;

image-20210406031321364

  • 这里,我们是要验证我们导入表的IAT表地址正确加载;

首先是文件地址;

 

image-20210406031651319

 

然后是内存地址,这个我们要看我们导入表的节表内容,查看加载地址;

 

image-20210406031857592

 

既然导入表没问题了,我们剩下的就是要完善我们的程序了。

  • 打开OD使用attach也就是附加,找到我们还没关上的错误窗口;

image-20210406032227161

 

进入后很明显不是正常的入口地址;

 

image-20210406032306787

 

问题不大,莫慌;

  • ctrl+G跳转到我们刚刚设置的入口点,记得把RVA(0x00001080)转换为RA(0x00401080);

image-20210406033153706

  • 下方的内存界面跳转到RA(0x00401000),查看内存中我们写入的两个字符串参数;

image-20210406033338641

 

然后开始,我们要写入我们的代码了;

1
2
3
4
5
6
push 0
push 00401070
push 00401060
push 0
call [00401050]
retn

image-20210406033945458

 

结果就是如此;

  • 然后我们右击,选择copy to executable—>selection;
  • 进入另外一个窗口,然后右击,选择backup—>save data to file。然后修改命名保存;

image-20210406034710690

 

image-20210406034743290

 

保存后退出所有界面,找到这个test1.exe文件,双击运行;

 

image-20210406034832624

成功

image-20210406034953182

补贴·浅谈导入表问题

一觉醒来,坛主大佬亲临,并且指出了并不能跨平台的问题;

  • 我赶紧的把文件拉到WIN10 64位系统运行;

image-20210406144002353

 

是可以运行的;

  • 再拖到windows XP系统;

一闪而过,可见的是hello world!字符串并没有传进去;

 

经过多方查找问题,终于找到问题所在;

 

需要将图中框起来的位置(Win32VersionValue字段)用0填充;

 

image-20210406174511398

 

就运行起来了;

 

image-20210406175542890

  • 再拖到windows7 64位系统;

image-20210406145250106

 

也是能运行起来的;

这里我就懵逼了,坛主大佬指的到底是哪里问题呢,还是先自己找一下,那就先复习一下导入表吧;

  • 这里数据目录找导入表上面有,我就不赘述了;

  • 我再复习了一下导入表的导入流程(以test1为例);

image-20210406145956340

 

这里的结构是:

1
2
3
4
5
6
7
8
9
10
IMAGE_IMPORT_DESCRIPTOR struct{
    union{                        // dword     000h -1;
        DWORD Characteristics;   
        DWORD OriginalFirstThunk;
    }
    DWORD TimeDateStamp;        // dword    004h -时间戳(可随便写);
    DWORD ForwarderChain;        // dword    008h -链表的前一个结构(可随便写);
    DWORD Name1;                // dword    00ch -指向链接库名字的指针;
    DWORD FirstThunk;            // dword    010h -2;
}
  • 与之对应的参数分别是(**要注意的是这些都是RVA,在文件中要换算成FOA
1
2
3
4
5
6
7
IMAGE_IMPORT_DESCRIPTOR struct{
    (RVA)00000000-->(FOA)00000000        // dword     000h -1;
    (RVA)CCCCCCCC        // dword    004h -时间戳(可随便写);
    (RVA)CCCCCCCC        // dword    008h -链表的前一个结构(可随便写);
    (RVA)00001030-->(FOA)00000230        // dword    00ch -指向链接库名字的指针;
    (RVA)00001050-->(FOA)00000250        // dword    010h -2;
}

说太多容易乱,我直接就开始白话了;

 

这里要说一下的是桥1指向的地址列表被称为INT表,桥2指向的地址列表被称为IAT表;

 

操作系统如何读取PE的导入表

  • 操作系统检查IAT(桥2)(0x00000210),指向的地址是0x00000250,目标0x00001040(这是个RVA),目标非零,所以这个导入表是有效的

image-20210406155627168

 

这里提一句,如果目标为0x00000000,那么就是continue操作,搜索下一个导入表,这里涉及一个无效导入表的奇技淫巧;

  • 再来访问库名称0x00001030(RVA),换算成FOA(0x00000230),目标是user32.dll\0字符串,系统获得库名称,调用loadLibrary加载动态库;

image-20210406160056003

 

这里提一句,加载字符串时候,不能超过260个字节,如果超过了,即使是有效导入表,也会被操作系统拒绝加载,这里涉及一个反调试手段的奇技淫巧;

  • 接着查询桥1INT表,如果为0,则调用桥2IAT表,将桥2指向地址(RVA0x00001050->FOA0x00000250)当成函数名称地址;

image-20210406165154120

 

而FOA0x00000250这个位置在加载后会被重定位API地址;

 

如果IAT00000000,系统判断是非法PE,直接停止后续运行;

 

找到的字符串,前一个word的函数序号,操作系统不参考,所以我填了nop(90);

  • 找到了函数名(MessageBoxA),执行GetProcessAddress,如果错误了,就中断执行;

  • 如果执行成功了,就访问IAT表(RVA0x00001050->FOA0x00000250)地址,并修正地址;

image-20210406171819699

 

修正后:

 

image-20210406171905494

  • 也就是说call [00401050]这句,中的IAT表地址0x00401050指向的函数地址是被修正过的,只要是支持PE可执行文件并且有user32.dll库的系统都能执行;

整个流程理下来,还是没搞清楚大佬说的写死IAT是什么意思;

 

现在只能去反汇编一个C语言写的MessageBox程序,一探究竟;

1
2
3
4
5
6
7
#include <stdio.h>
#include<Windows.h>
 
void main()
{
    MessageBox(0,"Hello world","kevin",0);
}
  • 使用REelease版编译;

image-20210406173050970

  • 先用CFF查看,注意这里是RVA地址;

image-20210406174050555

 

打开IDA看一下,这里是RA地址;

 

image-20210406174143932

 

image-20210406174202940

 

所以,编译器制作的PE文件也只是通过IAT跳转,系统加载的时候通过修正,得到当前系统的函数地址。

 

故并不存在跨平台问题

总结

手动制作PE文件是个非常有意思的事情;

 

原本只是为了写另外一篇帖子,发现需要用到这个,如果写在一起又太臃肿,所以单独水一贴吧;

 

关于大佬说的写死IAT的跨平台问题,我并没有找到问题,如果有大佬能指正,感激不尽;


安卓应用层抓包通杀脚本发布!《高研班》2021年6月班开始招生!

最后于 3天前 被平头猿小哥编辑 ,原因: 上传附件
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (8)
雪    币: 1372
活跃值: 活跃值 (333)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
十年后 活跃值 5天前
2
1
围观,前排点赞!
雪    币: 1849
活跃值: 活跃值 (4675)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 5天前
3
1
call [00401050] //你直接写死IAT了,不能跨平台,建议 将整个 输入表重建一份
雪    币: 1480
活跃值: 活跃值 (900)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
平头猿小哥 活跃值 5天前
4
0
嗯,感谢大佬指出这个问题。
雪    币: 1480
活跃值: 活跃值 (900)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
平头猿小哥 活跃值 5天前
5
0
kanxue call [00401050] //你直接写死IAT了,不能跨平台,建议 将整个 输入表重建一份
嗯,感谢大佬指出这个问题。
雪    币: 1480
活跃值: 活跃值 (900)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
平头猿小哥 活跃值 5天前
6
0
kanxue call [00401050] //你直接写死IAT了,不能跨平台,建议 将整个 输入表重建一份
大佬你好,我找了一下并且尝试探索,并不存在IAT表写死的问题,并且把过程补帖在这个帖子下方。还请指点问题所在;
雪    币: 1849
活跃值: 活跃值 (4675)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 3天前
7
0
平头猿小哥 大佬你好,我找了一下并且尝试探索,并不存在IAT表写死的问题,并且把过程补帖在这个帖子下方。还请指点问题所在;
你之前文章中没有提到IMAGE_IMPORT_DESCRIPTOR 结构,以为没构造导入表,直接IAT里跳到相应函数中去了。
雪    币: 1480
活跃值: 活跃值 (900)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
平头猿小哥 活跃值 3天前
8
0
嗯嗯,是我没有讲清楚,是我生疏了,感谢大佬,让我又复习了一边导入表;
雪    币: 1480
活跃值: 活跃值 (900)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
平头猿小哥 活跃值 3天前
9
0
kanxue 你之前文章中没有提到IMAGE_IMPORT_DESCRIPTOR 结构,以为没构造导入表,直接IAT里跳到相应函数中去了。
嗯嗯,是我没有讲清楚,是我生疏了,感谢大佬,让我又复习了一边导入表;
游客
登录 | 注册 方可回帖
返回