【问题标题】:Plausibility of directly modifying a binary executable without corrupting it直接修改二进制可执行文件而不破坏它的合理性
【发布时间】:2019-02-15 20:04:28
【问题描述】:

首先,万一有人想知道,这个问题仅用于教育目的。

假设我得到了一个二进制可执行文件(由 C 代码编译而成)。这个二进制可执行文件接受一个参数,一个密码,如果密码正确,它会写出一条秘密消息。在实践中$./jeff-binary jeffspassword 会产生Secret: Jeff's Secret Message

我想知道这个秘密,同时绕过知道密码的必要性 (jeffspassword)。我知道在创建这些二进制文件的 C 代码中,包含密码的路径是硬编码的。代码中有一部分内容为:fp = fopen("/etc/secret-password-dir/jeff/password", "r"); 显然,因为我不是 Jeff,所以我不知道有权读取或写入jeff

由于我可以访问此二进制文件,因此我目前所做的工作是在二进制文件中作为单个长十六进制字符串读取,然后在其中搜索匹配的十六进制字符串(根据示例)/etc/secret-password-dir/jeff/password 并替换它使用/home/fred/Documents/blank_password,然后创建了一个具有此单一更改的新二进制文件。目的是文件指针最终会假设密码是我输入blank_password(我知道)的任何内容,因此我可以使用非密码运行这个修改后的二进制文件,以便打印出 Jeff 的秘密。

具体来说,我目前正在运行的进程是这样​​的:

  1. 我有一个 C 程序,它读取 jeff-binary 的内容并将其作为十六进制字符串写入 hex_of_jeff_binary.txt

  2. 然后我有一个读取 hex_of_jeff_binary.txt 的 Python 脚本通过将 /etc/secret-password-dir/jeff/password 的十六进制表示替换为 /home/fred/Documents/blank_password 来执行一些字符串理解,然后将此修改后的二进制写入 jeff-binary-mod

  3. chmod +x jeff-binary-mod 使其可执行。

  4. 结果为:Segmentation fault (core dumped)。嗯。

我的问题是:我正在尝试做的事情可能吗?如果是这样,我哪里出错了?

【问题讨论】:

  • 代码在哪里崩溃了?还是您没有访问 C 代码的权限?
  • (1) 逐字节比较jeff-binaryjeff-binary-mode,看看除了对字符串的预期更改之外是否有任何差异。您可能会发现 od -xa 命令对于使用十六进制和 ASCII 字符显示文件很有用——您可以使用 od -xa 显示每个文件,然后比较 od 的两个输出。 (2) 如果你没有打开 etc/secret-password-dir/jeff/password 的权限,你为什么期望你写的一个可执行文件有这样的权限?如果原始可执行文件标记为 setuid 并且归 Jeff 所有,则它可能具有该权限。但你的副本不会。
  • 你有没有把原来的字符串换成相同长度的字符串?如果不是,您更改了二进制文件的大小,更重要的是,您在更改后使所有偏移量无效。
  • 一般来说,“修补”可执行二进制文件是极其困难的,因此对于大多数用途来说它可能是不可能的。但字符串常量是个例外,例如您要更改的路径名。 如果你不改变它们的长度,改变它们是非常简单的。如果新字符串的长度与旧字符串的长度相同或短,那就没问题了。如果新字符串比旧字符串长,你就不能这样做,除非你很幸运,而且超出字符串的数据并不重要。 (还记得保留空终止。)
  • 为什么要绕道成“十六进制字符串”?

标签: python c security


【解决方案1】:

我试图重现 OP 打算实现的目标(据我所知)。

  1. 我用 C 编写了一个小密码“安全”应用程序。

testSecret.c:

#include <string.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  const char *password = "MySecretPassword";
  if (argc != 2) {
    fprintf(stderr, "ERROR! Wrong number of command line arguments.\n");
    return -1;
  }
  if (strcmp(argv[1], password) == 0) {
    printf("Hello, proper receiver of secret message.\n");
  } else {
    fprintf(stderr, "Nice try but FAILED!\n");
  }
  return 0;
}

在 Windows 10 上的 cygwin64 中编译和测试:

