Sessiond

2026-02-22 Author: pwn_figueron Size: 24KB

Intro

This was a challenge from HackOn 2026 CTF created by @qixfnqu, it involved bypassing ASLR by leaking the binary’s base address and then building a Sigreturn Oriented Programming (SROP) by leveraging a stack pivot and a controlled sigreturn frame in order to, as always, spawn a shell.

Leaking the base address

The first step was to obtain a memory leak in order to defeat ASLR. Looking at the login() function:

void login(void)
{
  _DAT_00104070 = &DAT_00104080;
  printf("Enter username: ");
  read(0,&_DAT_00104060,0x10);
  printf("Logged in as %s",&DAT_00104060);
  putchar(10);
  return;
}

The vulnerability here is that read is not appending a null terminator \x00 to the input buffer, which allows for a buffer overflow and memory leak.

By sending a payload of 16 bytes, we can overflow the buffer and leak the contents of the adjacent memory. Because the binary is PIE-enabled, these leaked bytes correspond to a pointer inside the binary’s data section. By subtracting a known static offset:

exe.address = leak_ptr - 0x4080

We can obtain the base address of the binary.

Stack pivot

For now, we only have the base address of the binary, but this isn’t enough to get a shell in anyway, so we need to find another vulnerability.

Looking at the manage() function, we can see that there is a buffer overflow vulnerability in the way it handles user input:

void manage(void)

{
  undefined1 local_208 [512];
  
  puts("--New session data--");
  memset(&DAT_00104080,0,532);
  memset(local_208,0,500);
  printf("Size: ");
  __isoc23_scanf(&DAT_00102055,&DAT_00104290);
  getchar();
  if ((DAT_00104290 < 1) || (528 < DAT_00104290)) {
    puts("Invalid size");
  }
  else {
    printf("Data: ");
    fgets(&DAT_00104080,528,stdin);
    memcpy(local_208,&DAT_00104080,(long)DAT_00104290);
    puts("Processed");
  }
  return;
}

The vulnerability here is that the fgets function is reading user input into a buffer of size 528, but the memcpy function is copying the data into a smaller buffer of size 500, which allows for a buffer overflow of only 28 bytes. With this few space, we can set the return address but there is no much space for gadgets so we need a way to to find more space for a ROP chain

The solution is to use a stack pivot, which is a technique that allows us to change the value of the stack pointer rsp to point to a different location in memory where we can build our ROP chain In this case, as the first 512 bytes of the buffer were used as padding to reach the return address, we can use them to write our gadgets and set the return address to the start of the buffer, which will allow us to execute our ROP chain from there.

Building the SROP chain

Up to this point, we already had the base address thanks to the memory leak and a way to set up our ROP chain by pivoting to the stack. The next step was to find the correct gadgets.

The problem was that the binary didn’t have enough useful gadgets to build a common traditional ROP chain So after a bit of researching different techniques, i found SROP, which i didn’t know about before, and i decided to give it a try.

SROP

SROP is a type of ROP that leverages the sigreturn system call to execute arbitrary code. The idea is to create a fake sigreturn frame on the stack, which allows us to control the values of all registers when the sigreturn system call is invoked.

More in details, when a signal is triggered, the kernel saves the current state of the process in a sigreturn frame and then executes the signal handler using a special syscall called rt_sigreturn which restores the saved registered state.

In “exploiting language”, this means that if we can control what is in the frame and we can trigger a rt_sigreturn syscall, we can set the values of all registers to whatever we want, which is very powerful for exploitation.

For example, using pwntools we can set up a sigreturn frame like this:

frame = SigreturnFrame()
frame.rax = 0x3b          
frame.rdi = bin_sh_addr   
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr

If a sigreturn call is done with this frame, the registers will be set to the values we defined in the frame, allowing us to execute a shell.

Now that we know this, we can use this frame in our exploit to get a shell.

Why the stack pivot is necessary

There is an important detail about SROP: when rt_sigreturn is executed, the kernel restores the registers from whatever rsp is pointing to. It assumes that the current stack already contains a valid signal frame.

So our fake SigreturnFrame must be placed exactly where rsp points.

The overflow in manage() only gives us 28 extra bytes, which is enough to control the return address but not enough to store a full SROP chain. However, we fully control the first 512 bytes of the buffer.

By using leave ; ret, we pivot the stack to that controlled buffer. After the pivot, rsp points to our fake frame, and when we trigger rt_sigreturn, the kernel restores the registers from our controlled memory.

Without the pivot, the kernel would read the frame from the original stack and the exploit would fail.

Triggering the SROP

Now that we know how to build a SROP and what to put in the frame, we need to find a way to trigger the rt_sigreturn syscall.

The way to do this is to find a gadget that pops rax so we can set it to 0xf, which is the syscall number for rt_sigreturn, and then make a syscall.

When rax is set to 0xf and a syscall instruction is executed, the kernel interprets this as a call to rt_sigreturn.

At that point, it restores all registers from the memory pointed to by rsp, which now contains our fake SigreturnFrame thanks to the stack pivot.

Execution then continues at the rip value defined inside the frame. Since we set rax = 0x3b and the arguments for execve beforehand, the next syscall results in execve("/bin/sh", 0, 0), giving us a shell.

Conclusion

The tricky part for me in this challenge, was the knowledge that i needed to have about SROP, which i didn’t know about before. Once i found out about it, the rest of the exploit was pretty straightforward.

Exploit

Exploit can be found here