逆向 stdio.h 函数库 fopen 函数(调试版本)
0x01 fopen 函数
- 函数原型:
FILE *fopen(const char *filename, const char *mode)返回值为FILE类型 - 函数功能:使用给定的模式
mode打开filename所指向的文件 - 动态链接库:
ucrtbased.dll - C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
char str[] = "AAAAAAAAAA"; // 字符串只是为了定位程序的 main 函数
FILE* fp;
fp = fopen("D:\\1.txt", "w+");
if (fp == NULL)
{
cout << "文件打开失败" << endl;
}
else
{
cout << "文件打开成功" << endl;
fclose(fp);
}
return true;
}
- 上述程序功能主要是打开
D盘下的1.txt文件,若返回值不为空,则表示打开文件成功。运行结果如下图所示:

- 调试工具:x32dbg
- 逆向分析:这一次分析的函数和其他函数有很大的不同,
fopen属于操作型函数,不同于算法型函数,操作型函数没有复杂的算法,但是函数调用及传参比较复杂,尤其是fopen函数需要对操作系统底层进行操作,下面来进行逆向分析 - 首先利用字符串定位的老办法定位
main函数的位置,用来绕过main函数之前复杂的初始化过程。如图所示可以看到fopen函数的调用过程,其中第一个参数是打开文件的路径,第二个参数是w+符号

- 直接进入
fopen函数,可以看出在里面有调用了一个子函数ucrtbased.sub_560A560,且压入的参数和fopen函数的参数是一样的

- 进入
ucrtbased.sub_5A8AA560函数,单步向下调试:首先判断传入的第一个参数和第二个参数是否为空


- 其次判断第二个参数的第一和第二个字节是否为空,同时判断第一个参数的第一个字节是否为空(不知道为什么需要重复的判断)


- 参数判断完成之后调用
ucrtbased.sub_5A8D6ED0函数,函数的功能是对w+符号对象的操作进行初始化

- 进入
ucrtbased.sub_5A8D6ED0函数看看,发现此函数会使用关键段对一些数据进行初始化操作。里面涉及了一系列的API函数,包括_calloc_dbg、free_dbg、_unlock_file、_lock_file等。需要注意的是在ucrtbased.sub_5A8D6ED0函数的最后进行了_lock_file锁文件操作,并且将文件的FILE类型的句柄储存在局部变量[ebp-1c]当中。由于并不是fopen函数的核心功能,所以不多叙述。

需要注意的是以上 API 具有调试方面的功能,调试版本和运行版本可能会有区别
- 初始化完成之后,会调用
ucrtbased.sub_55EFAA0函数用于判断局部变量[ebp-1c]否为0,由于在初始化的过程中传入了[ebp-1c]的地址,所以该局部变量可能是为了判断初始化是否赋值成功

- 继续向下调试,发现会调用
ucrtbased.sub_55DC550函数获取[ebp-1c]地址的值作为调用ucrtbased.sub_6256AA20函数的第4个参数。其他三个参数如图中的注释所示(第一个参数和第二个参数就是文件的路径和打开的方式)。从参数中可以看出ucrtbased.sub_560AA20函数才是fopen函数的核心处理函数,下面主要看这个函数就可以了

F7单步进入ucrtbased.sub_560AA20这个函数,发现其会对传入的参数稍作处理,之后会调用ucrtbased.sub_5619C60这个子函数,继续F7跟进ucrtbased.sub_5619C60这个函数

- 在
ucrtbased.sub_5619C60函数中发现其会调用两个函数:ucrtbased.sub_55EF510函数和ucrtbased.sub_5619900函数

- 传入
ucrtbased.sub_55EF510函数的参数包括局部变量[ebp-4]和第四个参数[ebp+14]。函数的功能很简单,主要是将[ebp+14]地址的值赋值到[ebp-4]地址中去

- 而传入
ucrtbased.sub_62579900函数的参数有4个,如上图所示,其中第四个参数传递的局部变量[ebp-4]地址的值,也就是祖父函数[ebp-1c]的值。进入ucrtbased.sub_62579900函数单步调试,首先会判断传入的第一个参数是否为空,第一个参数是文件的路径;其次判断第二个参数是否为空,之后调用ucrtbased.sub_55EFAA0函数判断传入的第四个参数地址的值是否为0

