今天发现_alloca可以代替linux下的动态数组的定义,所以网上搜了下,归结如下:

==============================================

使用_alloca的一个Demo

// 感谢[迷迷糊糊]提供_alloca我知
// _alloca的功能是在栈上动态分配空间
// 在栈空间不足的情况下会产生SEH异常,需要__try保护
// 不能对同一段代码同时采用SEH异常机制和C++异常机制,因此我使用异常规格指明构造函数不抛出异常
// 以下是一个完整的演示 

#include <iostream>
#include <new>
#include <excpt.h>
#include <malloc.h>
using namespace std;
#define EXCEPTION_STACK_OVERFLOW ((unsigned long)0xC00000FDL)

int main( void )
{
    struct Test
    {
        int i;
        Test()  throw() : i(1) { cout << "construct" << endl; }
        ~Test() throw()        { cout << "destruct"  << endl; }
        void print() throw()   { cout << i << endl; }
    };

    __try
    {
        Test& t1 = *new( _alloca( sizeof(Test) ) ) Test;
        t1.print();
        t1.~Test();
    }
    __except( EXCEPTION_STACK_OVERFLOW==GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH )
    {
        cerr << "alloca fail./n";
    }

    return 0;

==============================================

用 lua 调用 Windows 的 API

昨天同事谈起能否给一个从 lua 中调用 Windows API 的简单方案。一开始觉得,如果是一个通用方案,那么至少需要先给出一个类似 windows.h 的原型声明,然后从 lua 来解析这些原型。大约写了几十行程序就实现了。后来又想了一下,似乎可以用一个更简单的方式,绕过原型,更简洁(但不保证安全)的方法来做到。

其间的问题就只有一个,每个 api 的参数都不一样,如何自动生成 C 中匹配的函数指针。似乎 C++ 的 template 是一个正统的解决方案。不过思考过几分钟以后,就被我否决了。实际用到的解决方案比较诡异:

先用 alloca 分配出正确的参数空间,再立刻填充这些参数,接下来以无参数的形式调用 api 。这样做,对于 __stdcall 的函数是没有问题的。好在 api 大多也是这样。

我写了这样一段程序来验证我的想法:

#include <stdio.h>
#include <malloc.h>
#include <assert.h>

typedef void (__stdcall *func_call)();

void __stdcall foo(int a,int b)
{
    printf("%d,%d/n",a,b);
}

void check(void *arg)
{
    assert((void**)arg-&arg==1);
}

void test()
{
    int *arg=(int*)_alloca(2*sizeof(int));
    arg[0]=1;
    arg[1]=2;
    check(arg);
    ((func_call)foo)();
}


void main()
{
    test();
}

这个方法唯一的漏洞,可能存在于 alloca 并不能正确的分配出需要的空间。因为由于某些对齐因素,我们不能保证分配出来的空间不会比需要的空间大一些。如果我们需要分配 12 个字节,alloca 却返回 16 字节就会出错。个人感觉,这个问题在大多数编译器上不会出现。不过安全起见,我写了个 check 函数运行时检查。

用这个程序验证无误后,就写了个简单的 lua 扩展。使用起来大约是这样的:

opendll = require("api.opendll")

getprocaddress =require("api.procaddress")

user32=opendll("user32.dll")

MessageBox=getprocaddress(user32,"MessageBoxA")

MessageBox(nil,"hello","test",0)

有点意思 :) 另外我还测试了 FindWindow , ShowWindow 等,都工作的很正常。

这个方案初步解决了 dll 中 api 的调用问题,但还并不实用。比如我们需要写一套 dll 管理模块(直接用 lua 完成即可)。更重要的是需要解决 api 调用中无处不在的 C struct 的传递问题。这个问题又分两类,一类是作为输入参数的 struct ,一类是作为输出参数的 struct 如 (GetWindowRect) 。我们可以用 lua 的 table 去模拟 struct 。作为输入参数做 lua table 到 c struct 的转换;而作为输出参数则做 c struct 到 lua table 的回转。或者干脆用 userdata 直接映射 struct ,再用 metable 去读写之。

另一个需要解决的问题是,有些 api 为了返回多个参数,以传入指针的形式接收返回值。lua 里是没有指针的概念的。简单的解决方法是统一用 struct 的方式解决,把单一指针看成是一个只有一个成员的 struct 指针。

因为做这个东西纯属娱乐,目前项目中并不会用到,所以我也就没有继续深入下去了。

==============================================

解决两个难懂的安全性问题

