如何将反汇编指令匹配回 C/C++ 源代码?

逆向工程 反编译 C++ C 小精灵
2021-06-19 00:25:12

基本上这就是我想知道的,如果你有一个反汇编的指令说:

jz 0x8048e1a

而且,您还有源代码。您如何弄清楚该指令与源代码中的内容有关?至少,是否有可能找出这条指令来自的基本块?

基本上,我有一个应用程序,它为我自己编译的 ELF 二进制文件吐出反汇编指令(源代码可用),现在我必须找出这个反汇编指令映射到源代码中的内容/位置。

我们还假设我在编译时不使用任何优化标志。

我可能应该提到我使用英特尔的 PIN 作为输出这些反汇编指令的应用程序。所以,我知道这些指令属于哪个函数/例程。但是,我只想在装配级别更精确一些。

感谢您的任何帮助/提示!

4个回答

您必须阅读和解释程序集。没有什么可以替代阅读程序集。如果您知道汇编指令的含义,并且您有源代码,那么两者之间的关系应该很清楚。将没有直接的标记来说明程序集来自哪一行。

因此,了解 C/C++ 中的基本结构在 ASM 中的外观。

循环是什么样的?(提示:在 ASM 中有几种写循环的方法)

结构体是什么样子的?

函数是什么样子的?

指针是什么样的?

如果你学好 ASM,这一切就都清楚了。

此外,了解编译器的工作原理,并学习调试正在运行的代码。

编辑:您实际上可以生成调试器符号来将代码与各个行号相关联,所以我上面所说的并不完全正确。

编辑:下面的答案特定于 PE 文件,现在@Achilles 指定他的问题与 ELF 文件有关,因此它没有回答上面的问题。

鉴于您拥有源代码并且可以为您的程序生成符号,您可以使用调试接口访问 SDK将汇编代码指令地址映射到源代码行号。特别是,您可能想要使用IDiaLineNumber 类

以下函数显示函数中使用的行号(由 表示pSymbol)。

void dumpFunctionLines( IDiaSymbol* pSymbol, IDiaSession* pSession ) {
ULONGLONG length = 0;
DWORD     isect  = 0;
DWORD     offset = 0;

pSymbol->get_addressSection( &isect );
pSymbol->get_addressOffset( &offset );
pSymbol->get_length( &length );
if ( isect != 0 && length > 0 )
{
    CComPtr< IDiaEnumLineNumbers > pLines;
    if ( SUCCEEDED( pSession->findLinesByAddr(
                                  isect,
                                  offset,
                                  static_cast<DWORD>( length ),
                                  &pLines)
                  )
       )
    {
        CComPtr< IDiaLineNumber > pLine;
        DWORD celt      = 0;
        bool  firstLine = true;

        while ( SUCCEEDED( pLines->Next( 1, &pLine, &celt ) ) &&
                celt == 1 )
        {
            DWORD offset;
            DWORD seg;
            DWORD linenum;
            CComPtr< IDiaSymbol >     pComp;
            CComPtr< IDiaSourceFile > pSrc;

            pLine->get_compiland( &pComp );
            pLine->get_sourceFile( &pSrc );
            pLine->get_addressSection( &seg );
            pLine->get_addressOffset( &offset );
            pLine->get_lineNumber( &linenum );
            printf( "\tline %d at 0x%x:0x%x\n", linenum, seg, offset );
            pLine = NULL;
            if ( firstLine )
            {
                // sanity check
                CComPtr< IDiaEnumLineNumbers > pLinesByLineNum;
                if ( SUCCEEDED( pSession->findLinesByLinenum(
                                              pComp,
                                              pSrc,
                                              linenum,
                                              0,
                                              &pLinesByLineNum)
                              )
                   )
                {
                    CComPtr< IDiaLineNumber > pLine;
                    DWORD celt;
                    while ( SUCCEEDED( pLinesByLineNum->Next( 1, &pLine, &celt ) ) &&
                            celt == 1 )
                    {
                        DWORD offset;
                        DWORD seg;
                        DWORD linenum;

                        pLine->get_addressSection( &seg );
                        pLine->get_addressOffset( &offset );
                        pLine->get_lineNumber( &linenum );
                        printf( "\t\tfound line %d at 0x%x:0x%x\n", linenum, seg, offset );
                        pLine = NULL;
                   }
                }
                firstLine = false;
            }
        }
    }
} }

您如何弄清楚该指令与源代码中的内容有关?

主要是直觉和经验。鉴于源代码的高级概述,人们可以发现函数以及指令所属的行,但到目前为止,我知道没有程序可以做到这一点。拥有调试信息当然有很大帮助,但不是万能的,而且通常并不总是可用。

至少,是否有可能找出这条指令来自的基本块?

一般来说,没有。在某些情况下,是的。现代编译器倾向于过于激进地优化代码。即使拥有编译器生成的所有调试信息,有时也不足以映射给定 insn 的精确位置,这可以通过尝试对此类应用程序进行源代码级调试来证明。

编辑:没有正确地注意到您自己构建了二进制文件。那么是的,至少应该可以将指令映射到给定调试信息的函数。

你用的是哪个编译器?我的背景更多是在 Windows 开发(而不是 Linux/BSD/OSX),所以我只能说这种情况。

但是在Visual Studio 中,您可以在调试 C/C++ 时看到生成的程序集。有关详细信息,请参阅此 MSDN 文章

如果您只对 ASM 感兴趣,您可能希望更改 Visual Studio 项目中的一些编译设置,以便减少项目中包含的调试信息。有关如何执行此操作的详细信息,请参见此处

主要涉及:

  • 禁用 C++ 异常
  • 调试信息格式设置为程序数据库 (/Zi)
  • 关闭缓冲区安全检查 (/GS-)
  • 关闭增量链接 (/NCREMENTAL:NO)

在此处输入图片说明

Embarcadero C++ Builder 和 Delphi(以前称为 Borland)也提供相同的功能。使用细节在这里在此处输入图片说明