Lately I discovered the repository [Yardanico/nim-strenc](https://github.com/Yardanico/nim-strenc), you can use it very easily in your Nim code by importing `strenc`.
Let's try it on this simple example. First you need to install the package using this command: `nimble install strenc`
In the hints we see the string "`demo string`" is automagically replaced by `m611v5u8zi(estring("\x16\x10\x19\x18V\n\f\t\x13\x13\e"), 1823285395)`, reducing our static analysis detections.
Before going deeper in the code of `nim-strenc`, we need to understand a bit of macro term-rewriting.
> Term rewriting macros are macros or templates that have not only a name but also a pattern that is searched for after the semantic checking phase of the compiler: This means they provide an easy way to enhance the compilation pipeline with user defined optimizations:
Term rewriting macro are applied recursively. This means that if the result of a term rewriting macro is eligible for another rewriting, the compiler will try to perform it, and so on, until no more optimizations are applicable.
Now let's try some basic macros. For example the following macro can be used to generate a debug statement if the variable debug is set to `true`
{% highlight py%}
import macros
const debug = true
template log(msg: string) =
if debug: stdout.writeLine(msg)
var x = 4
log("x has the value: " & $x)
{% endhighlight %}
This is nice but we can go deeper by interacting directly with variables and their types. If you are unsure about how a type is represented and handled in Nim, you can use `dumpTree` to generate the Abstract Syntax Trees (AST) at compile time. This will be very useful to debug our macros.
{% highlight py%}
import macros
dumpTree:
const cfgversion: string = "1.0"
const cfglicenseOwner = "John Doe"
{% endhighlight %}
The output will look like this:
StmtList
ConstSection
ConstDef
Ident "cfgversion"
Ident "string"
StrLit "1.0"
ConstSection
ConstDef
Ident "cfglicenseOwner"
Empty
StrLit "John Doe"
We now have everything to understand the code of `nim-strenc`. The code starts by defining a new type `estring` ([#L7](https://github.com/Yardanico/nim-strenc/blob/master/src/strenc.nim#L7)) otherwise our macro will be called recursively on our newly created string.
{% highlight py%}
type
estring = distinct string
{% endhighlight %}
Then it defines a proc to encrypt an estring using a key, note that the key is an integer. The name of this proc will be the same in every generated binary
{% highlight py%}
# Use a "strange" name
# We need {.noinline.} here because otherwise C compiler
# aggresively inlines this procedure for EACH string which results
result[i] = chr(uint8(result[i]) xor uint8((k shr f) and 0xFF))
k = k +% 1
{% endhighlight %}
The encryption is doing a basic XOR where the key is modified a bit after every character. in order to avoid having always the same key in the produced binary, nim-strenc uses a simple but effective trick, by generating a hash based on the compile time and date.
{% highlight py%}
var encodedCounter {.compileTime.} = hash(CompileTime & CompileDate) and 0x7FFFFFFF
{% endhighlight %}
Finally there is the macro to match every String Literal from the source code, encrypt it with the proc `m611v5u8zi` then it will rewrite the matched string to `m611v5u8zi(estring("\xFF...\xXX"), counter)`. Since the encryption is a XOR it can effectively use the same proc to encode and decode the string.
It uses `getAst` to generate the correct AST for our replaced string, and then the counter is modified.
{% highlight py%}
macro encrypt*{s}(s: string{lit}): untyped =
var encodedStr = m611v5u8zi(estring($s), encodedCounter)