- 其次判断传入的第
4个参数是否为0,其实这个[ebp+14]的值就是祖父函数的局部变量[ebp-1c]传进来的,通过参数溯源可以很容易的看出来

- 判断完成之后调用
ucrtbased.sub_5606F60函数,该函数的功能根据传入的参数w+初始化一些数据,之后需要用到

- 进入
ucrtbased.sub_5606F60函数调试看看,首先会初始化一些变量。接着判断传入的W+参数第一个字节是否为0

- 接下来就比较重要了,该判断会根据传入
fopen的第二个参数来对[ebp-10]和[ebp-c]这个两个局部变量赋值。举个例子:如果传入的参数为w+,则将[ebp-10]赋值为301,[ebp-c]赋值为2

- 接着对该函数中的其他变量进行赋值,以位为单位。其中
w+的第二个字节储存在局部变量[ebp-18]中

- 之后将
+号对应的Ascii码减去0x20,配合0xF3E7770做地址取值,取出来的值做为索引进行调用,jmp就相当于call,该功能主要是为了判别传入fopen函数的第二个参数后面是否带了加号跳转对应的地址

- 紧接着将
302、4、1放入传入的第一个参数的地址中去

- 之后经过 GS 验证之后函数返回第一个参数的地址

- 函数调用完成之后,将返回的值赋值到局部变量当中去,如下图所示:

- 之后调用
ucrtbased.sub_5619c00函数,压入的参数如图所示

- 单步进入
ucrtbased.sub_5619c00函数,发现其会调用底层API函数_sopen_s函数,由该函数完成对文件的共享访问

- _fopen_s 底层函数如图所示:

- 然后将传入的第四个参数的值做为地址,如图所示清空其地址中的值,需要注意的是在地址
+0x10的地方赋值为3,这个三就是传入_sopen_s函数的第一个参数,也就是文件的句柄

还记得第四个参数吗,就是上面说的祖父函数的
[ebp-1c]变量,其实根据函数的功能可以判断出此变量就是用于返回fopen函数的返回值,类型是 FILE 类型
- 最后进行
GS验证后返回第四个参数的地址

- 继续返回,返回值和上面一样不变


- 经过两次返回之后,将返回值储存在
[ebp-20]当中,之后调用ucrtbased.sub_560AA20函数

ucrtbased.sub_560AA20函数的功能主要是通过底层API函数_unlock_file来解锁文件,压入的参数为父函数的[ebp-1c]这个局部变量

- 最后取出
[ebp-20]的值放入eax中,fopen函数调用完毕


