请考虑以下示例代码:

program code


build/program-x86:     file format elf32-i386

Disassembly of section my_text:

080a9dfc <subroutine_fnc>:
 80a9dfc:   55                      push   %ebp
 80a9dfd:   89 e5                   mov    %esp,%ebp
 80a9dff:   57                      push   %edi
 80a9e00:   56                      push   %esi
 80a9e01:   53                      push   %ebx
 80a9e02:   83 ec 14                sub    q4312078qx14,%esp        // 20 bytes for local variables
 80a9e05:   c7 45 e0 00 00 00 00    movl   q4312078qx0,-0x20(%ebp)  // zero local variable at address bp-0x20
 80a9e0c:   8d 7d f3                lea    -0xd(%ebp),%edi   // pointer in area of the local variables
 80a9e0f:   8b 75 0c                mov    0xc(%ebp),%esi    // 2. parameter
 80a9e12:   83 c6 30                add    q4312078qx30,%esi        // add ascii ASCII '0' to the parameter

 80a9e15:   ba 01 00 00 00          mov    q4312078qx1,%edx         // constatnt 1
 80a9e1a:   8b 5d 08                mov    0x8(%ebp),%ebx    // 1. function parameter
 80a9e1d:   89 f9                   mov    %edi,%ecx         // local buffer
 80a9e1f:   b8 03 00 00 00          mov    q4312078qx3,%eax         // syscall read
 80a9e24:   cd 80                   int    q4312078qx80             // read(par1, ptr to local var, 1)
 80a9e26:   83 f8 01                cmp    q4312078qx1,%eax         // return value is 1?
 80a9e29:   74 0c                   je     80a9e37 <subroutine_fnc+0x3b>  // yes
 80a9e2b:   bb 01 00 00 00          mov    q4312078qx1,%ebx
 80a9e30:   b8 01 00 00 00          mov    q4312078qx1,%eax
 80a9e35:   cd 80                   int    q4312078qx80             // no exit(1)

 80a9e37:   0f b6 45 f3             movzbl -0xd(%ebp),%eax   // expand value to 32 bits
 80a9e3b:   3c 2f                   cmp    q4312078qx2f,%al         // is value < ASCII '0'
 80a9e3d:   7e 17                   jle    80a9e56 <subroutine_fnc+0x5a>  // yes end of the subroutine
 80a9e3f:   0f be d0                movsbl %al,%edx
 80a9e42:   39 f2                   cmp    %esi,%edx         // is value above >= par2 + '0'
 80a9e44:   7d 10                   jge    80a9e56 <subroutine_fnc+0x5a>  // yes => end
 80a9e46:   8b 45 0c                mov    0xc(%ebp),%eax    // read again param2
 80a9e49:   0f af 45 e0             imul   -0x20(%ebp),%eax  // multiply ebp-0x20 by param2
 80a9e4d:   8d 54 10 d0             lea    -0x30(%eax,%edx,1),%edx // add result with read character - ASCII '0'
 80a9e51:   89 55 e0                mov    %edx,-0x20(%ebp)  // store result to the local variable at ebp-0x20
 80a9e54:   eb bf                   jmp    80a9e15 <subroutine_fnc+0x19>  // repeat

 80a9e56:   8b 45 e0                mov    -0x20(%ebp),%eax  // function returns value from local variable at ebp-0x20
 80a9e59:   83 c4 14                add    q4312078qx14,%esp
 80a9e5c:   5b                      pop    %ebx
 80a9e5d:   5e                      pop    %esi
 80a9e5e:   5f                      pop    %edi
 80a9e5f:   5d                      pop    %ebp
 80a9e60:   c3                      ret    

