作者littleshan (我要加入劍道社!)
看板C_and_CPP
標題Re: [問題] shellcode 的 hello world
時間Wed Nov 21 01:46:28 2012
※ 引述《a613204 (胖胖)》之銘言:
: 大家好 最近想寫shellcode但是遇到很大的問題
: 開發環境是在Ubuntu 12.10中
: 對於整個流程不是很清楚
: 目前我的作法是先寫一個hello.c
: 裡面的程式碼像這樣
: #include <stdio.h>
: int main(void)
: {
: printf("Hello World");
: }
: 用gcc -o hello hello.c 編譯完成後
: 再用objdump -d hello 印出machine code
: 但是我不曉得要把那些machine code放到 char shellcode[] = "..your shellcode.."
: 有沒有人可以告訴我完整的流程?? 卡在這邊滿久的 感恩
我不知道你是看哪本書學來的方法
寫 shellcode 大概有幾個要注意的地方
1. 你不能呼叫 standard library
因為那些 function 的位址都是 link 後才知道的
但 shellcode 沒辦法和主程式 link
所以你也不知道 printf 的位址是什麼
2. 一般程式編譯後會有 text section 與 data section
前者放機器碼後者放預先設定好的變數內容
比如說你的 "Hello World" 這個字串會存在 data section
但是 shellcode 是一整塊程式碼
你可以想象成只有 text section 而沒有 data section 能用
所以為了宣告並取得一塊內容是 "Hello World" 的記憶體
你必需透過一些小手段
最常見的手段是用 call 這個指令
以下就是 hello world 的 shellcode 範例
為了簡化問題我用的是 32bit x86
BITS 32
call main
db "hello world", 10
main:
mov eax, 4
mov ebx, 0
pop ecx
mov edx, 12
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
首先是 call 這個指令
x86 指令的 call 有個神奇的特性,它可以吃一個相對位址
因為 main 這個地方的位址與 call 的位址差距是在組譯時就可以算出來的
所以這個 call 就相當於 near jmp 可以直接跳到 main 繼續執行
但這邊放 call 是有原因的
因為 call 會把它的 return address 送進 stack 中
而 return address 是什麼呢?就是它下一個指令的位址
也就是 "hello world" 這段資料的開頭
取得字串的位址後,接下來我們要把它印出來
我們沒辦法呼叫 printf,所以我們要去實作出 printf 的部份功能
也就是使用 system call 來輸出字串
Linux 上要輸出資料,使用的是 write 這個 system call
ssize_t write(int fd, const void *buf, size_t count);
Linux 上呼叫 system call 有它固定的規則:
* eax 中填入 system call 的代碼,write 的代碼是 4
* ebx 填入第一個參數,我們要輸出至 standard output 所以填 0
* ecx 填第二個參數,我們用 pop ecx 把 stack 中指向字串的指標放進 ecx
* edx 填第三個參數,這是 12 也就是字串長度加上後面的換行
填好參數後使用 0x80 interrupt 就會呼叫 system call 了
後面那三行則是離開程式,也就是呼叫 exit(0) 這個 system call
然後我們就可以來執行看看這個 shellcode
首先用 nasm 來組譯
~$ nasm -o hello hello.asm
然後把它轉成 C code
這邊就要提一下,有個工具叫 xxd 非常好用滴
~$ xxd -i hello
unsigned char hello[] = {
0xe8, 0x0c, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77,
0x6f, 0x72, 0x6c, 0x64, 0x0a, 0x59, 0xb8, 0x04, 0x00, 0x00, 0x00, 0xbb,
0x00, 0x00, 0x00, 0x00, 0xba, 0x0c, 0x00, 0x00, 0x00, 0xcd, 0x80, 0xb8,
0x01, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xcd, 0x80
};
unsigned int hello_len = 47;
然後把它放進你的程式碼:
unsigned char hello[] = {...};
int main(void){
typedef void (*FPTR)(void);
FPTR shellcode = (FPTR)hello;
(*shellcode)();
return 0;
}
試著編譯執行看看
~$ gcc -m32 hello.c
~$ ./a.out
Segmentation fault
然後就當掉了哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
之所以會當掉,是因為現在的 OS 都有 NX bit 的功能
只有 text section 中的資料才能當作程式碼執行
上面的宣告預設會把 hello 這個陣列放在 data section
所以一執行馬上就被 OS 擋下來了
解法有兩種,一種是用 __attribute__ 把它改到 text section
這個方法板上有人提過了
另一個方法是編譯的時候加上參數 -z execstack
會讓這個執行檔免於 OS 的限制
~$ gcc -m32 -z execstack hello.c
~$ ./a.out
hello world
玩 shellcode 要對 assembly 及 system programming 有完整理解
若你有不懂的地方,就先看看這部份的書
另外,這段 shellcode 有個缺點
就是它裡面包含了太多 0x00
大家都知道 0x00 就是 null character 也就是 C string 的尾巴
因此這塊 shellcode 沒辦法用 strcpy 之類的函式作複製
所以你也很難把它拿去做壞事
避免 shellcode 中出現 null 是可能的
但同樣地你要先學好 assembly
另外之二
: → a613204:有人可以講解一下嗎QQ
: → purincess:前一頁的不是也是原po嗎 XD
: → a613204:沒人知道該怎麼寫嗎
在討論區問問題
這句話是大忌
這個板上比我強的人兩隻手數不完
但看到這行 大概會覺得回去打LoL比較實在
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 202.39.238.241
※ 編輯: littleshan 來自: 202.39.238.241 (11/21 01:56)
→ purincess:你是說 "前一頁的不是也是原po嗎 XD" 這句話嗎 QQ 11/21 02:41
→ legnaleurc:應該是第三句吧 XD 有心人會覺得是群體嘲諷 11/21 03:04
推 BombCat:好有趣 11/21 08:41
推 Bencrie:大家都很客氣沒請他去 Code_Job XD 11/21 09:17
推 dirkc:詳細的回覆,雖還沒時間閱讀,先推 11/21 09:43
推 a613204:感謝~ 對我很有幫助的文章 11/21 10:46
推 cobrasgo:這篇真是佛心來的,不過我是建議自己碰到一個程度再來問 11/21 20:48
→ cobrasgo:我當初玩完shellcode之後也覺得自己進步了不少 11/21 20:48
推 carlcarl:推好心 11/22 18:01
推 final01:繁體不是有幾本翻簡體的書~寫的還不錯阿~值得讀讀阿 11/22 23:01
→ final01:不然原文蠻多這類的書 11/22 23:01
推 b98901056:好文推 不過還是要自己摸多一點啊 11/25 22:41