Back

/ 6 min read

c逆向之函数

  • 搞清楚调用约定stdcallcdeclfastcall,决定了参数传递、谁平衡栈。
  • 不能以push的个数来确定参数的个数。
  • 如果有参数,看ret平栈状态。
image-20250120171940527

优化

  • ebp省略优化。(帧优化)
  • 内联优化
  • 全程序优化(高版本限定):
    • 改调用约定
    • 变量传播

内联优化

使用\Ob0|1|2|3来设置内联优化级别。

0 /Od 下的默认值。 禁用内联扩展。

1 仅允许对标记为 inline__inline__forceinline 的函数或是在类声明中定义的 C++ 成员函数中进行扩展。

2 /O1/O2 下的默认值。 允许编译器扩展任何未明确标记为无内联的函数。

3 此选项指定比 /Ob2 更积极的内联,但具有相同的限制。 /Ob3 选项自 Visual Studio 2019 起可用。

库函数容易被内联优化strlen,且没有逆向恢复价值,release版会被内联优化。

  • 库函数强制内联。
  • 可以使用#pragma function(strcpy, strlen)msvc中强制库函数使用内建函数,而不进行内联。
image-20250121101558766

内联后特征

strlen

image-20250121100345002
int main(int argc, char *argv[]){
printf("strlen: %d", strlen(argv[0]));
return 0;
}
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
`string' DB 'strlen: %d', 00H ; `string'
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC ; COMDAT
mov eax, DWORD PTR _argv$[esp-4]
mov eax, DWORD PTR [eax]
lea edx, DWORD PTR [eax+1]
npad 7
$LL3@main:
mov cl, BYTE PTR [eax]
inc eax
test cl, cl
jne SHORT $LL3@main
sub eax, edx
push eax
push OFFSET `string'
call _printf
add esp, 8
xor eax, eax
ret 0
_main ENDP
  • mov eax, DWORD PTR _argv$[esp-4] mov eax, DWORD PTR [eax]将首地址给到eax
  • lea edx, DWORD PTR [eax+1]edx中存放首地址+1的地址值。
  • 使用循环不断test结束符。
  • 找到之后与edx相减,得到字符串长度。

strcpy

image-20250121102103248
int main(int argc, char *argv[]){
char* buf = (char*)malloc(100);
strcpy(buf, argv[1]);
return 0;
}
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC ; COMDAT
push 100 ; 00000064H
call _malloc
mov ecx, DWORD PTR _argv$[esp]
add esp, 4
mov ecx, DWORD PTR [ecx+4]
$LL3@main:
mov dl, BYTE PTR [ecx]
lea ecx, DWORD PTR [ecx+1]
mov BYTE PTR [eax], dl
lea eax, DWORD PTR [eax+1]
test dl, dl
jne SHORT $LL3@main
xor eax, eax
ret 0
_main ENDP
  • ecx存放argv的首元素,ecx+4前往下一个元素(指针4个字节)。
  • mov BYTE PTR [eax], dl将元素每次拷贝一个字节,传送到buf中。

strcmp

int my_strcmp(const char* str1, const char* str2) {
while (*str1 && (*str1 == *str2)) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
int main(int argc, char *argv[]){
char* buf = (char*)malloc(100);
memset(buf, 0xcc, 100);
printf("%d", strcmp(buf, argv[2]));
return 0;
}
# License: MSVC Proprietary
# The use of this compiler is only permitted for internal evaluation purposes and is otherwise governed by the MSVC License Agreement.
# See https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
`string' DB '%d', 00H ; `string'
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC ; COMDAT
push esi
push 100 ; 00000064H
call _malloc
push 100 ; 00000064H
mov esi, eax
push 204 ; 000000ccH
push esi
call _memset
mov ecx, DWORD PTR _argv$[esp+16]
add esp, 16 ; 00000010H
mov eax, DWORD PTR [ecx+8]
$LL3@main:
mov cl, BYTE PTR [esi]
cmp cl, BYTE PTR [eax]
jne SHORT $LN4@main
test cl, cl
je SHORT $LN5@main
mov cl, BYTE PTR [esi+1]
cmp cl, BYTE PTR [eax+1]
jne SHORT $LN4@main
add esi, 2
add eax, 2
test cl, cl
jne SHORT $LL3@main
$LN5@main:
xor eax, eax
push eax
push OFFSET `string'
call _printf
add esp, 8
xor eax, eax
pop esi
ret 0
$LN4@main:
sbb eax, eax
or eax, 1
push eax
push OFFSET `string'
call _printf
add esp, 8
xor eax, eax
pop esi
ret 0
_main ENDP
$LN4@main:
sbb eax, eax ; 根据 ZF 设置 eax 的符号位
or eax, 1 ; eax = -1 或 1,表示不相等
push eax
push OFFSET `string' ; 准备打印结果
call _printf
add esp, 8

如果发现字符不相等:

  • 使用 sbb eax, eaxor eax, 1 来设置 eax 的值为 -11,取决于哪个字符串更大。
  • 这种逻辑是 strcmp 的标准返回值定义:正数、零、负数。

调用约定

cdeclC调用约定

int __cdecl fun1(int n1, int n2){
return n1 + n2;
}
  • 参数从右往左入栈。
  • 调用者平栈。

汇编特征:

  • ret不含操作数。
image-20250120171456927

stdcall标准调用约定

int __stdcall fun2(int n1, int n2){
return n1 + n2;
}
  • 函数内部平栈。
  • 参数从右往左入栈。
; 调用
push esi
push edi
call int fun2(int,int) ; fun2
; 不需要外部平栈
; 内部实现
_n1$ = 8 ; size = 4
_n2$ = 12 ; size = 4
int fun2(int,int) PROC ; fun2, COMDAT
mov eax, DWORD PTR _n1$[esp-4]
add eax, DWORD PTR _n2$[esp-4]
ret 8 ; 特征 ret后面带值
int fun2(int,int) ENDP ; fun2

fastcall快速调用约定

int __fastcall fun3(int n1, int n2){
return n1 + n2;
}
  • ecxedx作为前两个参数,多余的参数,从右往左入栈。
  • 函数内部平栈。

汇编特征:

  • 未保存ecxedx就使用寄存器。
; 实现
int fun3(int,int) PROC ; fun3, COMDAT
lea eax, DWORD PTR [ecx+edx]
ret 0
int fun3(int,int) ENDP ; fun3
; 调用
mov edx, esi
mov ecx, edi
call int fun3(int,int) ; fun3

函数返回值

  • x86机器上返回64位返回值->edx.eax值。