总结
fopen函数总体来说还是比较复杂的,这里简单说一下流程:首先会对传入的第一个和第二个参数进行过滤处理,之后使用局部变量[ebp-1c]做为fopen函数的返回值,然后对[ebp-1c]局部变量(FILE结构)进行初始化操作,并使用_lock_file锁住文件,之后调用_sopen_s函数获取文件的共享权限,并且将句柄放入[ebp-1c]中,最后使用_unlock_file函数解锁文件并返回,返回值的类型为FILE。- 需要注意的是本次逆向分析是调试状态下的逆向分析,和真正运行状态下的程序流程可能会有一定的误差。另外就是调试器的注释中可能会有一些错误,还请谅解
逆向
stdio.h库的fopen函数到此结束,如有错误,欢迎指正
逆向 stdio.h 函数库 fopen 函数(调试版本)的更多相关文章
- 走进C标准库(2)——"stdio.h"中的fopen函数
其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...
- 逆向 stdio.h 函数库 fseek 函数(调试版本)
0x01 fseek 函数 函数原型:int fseek(FILE *stream, long int offset, int whence) 函数功能:设置流 stream 的文件位置为给定的偏移 ...
- 逆向 stdio.h 函数库 fwrite 函数(调试版本)
0x01 fwrite 函数 函数原型: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 函数功能:把 ...
- python函数库及函数标准库
一.系统库提供的内部函数 字符函数库: 1)str.islower() :字符串是否全部是小写 2)str.isspace() :字符串是否为空 3)help(str):查询字符串函数库 4)str. ...
- C函数库stdio.h概况
库变量 size_t 这是无符号整数类型,它是 sizeof 关键字的结果. FILE 这是一个适合存储文件流信息的对象类型. fpos_t 这是一个适合存储文件中任何位置的对象类 ...
- C语言函数库
C语言函数库 分类函数目录函数进程函数诊断函数接口子程序输入输出 str字符串操作函数mem操作存贮数组 数学函数 时间日期函数 转换函数 分类函数,所在函数库为ctype.h[top] int is ...
- C 标准库 - <stdio.h>
一般地,在C语言或C++中,会把用来#include的文件的扩展名叫 .h,称其为头文件. #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体 ...
- 【C语言入门教程】5.6 函数库和文件
函数库是为代码复用建立的,将同一类型,需要在不同的程序里使用的函数放置在一起,就组成了一个函数库.如 C 语言的标准库,它集合了开发者常用的函数.开发者自行编写的函数也可以组成函数库,通常称之为自定义 ...
- 从函数式编程到Ramda函数库(一)
函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比, ...
随机推荐
- Debian中的NVIDIA显卡驱动安装——超简单,一行命令
其实Debian的non-free固件中包含NVIDIA的显卡驱动,所以没必要在官网下run包一步一步来 sudo apt install nvidia-settings 安装时会提示与X冲突,没关系 ...
- 使用SQLSERVER 2008 R2 配置邮件客户端发送DB数据流程要领
设置邮件 QQ邮箱貌似不太行,建议用企业邮箱或者其他邮箱作为发件箱 新建一个邮件发件箱账号,具体邮件服务器按照各自邮件配置,是否使用ssl,自便 下一步,下一步,配置成功 use msdb Go DE ...
- cpu缓存和volatile
目录 CPU缓存的由来 CPU缓存的概念 CPU缓存的意义 缓存一致性协议-MESI协议 Store Buffers Store Forwarding Memory Barriers Invalida ...
- .NET并发编程-任务函数并行
本系列学习在.NET中的并发并行编程模式,实战技巧 请问普通: 被门夹过的核桃还能补脑吗 本小节开始学习基于任务的函数式并行.本系列保证最少代码呈现量,虽然talk is cheap, show me ...
- 编写自己的代码库(css3常用动画的实现)
编写自己的代码库(css3常用动画的实现) 1.前言 在月初的时候,发了CSS3热身实战--过渡与动画(实现炫酷下拉,手风琴,无缝滚动).js的代码库也发过两次,两篇文章.之前也写了css3的热身实战 ...
- P1089_津津的储蓄计划(JAVA语言)
package 顺序与分支; /* * 题目描述 津津的零花钱一直都是自己管理.每个月的月初妈妈给津津300元钱, 津津会预算这个月的花销,并且总能做到实际花销和预算的相同. 为了让津津学习如何储蓄, ...
- 庐山真面目之十二微服务架构基于Docker搭建Consul集群、Ocelot网关集群和IdentityServer版本实现
庐山真面目之十二微服务架构基于Docker搭建Consul集群.Ocelot网关集群和IdentityServer版本实现 一.简介 在第七篇文章<庐山真面目之七微服务架构Consul ...
- 学习C#第二天
变量 变量是什么? 在数学中,我们对变量的概念有一定的了解和认识,如y=x^2,其中,x,y都是变量. 定义 一个变量就是存储区(内存)中的一个存储单元 变量的声明及初始化 使用变量的步骤 声明一个变 ...
- [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.3 Boost 变换器实例
2.3 Boost 变换器实例 图2.13(a)所示的Boost变换器器是另一个众所周知的开关模式变换器,其能够产生幅值大于直流输入电压的直流输出电压.图2.13(b)给出了使用MOSFET和二极管的 ...
- java面试-生产环境服务器变慢,谈谈你的诊断思路
1.uptime:查询linux系统负载 11:16:16 系统当前时间 up 64 days, 19:23 从上次启动开始系统运行的时间3 users 连接数量,同一用户多个连接的时候算多个load ...