3

我有一些软件使用 RSA 身份验证代理的文档化 API。这是一个作为服务在域中的客户端计算机上运行的产品,并通过与集中安装的“RSA 身份验证管理器”通信来对本地用户进行身份验证。

Authentication Agent 的 API 在此处公开记录:Authentication Agent API 8.1.1 for C Developers Guide。但是,文档似乎不正确,我无权访问 RSA 头文件——它们不是公开的;只有 PDF 文档可供下载,无需向 RSA 支付 $$。如果这里有人可以访问最新的头文件,您能否帮我确认文档是否已过时?

API 文档中给出的函数签名似乎不正确——事实上,我完全相信它们在 x64 机器上是错误的。例如,最新的 PDF 文档显示如下:

int WINAPI AceSetUserData(SDI_HANDLE hdl, unsigned int userData)
int WINAPI AceGetUserData(SDI_HANDLE hdl, unsigned int *pUserData)

文档多次声明“userData”值是一个 32 位的数量,例如在 、 和 的AceInit文档AceSetUserDataAceGetUserData。文档的相关摘录AceGetUserData

该函数是同步的,调用者必须提供一个指针作为第二个参数,指向一个 32 位存储区(即无符号整数),将用户数据值复制到该存储区。

这显然是错误的——从一些实验来看,如果你传入一个指向填充 0xff 的缓冲区中心的指针,那么AceGetUserData肯定写出一个 64 位的值,而不是 32 位的数量。

我的版本aceclnt.dll是 8.1.3.563;相应的文档标记为“Authentication Agent API 8.1 SP1”,这对应于 Authentication Agent 本身的 7.3.1 版本。

测试代码

给出了完整的测试代码,即使它根本与问题无关......如果其他人运行测试代码(我知道它的作用!),我需要的是可以访问 RSA 标头的人可以确认函数签名的文件。

#include <assert.h>
#include <stdlib.h>
#include <stdint.h>

#ifdef WIN32
#include <Windows.h>
#include <tchar.h>
#define SDAPI WINAPI
#else
#define SDAPI
#endif
typedef int SDI_HANDLE;
typedef uint32_t SD_BOOL;
typedef void (SDAPI* AceCallback)(SDI_HANDLE);
#define ACE_SUCCESS                    1
#define ACE_PROCESSING                 150

typedef SD_BOOL (SDAPI* AceInitializeEx_proto)(const char*, char*, uint32_t);
typedef int (SDAPI* AceInit_proto)(SDI_HANDLE*, void*, AceCallback);
typedef int (SDAPI* AceClose_proto)(SDI_HANDLE, AceCallback);

typedef int (SDAPI* AceGetUserData_proto)(SDI_HANDLE, void*);
typedef int (SDAPI* AceSetUserData_proto)(SDI_HANDLE, void*);

struct Api {
  AceInitializeEx_proto AceInitializeEx;
  AceInit_proto AceInit;
  AceClose_proto AceClose;
  AceGetUserData_proto AceGetUserData;
  AceSetUserData_proto AceSetUserData;
} api;

static void api_init(struct Api* api) {
  // All error-checking stripped...
  HMODULE dll = LoadLibrary(_T("aceclnt.dll")); // leak this for the demo
  api->AceInitializeEx = (AceInitializeEx_proto)GetProcAddress(dll, "AceInitializeEx");
  api->AceInit = (AceInit_proto)GetProcAddress(dll, "AceInit");
  api->AceClose = (AceClose_proto)GetProcAddress(dll, "AceClose");
  api->AceGetUserData = (AceGetUserData_proto)GetProcAddress(dll, "AceGetUserData");
  api->AceSetUserData = (AceSetUserData_proto)GetProcAddress(dll, "AceSetUserData");

  int success = api->AceInitializeEx("C:\\my\\conf\\directory", 0, 0);
  assert(success);
}

