Back

/ 6 min read

c逆向之变量与数组

变量

image-20250122103224769

局部变量

  • 保存在栈上,先声明的变量在高地址,后声明在低地址。
  • 局部变量在栈上会内存对齐。
image-20250121163232701

静态局部变量

  • 等价全局变量(生命周期一致)。
image-20250121164230362

区分

int fun1(int n1){
static int i = n1; //
return i;
}

tls环境中,保存着i是否被初始化的标志,每次进函数的时候,都要进行判断。

通过汇编我们可以看到,静态局部变量的初始标志的检查是无锁的。

但是标志的改写,仍然是需要线程同步的。

unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
int `int fun1(int)'::`2'::i DD 01H DUP (?) ; `fun1'::`2'::i
int `int fun1(int)'::`2'::$TSS0 DD 01H DUP (?) ; `fun1'::`2'::$TSS0
`string' DB '%d', 00H ; `string'
_n1$ = 8 ; size = 4
int fun1(int) PROC ; fun1, COMDAT
mov eax, DWORD PTR fs:__tls_array
mov ecx, DWORD PTR __tls_index
mov ecx, DWORD PTR [eax+ecx*4]
mov eax, DWORD PTR int `int fun1(int)'::`2'::$TSS0
cmp eax, DWORD PTR __Init_thread_epoch[ecx]
jg SHORT $LN5@fun1
$LN2@fun1:
push DWORD PTR int `int fun1(int)'::`2'::i
push OFFSET `string'
call _printf
mov eax, DWORD PTR int `int fun1(int)'::`2'::i
add esp, 8
ret 0
$LN5@fun1:
push OFFSET int `int fun1(int)'::`2'::$TSS0
call __Init_thread_header
add esp, 4
cmp DWORD PTR int `int fun1(int)'::`2'::$TSS0, -1
jne SHORT $LN2@fun1
mov eax, DWORD PTR _n1$[esp-4]
push OFFSET int `int fun1(int)'::`2'::$TSS0
mov DWORD PTR int `int fun1(int)'::`2'::i, eax
call __Init_thread_footer
add esp, 4
jmp SHORT $LN2@fun1
int fun1(int) ENDP ; fun1

堆变量

  • 调用malloc,但是有可能被静态链接,如果sig文件未识别的话,往下找可以找到调用系统的API

全局变量

  • 保存在.rdata或者.data.bss,常量区或全局数据区、未初始化区。

有初始值

有初始值的全局变量

没有初始值

全局变量的动态初始化

image-20250122103617447
#include <stdio.h>
#include <cstdint>
int fun1(int n1);
int g_int = fun1(10); // 动态初始化
int fun1(int n1){
static int i = n1;
printf("%d", i);
return i;
}

生成汇编:

# 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
int g_int DD 01H DUP (?) ; g_int
int `int fun1(int)'::`2'::i DD 01H DUP (?) ; `fun1'::`2'::i
int `int fun1(int)'::`2'::$TSS0 DD 01H DUP (?) ; `fun1'::`2'::$TSS0
`string' DB '%d', 00H ; `string'
void `dynamic initializer for 'g_int''(void) PROC ; `dynamic initializer for 'g_int'', COMDAT
mov eax, DWORD PTR fs:__tls_array
mov ecx, DWORD PTR __tls_index
mov ecx, DWORD PTR [eax+ecx*4]
mov eax, DWORD PTR int `int fun1(int)'::`2'::$TSS0
cmp eax, DWORD PTR __Init_thread_epoch[ecx]
jg SHORT $LN7@dynamic
$LN4@dynamic:
push DWORD PTR int `int fun1(int)'::`2'::i
push OFFSET `string'
call _printf
mov eax, DWORD PTR int `int fun1(int)'::`2'::i
add esp, 8
mov DWORD PTR int g_int, eax ; g_int
ret 0
$LN7@dynamic:
push OFFSET int `int fun1(int)'::`2'::$TSS0
call __Init_thread_header
add esp, 4
cmp DWORD PTR int `int fun1(int)'::`2'::$TSS0, -1
jne SHORT $LN4@dynamic
push OFFSET int `int fun1(int)'::`2'::$TSS0
mov DWORD PTR int `int fun1(int)'::`2'::i, 10 ; 0000000aH
call __Init_thread_footer
add esp, 4
jmp SHORT $LN4@dynamic
void `dynamic initializer for 'g_int''(void) ENDP ; `dynamic initializer for 'g_int''
  • 首先生成一个dynamic initializer for 'g_int'函数。
  • 在入口之前执行动态初始化。

逆向时向上查找全局变量被引用地址,看看是否被动态初始化调用。(通过IDA的绘图向上溯源)

image-20250121171651979

数组

连续的变量如何证明为数组?

  • 类型是否一致?
  • 是否跟着循环、判断下标等语句?
  • 是否有初始化代码?
  • 数组寻址公式:ary + index * sizeof(type),或者指针累加。lea eax, ary``mov ary, ary+4
  • 数组的初始化通常有rep或者memset,特别是memset。(debug会使用rep),默认值会使用xmm0寄存器。

数组初始化

不完全初始化

第一种,未初始化的元素个数较少,会使用xmm0movups、movaps等指令处理。

int array_1[4] = {1, 2, 3}; // 第一种,不完全初始化
image-20250122104936920 image-20250122105143001

第二种,较多的元素未初始化,会使用memset将未初始化的元素全部初始化为0

image-20250122105342316

未初始化

并不会使用memset将元素初始化为0,而是会使用栈上的残留值。

image-20250122105712667

初始化为0

会使用memset将数组全部初始化为0

image-20250122105823176

全初始化

要么使用xmm0或者直接优化为多个mov语句。

image-20250122105930707

数组的遍历

一维数组的遍历,可以找到循环和一次乘法。

image-20250122110959243

二维数组,可以找到两层循环和两个步长

image-20250122111522007

二维数组/多维数组

在内存中,并不存在多维数组,只是步长更精细。

二维数组乘两次、三位乘三次…

务必记得乘法会被优化为移位。