0x01 fwrite 函数
- 函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) - 函数功能:把
ptr所指向的数组中的数据写入到给定流stream中 - 动态链接库:
ucrtbased.dll - CC++ 实现:
#define _CRT_SECURE_NO_WARNINGS // 用于排除安全限制
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp;
char c[] = "This is runoob";
char buffer[20];
/* 打开文件用于读写 */
fp = fopen("D:\1.txt", "w+");
/* 写入数据到文件 */
fwrite(c, strlen(c) + 1, 1, fp);
/* 查找文件的开头 */
fseek(fp, 0, SEEK_SET);
/* 读取并显示数据 */
fread(buffer, strlen(c) + 1, 1, fp);
printf("%s
", buffer);
fclose(fp);
return(0);
}
- 上述程序主要的功能就是打开路径为
D:1.txt文件,之后将字符串This is runoob写入文件后,再读取。运行结果如下图所示:

- 调试工具:
x32dbg - 逆向分析:要分析
fwrite函数首先需要定位函数的位置,由于这个程序比较简单,所以直接定位main函数即可。如下图所示通过直接定位字符串This is runoob就可以找出main函数的位置

- 在
main函数中看出有几个重要的API函数,例如:fopen、strlen、fwrite、fread这几个函数


- 而本次分析的是
fwrite函数,因此直接定位到该函数,传入的参数如下图注释所示。需要注意的是字符串的长度是通过上面的strlen函数获取的,而文件的句柄是通过fopen函数获得的

- 进入
fwrite函数进行分析。首先会对传入的参数进行过滤(这也是一些API函数的通用流程),判断传入的字符串、文件句柄、元素的个数是否为0,也就是是否存在的意思。如果有一个条件不符合就会调用ucrtbased._CrtDbgReportW函数做错误处理或直接清空eax返回

- 继续向下调试,发现会调用
ucrtbased.sub_F538CD0函数,这个函数的功能比较简单,就是将传入fwrite的参数赋值到[ebp-18]这个局部变量当中,为了方便起见将这个局部变量取名为fileinfo,而且根据赋值的特征,这个fileinfo很有可能是一个结构体

- 紧接着调用
ucrtbased.sub_F53DF10函数,压入的第一个参数为文件的句柄(fopen函数的返回值,类型为FILE),第二个参数为fileinfo结构体

- 进入
ucrtbased.sub_F53DF10函数,可以看出这个函数调用了三个子函数。第一个函数ucrtbased.sub_F51F4C0的功能是将传入的参数一(文件句柄)放入局部变量[ebp-8]中,第二个参数ucrtbased.sub_F51F4C0的功能是将文件句柄赋值到局部变量[ebp-c]当中去。之后压入[ebp-8]后调用ucrtbased.sub_F53DE80函数,需要注意的是,这里还有一个局部变量[ebp-1],通过ecx传入的

- 进入
ucrtbased.sub_F53DE80函数,发现这个函数会调用4个子函数,第一个函数ucrtbased.sub_F538D10的功能是调用_lock_fileAPI函数来锁住文件,为的是为接下来写入文件做铺垫,而传入ucrtbased.sub_F53DE80函数的参数是通过[ebp+8]来获取的,也就是文件句柄


- 既然通过了
_lock_file来锁住文件那么之后肯定需要解锁文件,解锁文件的系统API函数是_unlock_file,由该函数中的ucrtbased.sub_F538D30函数调用


- 而
ucrtbased.sub_F53DF50函数才是实现fwrite功能的核心函数,传入此函数的参数如下图中的注释所示

ucrtbased.sub_F53DF50函数中调用了 3 个子函数

- 第一个函数
ucrtbased.sub_F53AF70,主要功能是判断文件描述符是否于指定设备想关联

- 主要用到了
_fileno和_isatty两个API来判断,如果未关联就返回_isatty的返回值,如果没有就做一些处理,由于处理涉及调式和未调试的区别,比较复杂,所以不多述

- 进行完设备关联判断之后,调用
_fwrite_nolock这个API函数将传入的字符串写入规定的文件流,传入的参数如图所示,返回值储存在[ebp-8]中

- 最后看一下
ucrtbased.sub_F53AFA0函数

- 在这个函数内部首先会判断传入的第一个参数是否为
0,根据实际的运行流程在test ecx,ecx命令判断之后直接实现了跳转,函数直接返回了。为了详细的了解程序,还是看了一下这几个函数都调用了哪些 API 函数,经过查找后发现在ucrtbased.sub_F539070函数中调用了_fileno和_write,这个和上面处理的流程还是蛮类似的

- 最后函数返回,返回值就是
_fwrite_nolock函数的返回值




- 最后完成对
fwrite函数的调用

- 函数运行流程:
开始->参数过滤->_lock_file->_file_no->_isatty->_fwrite_nolock->unlock_file->结束
逆向 stdio.h 库的
fwrite函数到此结束,如有错误,欢迎指正