$ gcc -std=c11 -o testSecret testSecret.c

$ ./testSecret 
ERROR! Wrong number of command line arguments.

$ ./testSecret wrong
Nice try but FAILED!

$ ./testSecret MySecretPassword
Hello, proper receiver of secret message.

$
  1. 我使用 Hex-Editor 插件将二进制文件加载到 Notepad++。我寻找password的初始化文本。

  1. 常量字符串的第一个字节被0覆盖。(重要的是没有插入或擦除字节。否​​则后面的所有地址都会出错,二进制肯定会被破坏。)“补丁”文件保存为testSecretCracked.exe

  1. 测试破解的二进制文件:
$ ./testSecretCracked.exe 
ERROR! Wrong number of command line arguments.

$ ./testSecretCracked.exe ""
Hello, proper receiver of secret message.

$

破解后的新密码现在是""。因此,秘密简化为在 bash 上传递带有空字符串的参数的体验。


当然,这只是用于娱乐/教育目的的演示。这可能失败的原因有很多(例如 cmets 中提到的那个)。商业应用程序可能包含加密数据(在运行时解密)。一个简单的检查可能是对内部数据进行哈希处理,并将其与一个哈希码进行比较,该哈希码可以消除大部分修改的屏蔽。 (顺便说一句。我们的商业应用程序附带了硬件许可证检查,它将上述安全技术与其他技术相结合。)


只是为了好玩,我自动化了上面描述的简单“破解”:

testAutoCrack.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
  if (argc != 4) {
    fprintf(stderr, "ERROR! Wrong number of command line arguments.\n");
    return -1;
  }
  const char *inFile = argv[1];
  const char *outFile = argv[2];
  const char *password = argv[3];
  /* read binary */
  size_t size = 0x1000;
  char *buffer = malloc(size);
  if (!buffer) {
    fprintf(stderr, "ERROR! Out of memory.\n");
    return -1;
  }
  FILE *fIn = fopen(inFile, "rb");
  if (!fIn) {
    fprintf(stderr, "ERROR! Failed to open '%s'.\n", inFile);
    return -1;
  }
  size_t lenTotal = 0;
  for (;;) {
    size_t lenRead = size - lenTotal;
    size_t len = fread(buffer + lenTotal, 1, lenRead, fIn);
    lenTotal += len;
    if (len < lenRead) break; // EOF
    char *bufferNew = realloc(buffer, 2 * size);
    if (!bufferNew) {
      fprintf(stderr, "ERROR! Out of memory.\n");
      return -1;
    }
    buffer = bufferNew;
    size *= 2;
  }
  fclose(fIn);
  /* find password in binary */
  size_t lenPassword = strlen(password) + 1;
  if (lenTotal < lenPassword) {
    fprintf(stderr, "ERROR! Password longer than binary.\n");
    return -1;
  }
  size_t i = lenTotal - lenPassword;
  while (i-- && strncmp(buffer + i, password, lenPassword) != 0);
  if (i >= lenTotal) {
    fprintf(stderr, "Password '%s' not found!\n", password);
    return -1;
  }
  /* patch password */
  buffer[i] = '\0';
  /* write binary */
  FILE *fOut = fopen(outFile, "wb");
  if (!fOut) {
    fprintf(stderr, "ERROR! Failed to open '%s'.\n", outFile);
    return -1;
  }
  if (fwrite(buffer, 1, lenTotal, fOut) < lenTotal
    || fclose(fOut)) {
    fprintf(stderr, "ERROR! Failed to write '%s'.\n", outFile);
    return -1;
  }
  /* done */
  printf("'%s' successfully cracked.\n", inFile);
  return 0;
}

编译和测试:

$ gcc -std=c11 -o testAutoCrack testAutoCrack.c 

$ ./testAutoCrack testSecret.exe testSecretAutoCracked.exe MySecretPassword
'testSecret.exe' successfully cracked.

$ chmod a+x testSecretAutoCracked.exe

$ ./testSecretAutoCracked.exe ""
Hello, proper receiver of secret message.

$

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-02
    相关资源
    最近更新 更多