文件系统操作
文件操作
LOWORD,HIWORD,LOBYTE,HIBYTE
HIWORD,作用是取得某个4字节变量(即32位的值)在内存中处于高位的两个字节,即一个WORD长的数据(16位的无符号整数,范围从0到0xFFFF)。
LOWORD,作用是取得某个4字节变量(即32位的值)在内存中处于低位的两个字节,即一个WORD长的数据(16位的无符号整数,范围从0到0xFFFF)。
HIBYTE,作用是取得某个2字节变量(即16位的值)在内存中处于高位的一个字节,即一个BYTE长的数据(8位的无符号整数,范围从0到0xFF)。
LOBYTE,作用是取得某个2字节变量(即16位的值)在内存中处于低位的一个字节,即一个BYTE长的数据(8位的无符号整数,范围从0到0xFF)。
例如HIWORD(0xFA12EAC3)=0xFA12,LOWORD(0xFA12EAC3)=0xEAC3
HIBYTE(0xFA12)=0xFA,LOBYTE(0xFA12)=0x12
findFirstFileA
参数
如其名,是一个查找文件名的函数,包含两个参数
HANDLE FindFirstFileA(
[in] LPCSTR lpFileName,
[out] LPWIN32_FIND_DATAA lpFindFileData
);
- [in] lpFileName:目录或路径以及文件名。 文件名可以包含通配符,例如星号 (*) 或问号 (?) 。
- [out] lpFindFileData 指向 WIN32_FIND_DATA结构的指针 ,该结构接收有关找到的文件或目录的信息。
返回值
如果函数成功,则返回值是在对 FindNextFile 或 FindClose 的后续调用中使用的搜索句柄, 而 lpFindFileData 参数包含有关找到的第一个文件或目录的信息。
如果函数失败,因为找不到匹配的文件,可使用 GetLastError 函数,其将返回 ERROR_FILE_NOT_FOUND。
WIN32_FIND_DATAA 结构
成员
- dwFileAttributes:当前文件的文件属性,具体值可参考 windows 官方文档文件属性常量。可以简单理解16是文件夹,32是文件
- cFileName:获取当前文件的名称。
- nFileSizeHigh:文件大小的高阶 DWORD 值(以字节为单位)除非文件大小大于 MAXDWORD,否则此值为零。
- nFileSizeLow:文件大小的低序 DWORD 值(以字节为单位)。文件大小等于 (nFileSizeHigh * (MAXDWORD+1)) + nFileSizeLow
- ……
fopen
打开一个文件
参数
- filename:字符串,表示要打开的文件名称。
- mode:字符串,表示文件的访问模式
访问模式
mode参数 | 含义 |
---|---|
"r" | 打开一个文本文件,文件必须存在,只允许读 |
"r+" | 打开一个文本文件,文件必须存在,允许读写 |
"rb" | 打开一个二进制文件,文件必须存在,只允许读 |
"rb+" | 打开一个二进制文件,文件必须存在,允许读写 |
"w" | 新建一个文本文件,已存在的文件将内容清空,只允许写 |
"w+" | 新建一个文本文件,已存在的文件将内容清空,允许读写 |
"wb" | 新建一个二进制文件,已存在的文件将内容清空,只允许写 |
"wb+" | 新建一个二进制文件,已存在的文件将内容清空,允许读写 |
"a" | 打开或新建一个文本文件,只允许在文件末尾追写 |
"a+" | 打开或新建一个文本文件,可以读,但只允许在文件末尾追写 |
"ab" | 打开或新建一个二进制文件,只允许在文件末尾追写 |
"ab+" | 打开或新建一个二进制文件,可以读,但只允许在文件末尾追写 |
更多可查看Windows官方文档 |
fseek
将文件指针移到指定位置(将与 stream 关联的文件指针(如果有)移动到 origin 中为 offset 个字节的新位置)
参数
int fseek(
FILE *stream,
long offset,
int origin
);
- stream:指向 FILE 结构的指针。
- offset:origin 中的字节数。 可以理解为偏移量。
- origin:初始位置。
参数 origin 必须是 STDIO.H 中定义的以下常量之一:
宏 | 含义 | 值 |
---|---|---|
SEEK_CUR | 文件指针的当前位置。 | 1 |
SEEK_END | 文件结尾。 | 2 |
SEEK_SET | 文件开头。 | 0 |
ftell
获取文件指针的当前位置,并返回当前的文件位置。
参数
- stream:目标 FILE 结构。
返回值
返回当前的文件位置。
feof
测试流的文件尾。
参数
- stream:目标 FILE 结构。
返回值
如果读取操作已尝试读取超过文件的末尾,feof 函数将返回非零值;否则该函数返回 0。即读取文件结束返回0,否则返回非0值。
fgetc
从流中读取字符。
参数
- stream:目标 FILE 结构。
返回值
fgetc 返回作为 int 读取的字符或返回 EOF 以指示错误或文件尾。
可以理解为等效于getc函数
fgetwc是宽字符版本fgetc;当以文本模式或二进制模式打开时stream,它将 c 读取为多字节字符或宽字符。
fputc
将字符写入流。
参数
- c:要写入的字符。
- stream:指向 FILE 结构的指针。
返回值
其中每个函数都会返回写入的字符。 对于 fputc,返回值 EOF 指示一个错误。 对于 fputwc,返回值 WEOF 指示一个错误。
ReadFile
如其名,读入文件函数
参数
BOOL ReadFile(
[in] HANDLE hFile,
[out] LPVOID lpBuffer,
[in] DWORD nNumberOfBytesToRead,
[out, optional] LPDWORD lpNumberOfBytesRead,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
- [in] hFile:设备句柄 (例如文件、文件流、物理磁盘、卷、控制台缓冲区、磁带驱动器、套接字、通信资源、mailslot 或管道) 。必须使用读取访问权限创建 hFile 参数。
- [out] lpBuffer:指向接收从文件或设备读取数据的缓冲区的指针。可以简单理解为要接收文件内容的变量的指针
- [in] nNumberOfBytesToRead:要读取的最多字节数。
- [out, optional] lpNumberOfBytesRead:指向使用同步 hFile 参数时接收读取的字节数的变量的指针。 ReadFile 将此值设置为零,然后再执行任何工作或错误检查。 如果这是一个异步操作,请对此参数使用 NULL ,以避免潜在的错误结果。
- [in, out, optional] lpOverlapped:指向OVERLAPPED结构体的指针,如果文件或设备是以FILE_FLAG_OVERLAPPED标志打开的,这个参数必须有效,并且指定读取操作的起始位置;如果文件或设备不支持重叠操作,这个参数可以为NULL。
返回值
如果函数成功,则返回值为非零 (TRUE) 。
如果函数失败或异步完成,则返回值为零, (FALSE) 。
例
ReadFile(hObject, v11, 0x10u, &NumberOfBytesRead, 0);
这个函数调用的作用是从hObject指定的文件或设备读取16个字节的数据,存入v11指向的缓冲区,并把实际读取的字节数赋给NumberOfBytesRead变量。如果函数成功执行,返回值为非零;如果函数失败或正在完成异步操作,返回值为零,并且可以用GetLastError函数获取错误代码。
实例
[黄河流域公安院校网络安全技能挑战赛]WIFE
文件下载
查壳一下是无壳32位,直接用ida打开
很容易定位到这个函数
int __fastcall sub_401090(char a1)
{
char v1; // si
int result; // eax
FILE *v3; // edi
char v4; // si
FILE *v5; // esi
char v6; // al
char v7; // [esp-8h] [ebp-868h]
char v8; // [esp+0h] [ebp-860h]
HANDLE hFindFile; // [esp+14h] [ebp-84Ch]
struct _WIN32_FIND_DATAA FindFileData; // [esp+18h] [ebp-848h] BYREF
char NewFilename[256]; // [esp+158h] [ebp-708h] BYREF
char OldFilename[256]; // [esp+258h] [ebp-608h] BYREF
CHAR FileName[256]; // [esp+358h] [ebp-508h] BYREF
char Command[1028]; // [esp+458h] [ebp-408h] BYREF
v1 = a1;
memset(FileName, 0, sizeof(FileName));
sub_401050(FileName, "%s\\*.*", v1);
sub_401010(Format, (char)FileName);
hFindFile = FindFirstFileA(FileName, &FindFileData);
if ( hFindFile == (HANDLE)-1 )
return sub_401010("查找文件失败!\n", v8);
do
{
if ( FindFileData.dwFileAttributes == 16 )
{
if ( FindFileData.cFileName[0] != 46 )
{
memset(NewFilename, 0, sizeof(NewFilename));
sub_401050(NewFilename, "%s\\%s", v1);
sub_401010(asc_4031D4, (char)NewFilename);
Sleep(0x3E8u);
sub_401090(NewFilename);
}
}
else
{
memset(NewFilename, 0, sizeof(NewFilename));
sub_401050(NewFilename, "%s\\%s", v1);
sub_401010(asc_4031E8, (char)NewFilename);
v3 = fopen(NewFilename, "rb");
if ( v3 )
{
sub_401010("打开 %s 文件成功!\n", (char)NewFilename);
fseek(v3, 0, 2);
v4 = ftell(v3);
fseek(v3, 0, 0);
sub_401010(asc_403168, v4);
memset(OldFilename, 0, sizeof(OldFilename));
sub_401050(OldFilename, "%s\\%s", a1);
sub_401010("%s\n", (char)OldFilename);
v5 = fopen(OldFilename, "wb");
while ( !feof(v3) )
{
v6 = fgetc(v3);
fputc(v6 ^ 0x66, v5);
fputc(97, v5);
}
fclose(v3);
fclose(v5);
memset(Command, 0, 0x400u);
sub_401050(Command, "del \"%s\"", (char)NewFilename);
sub_401010("%s\n", (char)Command);
system(Command);
rename(OldFilename, NewFilename);
sub_401010("\n", v7);
v1 = a1;
}
else
{
sub_401010("打开文件失败\n", v8);
}
}
result = FindNextFileA(hFindFile, &FindFileData);
}
while ( result );
return result;
}
加密逻辑是遍历文件路径下的所有文件对其进行异或0x66与隔一位加字符a的操作,脚本如下
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
void jiemi(const char* filename,const char* pathname) {
char ch;
FILE* fp;
FILE* fpw;
char tmp[1024];
memset(tmp, 0, 1024);
sprintf(tmp, "%s\\tmp", pathname);
fp = fopen(filename, "rb");
fpw = fopen(tmp, "wb");
fseek(fpw, 0, SEEK_SET);
int i = 0;
while (!feof(fp)) {
ch = fgetc(fp);
if (0 == (i % 2)) {
i = 1;
fputc(ch ^ 0x66, fpw);
}
else {
i = 0;
continue;
}
}
fclose(fp);
fclose(fpw);
char commend[1024];
memset(commend, 0, 1024);
sprintf(commend, "del \"%s\"", filename); //访问路径包含空格增加双引号
system(commend);
rename(tmp, filename); //调用C语rename函数重命名文件
printf("\n");
return;
}
int main() {
jiemi("C:\\Users\\Tree\\Downloads\\Compressed\\wife\\secret","C:\\Users\\Tree\\Downloads\\Compressed\\wife");
return 0;
}
运行完脚本用01editor打开secret
发现是png文件
修改后缀名打开secret.png
flag{DHIUASHDUIASH}
[NKCTF2023]PMKF
定位到关键函数
int sub_401090()
{
int v1; // [esp+20h] [ebp-140h]
int v2; // [esp+24h] [ebp-13Ch]
int k; // [esp+2Ch] [ebp-134h]
int i; // [esp+30h] [ebp-130h]
int v5; // [esp+30h] [ebp-130h]
int j; // [esp+30h] [ebp-130h]
int v7; // [esp+30h] [ebp-130h]
char v8; // [esp+34h] [ebp-12Ch]
DWORD NumberOfBytesRead; // [esp+3Ch] [ebp-124h] BYREF
unsigned __int8 Buffer; // [esp+43h] [ebp-11Dh] BYREF
char v11[16]; // [esp+44h] [ebp-11Ch] BYREF
char v12[256]; // [esp+54h] [ebp-10Ch] BYREF
memset(v12, 0, sizeof(v12));
ReadFile(hObject, &Buffer, 1u, &NumberOfBytesRead, 0);
if ( Buffer != 5 )
{
sub_401690((int)"Wrong!\n");
CloseHandle(hObject);
exit(0);
}
ReadFile(hObject, v12, Buffer, &NumberOfBytesRead, 0);
for ( i = 0; i < Buffer; ++i )
{
if ( v12[i] != byte_405100[i] )
{
sub_401690((int)"Wrong!\n");
CloseHandle(hObject);
exit(0);
}
}
v5 = 0;
v8 = 0;
while ( v5 < Buffer )
v8 += v12[v5++];
ReadFile(hObject, v11, 0x10u, &NumberOfBytesRead, 0);
v2 = 18;
for ( j = 0; j < 16; ++j )
v11[j] ^= v8;
v7 = 0;
v1 = 1;
while ( v7 < 16 )
{
for ( k = 6; k >= 0; k -= 2 )
{
switch ( ((unsigned __int8)v11[v7] >> k) & 3 )
{
case 0:
v2 -= 18;
break;
case 1:
++v2;
break;
case 2:
v2 += 18;
break;
case 3:
--v2;
break;
default:
break;
}
if ( aN[v2] == 42 || aN[v2] == 32 )
{
v1 = 0;
break;
}
if ( aN[v2] == 75 )
{
sub_401690((int)"Congratulations! you found it!\n");
break;
}
}
++v7;
}
CloseHandle(hObject);
return v1;
}
从文件中读入一个5,然后从byte_405100中读取5个字符,点进去可知为"nkman"然后求和得v8,v8作为异或的值
aN为一个迷宫。
******************
N...*****...*....*
**.*****..*...**.*
...*****.*****.*.*
.**....*..*...*..*
....**.*.*..*...**
*.**...*.*.*******
..*****..*......**
.*......**.****.**
...*****...*K...**
******************
然后继续从文件中读入,异或v8,手动走一下迷宫
ddssaassdssassddwdddddwdwwwwwdwddsddwdddsssasaawaasassdddddssaaa
switch语句简单看一下得出上是0,右是1,下是2,左是3
1122332212232211011111010000010112110111222323303323221111122333
关键是这句话的理解for ( k = 6; k >= 0; k -= 2 ) (v11[v7] >> k) & 3
这句话是将v11的每两个二进制位转换为一个四进制位,
那我们就需要转换回去,转回16进制再加上05和'nkman'即为flag
flag:nkctf{056e6b6d616e4fef7eb0004415047000bea9eeb043aa}
v12 = [0x6E, 0x6B, 0x6D, 0x61, 0x6E]#nkman
v8 = 0
for i in range(5):
v8 += v12[i]
v8 ^= 0x200
#因为ReadFile(hObject, &Buffer, 1u, &NumberOfBytesRead, 0);
#这段读入函数只读取了v11中的0x10u即16个字节的数据,也就是取了低八位,
#所以等价于与v8的低八位的异或,v8是0x215,我们只取0x15
#有以下迷宫,N为起点,K为终点,只能走'.'的地方,上下左右分别用wsad表示
# ******************
# N...*****...*....*
# **.*****..*...**.*
# ...*****.*****.*.*
# .**....*..*...*..*
# ....**.*.*..*...**
# *.**...*.*.*******
# ..*****..*......**
# .*......**.****.**
# ...*****...*K...**
# ******************
path="ddssaassdssassddwdddddwdwwwwwdwddsddwdddsssasaawaasassdddddssaaa"
path =path.replace("w","0")
path =path.replace("d","1")
path =path.replace("s","2")
path =path.replace("a","3")
s = [path[i:i+4] for i in range(0, len(path), 4)]
qua = [int(x, 4) for x in s]
for i in range(len(qua)):
qua[i]^=v8
v11=['0x05', '0x6e', '0x6b', '0x6d', '0x61', '0x6e']
for i in range(len(qua)):
v11.append(hex(qua[i]).zfill(4))#有0x0出现用zfill填满四位
flag = 'nkctf{'+''.join(v11).replace('0x','')+'}'
print(flag)
本月学习其他知识点
python 相关
- 通过encode(),decode()可以实现字符串和二进制字符串的转化默认使用’utf-8’编码。如果需要,也可以指定其他的编码方式
- AES解密可以使用 Crypto.Cipher 里的 AES 包
- zfill可以用于在字符串的左边填充’0’字符,使字符串达到指定的长度。接受一个参数,表示要填充的长度。注意会越过前置的+-号
- 如果想填充其他字符,可以采用center,ljust,rjust,居中为center这时候原来的字符串将会在中间,扩充物出现在两边。ljust和rjust分别将字符串放在左边和右边而填充物填充于右边和左边。这三个都需要接收两个参数,分别是填充长度和填充字符
- 将列表中的元素连接成一个字符串可以用join,例如s='+'.join(a),就表示将列表a中的元素用'+'连接成一个字符串赋值给s
UPX 特征修复
参考
- [1] LOWORD和HIWORD函数
- [2] 微软技术文档
- [3] python字符串填充
- [4] 黄河流域公安院校网络安全技能挑战赛官方wp
本文来自博客园,作者:{Tree_24},转载请注明原文链接:{https://www.cnblogs.com/Tree-24/}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?