C语言基础
学习二进制,c语言是基础中的基础
c语言是高级语言中较为贴近底层的高级语言,需要考虑类型,返回值…但在c语言里面最重要的是指针的应用和理解。
(1)指针的使用使得不同区域的代码可以轻易的共享内存数据
当然也可以通过数据的复制达到相同的效果,但是这样往往效率不太好。指针节省内存主要体现在参数传递上,比如传递一个结构体指针变量和传递一个结构体变量,结构体占用内存越大,传递指针变量越节省内存的,也就是可以减少不必要的数据复制。
(2)C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等。
(3)有些操作必须使用指针完成
①如:操作申请的堆内存。
②又如:C语言中的一切函数调用中,值传递都是“按值传递”的。如果要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成。(如对一个函数传入一个x一个y,交换两者的值,如果只是按值传递,那在函数内交换并不影响函数外的x和y,而指针则可以做到)
(4)动态分配内存
大多数情况下,可以看到程序使用的内存是通过显式声明分配给变量的内存(也就是静态内存分配)。这一点对于节省计算机内存是有帮助的,因为计算机可以提前为需要的变量分配内存。但是在很多应用场合中,可能程序运行时不清楚到底需要多少内存,这时候可以使用指针,让程序在运行时获得新的内存空间(实际上应该就是动态内存分配),并让指针指向这一内存更为方便。
上面这些是从正向角度来分析为什么指针重要,那么在逆向中呢,指针为什么重要?
首先可以先说的是虽然我们在使用高级语言的过程中,指针在某些时候不是必须,但是计算机在编译过程中所有的操作都会换算成指针操作,那么当我们使用反编译器的时候,它们分析的全部都是指针操作,无法完全转变成我们所熟悉的c语言,我们称之为伪c代码,接下来放一个ida反编译后的代码:
char docheck()
{
unsigned __int8 *v0; // rax
__int64 v1; // rdx
__int64 v2; // rsi
__int64 v3; // rsi
__int64 v4; // rsi
__int64 v5; // rsi
__int64 v6; // rax
__int64 v7; // rdx
__int64 v8; // rdx
__int64 v9; // rdx
__int64 v10; // rdx
unsigned __int8 *v11; // rax
__int64 v12; // rdx
char v13; // cl
char v14; // cl
char v15; // cl
char v16; // cl
__int64 v17; // rdx
__int64 v18; // rsi
char result; // al
char v20; // al
char v21; // al
char v22; // al
char v23; // al
char v24; // al
int v25; // [rsp+0h] [rbp-10h]
__int16 v26; // [rsp+4h] [rbp-Ch]
int v27; // [rsp+8h] [rbp-8h]
__int16 v28; // [rsp+Ch] [rbp-4h]
v0 = &map[4];
v1 = 0LL;
while ( 1 )
{
v28 = 0;
v27 = 0;
v2 = *(v0 - 4);
if ( *((_BYTE *)&v27 + v2) )
break;
*((_BYTE *)&v27 + v2) = 1;
v3 = *(v0 - 3);
if ( *((_BYTE *)&v27 + v3) )
break;
*((_BYTE *)&v27 + v3) = 1;
v4 = *(v0 - 2);
if ( *((_BYTE *)&v27 + v4) )
break;
*((_BYTE *)&v27 + v4) = 1;
v5 = *(v0 - 1);
if ( *((_BYTE *)&v27 + v5) )
break;
*((_BYTE *)&v27 + v5) = 1;
if ( *((_BYTE *)&v27 + *v0) )
break;
++v1;
v0 += 5;
if ( v1 >= 5 )
{
v6 = 0LL;
while ( 1 )
{
v26 = 0;
v25 = 0;
v7 = map[v6];
if ( *((_BYTE *)&v25 + v7) )
break;
*((_BYTE *)&v25 + v7) = 1;
v8 = map[v6 + 5];
if ( *((_BYTE *)&v25 + v8) )
break;
*((_BYTE *)&v25 + v8) = 1;
v9 = map[v6 + 10];
if ( *((_BYTE *)&v25 + v9) )
break;
*((_BYTE *)&v25 + v9) = 1;
v10 = map[v6 + 15];
if ( *((_BYTE *)&v25 + v10) )
break;
*((_BYTE *)&v25 + v10) = 1;
if ( *((_BYTE *)&v25 + map[v6 + 20]) )
break;
if ( ++v6 >= 5 )
{
v11 = &map[4];
v12 = 0LL;
while ( 1 )
{
v13 = row[4 * v12];
if ( v13 == 2 )
{
if ( *(v11 - 4) > *(v11 - 3) )
return 0;
}
else if ( v13 == 1 && *(v11 - 4) < *(v11 - 3) )
{
return 0;
}
v14 = row[4 * v12 + 1];
if ( v14 == 1 )
{
if ( *(v11 - 3) < *(v11 - 2) )
return 0;
}
else if ( v14 == 2 && *(v11 - 3) > *(v11 - 2) )
{
return 0;
}
v15 = row[4 * v12 + 2];
if ( v15 == 2 )
{
if ( *(v11 - 2) > *(v11 - 1) )
return 0;
}
else if ( v15 == 1 && *(v11 - 2) < *(v11 - 1) )
{
return 0;
}
v16 = row[4 * v12 + 3];
if ( v16 == 2 )
{
if ( *(v11 - 1) > *v11 )
return 0;
}
else if ( v16 == 1 && *(v11 - 1) < *v11 )
{
return 0;
}
++v12;
v11 += 5;
if ( v12 >= 5 )
{
v17 = 4LL;
v18 = 0LL;
while ( 1 )
{
v20 = col[v17 - 4];
if ( v20 == 2 )
{
if ( map[v17 - 4] < map[v17 + 1] )
return 0;
}
else if ( v20 == 1 && map[v17 - 4] > map[v17 + 1] )
{
return 0;
}
v21 = col[v17 - 3];
if ( v21 == 1 )
{
if ( map[v17 - 3] > map[v17 + 2] )
return 0;
}
else if ( v21 == 2 && map[v17 - 3] < map[v17 + 2] )
{
return 0;
}
v22 = col[v17 - 2];
if ( v22 == 2 )
{
if ( map[v17 - 2] < map[v17 + 3] )
return 0;
}
else if ( v22 == 1 && map[v17 - 2] > map[v17 + 3] )
{
return 0;
}
v23 = col[v17 - 1];
if ( v23 == 2 )
{
if ( map[v17 - 1] < map[v17 + 4] )
return 0;
}
else if ( v23 == 1 && map[v17 - 1] > map[v17 + 4] )
{
return 0;
}
v24 = col[v17];
if ( v24 == 2 )
{
if ( map[v17] < map[v17 + 5] )
return 0;
}
else if ( v24 == 1 && map[v17] > map[v17 + 5] )
{
return 0;
}
++v18;
v17 += 5LL;
result = 1;
if ( v18 >= 4 )
return result;
}
}
}
}
}
return 0;
}
}
return 0;
}
在ida里面代码的变量,函数名将全部变为数字或者地址,分析时需要时刻脑子清晰这个变量是指针本身还是指针指向的值,在ida分析中可能会遇到多层指针,如果对指针的理解不够深,将寸步难行
汇编语言
汇编语言是最接近于机器语言的编程语言。如果说机器语言是计算机操作的本质,那么汇编语言就是最最接近本质的语言。汇编语言操作直接面向硬件,所以,我们在使用汇编语言的时候,我们能够感知计算机的运行过程和原理,从而能够对计算机硬件和应用程序之间的联系和交互形成一个清晰的认识。这也是最能够锻炼编程者编程思维逻辑的,只有这样,学习者才能形成一个软、硬兼备的编程知识体系,这是任何高级语言都无法给予的!相对于繁复的高级语言,汇编语言指令集合更简约,指令操作更直接,从汇编开始学习更符合循序渐进的学习原理!所以,对于计算机技术初学者或者自学者,汇编语言重要性无可替代!
对于C语言中的数据类型、形参实参、函数调用、全局变量、局部变量等概念及操作,我们都可以用汇编语言中的一些操作相关联,把这些抽象的概念和过程,通过汇编语言形成一个具体的映像,深度剖析,这样我们才能真正的学会、学好C语言。
在这里推荐王爽老师的《汇编语言》,经典!
下面是hello,world的汇编代码
.text:00401350 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401350 public _main
.text:00401350 _main proc near ; CODE XREF: ___mingw_CRTStartup+F8↑p
.text:00401350
.text:00401350 argc = dword ptr 8
.text:00401350 argv = dword ptr 0Ch
.text:00401350 envp = dword ptr 10h
.text:00401350
.text:00401350 push ebp
.text:00401351 mov ebp, esp //切换栈帧
.text:00401353 and esp, 0FFFFFFF0h //边际对齐指令,主流编译器的编译规则规定 “ 程序访问的地址必须向16字节对齐(被16整除)”,最后一位0代表的是最小的16字节,之前的7位都是16字节的倍数。即将最小的16字节置零即可。
即为16的 0 倍。
.text:00401356 sub esp, 10h // 开栈
.text:00401359 call ___main
.text:0040135E mov dword ptr [esp], offset Format ; "hello,world!"
.text:00401365 call _printf
.text:0040136A mov eax, 0
.text:0040136F leave //leave 所做的工作是还原上一帧的栈底指针与栈顶指针
.text:00401370 retn //ret 所做的工作是弹出栈顶的返回地址,并跳转到此地址。此时 %esp 指向调用函数所存储的被调函数的最后一个参数。
这里面还有好多栈的知识,当程序再功能强大一点,会用到计算机的各种知识,例如栈,堆,接口,线程,锁…
所以在学习汇编的过程中,要大范围的真正的深入了解计算机运行,虽不用理解特别复杂的,但是整个程序是如何在计算机上运行要理解
在学习汇编的过程中可以学习使用目前较为强大的一款动态反编译器x64dbg,比起od来说,它可以用在64位程序中,然后了解一下各个模块的作用是什么以及怎么使用。
加密算法
在实际逆向过程中,会遇到各种各样的加密算法,而这些加解密有可能是已经非常成熟的加解密算法,也可能是自己写的加解密,或者是对成熟加解密算法的变形,如果不能分辨识别成熟的加解密,那将非常浪费时间或者无法进行下去。
在这里推荐的学习方法是:
先在网上学习加解密的基本流程,之后自己学习着如何使用c语言实现加解密,在这一点上,我们不要求非得要自己闷头写代码,这里比较推荐看着别人比较成熟的加解密代码进行写,因为自己实现,先不说能不能自己独自实现有些非常复杂的加密算法,即使实现了,健壮性也非常差,和实际应用的加解密代码天差地别。写完之后还没有完,接下来就要使用目前地球上最强大的静态反汇编工具IDA PRO。把写好的exe文件拖入ida中,在ida中再次看看加解密代码变成了什么样子。在以后逆向的过程中,看到的加解密代码大概就是那个样子了,而不是源代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
void *v4; // esp
int v5; // eax
int v6; // eax
int v7; // ebx
int v8; // ebx
char v10[1000]; // [esp+18h] [ebp-94A0h]
char v11[2]; // [esp+400h] [ebp-90B8h]
char Source[4]; // [esp+403h] [ebp-90B5h]
int v13[1000]; // [esp+408h] [ebp-90B0h]
char Dest; // [esp+13A8h] [ebp-8110h]
char DstBuf; // [esp+1790h] [ebp-7D28h]
char Dst[20000]; // [esp+1B78h] [ebp-7940h]
char v17[1000]; // [esp+6998h] [ebp-2B20h]
char Buffer[10000]; // [esp+6D80h] [ebp-2738h]
int v19; // [esp+9490h] [ebp-28h]
int v20; // [esp+9494h] [ebp-24h]
int v21; // [esp+9498h] [ebp-20h]
int v22; // [esp+949Ch] [ebp-1Ch]
int v23; // [esp+94A0h] [ebp-18h]
int v24; // [esp+94A4h] [ebp-14h]
int j; // [esp+94A8h] [ebp-10h]
int i; // [esp+94ACh] [ebp-Ch]
v4 = alloca(v3);
__main();
memset(Buffer, 0, 0x2710u);
memset(v17, 0, sizeof(v17));
memset(Dst, 0, 0x4E20u);
memset(&DstBuf, 0, 0x3E8u);
memset(&Dest, 0, 0x3E8u);
printf(Format);
gets(Buffer);
v22 = strlen(Buffer);
if ( v22 % 3 )
{
v19 = v22 % 3;
v22 -= v22 % 3;
memset(v10, 0, sizeof(v10));
for ( i = 0; i < v22; ++i )
v10[v24++] = Buffer[i];
for ( i = 0; i < v22; ++i )
{
v21 = Buffer[i];
itoa(v21, &DstBuf, 2);
sprintf(&Dest, "%08s", &DstBuf);
strcat(Dst, &Dest);
}
v20 = 8 * v22 / 6;
for ( i = 0; i < v20; ++i )
{
v24 = 0;
for ( j = 6 * i; 6 * (i + 1) > j; ++j )
v17[v24++] = Dst[j];
v6 = b_to_o(v17);
v13[i] = v6;
}
v23 = i;
memset(v17, 0, 0x3E8u);
memset(Dst, 0, 0x4E20u);
if ( v19 == 1 )
{
v21 = Buffer[v22];
itoa(v21, &DstBuf, 2);
sprintf(&Dest, "%08s", &DstBuf);
strcat(Dst, &Dest);
strcpy(Source, "0000");
strcat(Dst, Source);
for ( i = 0; i <= 1; ++i )
{
v24 = 0;
for ( j = 6 * i; 6 * (i + 1) > j; ++j )
v17[v24++] = Dst[j];
v7 = v23++;
v13[v7] = b_to_o(v17);
}
v13[v23] = 64;
v13[v23 + 1] = 64;
}
else if ( v19 == 2 )
{
for ( i = 1; i <= v19; ++i )
{
v21 = Buffer[v22 - i];
itoa(v21, &DstBuf, 2);
sprintf(&Dest, "%08s", &DstBuf);
strcat(Dst, &Dest);
}
strcpy(v11, "00");
strcat(Dst, v11);
for ( i = 0; i <= 2; ++i )
{
v24 = 0;
for ( j = 6 * i; 6 * (i + 1) > j; ++j )
v17[v24++] = Dst[j];
v8 = v23++;
v13[v8] = b_to_o(v17);
}
v13[v23] = 64;
}
for ( i = 0; v20 + 4 > i; ++i )
putchar(boss[v13[i]]);
}
else
{
for ( i = 0; i < v22; ++i )
{
v21 = Buffer[i];
itoa(v21, &DstBuf, 2);
sprintf(&Dest, "%08s", &DstBuf);
strcat(Dst, &Dest);
}
v20 = 8 * v22 / 6;
for ( i = 0; i < v20; ++i )
{
v24 = 0;
for ( j = 6 * i; 6 * (i + 1) > j; ++j )
v17[v24++] = Dst[j];
v5 = b_to_o(v17);
v13[i] = v5;
}
for ( i = 0; i < v20; ++i )
putchar(boss[v13[i]]);
}
return 0;
}
大家能看出来这个加密算法是什么吗?给大家一分钟哈哈哈哈哈哈哈哈哈哈哈哈哈
揭晓一下
BASE64
所以大家可以发现这和写的c语言代码非常不一样,要熟悉他们,以后就要与他们为伍了