080a9e61 <toplevel_fnc>:
 80a9e61:   55                      push   %ebp
 80a9e62:   89 e5                   mov    %esp,%ebp
 80a9e64:   57                      push   %edi
 80a9e65:   56                      push   %esi
 80a9e66:   53                      push   %ebx
 80a9e67:   83 ec 20                sub    q4312078qx20,%esp         // reserve stack space for local variables
 80a9e6a:   c6 45 f3 41             movb   q4312078qx41,-0xd(%ebp)   // store ASCII 'A' at ebp-0xd
 80a9e6e:   c7 44 24 04 0a 00 00    movl   q4312078qxa,0x4(%esp)     // store 10 to the first 32-bit slot bellow stack top
 80a9e75:   00 
 80a9e76:   c7 04 24 00 00 00 00    movl   q4312078qx0,(%esp)        // store zero to the stack top
 80a9e7d:   e8 7a ff ff ff          call   80a9dfc <subroutine_fnc> // call subroutine_fnc(0,10)
 80a9e82:   89 c7                   mov    %eax,%edi          // store result
 80a9e84:   ba 80 01 00 00          mov    q4312078qx180,%edx
 80a9e89:   b9 42 02 00 00          mov    q4312078qx242,%ecx
 80a9e8e:   be 00 7f 0c 08          mov    q4312078qx80c7f00,%esi    // setup pointer to "data"
 80a9e93:   89 f3                   mov    %esi,%ebx
 80a9e95:   b8 05 00 00 00          mov    q4312078qx5,%eax          // syscall open
 80a9e9a:   cd 80                   int    q4312078qx80              // open("data", 0x242, 0x180)
 80a9e9c:   89 45 dc                mov    %eax,-0x24(%ebp)   // store result to ebp-0x24
 80a9e9f:   85 c0                   test   %eax,%eax          // set flags according to the eax test
 80a9ea1:   79 0e                   jns    80a9eb1 <toplevel_fnc+0x50>  // sign is not set (>=0)
 80a9ea3:   b8 01 00 00 00          mov    q4312078qx1,%eax
 80a9ea8:   89 c3                   mov    %eax,%ebx
 80a9eaa:   b8 01 00 00 00          mov    q4312078qx1,%eax          // syscall exit
 80a9eaf:   cd 80                   int    q4312078qx80              // exit(1)

 80a9eb1:   89 7d e0                mov    %edi,-0x20(%ebp)   // store subroutine_fnc result into ebp-0x20
 80a9eb4:   8d 75 f3                lea    -0xd(%ebp),%esi    // ebp-0xd is pointer to the 'A' character
 80a9eb7:   eb 22                   jmp    80a9edb <toplevel_fnc+0x7a>

 80a9eb9:   8b 5d dc                mov    -0x24(%ebp),%ebx   // fill ebx by open result (fd)
 80a9ebc:   89 f1                   mov    %esi,%ecx          // pointer to 'A'
 80a9ebe:   ba 01 00 00 00          mov    q4312078qx1,%edx
 80a9ec3:   b8 04 00 00 00          mov    q4312078qx4,%eax          // syscall write
 80a9ec8:   cd 80                   int    q4312078qx80              // write(fd from open, "A", 1)
 80a9eca:   85 c0                   test   %eax,%eax          // check result
 80a9ecc:   79 09                   jns    80a9ed7 <toplevel_fnc+0x76>
 80a9ece:   89 d3                   mov    %edx,%ebx          // setup sign
 80a9ed0:   b8 01 00 00 00          mov    q4312078qx1,%eax
 80a9ed5:   cd 80                   int    q4312078qx80              // exit(1)
 80a9ed7:   83 6d e0 01             subl   q4312078qx1,-0x20(%ebp)   // subtract 1 from ebp-0x20
 80a9edb:   83 7d e0 00             cmpl   q4312078qx0,-0x20(%ebp)   // value 0 reached
 80a9edf:   75 d8                   jne    80a9eb9 <toplevel_fnc+0x58> // no => repeat

 80a9ee1:   8b 5d dc                mov    -0x24(%ebp),%ebx   // fd from open syscall
 80a9ee4:   b8 06 00 00 00          mov    q4312078qx6,%eax          // syscall close
 80a9ee9:   cd 80                   int    q4312078qx80              // close(fd from open)
 80a9eeb:   85 c0                   test   %eax,%eax          // test result
 80a9eed:   79 0e                   jns    80a9efd <toplevel_fnc+0x9c>
 80a9eef:   b8 01 00 00 00          mov    q4312078qx1,%eax
 80a9ef4:   89 c3                   mov    %eax,%ebx
 80a9ef6:   b8 01 00 00 00          mov    q4312078qx1,%eax
 80a9efb:   cd 80                   int    q4312078qx80              // for error exit exit(1)

 80a9efd:   89 f8                   mov    %edi,%eax          // restore saved result of
                                                                  // subroutine_fnc call
 80a9eff:   83 c4 20                add    q4312078qx20,%esp
 80a9f02:   5b                      pop    %ebx
 80a9f03:   5e                      pop    %esi
 80a9f04:   5f                      pop    %edi
 80a9f05:   5d                      pop    %ebp
 80a9f06:   c3                      ret    

