Linux 中的逆向工程工具——Strings、nm、ltrace、strace、LD_PRELOAD

举报
Tiamo_T 发表于 2022/06/07 09:36:00 2022/06/07
3.7k+ 0 0
【摘要】 本文介绍了可用于在 Linux 环境中对可执行文件进行逆向工程的工具和命令。 逆向工程是弄清楚软件做了什么的行为,没有可用的源代码。逆向工程可能无法为您提供软件的确切详细信息。但是您可以很好地理解软件是如何实现的。

本文介绍了可用于在 Linux 环境中对可执行文件进行逆向工程的工具和命令。

逆向工程是弄清楚软件做了什么的行为,没有可用的源代码。逆向工程可能无法为您提供软件的确切详细信息。但是您可以很好地理解软件是如何实现的。

逆向工程涉及以下三个基本步骤:

  1. 收集信息
  2. 确定程序行为
  3. 拦截库调用


一、收集信息

第一步是收集有关目标程序及其功能的信息。对于我们的示例,我们将采用“who”命令。'who' 命令打印当前登录用户的列表。

1.Strings命令

字符串是打印文件中可打印字符的字符串的命令。所以现在让我们用它来对付我们的目标(谁)命令。

# strings /usr/bin/who

一些重要的字符串是,


users=%lu
EXIT
COMMENT
IDLE
TIME
LINE
NAME
/dev/
/var/log/wtmp
/var/run/utmp
/usr/share/locale
Michael Stone
David MacKenzie
Joseph Arceneaux

从 about 输出中,我们可以知道“谁”正在使用一些 3 个文件(/var/log/wtmp、/var/log/utmp、/usr/share/locale)。

阅读更多:Linux 字符串命令示例(在 UNIX 二进制文件中搜索文本)

2.nm 命令

nm 命令,用于列出目标程序中的符号。通过使用 nm,我们可以了解本地和库函数以及使用的全局变量。nm 无法在使用“strip”命令进行条带化的程序上工作。

注意:默认情况下,'who' 命令被剥离。对于这个例子,我再次编译了“who”命令。

# nm /usr/bin/who

这将列出以下内容:

08049110 t print_line
08049320 t time_string
08049390 t print_user
08049820 t make_id_equals_comment
080498b0 t who
0804a170 T usage
0804a4e0 T main
0804a900 T set_program_name
08051ddc b need_runlevel
08051ddd b need_users
08051dde b my_line_only
08051de0 b time_format
08051de4 b time_format_width
08051de8 B program_name
08051d24 D Version
08051d28 D exit_failure

在上面的输出中:

  • t|T – 符号出现在 .text 代码部分
  • b|B – 符号位于未初始化的 .data 部分
  • D|d – 符号位于 Initialized .data 部分。

大写字母或小写字母确定符号是本地的还是全局的。

从 about 输出中,我们可以知道以下内容,

  • 它具有全局功能(main、set_program_name、usage 等)
  • 它有一些本地函数(print_user、time_string 等)
  • 它具有全局初始化变量(版本,exit_failure)
  • 它具有未初始化的变量(time_format、time_format_width 等)

有时,通过使用函数名称,我们可以猜测函数将做什么。

二、确定程序行为

3. ltrace 命令

它跟踪对库函数的调用。它在该进程中执行程序。

# ltrace /usr/bin/who

输出如下所示。

utmpxname(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0
setutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 1
getutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
realloc(NULL, 384) = 0x09ed59e8
getutxent(0, 384, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
realloc(0x09ed59e8, 768) = 0x09ed59e8
getutxent(0x9ed59e8, 768, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
realloc(0x09ed59e8, 1152) = 0x09ed59e8
getutxent(0x9ed59e8, 1152, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
realloc(0x09ed59e8, 1920) = 0x09ed59e8
getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
realloc(0x09ed59e8, 3072) = 0x09ed59e8
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860
getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)

您可以观察到有一组对 getutxent 及其库函数系列的调用。您还可以注意到 ltrace 按照程序中调用函数的顺序给出结果。

现在我们知道“who”命令通过调用 getutxent 及其系列函数来获取登录用户。

4. strace 命令

strace 命令用于跟踪程序进行的系统调用。如果一个程序没有使用任何库函数,并且它只使用系统调用,那么使用普通的 ltrace,我们无法跟踪程序的执行。

# strace /usr/bin/who
[b76e7424] brk(0x887d000)               = 0x887d000
[b76e7424] access("/var/run/utmpx", F_OK) = -1 ENOENT (No such file or directory)
[b76e7424] open("/var/run/utmp", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
.
.
.
[b76e7424] fcntl64(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=0, len=0}) = 0
[b76e7424] read(3, "\10\325"..., 384) = 384
[b76e7424] fcntl64(3, F_SETLKW, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0

您可以观察到,每当调用 malloc 函数时,它都会调用 brk() 系统调用。getutxent 库函数实际上调用 'open' 系统调用来打开 '/var/run/utmp' 并放置一个读锁并读取内容然后释放锁。

现在我们确认 who 命令读取 utmp 文件以显示输出。

'strace' 和 'ltrace' 都有一组可以使用的好选项。

  • -p pid – 附加到指定的 pid。如果程序已经在运行并且您想知道它的行为,这很有用。
  • -n 2 – 将每个嵌套调用缩进 2 个空格。
  • -f - 跟随叉子

三、拦截库调用

5. LD_PRELOAD & LD_LIBRARY_PATH

LD_PRELOAD 允许我们将库添加到程序的特定执行中。此库中的函数将覆盖实际的库函数。

注意:我们不能将它与设置了“suid”位的程序一起使用。

让我们看下面的程序。

#include <stdio.h>
int main() {
  char str1[]="TGS";
  char str2[]="tgs";
  if(strcmp(str1,str2)) {
    printf("String are not matched\n");
  }
  else {
    printf("Strings are matched\n");
  }
}

编译并执行程序。

# cc -o my_prg my_prg.c
# ./my_prg

它将打印“字符串不匹配”。

现在我们将编写自己的库,我们将了解如何拦截库函数。

#include <stdio.h>
int strcmp(const char *s1, const char *s2) {
  // Always return 0.
  return 0;
}

编译并将 LD_LIBRARY_PATH 变量设置为当前目录。

# cc -o mylibrary.so -shared library.c -ldl
# LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH

现在将创建一个名为“library.so”的文件。
将 LD_PRELOAD 变量设置为此文件并执行字符串比较程序。

# LD_PRELOAD=mylibrary.so ./my_prg

现在它将打印“字符串匹配”,因为它使用了我们版本的 strcmp 函数。

注意:如果你想拦截任何库函数,那么你自己的库函数应该和原始库函数有相同的原型。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。