0
点赞
收藏
分享

微信扫一扫

逆向 stdio.h 函数库 fwrite 函数(调试版本)

菜菜捞捞 2022-07-27 阅读 63

0x01 fwrite

  • 函数原型:​​size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)​
  • 函数功能:把​​ptr​​​ 所指向的数组中的数据写入到给定流​​stream​​ 中
  • 动态链接库:​​ucrtbased.dll​
  • C\C++ 实现:

#define// 用于排除安全限制
#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\n", buffer);
fclose(fp);

return(0);
}

  • 上述程序主要的功能就是打开路径为​​D:\1.txt​​​ 文件,之后将字符串​​This is runoob​​ 写入文件后,再读取。运行结果如下图所示:
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed

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

  • 在​​main​​​ 函数中看出有几个重要的​​API​​​ 函数,例如:​​fopen、strlen、fwrite、fread​​ 这几个函数
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_03


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_04

  • 而本次分析的是​​fwrite​​​ 函数,因此直接定位到该函数,传入的参数如下图注释所示。需要注意的是字符串的长度是通过上面的​​strlen​​​ 函数获取的,而文件的句柄是通过​​fopen​​ 函数获得的
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_05

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

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

  • 紧接着调用​​ucrtbased.sub_F53DF10​​​ 函数,压入的第一个参数为文件的句柄(​​fopen​​​ 函数的返回值,类型为​​FILE​​​),第二个参数为​​fileinfo​​ 结构体
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_08

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

  • 进入​​ucrtbased.sub_F53DE80​​​ 函数,发现这个函数会调用​​4​​​ 个子函数,第一个函数​​ucrtbased.sub_F538D10​​​ 的功能是调用​​_lock_file​​​​API​​​ 函数来锁住文件,为的是为接下来写入文件做铺垫,而传入​​ucrtbased.sub_F53DE80​​​ 函数的参数是通过​​[ebp+8]​​ 来获取的,也就是文件句柄
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_10


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_3d_11

  • 既然通过了​​_lock_file​​​ 来锁住文件那么之后肯定需要解锁文件,解锁文件的系统​​API​​​ 函数是​​_unlock_file​​​,由该函数中的​​ucrtbased.sub_F538D30​​ 函数调用
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_12


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_13

  • 而​​ucrtbased.sub_F53DF50​​​ 函数才是实现​​fwrite​​ 功能的核心函数,传入此函数的参数如下图中的注释所示
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_14

  • ​ucrtbased.sub_F53DF50​​ 函数中调用了 3 个子函数
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_15

  • 第一个函数​​ucrtbased.sub_F53AF70​​ ,主要功能是判断文件描述符是否于指定设备想关联
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_16

  • 主要用到了​​_fileno​​​ 和​​_isatty​​​ 两个​​API​​​ 来判断,如果未关联就返回​​_isatty​​ 的返回值,如果没有就做一些处理,由于处理涉及调式和未调试的区别,比较复杂,所以不多述
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_17

  • 进行完设备关联判断之后,调用​​_fwrite_nolock​​​ 这个​​API​​​ 函数将传入的字符串写入规定的文件流,传入的参数如图所示,返回值储存在​​[ebp-8]​​ 中
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_18

  • 最后看一下​​ucrtbased.sub_F53AFA0​​ 函数
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_19

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

  • 最后函数返回,返回值就是​​_fwrite_nolock​​ 函数的返回值
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_21


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_22


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_23


  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_f5_24

  • 最后完成对​​fwrite​​ 函数的调用
  • 逆向 stdio.h 函数库 fwrite 函数(调试版本)_sed_25

  • 函数运行流程:​​开始->参数过滤->_lock_file->_file_no->_isatty->_fwrite_nolock->unlock_file->结束​

逆向 stdio.h 库的 ​​fwrite​​ 函数到此结束,如有错误,欢迎指正



举报

相关推荐

0 条评论