《黑客攻防-系统实战》--shellcode
《黑客攻防-系统实战》--shellcode
shellcode
shellcode 是一组可注入的指令,可以在被攻击得到程序内运行,因为shellcode要直接操作寄存器和程序函数,所以通常用汇编语言编写并被翻译为十六进制操作码,因此不能用高级语言编写shellcode, 即使细微的差别有可能导致shellcode无法准确执行,这些导致编写shellcode难度的原因
shellcode最初的作用是漏洞利用程序的特殊部分,破解漏洞就是预先把shellcode注入到缓冲区,然后欺骗目标程序执行它
理解系统调用
写shellcode的目的就是想让目标程序以不同于设计者预期的方式运行,而操纵程序的方式之一就是强制它产生系统调用,通过系统调用可以获取一些特权操作,访问系统内核,
调用系统调用方法:1)使用C库包装(libc) 2)使用汇编指令(把适当的参数加载到寄存器,然后调用软中断)执行系统调用
系统调用的过程
linux环境程序通过int 0x80软中断来执行系统调用,程序执行int 0x80时,CPU切换到内核模式并执行相应的系统调用,使用fastcall约定, 提高寄存器的使用率
calling convention)即C调用约定按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的。 另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++ f( *p) –>> _f@
调用过程:
1)系统调用编号载入EAX
2)把系统调用参数压入栈中(注意:最多支持6个参数,分别保存在:EBX,ECX,EDX,ESI,EDI,EBP, 超过六个,通过第一个指定数据结构传递)
3)执行int 0x80指令
4)CPU切换到内核模式
5)执行系统调用
举个例子:
1 #include <stdio.h>2 3 int main()4 {5 exit(0);6 return 0;7 }
exit() -> _exit()
反汇编看一下:
call *%gs:0x10 ;系统调用从这里进入,会调用到int80
mov 0x4(%esp), %ebx; 退出把系统调用参数加载到ebx, 系统调用之前压栈
编写shellcode
注意:
shellcode大小:由于较小的shellcode 可以注入更多的缓冲区,可以用来攻击更多的程序,所以要使得shellcode尽量保持简单,紧凑
当攻击问题程序的时候,需要把shellcode复制到缓存区,另外还要加上调用shellcode的指令, 所以必须要小
分析程序:
前面exit系统调用分析主要完成三个动作:
1)把0放到EBX
2)把1放到EAX
3)执行int 0x80 指令来产生系统调用
1. hello.c
1 section .data ;section declaration 2 msg db "Hello, world!",0xA ;our dear string 3 len equ $ - msg ;length of our dear string 4 section .text ;section declaration 5 ;we must export the entry point to the ELF linker or 6 global _start ;loader. They conventionally recognize _start as their 7 ;entry point. Use ld -e foo to override the default. 8 _start: 9 ;write our string to stdout10 mov eax,4 ;system call number (sys_write)11 mov ebx,1 ;first argument: file handle (stdout)12 mov ecx,msg ;second argument: pointer to message to write13 mov edx,len ;third argument: message length14 int 0x80 ;call kernel15 ;and exit16 mov eax,1 ;system call number (sys_exit)17 xor ebx,ebx ;first syscall argument: exit code18 int 0x80 ;call kernel
2. objdump -d hello
1 foobar: fiobjdump -d foobarle format elf32-i386 2 3 4 Disassembly of section .text: 5 6 08049000 <.text>: 7 8049000: b8 04 00 00 00 mov $0x4,%eax 8 8049005: bb 01 00 00 00 mov $0x1,%ebx 9 804900a: b9 00 a0 04 08 mov $0x804a000,%ecx10 804900f: ba 0e 00 00 00 mov $0xe,%edx11 8049014: cd 80 int $0x8012 8049016: b8 01 00 00 00 mov $0x1,%eax13 804901b: 31 db xor %ebx,%ebx14 804901d: cd 80 int $0x80
3. 把操作码写到字符串数组中执行
char shellcode[] = "\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\xa0\x04\x08"
"\xba\x0e\x00\x00\x00"
"\xcd\x80"
"\xb8\x01\x00\x00\x00"
"\x31\xdb"
"\xcd\x80"
gcc -o wack wack.c -m32 -z execstack
跳板技术(转https://zhuanlan.zhihu.com/p/88459547)
程序每次运行后在内存中的指令地址都是变化的,所以shellcode入口地址也是动态的,所以为了能够动态找到shellcode的位置,引入了跳板技术。
如图所示,左边表示存储返回地址的栈帧填充为shellcode入口地址,这种方式下次运行时入口地址将发生变化导致失败,右边表示跳板技术后,通过esp来定位shellcode,这种方式可保证下次运行exp依然有效。
跳板技术是用来动态跳转shellcode的,shellcode代码需要从函数返回后esp的栈顶位置开始,然后函数返回到JMP ESP指令处,指令执行后跳到esp位置进入shellcode入口。
注: 对于不同的返回指令的不同,函数返回后esp的指向也有所不同。一般执行ret指令后esp+4,此时shellcode放在存放返回地址的栈帧的下一位置。若是ret N指令,执行后esp+4+N,则shellcode需要放在计算出的对应位置处才行。JMP ESP指令的地址要已知,在xp中JMP ESP可以通过加载kernel32.dll、user32.dll、mfc32.dll等这些经常被加载到内存中的库中寻找,一般地址都是确定的。利用C实现查找代码如下:
1 # include <stdio.h> 2 #include <windows.h> 3 4 main() 5 { 6 HINSTANCE hLib; 7 hLib = LoadLibrary("user32.dll"); 8 if(!hLib) 9 {10 printf("Load dll error!\n");11 exit(0);12 }13 14 byte* ptr = (byte*) hLib;15 int address;16 int position;17 bool done_flag = false;18 19 for(position=0; !done_flag; position++)20 {21 try22 {23 if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)24 {25 // jmp esp 的机器码 为 0xFFE426 address = (int)ptr + position;27 printf("Find OPcode at 0x%08lX\n", address);28 }29 }30 catch(...)31 {32 address = (int)ptr + position;33 printf("End of 0x%08lX\n", address);34 done_flag = true;35 }36 }37 }
抬高栈顶保护shellcode
如果shellcode放在返回地址栈帧之前,那么在函数返回后栈顶位置会在shellcode下方,虽然出栈后的数据不被清空,但是却会受入栈操作的影响,所以shellcode中若存在push操作,很有可能破坏shellcode结构:
所以要在shellcode开头适当先抬高栈顶让shellcode在栈顶下方,这样push就不会影响shellcode。
抬高栈顶可以用sub esp, N,N大于shellcode长度即可。
<< 上一篇
下一篇 >>