Lab 6-1
问题
1.在main函数调用的唯一子过程中发现的主要代码结构是什么?
解答: 我们照着书中的步骤走一遍
先静态分析一下
然后我们会发现这个WININET.DLL
的导入有个函数InternetGetConnectedState
,然后我们查询一下MSDN
的说明
这是用于检测本地系统的连接状态的
这个函数会主要有这么几个值选项
有LAN
方式,也有MODEM
拨号方式,还有OFFLINE
不在线状态,到此我们大概知道了这个函数是干什么的了
我们还发现这个比较不常见的函数GetACP
、GetCPinfo
这个GetACP
函数是干这个的
然后就是GetCPinfo
然后还要注意的就是这个GetCommandLineA
,这个是获取输入参数的,比如
./a.exe a b c
那GetCommandLineA
之后就会获得./a.exe a b c
这个字符串
说明这个程序有可能需要输入参数
然后我们检查一下字符串
这是我们从书中看到的那个字符串,除了这个,我们还可以看到这些有趣的字符串
这个程序有可能会弹出一个窗口(MessageBoxA
)
这是整个程序的大概函数执行过程
然后我们点开Flow chart
会显示这个图
调用函数sub_401000
之后,将函数的返回值存入[ebp+var_4]
中,我们查看一下sub_401000
的内容,看看会返回什么值
这段代码调用了InternetGetConnectState
之后,将返回值eax
也存入了[ebp+var_4]
,然后比较了返回值和0
的大小
在MSDN
中写着
Returns TRUE if there is an active modem or a LAN Internet connection, or FALSE if there is no Internet connection, or if all possible Internet connections are not currently active. For more information, see the Remarks section.
也就是如果网络是可用的,就返回TRUE
也就是1
,反之亦然
call ds:InternetGetConnectedState
mov [ebp+var_4], eax
cmp [ebp+var_4], 0
jz short loc_40102B
我们来看这段汇编,如果网络可用,返回了1
,1>0
,所以ZF
标志位为0
,jz
不跳转
然后输出"Success: Internet Connection\n"
,这里并没有直接用printf
系列的函数输出这个字符串,而是存入了buf
中
然后如果相反,则jz
跳转
则输出这个"Error 1.1: No Internet\n"
,逻辑上和我们分析的一样
然后这个很明显的就是if
语句的一般形式
int internet_status = InternetGetConnectedState();
if(internet_status > 0)
{
some_function("Success: Internet Connection\n");
}
else another_function("Error 1.1: No Internet\n");
这个函数大概就是这么个流程
然后还有最开始那个跳转的地方,是根据上面那个函数的返回值判断的
而这个函数的返回值是这样生成的
如果成功了(也就是可以联网了),则
mov eax, 1
然后返回的也就是1
了
如果不可以了,则
xor eax, eax
然后返回的就是0
了
我们回到主函数
这里将函数sub_401000
的返回值和0
比较,我们来分析一下这个逻辑顺序
如果可以联网了,返回1
,然后cmp 1. 0
的话,ZF=0
,jnz
跳转,然后就去执行loc_401056
地方的代码了,最终retn 1
反之,如果不可以联网,则最终retn 0
于是我们可以写出这个汇编的C语言的大概样子
// 下面这些一般都是基本字符处理会用到的
// 但是这个程序不一定都用得到
// 只是为了给这个函数有点C语言的样子
// 注意:这是纯C代码,不是VC++代码
// 涉及到Windows API的部分我会用伪代码来表示
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int sub_401000(void)
{
// 下面这些是一些windows C#的代码
// 需要在开头导入一些Windows的SDK才能使用(这里没有导)
DWORD lpdwFlags = 0;
DWORD dwReserved = 0;
BOOL Online;
Online=InternetGetConnectedState(&lpdwFlags, &dwReserved);
if(Online = TRUE)
{
// sub_40105F是干什么的函数,我们下一个问题分析
sub_40105F("Success: Internet Connection\n");
return 1;
}
sub_40105F("Error 1.1: No Internet\n");
return 0;
}
int main(void)
{
// 因为汇编中就是var_4,然后我又懒得给他去个正常程序会用到的名字
// 一般这个名字会是像a,b,c,n,m这样的名字
int var_4 = -4;
var_4 = sub_401000();
if(var_4 > 0)
{
return 1;
}
else return 0;
}
具体sub_40105F
是干什么的,我们下面分析
2.位于0x40105F的子过程是什么?
解答: 书中的分析是这样的
其中一种方法是,找到调用子例程前被压到栈上的参数,在这里的两处,都有一个格式化字符串被压栈,并且字符串结尾是\n这个换行符
然后就推断这个函数是printf
,但是这未免有点牵强(我感觉)
我们来看这个函数
我把它复制出来一步一步分析
先对比printf
的内部实现是这样的
int __cdecl printf (
const char *format,
...
)
/*
* stdout 'PRINT', 'F'ormatted
*/
{
va_list arglist;
int buffing;
int retval;
va_start(arglist, format);
_ASSERTE(format != NULL);
_lock_str2(1, stdout);
buffing = _stbuf(stdout); // 记住这个函数_stbuf
retval = _output(stdout,format,arglist); // 还有记住这里调用了一次外部函数
_ftbuf(buffing, stdout); // 记住这个函数_ftbuf
_unlock_str2(1, stdout);
return(retval);
}
然后再来看这段汇编就会好理解多了
sub_40105F proc near
arg_0= dword ptr 4
arg_4= dword ptr 8
push ebx ; 这个和下面这个都不用管
push esi
mov esi, offset stru_407098 ; 这个就很重要了
push edi
push esi
call __stbuf ; __stbuf的入参是最后一个push进去的那个
; 也就是上面那个文件描述符
; 如果这个入参是stdout则就是输出到标准输出
; 记得上面的_stbuf函数不?
mov edi, eax
lea eax, [esp+10h+arg_4]
push eax ; int
push [esp+14h+arg_0] ; int
push esi ; FILE *
call sub_401282 ; 记得上面那个调用外部函数不?
push esi
push edi
mov ebx, eax
call __ftbuf ; 记得上面的那个_ftbuf函数不?
add esp, 18h
mov eax, ebx
pop edi
pop esi
pop ebx
retn
sub_40105F endp
如果你记住了上面提的那三个地方的函数,这个汇编你一看就会明白了
但是这里输入的是offset stru_407098
,而不是像printf
的内部实现那里的stdout
,然后我们查看一下这个汇编里的文件描述符
然后我们可以继续看看这个文件是啥
复制出来就是
FILE <0, 0, 0, 2, 1, 0, 0, 0>
然后这个代表什么意思,因为windows是闭源的操作系统,所以我Google了半天也没找到windows的文件描述符(file descriptor)的结构(structure)
只找到一个C语言学习网站里面的描述,是这样的
typedef struct
{
short level ; 1
short token ; 2
short bsize ; 3
char fd ; 4
unsigned flags ; 5
unsigned char hold ; 6
unsigned char *buffer ; 7
unsigned char * curp ; 8
unsigned istemp; 9
}FILE ;
这个有9
个项,但是我们从IDA
里面拷出来的就8
个项
1 2 3 4 5 6 7 8
FILE <0, 0, 0, 2, 1, 0, 0, 0>
这个版本不对,我们继续找,终于找到一个,stackoverflow
上有人说,这个定义在stdio.h
中
但是我/usr/include/stdio.h
找了半天没找到这个定义,我发现这个
注意看着绿色那行
typedef struct _IO_FILE FILE;
学过C语言的同学都知道,这是将struct _IO_FILE
定义为FILE
对C语言不熟的同学我稍微解释一下
也就是平时可能我们定义一个FRIENDS
的结构是这样的
struct FRIENDS{
int age;
char name[20];
};
然后要使用这个结构的话
struct FRIENDS one_friend;
但是如果我们在代码include
的下面加上这句话
typedef struct FRIENDS FRIENDS;
那么现在struct FRIENDS
= FRIENDS
加了这句话我们使用这个结构就是这样了
FRIEND one_friend;
所以从刚刚这个
我们可以看出这个FILE
的结构其实就是_IO_FILE
的结构,现在我们就去找这个结构
其实这个结构就在/usr/include/libio.h
里面,当然最后会发现,这是Linux
的文件描述符的结构
但是这个太长了,我就不贴出来了,我们继续找windows下的
然后有人说,在MinGW
里面有,MinGW
是Minimalist GNU for Windows
的缩写
最终在这里github上的mingw代码找到了这个定义
然后跑太远,回来,对比一下我们的
FILE <0, 0, 0, 2, 1, 0, 0, 0>
根据这个来,我们的这个文件描述符的各项的对应关系就是
struct _iobuf {
char *_ptr; // -> 0
int _cnt; // -> 0
char *_base; // -> 0
int _flag; // -> 2
int _file; // -> 1
int _charbuf; // -> 0
int _bufsiz; // -> 0
char *_tmpfname; // -> 0
};
typedef struct _iobuf FILE;
然后这就好理解了
这里其他的都可以用不用看,主要来看看这个_file
这个
这个代表了打开的文件在系统中的编号,一般我们编程的时候打开的句柄(也就是文件描述符)编号都是比较大的,因为本身系统就已经打开了比较多的文件原因
但是一般有三个文件的文件描述符是固定写死的,还比较小,那就是stdin
、stdout
、stderr
在系统中对应的值就是
stdin -> 0
stdout -> 1
stderr -> 2
然后我们搞了这么几百字,就可以搞明白这个文件描述符是干啥的了,这个文件描述符指向的是stdout
,一般这个在计算机上是代表屏幕
所以根据前面的贴出来的printf
函数的内部实现和对照汇编代码,我们最后发现这个文件描述符是stdout
,我们基本就可以确定这个函数就printf
3.这个程序的目的是什么?
解答: 上一问发散的有点深,就光分析那一个小函数去了
这个如果你能跟着我的思路来到这里,那现在恶意程序的全部代码都已经被我们吃透了
这个就是一个检测本地能不能使用网路连接的程序,恶意代码可以用于检测本地的连接是否ok,如果ok,程序会打印那个字符串,然后返回1
,如果不ok,那也是打印一个字符串,然后返回0
本文完