这篇文章发表于 1405 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
这部分主要是关于针对Linux平台的shellcode亲手编写的基础方法。选自国外某位大哥的视频,在此进行整合了一下。
准备就是在Linux环境下安装好nasm
即可。以下的环境是64位的,因此一些命令是适合于64位的。不过我生成的程序都是32位的。
一定要积极去调试生成的程序!我基本上不会在文中提及这部分,但是这是最重要的部分。
编写exit
以下就是exit
程序的源代码demo1.nasm
:
1 2 3 4 5 6 7 8 9 10 11
| ;This is demo1.nasm global _start
section .text _start:
xor eax, eax mov al, 0x1 ;mov eax, 1 ;;;后面被注释的部分会在之后的code中产生\x00字符,这个在shellcode中是致命错误,会引发Segmentation fault (core dumped)。于是使用这两条命令来实现 xor ebx, ebx mov bl, 0xa ;mov ebx, 10 ;;;同上 int 0x80 ;退出
|
对其进行编译:
1 2 3
| nasm -f elf32 demo1.nasm -o demo1.o ld -m elf_i386 demo1.o -o demo1
|
链接后得到demo1
,这时可以直接运行一下该程序,嗯,它退出了。
然后使用以下命令完成对该demo1
程序的shellcode提取:
1 2
| objdump -d ./demo1 |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
|
然后将它写入下面模板exit.c
的code数组中:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> #include <string.h> unsigned char code[] = \ "\x31\xc0\xb0\x01\x31\xdb\xb3\x0a\xcd\x80"; int main(){ printf("Shellcode Length: %d \n", strlen(code)); int (*ret)() = (int(*)())code; ret(); }
|
运行之后也是直接退出。以上大概就是亲手编写shellcode的一个基本操作框架。
编写Hello world!
接下来就直接给出程序demo2.nasm
和大致说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| global _start
section .text
_start:
jmp short call_shellcode
shellcode:
xor eax, eax mov al, 0x4
xor ebx, ebx mov bl, 0x1
pop ecx
xor edx, edx mov dl, 0xd
int 0x80
; exit the function xor eax, eax mov al, 0x1 xor ebx, ebx int 0x80
call_shellcode:
call shellcode message: db "Hello world!", 0xa
|
这里这样组织函数结构是为了让通过栈来使得字符串能够被轻松找到。如果将字符串写死在section .data
里面的话,提取出来的shellcode并不会被最终的C语言程序所接受,理由很简单——地址都变了还怎么找……因此这里非常聪明地利用了栈的特性。
至于eax
,ebx
,ecx
,edx
的赋值——对于eax
,参看Linux自带的unistd_32.h
里面的内容,很容易能找到API函数write
的代号是4。后面的几个寄存器其实是相当于把参数传入到一个函数的感觉,可以自行查阅资料来调查这个函数的参数位置该怎么安排,也可以尝试使用如下的办法(因为C程序最终调用的都是Linux提供的API):
1、先编写一个C程序why
:
1 2 3 4 5
| #include <stdio.h> int main(){ printf("%s", "UCASZ"); }
|
2、使用strace
命令:
1 2 3 4 5 6 7
| strace -f ./why
------------------------output------------------------ ... write(1, "UCASZ", 5UCASZ) = 5 ... ------------------------output------------------------
|
这样输出中体现的格式就应该能够和之前的处理挂上钩了。
编写execve
以下的nasm文件产生ELF文件能够执行但是提取出来的shellcode并不能运行,理由自己请调试理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| section .data ; strings arg0 db "/bin/sh",0 arg1 db "sh",0 arg2 db "-c",0 arg3 db "/bin/sh",0
; arrays align 4 argv dd arg1, arg2, arg3, 0 envp dd 0
section .text global _start _start:
xor eax, eax mov al, 0xb ; SYS_execve xor ebx, ebx mov ebx, arg3 ; filename mov ecx, argv ; argv mov edx, envp ; envp int 0x80 ; syscall
xor eax, eax mov al, 0x1 int 0x80
|
需要一些小技巧来完成编写。在此之前,需要man execve
来研究一下各个寄存器该如何配置。
1 2
| int execve(const char *pathname, char *const argv[], char *const envp[]);
|
因此,以/bin/sh
为例,这里有一种简单易行的填入方案:eax=0xb
,ebx="/bin/sh"
,ecx=ptr($ebx)
,edx=0x0
。然而在具体的处理方面还是需要一些技巧(比如利用栈),从这篇文章中摘的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| global _start
section .text
_start:
xor eax, eax push eax
;拼合//bin/bash(第一位的斜杠是为了凑字节数) push 0x68732f6e ; hs/n push 0x69622f2f ; ib//
mov ebx, esp
push eax mov edx, esp
push ebx mov ecx, esp
mov al, 0xb int 0x80
|
之后的工作便是对shellcode进行加密解密等操作了,这里既能够按照。
最后推荐一个别人的自动化程序:https://github.com/NytroRST/ShellcodeCompiler
UCASZ
人生匆忙,文章仓皇。内容如有问题请及时指正,谢谢。