add svartalheim writeup
Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
@ -1,133 +0,0 @@
 | 
			
		||||
* {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body, html {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sr-only {
 | 
			
		||||
    position:absolute;
 | 
			
		||||
    left:-10000px;
 | 
			
		||||
    top:auto;
 | 
			
		||||
    width:1px;
 | 
			
		||||
    height:1px;
 | 
			
		||||
    overflow:hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-moz-selection {
 | 
			
		||||
  color: #000b13;
 | 
			
		||||
  background: #c42337;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::selection {
 | 
			
		||||
  color: #040404;
 | 
			
		||||
  background: #d7d9ce;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    background-color: #040404;
 | 
			
		||||
    color: #119da4;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
    font-size: 1.0em;
 | 
			
		||||
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
 | 
			
		||||
    max-width: 1500px;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header h1 {
 | 
			
		||||
    font-size: 4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header h2 {
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header img {
 | 
			
		||||
    border-radius: 100%;
 | 
			
		||||
    width: 220px;
 | 
			
		||||
    margin: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header .socials {
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
    font-size: 1.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header .socials li {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header .socials a {
 | 
			
		||||
    color: inherit;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header .socials a:hover {
 | 
			
		||||
    color: #d7d9ce;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles {
 | 
			
		||||
    margin-top: 50px;
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles h3 {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
    font-weight: 800;
 | 
			
		||||
    color: #f4b65c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles ul {
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles li {
 | 
			
		||||
    margin-top: 0.4em;
 | 
			
		||||
    margin-bottom: 0.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles a {
 | 
			
		||||
    color: #7f8589;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.articles a:hover {
 | 
			
		||||
    color: #c42337;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article h1 {
 | 
			
		||||
    color: #f4b65c;
 | 
			
		||||
    margin-bottom: 0.7em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article p {
 | 
			
		||||
    padding-top: 3px;
 | 
			
		||||
    padding-bottom: 3px;
 | 
			
		||||
    text-align: justify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article a {
 | 
			
		||||
    color: #119da4;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article a:hover {
 | 
			
		||||
    color: #d7d9ce;
 | 
			
		||||
}
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>Juju</title>
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=0.6">
 | 
			
		||||
        <link rel="stylesheet" href="../assets/style.css">
 | 
			
		||||
        <link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <article>
 | 
			
		||||
            <h1>Article</h1>
 | 
			
		||||
            <p>Test</p>
 | 
			
		||||
        </article>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>Juju</title>
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=0.6">
 | 
			
		||||
        <link rel="stylesheet" href="../../assets/style.css">
 | 
			
		||||
        <link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <article>
 | 
			
		||||
            <h1>Introduction à la rétro-ingénierie et à l'exploitation logicielle</h1>
 | 
			
		||||
            <h2>
 | 
			
		||||
                <a href="https://www.youtube.com/watch?v=5g2eZSST7YE">
 | 
			
		||||
                     <i class="ri-youtube-line"></i>
 | 
			
		||||
                </a>
 | 
			
		||||
 | 
			
		||||
                <a href="binary_exploitation.pdf">
 | 
			
		||||
                     <i class="ri-slideshow-line"></i>
 | 
			
		||||
                </a>
 | 
			
		||||
            </h2>
 | 
			
		||||
        </article>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>Juju</title>
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=0.6">
 | 
			
		||||
        <link rel="stylesheet" href="../assets/style.css">
 | 
			
		||||
        <link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <article>
 | 
			
		||||
            <h1>Posts</h1>
 | 
			
		||||
            <li>
 | 
			
		||||
                <h3><a href=conf_exploit/>Introduction à la rétro-ingénierie et à l'exploitation logicielle [FR]</a></h3>
 | 
			
		||||
            </li>
 | 
			
		||||
        </article>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								html/favicon.ico
									
									
									
									
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 Before Width: | Height: | Size: 15 KiB  | 
@ -1,60 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
	Credits where it's due, https://landryl.fr who allowed me to use his css
 | 
			
		||||
-->
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>Juju</title>
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=0.6">
 | 
			
		||||
        <link rel="stylesheet" href="assets/style.css">
 | 
			
		||||
        <link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" rel="stylesheet">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <header>
 | 
			
		||||
            <img src="juju.jpg" alt="Photo of myself">
 | 
			
		||||
            <h1>Juju</h1>
 | 
			
		||||
            <ul class="socials">
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="https://github.com/Azomasiel/">
 | 
			
		||||
                        <i class="ri-github-line"></i>
 | 
			
		||||
                        <span class="sr-only">Github profile</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="https://git.juju.re/explore/repos">
 | 
			
		||||
                        <i class="ri-open-source-line"></i>
 | 
			
		||||
                        <span class="sr-only">Turbogit</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="https://www.linkedin.com/in/julien-clement-0891ab199/">
 | 
			
		||||
                        <i class="ri-linkedin-box-line"></i>
 | 
			
		||||
                        <span class="sr-only">Linkedin profile</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="https://www.root-me.org/Azomasiel">
 | 
			
		||||
                        <i class="ri-skull-line"></i>
 | 
			
		||||
                        <span class="sr-only">Root-me profile</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="https://cryptohack.org/user/Azomasiel/">
 | 
			
		||||
                        <i class="ri-shield-keyhole-line"></i>
 | 
			
		||||
                        <span class="sr-only">Cryptohack profile</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
 | 
			
		||||
            </ul>
 | 
			
		||||
            <ul class="socials">
 | 
			
		||||
                <li>
 | 
			
		||||
                    <a href="blog/">
 | 
			
		||||
                        <i class="ri-booklet-line"></i>
 | 
			
		||||
                        <span class="sr-only">Github profile</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </header>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								html/juju.jpg
									
									
									
									
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 Before Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										429
									
								
								jujure/content/writeups/fcsc_2024/svartalfheim.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						@ -0,0 +1,429 @@
 | 
			
		||||
---
 | 
			
		||||
title: "Lifting a reloc based VM | Svartalfheim @ FCSC 2024"
 | 
			
		||||
date: "2024-04-09 18:00:00"
 | 
			
		||||
author: "Juju"
 | 
			
		||||
tags: ["Reverse", "Writeup", "fcsc"]
 | 
			
		||||
toc: true
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Intro
 | 
			
		||||
 | 
			
		||||
Svartalfheim is a weird reversing challenge. It seems like a simple x64 ELF with
 | 
			
		||||
only a few bytes of machine code, but after playing with it, you might notice
 | 
			
		||||
some quantum behaviours. The program might be patching itself when you are
 | 
			
		||||
not looking at it, so stay alert :eyes:.
 | 
			
		||||
 | 
			
		||||
{{< image src="/brachiosaure/meme.jpg" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
## Challenge description
 | 
			
		||||
`reverse` | `479 pts` `9 solves` `:star::star::star:`
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Trouvez le flag accepté par le binaire.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Author: `Quanthor_ic`
 | 
			
		||||
 | 
			
		||||
## Given files
 | 
			
		||||
 | 
			
		||||
[svartalfheim](/svartalfheim/svartalfheim)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Writeup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
Things are already weird without opening up any disassembler:
 | 
			
		||||
`file` tells us the the binary is dynamically linked but ldd says otherwise.
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ file svartalfheim
 | 
			
		||||
svartalfheim: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
 | 
			
		||||
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, no
 | 
			
		||||
section header
 | 
			
		||||
 | 
			
		||||
$ ldd svartalfheim
 | 
			
		||||
	not a dynamic executable
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Here is the decompiled code of the entrypoint.
 | 
			
		||||
 | 
			
		||||
{{< code file="/static/svartalfheim/main.c" language="c" >}}
 | 
			
		||||
 | 
			
		||||
Basically, it simply deletes a file named `_` from the current directory, then
 | 
			
		||||
re-create it and opening it write mode.
 | 
			
		||||
 | 
			
		||||
The process then dumps itself into the opened file, close it and execve the
 | 
			
		||||
dumped file.
 | 
			
		||||
 | 
			
		||||
Cool so this should do absolutely nothing except calling itself endlessly.
 | 
			
		||||
 | 
			
		||||
But what if we give it a try:
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
./svartalfheim
 | 
			
		||||
Welcome to Svartalfheim
 | 
			
		||||
FCSC{test}
 | 
			
		||||
Nope
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
WTF is going on ?
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Quantum binary
 | 
			
		||||
 | 
			
		||||
So first I wanted a way to see what was happening between each execution.
 | 
			
		||||
 | 
			
		||||
I patched the `execve` with a breakpoint in the binary.
 | 
			
		||||
 | 
			
		||||
It will now crash instead of starting itself again and I can inspect the
 | 
			
		||||
`_` file before the next instance deletes it.
 | 
			
		||||
 | 
			
		||||
Here is the diff of the hexdump of the patched binary (breakpoint instead
 | 
			
		||||
of execve) with the hexdump of the `_` file after a single execution:
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ diff svartalfheim_breakpointed.hex first_run.hex 
 | 
			
		||||
517c517
 | 
			
		||||
< 00002040: 0700 0000 0000 0000 e821 0400 0000 0000  .........!......
 | 
			
		||||
---
 | 
			
		||||
> 00002040: 0700 0000 0000 0000 3037 0400 0000 0000  ........07......
 | 
			
		||||
519c519
 | 
			
		||||
< 00002060: 0800 0000 0000 0000 3000 0000 0000 0000  ........0.......
 | 
			
		||||
---
 | 
			
		||||
> 00002060: 0800 0000 0000 0000 6000 0000 0000 0000  ........`.......
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
A total of 3 bytes have changed, so I go check the corresponding addresses
 | 
			
		||||
in my decompiler:
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/rela_patched.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
So the first two bytes that are patched are the relation table addr inside
 | 
			
		||||
the dynamic table.
 | 
			
		||||
 | 
			
		||||
The last byte patched is the size of said relocation table.
 | 
			
		||||
 | 
			
		||||
This means that at the next execution, the program will have different relocations.
 | 
			
		||||
 | 
			
		||||
Maybe let's take a look at the original relocation table:
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/original_relocs.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
Unusual relocations indeed. So the first one points to the relocation table
 | 
			
		||||
addresse inside the dynamic table and the second one to the relocation table
 | 
			
		||||
size, also in the dynamic table, the two values that were patched in the next
 | 
			
		||||
binary. We can already guess that the relocation table will be patched at
 | 
			
		||||
every execution, running new relocations every time, just like a processor
 | 
			
		||||
run instructions and increments its program counter. This might be a
 | 
			
		||||
relocation based virtual machine.
 | 
			
		||||
 | 
			
		||||
## Reloc based virtual machine
 | 
			
		||||
 | 
			
		||||
### Figuring out the instruction set
 | 
			
		||||
 | 
			
		||||
So since the relocation table have changed in the next binary, let's open this
 | 
			
		||||
one and check the new relocation table:
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/second_relocs.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
Once again we can see relocs pointing to DT_RELA and DT_RELASZ values, but there are also two other addresses that are patched.
 | 
			
		||||
 | 
			
		||||
When looking them up, we can see that these two addresses are located inside
 | 
			
		||||
the symbol table. To be precise, the values of symbol `1` and `2` are patched.
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/second_symtab.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
Great so now let's run the binary a second time and inspect the third relocation table.
 | 
			
		||||
 | 
			
		||||
I will stop on this one a bit longer because it actually contains the entire
 | 
			
		||||
instruction set.
 | 
			
		||||
 | 
			
		||||
But first, what is *really* happening ?
 | 
			
		||||
 | 
			
		||||
The relocation table holds ... relocations indeed.
 | 
			
		||||
 | 
			
		||||
Relocations are applied by `ldso` when a process is loaded into memory
 | 
			
		||||
(so at `execve`).
 | 
			
		||||
 | 
			
		||||
Applying a relocation has different effects depending on the relocation type
 | 
			
		||||
(denoted in the lower 32 bits of the `info` field of the reloc).
 | 
			
		||||
 | 
			
		||||
However, most relocation types imply dereferencing the `value` of a symbol (see the above screenshot for an example of the symbol table) and storing it in the relocation address.
 | 
			
		||||
 | 
			
		||||
The said symbol is denoted by the higher 32 bits of the `info` field of the reloc.
 | 
			
		||||
 | 
			
		||||
Now let's really look at the third relocation table and run it in our mind:
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/third_relocs.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
The first relocation is of type `0x8`, and has symbol `0x0` (which mean no symbol)
 | 
			
		||||
 | 
			
		||||
It points to the address of the `value`` of symbol `0x5`.
 | 
			
		||||
 | 
			
		||||
Relocation type `0x8` will simply put its `addend` value at the address pointed by its `addr` field. Thus storing `0xff` in the `0x5` symbol `value`
 | 
			
		||||
 | 
			
		||||
Basically this relocation is a `mov mem, imm` instruction.
 | 
			
		||||
 | 
			
		||||
Second relocation is of type `0x1` and symbol `0x1`.
 | 
			
		||||
 | 
			
		||||
This relocation will take the `value` of symbol `0x1`, add the reloc
 | 
			
		||||
`addend` value, and store the result at the relocation `addr`
 | 
			
		||||
 | 
			
		||||
So it looks like some sort of `add mem, reg, imm` instruction, considering
 | 
			
		||||
symbols as registers.
 | 
			
		||||
 | 
			
		||||
I'll do the third relocation and we will have the whole instruction set:
 | 
			
		||||
 | 
			
		||||
The type is `0x5`, symbol `0x1`. It will take the value of the corresponding symbol, dereference it, and store it in the reloc `addr`.
 | 
			
		||||
 | 
			
		||||
The assembly for this might look like `mov mem, [reg]`
 | 
			
		||||
 | 
			
		||||
Here we go, that's it, an instruction set of 3 instruction, cannot even
 | 
			
		||||
branch or add 2 registers.
 | 
			
		||||
 | 
			
		||||
Let's write an interpreter for the VM so we can debug it.
 | 
			
		||||
 | 
			
		||||
### Writing the interpreter
 | 
			
		||||
 | 
			
		||||
Basically the interpreter will have multiple role in the analysis:
 | 
			
		||||
 | 
			
		||||
* Get an execution trace and disassembly of the virtual machine
 | 
			
		||||
* Set breakpoints during execution
 | 
			
		||||
* Dump process to inspect the patched ELF at any stage easily and fast
 | 
			
		||||
 | 
			
		||||
{{< code file="/static/svartalfheim/interpreter.py" language="py" >}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
This interpreter stops every time that the VM patches the native code section
 | 
			
		||||
of the binary, this way I can stop whenever IO is performed, dump the binary
 | 
			
		||||
and analyse it.
 | 
			
		||||
 | 
			
		||||
The VM patches the native code a total of 7 times:
 | 
			
		||||
 | 
			
		||||
* Setup a syscall to write to prompt on stdout
 | 
			
		||||
* Immediately after, reset the native code the its original content
 | 
			
		||||
* Setup a syscall to read the flag from stdin
 | 
			
		||||
* Immediately after, reset the native code the its original content
 | 
			
		||||
* Setup a syscall to write the flag validation
 | 
			
		||||
* Immediately after, reset the native code the its original content
 | 
			
		||||
* Setup a syscall to exit the program instead of the `execve` it again
 | 
			
		||||
 | 
			
		||||
Investigating the third dumped binary will show us the flag address given to `read`, which will allow us to inject it in our interpreter:
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/radare_read.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
The interpreter also builds a disassembled execution trace:
 | 
			
		||||
 | 
			
		||||
I tried to make it readable as if it was intel assembly.
 | 
			
		||||
 | 
			
		||||
I added some comments for easier analysis:
 | 
			
		||||
* NATIVE CODE LOADING means tha this block (a complete run of a single relocation table) has patched the native code section
 | 
			
		||||
* The comment hexstring is the data that is being outputed in the destination operand
 | 
			
		||||
* PATCHING CODE means that this instruction has a destination address pointing to the next instruction, meaning it is trying to patch its own code
 | 
			
		||||
* PATCHING FAR is the same but on an instruction of the same block but not the next one
 | 
			
		||||
 | 
			
		||||
These were really helpful during analysis to have a reminder to check for code patching.
 | 
			
		||||
 | 
			
		||||
You might be saying that the shown assembly doesn't correspond to the
 | 
			
		||||
instruction defined above as there was no such instruction as
 | 
			
		||||
`add reg, reg, imm`, it is indeed true, but the trick is that every registers
 | 
			
		||||
are memory mapped (since they are simply symbols in the symtab of the ELF), so a memory deref can actually be a register and my disassembler lifts this.
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
0x48080: mov [0x480f0], $0xffffffffffffffa0  # a0ffffffffffffff  PATCHING FAR
 | 
			
		||||
0x48098: add [0x480b0], r4, $0x480d8         # f080040000000000  PATCHING CODE
 | 
			
		||||
0x480b0: mov [0x480f0], $0x0                 # 0000000000000000  PATCHING FAR
 | 
			
		||||
0x480c8: add r1, r1, $0x0                    # 1036040000000000
 | 
			
		||||
0x480e0: add r1, r1, $0x0                    # 1036040000000000
 | 
			
		||||
0x480f8: add r1, r1, $0x10                   # 2036040000000000
 | 
			
		||||
0x48110: mov DT_RELA, [r1].8                 # 5881040000000000
 | 
			
		||||
0x48128: add r2, r1, $0x8                    # 2836040000000000
 | 
			
		||||
0x48140: mov DT_RELASZ, [r2].8               # 7800000000000000
 | 
			
		||||
#End of block
 | 
			
		||||
0x48158: mov r7, $0x5                        # 0500000000000000
 | 
			
		||||
0x48170: add r1, r1, $0x10                   # 3036040000000000
 | 
			
		||||
0x48188: mov DT_RELA, [r1].8                 # d081040000000000
 | 
			
		||||
0x481a0: add r2, r1, $0x8                    # 3836040000000000
 | 
			
		||||
0x481b8: mov DT_RELASZ, [r2].8               # f000000000000000
 | 
			
		||||
#End of block
 | 
			
		||||
# NATIVE CODE LOADING:
 | 
			
		||||
0x481d0: mov [0x4100e], $0xba48              # 48ba000000000000
 | 
			
		||||
0x481e8: mov [0x41016], $0xbe480000          # 000048be00000000
 | 
			
		||||
0x48200: mov [0x4101e], $0x6a5f016a00000000  # 000000006a015f6a
 | 
			
		||||
0x48218: mov [0x41026], $0x90909090050f5801  # 01580f0590909090
 | 
			
		||||
0x48230: add [0x41010], r7, $0x0             # 0500000000000000
 | 
			
		||||
0x48248: add [0x4101a], r8, $0x0             # 23a2050000000000
 | 
			
		||||
0x48260: add r1, r1, $0x10                   # 4036040000000000
 | 
			
		||||
0x48278: mov DT_RELA, [r1].8                 # c082040000000000
 | 
			
		||||
0x48290: add r2, r1, $0x8                    # 4836040000000000
 | 
			
		||||
0x482a8: mov DT_RELASZ, [r2].8               # c000000000000000
 | 
			
		||||
#End of block
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Side channel attempt
 | 
			
		||||
 | 
			
		||||
My first attempt at a solver was really simple, I though that maybe the
 | 
			
		||||
VM would check bytes one by one.
 | 
			
		||||
 | 
			
		||||
So I added a method to inject the flag into my interpreter's memory and tried
 | 
			
		||||
to bruteforce the first char, watching for execution trace length every time.
 | 
			
		||||
 | 
			
		||||
But all 256 possible bytes gave the same number of instructions
 | 
			
		||||
 | 
			
		||||
At this points I was thinking about lifting the code a little more to reduce
 | 
			
		||||
the trace size before reading it, but I was in the mood to read 65000+ lines of the same 3 instructions.
 | 
			
		||||
 | 
			
		||||
## Analysing the execution trace
 | 
			
		||||
 | 
			
		||||
Just kidding I did not actually read the whole execution trace.
 | 
			
		||||
 | 
			
		||||
I knew that the flag start with `FCSC{`, so I dumped 2 execution trace:
 | 
			
		||||
 | 
			
		||||
* One with the flag as `FCSC{test`
 | 
			
		||||
* The other one with `HCSC{test`
 | 
			
		||||
 | 
			
		||||
I put them side by side (`FCSC{` on the left, `HCSC{` on the right), jumped right after the `read` call, and started comparing them, and lifting the code on paper.
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/trace_read.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
I will not show you the whole execution trace to keep your eyes safe, but
 | 
			
		||||
the VM starts by a bunch of `mov reg, imm` instructions to initialize some
 | 
			
		||||
variables.
 | 
			
		||||
 | 
			
		||||
And then a fun pattern appears:
 | 
			
		||||
 | 
			
		||||
### `add mem, reg, reg`
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/add_reg_reg.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
These three instructions together can actually be lifted to `add r13.b, r6, r13`
 | 
			
		||||
 | 
			
		||||
It is crucial to understand this simple pattern before we continue to how
 | 
			
		||||
branching are handled in this VM.
 | 
			
		||||
 | 
			
		||||
Start by looking the first instruction: it takes the value of `r13`, add 0,
 | 
			
		||||
and store it at `0x45a50`. The first thing to notice is that `add mem, reg,
 | 
			
		||||
$0x0` is actually equivalent to a `mov mem, reg`, but there is no such
 | 
			
		||||
instruction in our instruction set (`mov mem, [reg]` will deref the reg) thus the add instruction trick.
 | 
			
		||||
 | 
			
		||||
Then if you look the destination address, it points to the the next instruction `addend`. Looking at the comment, we know that the output value is `0x0` on 8 bytes.
 | 
			
		||||
 | 
			
		||||
So now when we look at the second instruction, it is indeed `add r13, r6,
 | 
			
		||||
$0x0`, but said immediate `$0x0` was patched by previous instruction, with the
 | 
			
		||||
value of `r13`, even if the instruction add an immediate, in this context,
 | 
			
		||||
the immediate was patched with a register. Thus performing a `add mem, reg, reg`
 | 
			
		||||
 | 
			
		||||
The third instruction simply zeros out the 7 higher bytes of the `r13`
 | 
			
		||||
register, my disassembler did not lift the addresses of the higher bytes of
 | 
			
		||||
regs but trust me on this one.
 | 
			
		||||
 | 
			
		||||
The comment on the second instruction shows us that the addition had a result of `0x1` (64 bits little endian) (`r6` had value 1), so these 3 instructions simply increments `r13`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Lookup tables
 | 
			
		||||
 | 
			
		||||
I skipped a few instructions, all you need to know is that `r3` points
 | 
			
		||||
to the first byte of the flag, and that `r12` was initialized with a byte
 | 
			
		||||
coming from an array indexed with the same counter as the flag.
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/lut_lookup.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
The first block simply loads the current flag byte in `r11` (`0x46` = `'F'`)
 | 
			
		||||
and the second one basically substitute the byte from the flag based on a lookup table.
 | 
			
		||||
 | 
			
		||||
The LuT is indexed based on the flag byte and `r12`, which I assume is some
 | 
			
		||||
sort of nonce to add the information of the position of the byte in the
 | 
			
		||||
flag during the substitution.
 | 
			
		||||
 | 
			
		||||
Here is a c equivalent I lifted on paper:
 | 
			
		||||
 | 
			
		||||
```c
 | 
			
		||||
uint64_t r12 = nonce[i];
 | 
			
		||||
uint64_t r11 = flag[i];
 | 
			
		||||
uint64_t r3 = (r11 << 32) + r12;
 | 
			
		||||
uint64_t *LuT = 0x49000;
 | 
			
		||||
r11 = *(LuT + r3);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The next few blocks are not that important, store the LuTed byte in memory,
 | 
			
		||||
basically increment string iterators, decrement size counters
 | 
			
		||||
 | 
			
		||||
But then comes the one most important code pattern of this VM:
 | 
			
		||||
 | 
			
		||||
### Branching
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/for.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
These two blocks perform a branch
 | 
			
		||||
 | 
			
		||||
It essentially is a `test r7; jne mem` heres how it works after lifting:
 | 
			
		||||
 | 
			
		||||
```c
 | 
			
		||||
// First block
 | 
			
		||||
jump_table = 0x59000;
 | 
			
		||||
r4 = jump_table[r7]; // r7 is remaining flag len
 | 
			
		||||
// Second block
 | 
			
		||||
r1 = r1 + 0x10 - 0xf0 + r4;
 | 
			
		||||
// DT_RELA = r1, r1 is the program counter
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
So `0x59000` contains a jump table indexed on the remaining flag size.
 | 
			
		||||
 | 
			
		||||
Here you can see at instruction `0x46628`, the jump offset is in the comment.
 | 
			
		||||
 | 
			
		||||
I noticed it changed when `r7` reached 0.
 | 
			
		||||
 | 
			
		||||
It is essentialy the first `for` loop iterating on the flag, applying lookup
 | 
			
		||||
tables on each byte.
 | 
			
		||||
 | 
			
		||||
### Flag checking
 | 
			
		||||
 | 
			
		||||
After that there is a really similar block of code, also performing lookups
 | 
			
		||||
of some sort I did not really bother to understand (as the ones of the
 | 
			
		||||
previous step) because I found a really interesting branch which was not a loop.
 | 
			
		||||
 | 
			
		||||
I notice a similar pattern than the for loop above, sligthly differentm but
 | 
			
		||||
still some kind of jump table.
 | 
			
		||||
 | 
			
		||||
What stroke me is that as you can see on the screenshot bellow, it was
 | 
			
		||||
the first time in the whole execution trace, that my purposely wrong flag, ran
 | 
			
		||||
to a different branch than the one starting with `FCSC{`
 | 
			
		||||
 | 
			
		||||
{{< image src="/svartalfheim/check.png" style="border-radius: 8px;" >}}
 | 
			
		||||
 | 
			
		||||
What I did not notice at first is that there are two different branch in
 | 
			
		||||
the screenshot (with the jump offsets marked in red). I noticed it quickly and
 | 
			
		||||
backported it to my solver.
 | 
			
		||||
I do not actually know what is the meaning of these 2 checks regarding the
 | 
			
		||||
previous look up tables but all I know is that I needed to hit jump
 | 
			
		||||
offset `0x18` twice for a flag byte to be valid.
 | 
			
		||||
 | 
			
		||||
So I modified my interpreter to add a breakpoint at the addresses marked in red,
 | 
			
		||||
check the value moved in `r4` is `0x18`.
 | 
			
		||||
 | 
			
		||||
And then bruteforced byte by byte:
 | 
			
		||||
 | 
			
		||||
While bruteforcing the `n`th byte, I need to hit the breakpoint succesfully (with `0x14` in `r4`) `2*n` times. If the breakpoint check fails once then the byte is fucked up.
 | 
			
		||||
 | 
			
		||||
## Solver
 | 
			
		||||
 | 
			
		||||
Here is the complete solver code, with the interpreter, correct breakpoints and
 | 
			
		||||
bruteforcing
 | 
			
		||||
 | 
			
		||||
{{< code file="/static/svartalfheim/solve.py" language="python" >}}
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ ./solve.py
 | 
			
		||||
bytearray(b'F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
 | 
			
		||||
bytearray(b'FC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
 | 
			
		||||
bytearray(b'FCS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
 | 
			
		||||
...
 | 
			
		||||
bytearray(b'FCSC{162756828312aad562394d47c854134803a092d7f5b9eb795528f4a0f16f7c65}')
 | 
			
		||||
 | 
			
		||||
$ ./svartalfheim
 | 
			
		||||
Welcome to Svartalfheim
 | 
			
		||||
FCSC{162756828312aad562394d47c854134803a092d7f5b9eb795528f4a0f16f7c65}
 | 
			
		||||
Well done!
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/add_reg_reg.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/check.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/for.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 214 KiB  | 
							
								
								
									
										228
									
								
								jujure/static/svartalfheim/interpreter.py
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						@ -0,0 +1,228 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import struct
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
regs = {
 | 
			
		||||
    0x42048: "DT_RELA",
 | 
			
		||||
    0x42068: "DT_RELASZ",
 | 
			
		||||
    0x42088: "r0",
 | 
			
		||||
    0x420a0: "r1",
 | 
			
		||||
    0x420b8: "r2",
 | 
			
		||||
    0x420d0: "r3",
 | 
			
		||||
    0x420e8: "r4",
 | 
			
		||||
    0x42100: "r5",
 | 
			
		||||
    0x42118: "r6",
 | 
			
		||||
    0x42130: "r7",
 | 
			
		||||
    0x42148: "r8",
 | 
			
		||||
    0x42160: "r9",
 | 
			
		||||
    0x42178: "r10",
 | 
			
		||||
    0x42190: "r11",
 | 
			
		||||
    0x421a8: "r12",
 | 
			
		||||
    0x421c0: "r13",
 | 
			
		||||
    0x421d8: "r14",
 | 
			
		||||
    0x421f0: "r15",
 | 
			
		||||
 | 
			
		||||
    0x42090: "r0.size",
 | 
			
		||||
    0x420a8: "r1.size",
 | 
			
		||||
    0x420c0: "r2.size",
 | 
			
		||||
    0x420d8: "r3.size",
 | 
			
		||||
    0x420f0: "r4.size",
 | 
			
		||||
    0x42108: "r5.size",
 | 
			
		||||
    0x42120: "r6.size",
 | 
			
		||||
    0x42138: "r7.size",
 | 
			
		||||
    0x42150: "r8.size",
 | 
			
		||||
    0x42168: "r9.size",
 | 
			
		||||
    0x42180: "r10.size",
 | 
			
		||||
    0x42198: "r11.size",
 | 
			
		||||
    0x421b0: "r12.size",
 | 
			
		||||
    0x421c8: "r13.size",
 | 
			
		||||
    0x421e0: "r14.size",
 | 
			
		||||
    0x421f8: "r15.size"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Stream:
 | 
			
		||||
    i: int
 | 
			
		||||
    buf: bytes
 | 
			
		||||
 | 
			
		||||
    def __init__(self, buf) -> None:
 | 
			
		||||
        self.pos = 0
 | 
			
		||||
        self.buf = buf
 | 
			
		||||
 | 
			
		||||
    def read_u8(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<B", self.buf[self.pos:self.pos + 1])
 | 
			
		||||
            self.pos += 1
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u16(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<H", self.buf[self.pos:self.pos + 2])
 | 
			
		||||
            self.pos += 2
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u32(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<I", self.buf[self.pos:self.pos + 4])
 | 
			
		||||
            self.pos += 4
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u64(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<Q", self.buf[self.pos:self.pos + 8])
 | 
			
		||||
            self.pos += 8
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def is_done(self):
 | 
			
		||||
        return self.pos >= len(self.buf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RelaEnt:
 | 
			
		||||
    def __init__(self, stream):
 | 
			
		||||
        self.addr = stream.read_u64()
 | 
			
		||||
        self.type = stream.read_u32()
 | 
			
		||||
        self.symbol = stream.read_u32()
 | 
			
		||||
        self.addend = stream.read_u64()
 | 
			
		||||
 | 
			
		||||
class Rela:
 | 
			
		||||
    def __init__(self, rela_addr, rela_size, elf):
 | 
			
		||||
        self.rela_addr = rela_addr
 | 
			
		||||
        self.rela_size = rela_size
 | 
			
		||||
        self.elf = elf
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        if self.rela_size <= 0:
 | 
			
		||||
            return None
 | 
			
		||||
        stream = Stream(self.elf.get_data(self.rela_addr, 0x18))
 | 
			
		||||
        entry = RelaEnt(stream)
 | 
			
		||||
        entry.offset = self.rela_addr
 | 
			
		||||
        self.rela_addr += 0x18
 | 
			
		||||
        self.rela_size -= 0x18
 | 
			
		||||
        return entry
 | 
			
		||||
 | 
			
		||||
    def peek(self):
 | 
			
		||||
        if self.rela_size <= 0:
 | 
			
		||||
            return None
 | 
			
		||||
        stream = Stream(self.elf.get_data(self.rela_addr, 0x18))
 | 
			
		||||
        entry = RelaEnt(stream)
 | 
			
		||||
        entry.offset = self.rela_addr
 | 
			
		||||
        return entry
 | 
			
		||||
 | 
			
		||||
class Symbol:
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        stream = Stream(data)
 | 
			
		||||
        self.name = stream.read_u16()
 | 
			
		||||
        self.info = stream.read_u8()
 | 
			
		||||
        self.other = stream.read_u8()
 | 
			
		||||
        self.shndx = stream.read_u32()
 | 
			
		||||
        self.value = stream.read_u64()
 | 
			
		||||
        self.size = stream.read_u64()
 | 
			
		||||
 | 
			
		||||
class Elf:
 | 
			
		||||
    def __init__(self, path):
 | 
			
		||||
        with open(path, 'rb') as f:
 | 
			
		||||
            self._data = bytearray(f.read())
 | 
			
		||||
 | 
			
		||||
        self.dynt_rela = 0x42048
 | 
			
		||||
        self.dynt_relasz = 0x42068
 | 
			
		||||
        self.symtab = 0x42080
 | 
			
		||||
        self.breakpoints = {}
 | 
			
		||||
 | 
			
		||||
    def dump(self, path):
 | 
			
		||||
        with open(path, 'wb') as f:
 | 
			
		||||
            f.write(self._data)
 | 
			
		||||
 | 
			
		||||
    def get_data(self, addr, size):
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        return self._data[addr:addr + size]
 | 
			
		||||
 | 
			
		||||
    def set_data(self, addr, data):
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        self._data[addr:addr + len(data)] = data
 | 
			
		||||
 | 
			
		||||
    def set_flag(self, flag):
 | 
			
		||||
        addr = 0x5a100
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        self._data[addr:addr + len(flag)] = flag
 | 
			
		||||
 | 
			
		||||
    def get_rela(self):
 | 
			
		||||
        rela_addr = Stream(self.get_data(self.dynt_rela, 8)).read_u64()
 | 
			
		||||
        rela_size = Stream(self.get_data(self.dynt_relasz, 8)).read_u64()
 | 
			
		||||
        return Rela(rela_addr, rela_size, self)
 | 
			
		||||
 | 
			
		||||
    def get_symbol(self, i):
 | 
			
		||||
        sym_data = self.get_data(self.symtab + 0x18 * i, 0x18)
 | 
			
		||||
        return Symbol(sym_data)
 | 
			
		||||
 | 
			
		||||
    def apply_relocs(self):
 | 
			
		||||
        rela = self.get_rela()
 | 
			
		||||
        patched_code = False
 | 
			
		||||
        asm = ""
 | 
			
		||||
        while True:
 | 
			
		||||
            entry = rela.pop()
 | 
			
		||||
            if not entry:
 | 
			
		||||
                break
 | 
			
		||||
            symbol = self.get_symbol(entry.symbol)
 | 
			
		||||
            dst_name = regs[entry.addr] if entry.addr in regs else f'[{hex(entry.addr)}]'
 | 
			
		||||
            line = f'{hex(entry.offset)}: '
 | 
			
		||||
            if entry.type == 1:
 | 
			
		||||
                data_int = (symbol.value + entry.addend) % 2**64
 | 
			
		||||
                data = struct.pack('<Q', data_int)
 | 
			
		||||
                line += f'add {dst_name}, r{entry.symbol}, ${hex(entry.addend)}'
 | 
			
		||||
 | 
			
		||||
            elif entry.type == 5:
 | 
			
		||||
                data = self.get_data(symbol.value + entry.addend, symbol.size)
 | 
			
		||||
                line += f'mov {dst_name}, [r{entry.symbol}].{symbol.size}'
 | 
			
		||||
                if entry.addend != 0:
 | 
			
		||||
                    raise
 | 
			
		||||
 | 
			
		||||
            elif entry.type == 8:
 | 
			
		||||
                data_int = entry.addend
 | 
			
		||||
                data = struct.pack('<Q', data_int)
 | 
			
		||||
                src_name = f'&{regs[entry.addend]}' if entry.addend in regs else f'${hex(entry.addend)}'
 | 
			
		||||
                line += f'mov {dst_name}, {src_name}'
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                print(hex(entry.type))
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
            line += ' ' * (50 - len(line)) + f'# {bytes(data).hex()}'
 | 
			
		||||
            if entry.addr > entry.offset and entry.addr < 0x49000:
 | 
			
		||||
                next_instr = rela.peek()
 | 
			
		||||
                if not (entry.addr >= next_instr.offset and entry.addr < next_instr.offset + 0x18):
 | 
			
		||||
                    line += "  PATCHING FAR"
 | 
			
		||||
                else:
 | 
			
		||||
                    line += '  PATCHING CODE'
 | 
			
		||||
            asm += line + '\n'
 | 
			
		||||
 | 
			
		||||
            self.set_data(entry.addr, data)
 | 
			
		||||
            if entry.addr >= 0x41000 and entry.addr < 0x41083:
 | 
			
		||||
                patched_code = True
 | 
			
		||||
        asm += '#End of block\n'
 | 
			
		||||
        return asm, patched_code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    elf = Elf('./svartalfheim')
 | 
			
		||||
    asm = ""
 | 
			
		||||
    n = 0
 | 
			
		||||
    while n <= 7:
 | 
			
		||||
        step_asm, patched_code = elf.apply_relocs()
 | 
			
		||||
        if patched_code:
 | 
			
		||||
            asm += '# NATIVE CODE LOADING:\n'
 | 
			
		||||
            n += 1
 | 
			
		||||
            elf.dump(f'./dumps/pydumped{n}')
 | 
			
		||||
        asm += step_asm
 | 
			
		||||
    print(asm)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/lut_lookup.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 403 KiB  | 
							
								
								
									
										9
									
								
								jujure/static/svartalfheim/main.c
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						@ -0,0 +1,9 @@
 | 
			
		||||
int64_t _start()
 | 
			
		||||
{
 | 
			
		||||
    int64_t path = '_';
 | 
			
		||||
    syscall(sys_unlink {0x57}, &path);
 | 
			
		||||
    int32_t fd = syscall(sys_open {2}, &path, O_CREAT | O_WRONLY);
 | 
			
		||||
    syscall(sys_write {1}, fd, &__elf_header, 0x1a228);
 | 
			
		||||
    syscall(sys_close {3}, fd);
 | 
			
		||||
    syscall(sys_execve {0x3b}, &path, nullptr, nullptr);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/original_relocs.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/radare_read.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 502 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/rela_patched.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 35 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/second_relocs.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 82 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/second_symtab.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 75 KiB  | 
							
								
								
									
										273
									
								
								jujure/static/svartalfheim/solve.py
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						@ -0,0 +1,273 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import struct
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
regs = {
 | 
			
		||||
    0x42048: "DT_RELA",
 | 
			
		||||
    0x42068: "DT_RELASZ",
 | 
			
		||||
    0x42088: "r0",
 | 
			
		||||
    0x420a0: "r1",
 | 
			
		||||
    0x420b8: "r2",
 | 
			
		||||
    0x420d0: "r3",
 | 
			
		||||
    0x420e8: "r4",
 | 
			
		||||
    0x42100: "r5",
 | 
			
		||||
    0x42118: "r6",
 | 
			
		||||
    0x42130: "r7",
 | 
			
		||||
    0x42148: "r8",
 | 
			
		||||
    0x42160: "r9",
 | 
			
		||||
    0x42178: "r10",
 | 
			
		||||
    0x42190: "r11",
 | 
			
		||||
    0x421a8: "r12",
 | 
			
		||||
    0x421c0: "r13",
 | 
			
		||||
    0x421d8: "r14",
 | 
			
		||||
    0x421f0: "r15",
 | 
			
		||||
 | 
			
		||||
    0x42090: "r0.size",
 | 
			
		||||
    0x420a8: "r1.size",
 | 
			
		||||
    0x420c0: "r2.size",
 | 
			
		||||
    0x420d8: "r3.size",
 | 
			
		||||
    0x420f0: "r4.size",
 | 
			
		||||
    0x42108: "r5.size",
 | 
			
		||||
    0x42120: "r6.size",
 | 
			
		||||
    0x42138: "r7.size",
 | 
			
		||||
    0x42150: "r8.size",
 | 
			
		||||
    0x42168: "r9.size",
 | 
			
		||||
    0x42180: "r10.size",
 | 
			
		||||
    0x42198: "r11.size",
 | 
			
		||||
    0x421b0: "r12.size",
 | 
			
		||||
    0x421c8: "r13.size",
 | 
			
		||||
    0x421e0: "r14.size",
 | 
			
		||||
    0x421f8: "r15.size"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Stream:
 | 
			
		||||
    i: int
 | 
			
		||||
    buf: bytes
 | 
			
		||||
 | 
			
		||||
    def __init__(self, buf) -> None:
 | 
			
		||||
        self.pos = 0
 | 
			
		||||
        self.buf = buf
 | 
			
		||||
 | 
			
		||||
    def read_u8(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<B", self.buf[self.pos:self.pos + 1])
 | 
			
		||||
            self.pos += 1
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u16(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<H", self.buf[self.pos:self.pos + 2])
 | 
			
		||||
            self.pos += 2
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u32(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<I", self.buf[self.pos:self.pos + 4])
 | 
			
		||||
            self.pos += 4
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def read_u64(self) -> Optional[int]:
 | 
			
		||||
        try:
 | 
			
		||||
            val = struct.unpack("<Q", self.buf[self.pos:self.pos + 8])
 | 
			
		||||
            self.pos += 8
 | 
			
		||||
            return val[0]
 | 
			
		||||
        except:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def is_done(self):
 | 
			
		||||
        return self.pos >= len(self.buf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RelaEnt:
 | 
			
		||||
    def __init__(self, stream):
 | 
			
		||||
        self.addr = stream.read_u64()
 | 
			
		||||
        self.type = stream.read_u32()
 | 
			
		||||
        self.symbol = stream.read_u32()
 | 
			
		||||
        self.addend = stream.read_u64()
 | 
			
		||||
 | 
			
		||||
class Rela:
 | 
			
		||||
    def __init__(self, rela_addr, rela_size, elf):
 | 
			
		||||
        self.rela_addr = rela_addr
 | 
			
		||||
        self.rela_size = rela_size
 | 
			
		||||
        self.elf = elf
 | 
			
		||||
 | 
			
		||||
    def pop(self):
 | 
			
		||||
        if self.rela_size <= 0:
 | 
			
		||||
            return None
 | 
			
		||||
        stream = Stream(self.elf.get_data(self.rela_addr, 0x18))
 | 
			
		||||
        entry = RelaEnt(stream)
 | 
			
		||||
        entry.offset = self.rela_addr
 | 
			
		||||
        self.rela_addr += 0x18
 | 
			
		||||
        self.rela_size -= 0x18
 | 
			
		||||
        return entry
 | 
			
		||||
 | 
			
		||||
    def peek(self):
 | 
			
		||||
        if self.rela_size <= 0:
 | 
			
		||||
            return None
 | 
			
		||||
        stream = Stream(self.elf.get_data(self.rela_addr, 0x18))
 | 
			
		||||
        entry = RelaEnt(stream)
 | 
			
		||||
        entry.offset = self.rela_addr
 | 
			
		||||
        return entry
 | 
			
		||||
 | 
			
		||||
class Symbol:
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        stream = Stream(data)
 | 
			
		||||
        self.name = stream.read_u16()
 | 
			
		||||
        self.info = stream.read_u8()
 | 
			
		||||
        self.other = stream.read_u8()
 | 
			
		||||
        self.shndx = stream.read_u32()
 | 
			
		||||
        self.value = stream.read_u64()
 | 
			
		||||
        self.size = stream.read_u64()
 | 
			
		||||
 | 
			
		||||
class Elf:
 | 
			
		||||
    def __init__(self, path):
 | 
			
		||||
        with open(path, 'rb') as f:
 | 
			
		||||
            self._data = bytearray(f.read())
 | 
			
		||||
 | 
			
		||||
        self.dynt_rela = 0x42048
 | 
			
		||||
        self.dynt_relasz = 0x42068
 | 
			
		||||
        self.symtab = 0x42080
 | 
			
		||||
        self.breakpoints = {}
 | 
			
		||||
 | 
			
		||||
    def dump(self, path):
 | 
			
		||||
        with open(path, 'wb') as f:
 | 
			
		||||
            f.write(self._data)
 | 
			
		||||
 | 
			
		||||
    def get_data(self, addr, size):
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        return self._data[addr:addr + size]
 | 
			
		||||
 | 
			
		||||
    def set_data(self, addr, data):
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        self._data[addr:addr + len(data)] = data
 | 
			
		||||
 | 
			
		||||
    def set_flag(self, flag):
 | 
			
		||||
        addr = 0x5a100
 | 
			
		||||
        addr -= 0x40000
 | 
			
		||||
        self._data[addr:addr + len(flag)] = flag
 | 
			
		||||
 | 
			
		||||
    def get_rela(self):
 | 
			
		||||
        rela_addr = Stream(self.get_data(self.dynt_rela, 8)).read_u64()
 | 
			
		||||
        rela_size = Stream(self.get_data(self.dynt_relasz, 8)).read_u64()
 | 
			
		||||
        return Rela(rela_addr, rela_size, self)
 | 
			
		||||
 | 
			
		||||
    def get_symbol(self, i):
 | 
			
		||||
        sym_data = self.get_data(self.symtab + 0x18 * i, 0x18)
 | 
			
		||||
        return Symbol(sym_data)
 | 
			
		||||
 | 
			
		||||
    def set_breakpoint(self, addr, func, res):
 | 
			
		||||
        self.breakpoints[addr] = (func, res)
 | 
			
		||||
 | 
			
		||||
    def apply_relocs(self):
 | 
			
		||||
        rela = self.get_rela()
 | 
			
		||||
        patched_code = False
 | 
			
		||||
        asm = ""
 | 
			
		||||
        while True:
 | 
			
		||||
            entry = rela.pop()
 | 
			
		||||
            if not entry:
 | 
			
		||||
                break
 | 
			
		||||
            symbol = self.get_symbol(entry.symbol)
 | 
			
		||||
            dst_name = regs[entry.addr] if entry.addr in regs else f'[{hex(entry.addr)}]'
 | 
			
		||||
            line = f'{hex(entry.offset)}: '
 | 
			
		||||
            if entry.type == 1:
 | 
			
		||||
                data_int = (symbol.value + entry.addend) % 2**64
 | 
			
		||||
                data = struct.pack('<Q', data_int)
 | 
			
		||||
                line += f'add {dst_name}, r{entry.symbol}, ${hex(entry.addend)}'
 | 
			
		||||
 | 
			
		||||
            elif entry.type == 5:
 | 
			
		||||
                data = self.get_data(symbol.value + entry.addend, symbol.size)
 | 
			
		||||
                line += f'mov {dst_name}, [r{entry.symbol}].{symbol.size}'
 | 
			
		||||
                if entry.addend != 0:
 | 
			
		||||
                    raise
 | 
			
		||||
 | 
			
		||||
            elif entry.type == 8:
 | 
			
		||||
                data_int = entry.addend
 | 
			
		||||
                data = struct.pack('<Q', data_int)
 | 
			
		||||
                src_name = f'&{regs[entry.addend]}' if entry.addend in regs else f'${hex(entry.addend)}'
 | 
			
		||||
                line += f'mov {dst_name}, {src_name}'
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                print(hex(entry.type))
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
            line += ' ' * (50 - len(line)) + f'# {bytes(data).hex()}'
 | 
			
		||||
            if entry.addr > entry.offset and entry.addr < 0x49000:
 | 
			
		||||
                next_instr = rela.peek()
 | 
			
		||||
                if not (entry.addr >= next_instr.offset and entry.addr < next_instr.offset + 0x18):
 | 
			
		||||
                    line += "  PATCHING FAR"
 | 
			
		||||
                else:
 | 
			
		||||
                    line += '  PATCHING CODE'
 | 
			
		||||
            asm += line + '\n'
 | 
			
		||||
 | 
			
		||||
            if entry.offset in self.breakpoints:
 | 
			
		||||
                stop = not self.breakpoints[entry.offset][0](self, entry, line, data, self.breakpoints[entry.offset][1])
 | 
			
		||||
                if stop:
 | 
			
		||||
                    return asm, stop
 | 
			
		||||
 | 
			
		||||
            self.set_data(entry.addr, data)
 | 
			
		||||
            if entry.addr >= 0x41000 and entry.addr < 0x41083:
 | 
			
		||||
                patched_code = True
 | 
			
		||||
 | 
			
		||||
        asm += '#End of block\n'
 | 
			
		||||
        return asm, patched_code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fetch_r4(elf, entry, asm, data, res):
 | 
			
		||||
    if data != b'\x00':
 | 
			
		||||
        res.append(True)
 | 
			
		||||
        return True
 | 
			
		||||
    else:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
def check(elf, entry, asm, data):
 | 
			
		||||
    if data == b'\x00':
 | 
			
		||||
        print(asm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    elf = Elf('./svartalfheim')
 | 
			
		||||
    asm = ""
 | 
			
		||||
    n = 0
 | 
			
		||||
    while n <= 3:
 | 
			
		||||
        #print('---')
 | 
			
		||||
        step_asm, patched_code = elf.apply_relocs()
 | 
			
		||||
        if patched_code:
 | 
			
		||||
            asm += '# NATIVE CODE LOADING:\n'
 | 
			
		||||
            n += 1
 | 
			
		||||
            elf.dump(f'./dumps/pydumped{n}')
 | 
			
		||||
        asm += step_asm
 | 
			
		||||
    flag = bytearray(0x46)
 | 
			
		||||
    for i in range(0x46):
 | 
			
		||||
        for b in range(0x30, 127):
 | 
			
		||||
            n = 4
 | 
			
		||||
            flag[i] = b
 | 
			
		||||
            before_read = copy.deepcopy(elf)
 | 
			
		||||
            res = []
 | 
			
		||||
            before_read.set_breakpoint(0x47000, fetch_r4, res)
 | 
			
		||||
            before_read.set_breakpoint(0x47270, fetch_r4, res)
 | 
			
		||||
            before_read.set_flag(flag)
 | 
			
		||||
            while n < 7:
 | 
			
		||||
                step_asm, patched_code = before_read.apply_relocs()
 | 
			
		||||
                if patched_code:
 | 
			
		||||
                    asm += '# NATIVE CODE LOADING:\n'
 | 
			
		||||
                    n += 1
 | 
			
		||||
                    elf.dump(f'./dumps/pydumped{n}')
 | 
			
		||||
                    break
 | 
			
		||||
                asm += step_asm
 | 
			
		||||
            if len(res) == 2 * (i + 1):
 | 
			
		||||
                print(flag)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        #print(asm)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/svartalfheim
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/third_relocs.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 96 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								jujure/static/svartalfheim/trace_read.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 708 KiB  |