Lab 9-3
问题
1.Lab09-03.exe导入了哪些DLL?
解答: 我们还是跟着书上的步骤开始,先看看Lab09-03.exe
导入了哪些DLL
这里我们可以看出,导入了KERNEL32.DLL
、NETAPT32.DLL
、DLL1.DLLL
和DLL2.dll
这四个DLL
,然后我们去IDA
里面看看
我们跟随书中做法,找到LoadLibrary
调用并检查
反正我是找不到哪里查看LoadLibrary
的两个交叉引用
第一个调用call
是这里
这里的入参是DLL3.dll
,这里从DLL3.dll
导入函数
然后第二个调用在这里
这里的入参是user32.dll
第三个call
和第一个一样
第四个call
和第二个一样
然后这就是书中所谓的交叉引用,这些DLL
在程序执行过程中可能被动态的加载
所以这个题的答案如上所述那四个DLL
2.DLL1.dll、DLL2.dll、DLL3.dll要求的基地址是多少?
解答: 我们按照书中的方法做一遍看看
我们用打开PEview
来查看这个DLL
请求的基准地址(程序的基准地址是值加载这个DLL
的时候去哪里找他的那个地址)
按照书上的说法是这样看
然后请求的基准地址是0x100000000
,也就是Data
那里的值
然后我们再打开DLL2.dll
这里的请求的地址也是0x10000000
的基准地址
DLL3.dll
也是0x10000000
3.当使用OllyDbg调试Lab09-03.exe时,为DLL1.dll、DLL2.dll、DLL3.dll分配的基地址是什么?
解答: 还是根据书上的办法我们走一遍
根据我们第一问的分析,DLL3.dll
是动态加载的,只有DLL1.dll
和DLL2.dll
是一开始就被导入的
所以DLL1.dll
和DLL2.dll
会被马上加载
这里要动态调试,用的是OllyDbg
我们先在第一个调用LoadLibrary
的地址加一个断点,也就是这里
第一个call
的调用是这里0x401041
我们在OD
里面找到这里加上断电
我们在这里设置断点,然后运行
然后运行暂停之后打开View->Memory
这个地方,来查看此时的内存地址
这是我的显示,我们在这里面找,会找到一个叫DLL1
的导入
我们可以看到,在这里,我们的DLL1
获得了它期望的基准地址,为0x10000000
(7个0)
然后我们找DLL2
然后我们可以看到DLL2
获得的基准地址是0x3d0000
(各个机器上的可能不同,如作者机器上就是0x320000
)
然后我们继续找DLL3
这里没找到,我们执行下一步LoadLibrary
这个函数
刚刚我们断点是在LoadLibrary
这里就停住了,所以没有DLL3.dll
的动态导入
然后我们单步执行之后就会发现内存中已经导入了DLL3.dll
这里我们放大一点看看
这里的DLL3
的基准地址就是0x580000
所以这题的答案就是上面这些,答案不同机器可能不同,但是方法知道就行了
4.当Lab09-03.exe调用DLL1.dll中的一个导入函数时,这个导入函数都做了什么?
解答: 我们开始找这个DLL1
的导入函数
用IDA
来分析这个代码
可以看出在IDA
中下显示的是三个函数,其中有一个是从DLL1.dll
中导入的
其他两个都DLL2.dll
导入的函数
这里的地址是0x405000
然后我们就开始用IDA
来反汇编DLL1.dll
这里并没有找到这个printf
类似的输出函数
然后我们看看这个dword_10008030
是个什么
我们可以看到这个有个字符串
"DLL 1 mystery data %d"
这个应该某个printf
系列的函数要调用的,因为我们看到%d
这个东西
然后我们研究一个这个dword_10008030
的交叉引用
我们可以看到这个的引用
第一个就是我们刚刚的DLLMain
函数
下一个就是在DLL1Print
导出函数里面的
DLL1Print
函数的大概像这样
在调用sub_10001038
函数之前,压栈了两个参数,一个是eax
,其实就是dword_10008030
,还有一个aDll1MysteryDat
,其实这两个值一开始是指向一个地址的相同值,但是后来不是经过DLLMain
函数的一顿操作之后
eax
是GetCurrentProcessId
的返回值,所以这时候经过MOV
操作之后,dword_10008030
已经成为了这个返回值的
所以这时候入参一个是pid
,一个是那个字符串
所以这里我们可以猜测这个sub_10001038
其实就是某个printf
系列函数,用于格式化输出
然后我们可以进去这个函数看看到底是什么样的
这里又调用了sub_10001439
,调用之前我们可以看到这个函数的入参有一个FILE*
类型的指针,所以这个函数很有可能是fprintf
,用于将一个字符串输出到一个文件
然后我们打开DLL2.dll
我们可以发现这个DLL2
的大概结构是这样,有两个导出函数,一个是DLL2ReturnJ
,然后一个是DLL2Print
我们看看DLL2Print
这个导出函数
这里有个字符串DLL 2 mystery data %d\n
,之前有一个全局变量dword_1000B078
我们查查这个全局变量的引用
这里有三个引用,其中一个在DLLMain
里面,一个在DLL2Print
里面,一个在DLL2Return
里面
在DLLMain
里面,这个全局变量存储的是CreateFileA
之后返回的句柄,打开的文件名是temp.txt
在DLL2ReturnJ
导出函数中,这里将eax
的值用这个全局变量复制之后,然后返回给调用者
所以这里的DLL2Print
这里的sub_1000105A
是printf
系列函数中的一个
所以DLL2Print
的作用就是输出temp.txt
的句柄,然后这个句柄被DLL2ReturnJ
返回
然后我们回到Lab09-03.exe
里面
我们看到这里调用完DLL2ReturnJ
之后 ,就把返回值eax
赋值给了hFile
,也就是书上的hObject
这个东西
然后又传递给了WriteFile
之后,那个恶意网址的内容就被写入了文件中,也就是temp.txt
里面
然后我们继续看下一个调用
然后下一个调用是很正常的关闭句柄,我们继续往下
然后这个调用是LoadLibraryA
载入DLL3.dll
然后下一个调用GetProcAddress
动态解析DLL3Print
和DLL3GetStructure
我们似乎还没有分析过DLL3.dll
,现在我们开始用IDA
开始打开看看
DLL3.dll
打开之后就是这样的
我们可以先看看DLLMain
函数的样子
这里第一个调用是MultiByteToWideChar
这个东西
在MSDN
里面,这个MultiByteToWideChar
是这样定义的
Maps a character string to a UTF-16 (wide character) string. The character string is not necessarily from a multibyte character set.
将字符串映射到UTF-16(宽字符)字符串。字符串不一定来自多字节字符集
这是一个对字符串进行操作的函数
而这个字符串是这样的
ping www.malwareanalysisbook.com
这看着像一个命令的样子
然后我们出来,看看其他的导出函数是什么样的
点击小+号就可以出来了一个
这是DLL3Print
这个函数
可以看出这个函数有个字符串
DLL 3 mystery data %d\n
那个没标注的函数sub_1001087
就是一个类printf
的函数
然后我们点击第三个小+号就出来DLL3GetStructure
这个导出函数了
这里是将dword_1000B0A0
赋值给了eax
指向的地址
然后返回eax
然后回到Lab09-03.exe
里面,继续分析
然后这里先调用了NetScheduleJobAdd
,光看名字貌似是个网路任务添加的函数,我们看看MSDN
里面的解释
The NetScheduleJobAdd function submits a job to run at a specified future time and date. This function requires that the schedule service be started on the computer to which the job is submitted.
NetScheduleJobAdd函数提交一个作业在指定的未来时间和日期运行。此功能要求在提交作业的计算机上启动计划服务。
没错的,这个函数会当时开启一个服务
然后这个函数的入参解释是这样的
NET_API_STATUS NetScheduleJobAdd(
_In_opt_ LPCWSTR Servername,
_In_ LPBYTE Buffer,
_Out_ LPDWORD JobId
);
根据IDA
里面的显示和对照MSDN
,我们可以分析得出一下结论
NET_API_STATUS NetScheduleJobAdd(
_In_opt_ LPCWSTR Servername = 0,
_In_ LPBYTE Buffer = [ebp + Buffer],
_Out_ LPDWORD JobId = [ebp + JobId]
);
然后设置完NetScheduleJobAdd
之后
开始调用Sleep
函数休眠2710h
秒
书上说,这个Buffer
是DLL3.dll
中的导出函数DLL3GetStructure
的返回值
我们来分析一下
调用完GetProcAddress
之后,返回值被保存在eax
里面,然后被保存到了[ebp+var_10]
里面
然后通过lea
把[ebp+Buffer]
的值的地址保存到了edx
然后在把这个edx
压栈之后,开始调用[ebp+var_10]
也就是eax
也就是DLL3GetStructure
这个导出函数
因为我们知道这个[ebp+Buffer]
是_In_ LPBYTE
结构,书上是说是AT_INFO
,但是我们查MSDN
显示的已经不是这个结构了
所以在DLL3GetStructure
导出函数里面我们可以清晰的看到,入参是[ebp+arg_0]
也就是[ebp+Buffer]
然后经过一个mov
指令,将dword_1000B0A0
赋值给了[ebp+Buffer]
所以这个dword_100B0A0
的结构应该就是Buffer
的结构也就是_In_ LPBYTE
(书上是AT_INFO
)
然后我们这个为什么分析完了之后,我们按照书上的做一下
我们再用IDA
打开DLL3.dll
,然后找到导出函数DLL3GetStructure
在结构体窗口按INSERT
键添加标准的结构体AT_INFO
(书上是这么说)
结构体窗口在这里
然后我们按INSERT
我们先试试这个AT_INFO
存不存在
我们这里点Add standard structure
然后我们查找这个结构体
这个结构体是存在的
我们查查_In_ LPBYTE
这个结构体
并不存在
然后我们再去查看MSDN
,发现下面有这么一句话
ok,的确是AT_INFO
,然后添加这个结构
然后我们来到导出函数界面
双击这个结构体之后出现这些
找到这个东西Struct var
选择结构体
然后来到DLLMain
这里
我们就会发现这里的显示更加明白了
对比一下以前的DLLMain
然后我们就知道这个DLL3GetStructure
大概是干什么的了
这里初始化了一个AT_INFO
的结构之后,返回给Lab09-03.exe
我们看一下具体的AT_INFO
的结构定义
typedef struct _AT_INFO {
DWORD_PTR JobTime;
DWORD DaysOfMonth;
UCHAR DaysOfWeek;
UCHAR Flags;
LPWSTR Command;
} AT_INFO, *PAT_INFO, *LPAT_INFO;
根据IDA
里面的显示,我们可以完成如下的结构赋值
typedef struct _AT_INFO {
DWORD_PTR JobTime = 36EE80h;
DWORD DaysOfMonth = 0;
UCHAR DaysOfWeek = 7Fh;
UCHAR Flags = 11h;
LPWSTR Command = "ping www.malwareanalysisbook.com";
} AT_INFO, *PAT_INFO, *LPAT_INFO;
这里比较容易理解的就是Command
元素了,其他的我们一个一个对对看
JobTime
Type: DWORD_PTR
A pointer to a value that indicates the time of day at which the job is scheduled to run. The time is the local time at a computer on which the schedule service is running; it is measured from midnight, and is expressed in milliseconds.
指向指示作业计划运行的时间的值的指针。时间是运行日程安排服务的计算机的本地时间;它是从午夜开始测量的,用毫秒表示。
JobTime = 36EE80h
翻译过来就是3600000d
毫秒,3600d
秒,然后60
分钟,最后就是一个小时
也就是从午夜后的一个小时开始运行这个东西
然后下一个
DaysOfMonth
Type: DWORD
A set of bit flags representing the days of the month. For each bit that is set, the scheduled job will run at the time specified by the JobTime member, on the corresponding day of the month. Bit 0 corresponds to the first day of the month, and so on.
The value of the bitmask is zero if the job was scheduled to run only once, at the first occurrence specified by the JobTime member.
代表月份日期的一组比特标志。对于设置的每个位,计划的作业将在JobTime成员指定的时间运行,在该月的相应日期。位0对应于月份的第一天,依此类推。
如果在JobTime成员指定的第一个事件中计划只运行一次,则位掩码的值为零。
根据上面的解释,DaysOfMonth = 0
代表的意思就是在每个月的第一天运行
然后
DaysOfWeek
Type: UCHAR
A set of bit flags representing the days of the week. For each bit that is set, the scheduled job will run at the time specified by the JobTime member, on the corresponding day of the week. Bit 0 corresponds to Monday, and so on.
The value of the bitmask is zero if the job was scheduled to run only once, at the first occurrence specified by the JobTime member.
代表星期几的一组比特标志。对于设置的每个位,计划的作业将在JobTime成员指定的时间在相应的星期几运行。位0对应于星期一,以此类推。
如果在JobTime成员指定的第一个事件中计划只运行一次,则位掩码的值为零。
那这个DaysOfWeek = 7Fh
,其实就是127d
,这个值明显大于0x6h
也就是星期天,书上说的每个星期任意一天的1:00
运行Command
然后我们看Flag
A set of bit flags describing job properties.
When you submit a job using a call to the NetScheduleJobAdd function, you can specify one of the following values.
描述作业属性的一组位标志。
当您使用对NetScheduleJobAdd函数的调用提交作业时,可以指定下列其中一个值。
Flags = 11h
,我们用替换看看这个值是代表哪个值
这个值在替换之后就是这个JOB_NOTIFY_FIELD_START_TIME
,这个值在MSDN
里面找不到
忽略它,然后书上说,这个结构的意思就是在一周中的任意一天1:00AM
执行ping malwareanalysisbook.com
这个命令
5.当Lab09-03.exe调用WriteFile函数的时候,它写入的文件名是什么?
解答: 这个我们一点一点的继续分析
我们先找到这个WriteFile
的位置
这里我们上面已经分析过了,是写入从DLL2ReturnJ
打开返回的temp.txt
6.当 Lab09-03.exe使用NetScheduleJobAdd创建一个job时,从哪里获取第二个参数的数据?
解答: 如下图
NetScheduleJobAdd
这个函数的第二个参数就是ecx
别名Buffer
,这个Buffer
我们上面说过是怎么传入这个参数,从DLL3GetStructure
里面初始化之后的缓冲区
7.在运行或调试Lab09-03.exe时,你会看到Lab09-03.exe打印出三块神秘数据。DLL 1的神秘数据,DLL 2的神秘数据,DLL 3的神秘数据分别是什么?
解答: 在DLL1
中打印出来的是当前进程的PID
,在DLL2
中打印出来的是temp.txt
文件的句柄,在DLL3
中打印出来的是ping malwareanalysisbook.com
在内存中的地址
8.如何将DLL2.dll加载到IDA Pro中,使得它与OllyDbg使用的加载地址匹配?
解答: 书上的说法是这样的
当使用
IDA PRO
加载DLL
时选择手动加载,当提示时,输入新的映像基准地址。
这里选择这个Manual load
然后编辑地址
输入你想加载的地址就行了
本文完