static void demoFunction(SDI_HANDLE handle) {
  union {
    unsigned char testBuffer[sizeof(void *) * 3];
    void *forceAlignment;
  } u;

  memset(u.testBuffer, 0xA5, sizeof u.testBuffer);

  int err = api.AceGetUserData(handle, (void*)(u.testBuffer + sizeof(void*)));
  assert(err == ACE_SUCCESS);

  fputs("DEBUG: testBuffer =", stderr);
  for (size_t i = 0; i < sizeof(u.testBuffer); i++) {
    if (i % 4 == 0)
      putc(' ', stderr);
    fprintf(stderr, "%02x", u.testBuffer[i]);
  }
  fputc('\n', stderr);
  // Prints:
  // DEBUG: testBuffer = a5a5a5a5 a5a5a5a5 00000000 00000000 a5a5a5a5 a5a5a5a5
  // According to the docs, this should only write out a 32-bit value
}

static void SDAPI demoCallback(SDI_HANDLE h) {
  fprintf(stderr, "Callback invoked, handle = %p\n", (void*)h);
}

int main(int argc, const char** argv)
{
  api_init(&api);
  SDI_HANDLE h;

  int err = api.AceInit(&h, /* contentious argument */ 0, &demoCallback);
  assert(err == ACE_PROCESSING);

  demoFunction(h);

  api.AceClose(h, 0);

  return 0;
}
4

2 回答 2

1

当您从文档中复制了函数/类型定义时,您基本上没有也永远不会拥有您正在使用的 .dll 版本的正确定义,并且可能总是以崩溃或更糟糕的情况结束,未定义行为。

你可以做的是调试相应的.dll:

你运行 Visual Studio 吗?我记得 VS 可以在调试模式下输入函数调用并显示程序集,但不确定今天的情况。但是任何反汇编程序都应该做到这一点。从x64 ABI寄存器rcx获取第一个参数,rdx第二个参数。如果函数在内部使用 32 位寄存器名称或清除高 32 位,则您可以假定为 32 位整数。如果它使用它来加载地址(例如lea指令),您可以假设一个指针。但正如你所看到的,那可能不是你想走的路……

那你还剩下什么?

您链接的文档说明了 32 位和 64 位库 - 取决于您使用的平台。我猜您使用的是 64 位库,并且 RSA 没有更新此库的文档,但在某些时候开发人员需要将库升级到 64 位。

所以这样想:如果你是 API 开发人员,什么可以迁移到 64 位,什么不可以。例如,所有需要在 32/64 实现中工作的东西(通过网络发送或存储和共享在磁盘上的东西)都不能被触及。但是实例本地的所有内容都可以迁移。由于这userData似乎是一个运行时的东西,所以支持平台提供的任何东西都是有意义的:unsigned long64 位和unsigned int32 位。

您已经发现它userData必须是 64 位的。但不是因为函数写出一个 64 位整数,而是因为函数看到一个 64 位值开始。由于整数是按值传递的(我猜一般来说,但肯定是在WINAPI中),如果函数是 32 位数据类型,绝对不可能看到完整的 64 位值。所以最有可能的是,API 开发人员将数据类型更改为unsigned long(无论如何都更改为 64 位类型)。

PS:如果您最终将指针放入 userData,请将指针转换为uintptr_t并存储/读取该类型。

于 2017-03-14T07:39:00.500 回答
0

To avoid questions of undefined behavior, please replace your test function with this one, and report what it prints. Please also show us the complete test program, so that people who have access to this library can compile and run it for themselves and tinker with it. I would especially like to see the declarations of the api global and its type, and the code that initializes api, and to know where the type came from (did you make it up as part of this reverse engineering exercise or did you get it from somewhere?)

static void demoFunction(SDI_HANDLE handle) {
  int err = api.AceSetUserData(handle, 0);
  assert(err == ACE_SUCCESS);

  union {
    unsigned char testBuffer[sizeof(void *) * 3];
    void *forceAlignment;
  } u;

  memset(u.testBuffer, 0xA5, sizeof u.testBuffer);

  err = api.AceGetUserData(handle, (void *)(u.testBuffer + sizeof(void*)));
  assert (err == ACE_SUCCESS);

  fputs("DEBUG: testBuffer =", stderr);
  for (size_t i = 0; i < sizeof(u.testBuffer); i++) {
    if (i % 4 == 0)
        putc(' ', stderr);
    printf(stderr, "%02x", u.testBuffer[i]);
  }
  fputc('\n', stderr);
}

(If your hypothesis is correct, the output will be

DEBUG: testBuffer = a5a5a5a5 a5a5a5a5 00000000 00000000 a5a5a5a5 a5a5a5a5

.)

于 2017-03-13T16:24:49.407 回答