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