C#黑客编程必须掌握的知识 (上)
Win32API平台调用
托管代码调用win32API必须的三个步骤:
-
获得非托管函数的信息,即dll的名称,需要调用的非托管函数名等信息
-
在托管代码中对非托管函数进行声明,并且附加平台调用所需要属性
-
在托管代码中直接调用第二步中声明的托管函数 代码调用MessageBoxW API:
[DllImport("user32.dll",CharSet=CharSet.Unicode,EntryPoint="MessageBoxW",SetLastError=true)]
publicstaticexternintMessageBoxW(IntPtr hWnd,String text,String caption,uint type);
staticvoidMain(string[] args)
{
MessageBoxW(IntPtr.Zero,"你好","你好",0);
}
非托管函数API声明必须具有static extern 关键字,返回值根据目标API返回值设定即可。
[DllImport("user32.dll",CharSet=CharSet.Unicode,EntryPoint="MessageBoxW")]
上述代码定义了平台调用所需的属性,DllImport指定了API所在的模块,CharSet设定了字符集,如果你使用的字符串包含中文等字符,这里记得设定为Unicode,EntryPoint指定函数的实际入口,意思就是说你这里指定了某个入口函数后,你的函数声明的时候用任意函数名也可。比如:
[DllImport("user32.dll",CharSet=CharSet.Unicode,EntryPoint="MessageBoxW",SetLastError=true)]
publicstaticexternintMessageBoxssssW(IntPtr hWnd,String text,String caption,uint type);
staticvoidMain(string[] args)
{
MessageBoxssssW(IntPtr.Zero,"你好","你好",0);
}
获取Win32 ERROR信息
获取win32非托管代码的error信息,需要在平台调用API声明的属性中添加SetLastError=true,即:
[DllImport("kernel32.dll",CharSet=CharSet.Unicode,EntryPoint="GetFileAttributes",SetLastError=true)]
publicstaticexternintGetFileAttributes(String filename);
staticvoidMain(string[] args)
{
GetFileAttributes("notfound.exe");
int code =Marshal.GetLastWin32Error();
Win32Exception win32exception =newWin32Exception(code);
if(code !=0)
{
Console.WriteLine("调用Win32函数发生错误,错误信息为 : {0}", win32exception.Message);
}
else
{
Console.WriteLine("调用Win32函数成功,返回的信息为:{0}", win32exception.Message);
}
}
直接复制到本机结构中的类型和非直接复制到本机结构中的类型
-
大多数数据类型在托管和非托管内存中具有共同的表示形式,而且不需要互操作封送处理程序进行特殊处理。这些类型称为 blittable 类型,因为它们在托管和非托管代码之间传递时不需要进行转换。
windows数据类型 C数据类型 托管数据类型 Uchar unsigned char System.Byte Char char System.SByte Short/Int16 short System.Int16 USHORT/WORD unsigned short System.UInt16 Int int System.Int32 UINT/DWORD unsigned int System.UInt32 INT64/LONGLONG _int64 System.Int64 UINT64/ULONGLONG _uint64 System.UInt64 INT_PTR void* System.IntPtr HANDLE void* System.UIntPtr FLOAT float System.Single DOUBLE _double System.Double -
在非托管环境中,某些托管数据类型要求具有不同的表示形式。必须将这些非 blittable 数据类型转换为可以封送的形式。例如,托管字符串就是非 blittable 类型,因为这些字符串必须转换为字符串对象后才能进行封送。
类型描述
System.Array转换为 C 样式数组或 SAFEARRAY。
System.Boolean转换为1、2或4字节的值,true表示1或-1。
System.Char转换为Unicode或 ANSI 字符。
System.Class转换为类接口。
System.Object转换为变量或接口。
System.String转换为空引用中的终止字符串或转换为 BSTR。非托管代码不能更新此字符串。
System.StringBuilder ANSI字符串/Unicode字符串,非托管代码可以更新此字符串,并传回给托管代码。
System.ValueType转换为具有固定内存布局的结构。
字符串的封送处理
我们以GetModuleFileNameA API举例,这个API接收一个字符串地址用于写入模块名称字符串,返回值为实际写入lpFilename缓冲区的长度
DWORD GetModuleFileNameA(
[in, optional] HMODULE hModule,
[out] LPSTR lpFilename,
[in] DWORD nSize
);
c++中我们调用的话代码一般这么写:
char buffer[256];
DWORD NameSTR=GetModuleFileNameA(NULL,buffer,256);
C#中调用首先定义函数声明:
[DllImport("kernel32.dll",CharSet=CharSet.Unicode,EntryPoint="GetModuleFileNameW",SetLastError=true)]
staticexternintGetModuleFileNameW(IntPtr hModule,StringBuilder lpFilename,int nSize);
注意上述Windows参数类型为LPSTR,我们在托管代码中需要使用StringBuilder类型,代码如下:
ref和out关键字
ref 和 out关键字会在涉及到结果传出的过程时用到。
out关键字:
用于从方法中返回参数值,方法的内部必须为out参数赋值,适用于返回多个值的场景,传递参数是out关键字需要一起传递:
ref 关键字:
ref 关键字用于将参数以引用方式传递给方法。这意味着方法中对该参数的修改会影响到调用方法时的原始变量。使用 ref 时,调用方和方法方都能看到相同的内存位置,因此任何对参数的修改都会反映到调用方的变量上。
主要区别
-
初始化要求:
ref:传递前,调用方的变量必须已初始化。
out:传递前,调用方的变量可以不初始化。 -
方法内部赋值:
ref:方法可以选择修改或不修改传入的值,但如果修改了,调用方的值会反映出来。
out:方法必须对参数进行赋值。 -
使用场景:
ref 用于方法需要访问和修改传入参数的场景。
out 用于方法需要返回多个值的场景。
这是一个纯粹,开放,前沿的技术交流社区,成员主要有互联网大厂安全部门任职的成员,乙方红队专家,以及正在学习入门的小白等,社区涉及的领域知识包括但不限于渗透,免杀开发,红蓝对抗,安全建设,考试认证,岗位招聘等等方面,还可以结识很多志同道合的朋友,提升自己的技术栈,开阔视野,提升眼界👇👇👇
欢迎加入交流圈
扫码获取更多精彩
原文始发于微信公众号(黑晶):C#黑客编程必须掌握的知识 (上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论