program data


build/program-x86:     file format elf32-i386

Contents of section my_data:
 80c7f00 64617461 00                          data.           


一般来说,我如何才能了解subroutine_fnc使用哪些参数?我对这种通用方法感兴趣。我了解并非总是有100%的可能,但是至少在学习基础方面我很有趣。

#1 楼

基础知识

1。必需的信息:调用约定

为了确定如何将参数传递给函数,必须知道调用约定。

函数调用约定取决于目标体系结构和编译器1 (另请参见:Agner Fog的不同C ++编译器和操作系统的调用约定)。未明确声明用于创建上面要反汇编的代码的编译器并不是很重要,因为输出中有足够的信息来确定目标体系结构和调用约定。观察到指令集是x86,并且调用约定是cdecl

2。识别调用约定

在这种情况下,我们可以从上面的反汇编中推断出调用约定。根据cdecl约定,在保存和恢复寄存器方面,我们观察到了符合被调用者函数预期的行为: >
080a9e61 <toplevel_fnc>:
 80a9e61:   55                      push   %ebp
 80a9e62:   89 e5                   mov    %esp,%ebp
 80a9e64:   57                      push   %edi         
 80a9e65:   56                      push   %esi         
 80a9e66:   53                      push   %ebx         
 80a9e67:   83 ec 20                sub    
080a9dfc <subroutine_fnc>:
 80a9dfc:   55                      push   %ebp
 80a9dfd:   89 e5                   mov    %esp,%ebp
 80a9dff:   57                      push   %edi
 80a9e00:   56                      push   %esi
 80a9e01:   53                      push   %ebx
 80a9e02:   83 ec 14                sub    
080a9e61 <toplevel_fnc>:
 80a9e61:   55                      push   %ebp
 80a9e62:   89 e5                   mov    %esp,%ebp
 80a9e64:   57                      push   %edi
 80a9e65:   56                      push   %esi
 80a9e66:   53                      push   %ebx
 80a9e67:   83 ec 20                sub    
080482f0 <main>:
 80482f0:       b8 01 00 00 00          mov    q4312078qx1,%eax
 80482f5:       c3                      ret

080483f0 <function>:
 80483f0:       8b 44 24 08             mov    0x8(%esp),%eax
 80483f4:       03 44 24 04             add    0x4(%esp),%eax
 80483f8:       03 44 24 0c             add    0xc(%esp),%eax
 80483fc:       c3                      ret    
x20,%esp 80a9e6a: c6 45 f3 41 movb q4312078qx41,-0xd(%ebp) 80a9e6e: c7 44 24 04 0a 00 00 movl q4312078qxa,0x4(%esp) <- arg 2 80a9e75: 00 80a9e76: c7 04 24 00 00 00 00 movl q4312078qx0,(%esp) <- arg 1 80a9e7d: e8 7a ff ff ff call 80a9dfc <subroutine_fnc>
x14,%esp . . . 80a9e59: 83 c4 14 add q4312078qx14,%esp 80a9e5c: 5b pop %ebx 80a9e5d: 5e pop %esi 80a9e5e: 5f pop %edi 80a9e5f: 5d pop %ebp 80a9e60: c3 ret
x20,%esp . . . 80a9eff: 83 c4 20 add q4312078qx20,%esp 80a9f02: 5b pop %ebx 80a9f03: 5e pop %esi 80a9f04: 5f pop %edi 80a9f05: 5d pop %ebp 80a9f06: c3 ret


