[TOC]
文件操作
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
参数
如其名,是一个查找文件名的函数,包含两个参数
1 2 3 4
| 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 个字节的新位置)
参数
1 2 3 4 5
| 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
获取文件指针的当前位置,并返回当前的文件位置。
参数
返回值
返回当前的文件位置。
feof
测试流的文件尾。
参数
返回值
如果读取操作已尝试读取超过文件的末尾,feof 函数将返回非零值;否则该函数返回 0。即读取文件结束返回0,否则返回非0值。
fgetc
从流中读取字符。
参数
返回值
fgetc 返回作为 int 读取的字符或返回 EOF 以指示错误或文件尾。
可以理解为等效于getc函数
fgetwc是宽字符版本fgetc;当以文本模式或二进制模式打开时stream,它将 c 读取为多字节字符或宽字符。
fputc
将字符写入流。
参数
- c:要写入的字符。
- stream:指向 FILE 结构的指针。
返回值
其中每个函数都会返回写入的字符。 对于 fputc,返回值 EOF 指示一个错误。 对于 fputwc,返回值 WEOF 指示一个错误。
ReadFile
如其名,读入文件函数
参数
1 2 3 4 5 6 7
| 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打开
很容易定位到这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| int __fastcall sub_401090(char a1) { char v1; int result; FILE *v3; char v4; FILE *v5; char v6; char v7; char v8; HANDLE hFindFile; struct _WIN32_FIND_DATAA FindFileData; char NewFilename[256]; char OldFilename[256]; CHAR FileName[256]; char Command[1028];
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的操作,脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #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); 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
定位到关键函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| int sub_401090() { int v1; int v2; int k; int i; int v5; int j; int v7; char v8; DWORD NumberOfBytesRead; unsigned __int8 Buffer; char v11[16]; char v12[256];
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为一个迷宫。
1 2 3 4 5 6 7 8 9 10 11
| ****************** 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}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| v12 = [0x6E, 0x6B, 0x6D, 0x61, 0x6E] v8 = 0 for i in range(5): v8 += v12[i] v8 ^= 0x200
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)) 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 特征修复
参考