经常讨论一些您发现的少量错误并让人们知道它们是很有益的。在本文中我想讨论两个主题:

  交互式服务
  调用 _alloca()

  安全性、服务和交互式桌面

  与 Unix 守护程序类似,服务是 Microsoft Windows NT? 的中枢,可以向操作系统和用户提供重要功能而无需用户的参与。创建服务时,有一些问题需要注意。

  Microsoft Windows? 中的服务通常是控制台应用程序,它们的运行无需用户参与,也没有用户界面。但在某些实例中,服务可能需要与用户进行交互。运行在较高安全环境中的服务(如 SYSTEM)不应作为交互式服务运行。在 Windows 用户界面中,桌面是安全边界,在交互式桌面上运行的任何应用程序可以与交互式桌面上的任何窗口交互,即使窗口并不可见。无论创建窗口的应用程序的安全环境和应用程序的安全环境如何,都是这样。 由于这些设计特点,任何在交互式桌面上打开窗口的服务都会向登录用户所执行的应用程序公开。如果服务试图使用窗口消息控制其功能,则登录用户可以通过使用恶意消息来干扰该功能。

  将服务作为 SYSTEM 运行的做法(即,服务通过调用 OpenWindowStation 和 GetThreadDesktop 来支持交互式桌面)十分不可取。请注意,Windows 将来的版本可能会完全取消对交互式服务的支持。

  我们建议服务编写人员使用客户端/服务器技术(例如 RPC、套接字、命名管道或 COM)实现与来自某个服务的登录用户的交互,使用带 MB_SERVICE_NOTIFICATION 的 MessageBox 显示简单的状态。如果您的服务代码具有以下任何属性,请提高警惕:

 

  • 作为 LocalSystem 运行,并且服务在安全配置管理器中进行了标记(“登录为”->“允许服务与桌面交互”),或注册表项 -> HKLM/CCS/Services/MyService/Type & 0x0100 == 0x0100)
    CreateService,并且 dwServiceType & SERVICE_INTERACTIVE_PROCESS == SERVICE_INTERACTIVE_PROCESS
  • 调用 MessageBox(),其中 uType and (MB_DEFAULT_DESKTOP_ONLY | MB_SERVICE_NOTIFICATION | MB_SERVICE_NOTIFICATION_NT3X) != 0
  • 调用 OpenDesktop("winsta0",...) 并在该桌面上创建用户界面
  • 在 OpenDesktop 上调用 LoadLibrary/GetProcAddress


  小心 _alloca

  _alloca 函数可以在堆栈中分配动态内存。分配的空间将在调用函数退出时自动释放,而不只是在分配超出范围时释放。下面是使用 _alloca 的示例代码:

void function(char *szData) {
PVOID p = _alloca(lstrlen(szData));
// 使用 p
}

  如果攻击者提供一个比堆栈大小还要长的 szData,_alloca 会引发一个异常并导致应用程序停止。如果该代码位于服务器中,则情况会更糟。处理这种错误情况的正确方法是将对 _alloca 的调用打包在异常处理程序中,并在出现错误时重置堆栈。

void function(char *szData) {
__try {
PVOID p = _alloca(lstrlen(szData));
// 使用 p
} __except ((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH) {
_resetstkoflw();
}
}

  相关问题:ATL 转换宏

  您还应当小心某些调用 _alloca 的 ATL 字符串转换宏。这些宏包括 A2W、W2A 和 CW2CT 等。如果您的代码是服务器代码,则调用其中任何转换函数时都必须考虑数据的长度。这是不要轻易相信输入的又一个示例。如果攻击者向您的代码提供一个 10 MB 的字符串,便会摧毁堆栈并引发异常;或者如果未引发异常,则导致失败。所以千万不要这样做!

  发现缺陷

  没有人看出上星期的代码中的错误,但很多人已接近目标。其中的问题是为明文和密文使用了相同的缓冲区。您永远都不能这样做。

  乍看起来,使用相同的缓冲区存储明文,然后加密明文产生的密文似乎很好。在大多数情况下也是这样。但在多线程环境中就不是这样了。设想一下,您的代码中出现了一个“竞争状态”,而您却并不知道。(竞争状态是由对软件中的事件的相对时间产生意外的严格依赖而引起的。它们通常与同步错误一起出现。)坦白地说,您永远不会知道存在着严重的竞争状态,等知道时已经太晚了。请再考虑一下,您的应用程序的正常流程如下所示:

  使用明文加载缓冲区。
  加密缓冲区。
  将缓冲区内容发送给接收者。

  这看起来很正常。但是,设想您有一个多线程应用程序,由于某种原因,最后两个步骤由于竞争状态而被交换:

  使用明文加载缓冲区。
  将缓冲区环境发送给接收者。
  加密缓冲区。

  接收者只接收到一些明文!这是在 Internet Information Server 4.0 中修复的一个错误。在非常特殊的负载和极少数情况下,当使用安全套接字层 (SSL) 保护从服务器至用户的数据通道时,服务器可能会遵循此模式,并将一个未加密的数据信息包发送给用户。这种潜在问题的破坏很小;只向用户(或者可能是一个攻击者)发送了一个信息包。并且当用户接收到信息包时,客户端软件将断开连接。据说该问题已被 Microsoft 修复了。

  修复的方法是使用两个缓冲区。一个缓冲区用于明文,另一个用于密文,并确保密文在执行不同调用时已被清空。

  您能指出此代码中的错误吗?

void ShuffleAndUpdate(char *szName, char *szPwd,
DWORD index,
DWORD d) {
DWORD dwArray[32];
ZeroMemory(dwArray,sizeof(dwArray));
BOOL fAllowAccess = FALSE;
if (IsValidUser(szName,szPwd)) {
fAllowAccess = TRUE;
ShuffleArray(dwArray,szName);
}
dwArray[index]= d;
if (fAllowAccess) {
// 执行某些敏感的操作
}
}

 

Logo

更多推荐