在两个函数中,常规的x86函数序言都将值保存在堆栈中的寄存器%edi%esi%ebx中。这些寄存器称为被调用者保存寄存器(相对于调用者保存寄存器%eax%ecx%edx)。然后在执行ret之前恢复这些寄存器的先前值。

注意:函数<toplevel_fnc>的堆栈框架明显与16字节边界对齐,这表明GCC可能是用于生成代码的编译器。3。将参数传递给cdecl中的函数


要进行子路由调用,调用者应:


在调用子例程之前,调用者应保存指定为调用者保存的某些寄存器的内容。保存呼叫者的寄存器是EAX,ECX,EDX。由于允许被调用的子例程修改这些寄存器,因此,如果调用者在子例程返回之后依赖于它们的值,则调用者必须将这些寄存器中的值压入堆栈(以便可以在子例程返回之后将其恢复。
要将参数传递给子例程,请在调用之前将其推入堆栈。应按反序将参数推入(即最后一个参数)。由于堆栈变小,因此第一个参数将存储在最低地址(此反演
要调用子例程,请使用call指令。该指令将返回地址放在堆栈上参数的顶部,并跳转到子例程代码。该子例程代码应遵循以下被调用者规则。2被调用的功能:
q4312078q


一般来说,我如何才能了解<subroutine_fnc>使用了哪些参数?


没有优化),如果知道调用约定,则很有可能可以发现函数参数。

基本知识不是这么简单:我们检查当使用subroutine_fnc参数(最大优化)执行gcc(最大优化)时,从简单示例源生成的某些目标代码的分解(请参见下文):至-O3吗? <main>的参数是什么?这两个函数之间的关系(如果有)是什么?

我们可以看到,优化的代码中根本没有大量信息。

优化的目标代码和未优化的目标代码之间的差异很大。未优化的源代码汇编可以在这里找到:https://godbolt.org/g/HS57Wp

这里是源代码(尝试猜测正在发生什么,然后将鼠标指针移到从下面的非常简单的示例中可以看出,优化将调用约定抛到了窗外,离开了很难理解代码中到底发生了什么。在优化的代码中,在<function>中没有int function(int a, int b, int c); //prototype int main(void) { int a = 1; int b = 2; int c = 3; int k = function(a, b, c); return k / 6; } int function(int a, int b, int c) { return a + b + c; }指令,这使得参数识别变得相当困难。 >
更简单的基础知识:变量类型恢复


如何轻松确定elf32-i386中方法参数的大小和类型? />从反汇编目标代码中推导函数参数类型称为类型恢复,并且与变量恢复密切相关。两者都是难题,也是研究的课题。

变量和类型的概念在目标代码中不存在。变量名是被赋予存储地址的标签,该存储地址对应于位于该地址的数据。虽然类型信息对于编译器评估源代码的语法正确性和语义正确性是必需的,但是直接由CPU执行的目标代码不会保留此信息(至少不是直接保留)。


类型恢复任务(为每个变量提供高级类型)更具挑战性。类型恢复具有挑战性,因为高级类型通常在编译过程的早期就被编译器丢弃。在已编译的代码本身中,我们具有可字节寻址的存储器
和寄存器。例如,如果将变量放入eax中,则很容易得出结论,其类型与32位寄存器兼容,但是很难推断出高级类型,例如带符号整数,指针,联合和结构。

当前的类型恢复解决方案采用动态方法来导致较差的程序覆盖率,或者使用无原则的启发式方法(通常会给出错误的结果)。当前基于静态的工具通常会利用一些与知名函数原型有关的知识来推断参数,然后使用专有的启发式方法来猜测剩余变量的类型,例如局部变量。 3


不同的反编译器对此类问题采用不同的方法。以下是TIE(可执行文件的类型推断)所采用的方法: br />
1。调用约定(Wikipedia)

2。 x86汇编指南(应注意,“参数”一词使用不正确-应该是“参数”)

3。 TIE:二进制程序中的类型的原则性逆向工程

评论


哇,真棒!

–TomášZato-恢复莫妮卡
17年6月22日在1:23