C#黑客编程必须掌握的知识 (下)
结构体封送
win32API中有很多需要结构体的场景,当你使用C#进行平台调用的时候,也需要遵循相应的规则,如果在windows平台存在一个如下的结构体:
typedefstruct_mstruct{ DWORD Reserve1;int Reserve2;wchar_t str[128]; WORD Reserve3; byte Reserve4;}mstruct,*pmstruct;
则在c#代码中需要做如下定义:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]publicstruct mstruct {public UInt32 Reserve1;publicint Reserve2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]publicstring str;public UInt16 Reserve3;publicbyte Reserve4;}
首先是结构体StructLayout属性,这个属性可以指定结构体在内存的布局:
-
• LayoutKind.Sequential - 根据结构体定义字段的顺序在内存进行布局。 -
• LayoutKind.Explicit - 需要在每个字段上设定[FieldOffset()],来确定在在结构体中的偏移位置。 -
• LayoutKind.Auto - 由.net运行时自动确定所需的布局方式 在c风格中结构体中的字符数组 wchar_t str[128]
在c#中定义时,需要设定传入字符串SizeConst大小属性,通过nmanagedType.ByValTStr设定封送类型,同时在StructLayout中指定编码格式。
此外 StructLayout特性支持三种附加字段:CharSet、Pack、Size:
-
• CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。 -
• Pack 定义结构体字段在内存中的对齐方式。 -
• Size 指示结构的绝对大小,该字段必须等于或大于类或结构成员的总大小(以字节为单位)。
C#中的指针
C#不支持指针,我们可以使用unsafe关键词,开启不安全代码(unsafe code)开发模式,从而可以访问内存,达到指针的效果。
staticint[] Modify(){int[] array=newint[10];for (int i = 0; i < array.Length; i++) { array[i] = i; }return array;}staticvoidMain(){var array = Modify();unsafe {fixed (int* ptr = array) {for (int i = 0; i < array.Length; i++) { Console.WriteLine(*(ptr+i)); } } } Console.ReadLine();}
我们首先构造一个托管类型的整数数组,假设我们需要通过访问内存来访问元素内容。首先所以的涉及到指针的代码都必须放在unsafe代码块中,然后我们需要用fixed关键字来获取指向托管对象的指针,并且保证该指针不会被移动。接下来就跟c语言一样,正常访问内存数据就行。
PtrToStructure与StructureToPtr
这两个函数,从字面意思上来看,分别是获取结构指针以及将一块内存封装为结构体,官方的说法是:将数据从非托管内存块编组到托管对象(前者),将数据从托管对象编组到非托管内存块(后者)。
API原型:
publicstaticobject? PtrToStructure (IntPtr ptr, Type structureType);ptr - 指向非托管内存块的指针structureType - 要创建的对象的类型publicstaticvoidStructureToPtr (object structure, IntPtr ptr, bool fDeleteOld);structure - 保存要转换的托管对象ptr - 指向非托管内存块的指针fDeleteOld - true表示在此方法复制数据之前对ptr参数调用DestroyStructure(IntPtr, Type)方法
官方案例:
publicstruct Point{publicint x;publicint y;}staticvoidMain(){// Create a point struct. Point p; p.x = 1; p.y = 1; Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");// Initialize unmanged memory to hold the struct. IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));try {// Copy the struct to unmanaged memory. Marshal.StructureToPtr(p, pnt, false);// Create another point. Point anotherP;// Set this Point to the value of the// Point in unmanaged memory. anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point)); Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + "."); }finally {// Free the unmanaged memory. Marshal.FreeHGlobal(pnt); }}
利用委托来动态调用Win32 api
在win32中我们一般动态调用某个API一般会定义函数指针,然后利用GetProcAddress得到API地址,然后利用函数指针来执行。c#中并没有函数指针的定义,但是可以通过委托来实现同样的效果:
[DllImport("Kernel32", CharSet = CharSet.Ansi, EntryPoint = "LoadLibraryA", SetLastError = true)]publicstaticextern IntPtr LoadLibraryA(String funcname);[DllImport("Kernel32")]publicstaticextern IntPtr GetProcAddress(IntPtr handle, String funcname);[UnmanagedFunctionPointer(CallingConvention.StdCall,CharSet=chars)]privatedelegateintDMessageBoxW(IntPtr hwnd, string text, string cap, uint type);staticvoidMain(){ DMessageBoxW dMessageBoxW = (DMessageBoxW)Marshal.GetDelegateForFunctionPointer(GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxW"), typeof(DMessageBoxW)); dMessageBoxW(IntPtr.Zero, "你好", "世界", 0);}
定义委托时,参数类型以及个数需要保持一致,同时需要指定调用约定,然后可以利用GetDelegateForFunctionPointer来为我们的函数地址转换为我们的委托,然后调用即可。
这是一个纯粹,开放,前沿的技术交流社区,成员主要有互联网大厂安全部门任职的成员,乙方红队专家,以及正在学习入门的小白等,社区涉及的领域知识包括但不限于渗透,免杀开发,红蓝对抗,安全建设,考试认证,岗位招聘等等方面,还可以结识很多志同道合的朋友,提升自己的技术栈,开阔视野,提升眼界👇👇👇
欢迎加入交流圈
扫码获取更多精彩
原文始发于微信公众号(黑晶):C#黑客编程必须掌握的知识 (下)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论