TAMUctf 2020: Rusty At Reversing (Reverse)
Date: 2020-03-29
J'ai récemment participer avec l'équipe [Ropkek](https://ctftime.org/team/112647), pendant au moins une journée, au [TamuCTF](https://tamuctf.com/), organisé par des étudiants de l'université A&T du Texas. Un sympathique CTF avec divers challenges de difficulté variée.
```
librusty_at_reversing.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=c4b7c1decfcaa244827e5289a0f88888665caa25, stripped
```
Ce challenge nous met à disposition un objet partagé qui, comme son nom l'indique, est une bibliothèque écrite en Rust.
```
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __cxa_finalize
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000001220 292 FUNC GLOBAL DEFAULT 9 decrypt
6: 0000000000001100 288 FUNC GLOBAL DEFAULT 9 encrypt
7: 0000000000001350 219 FUNC GLOBAL DEFAULT 9 get_flag
```
Cependant, malgré que l'objet soit strippé, il reste le symbole des fonctions exportées dont la fameuse fonction **get_flag.**

Effectivement, cette fonction semble bien contenir le drapeau; elle copie un tableau de constantes pour ensuite le passer comme argument à la fonction **decrypt**. C'est plutôt clair!
La fonction "decrypt" étant assez conséquente; j'ai donc opter pour faire un petit usage du framework [Triton](https://triton.quarkslab.com/).
J'ai commencer par charger l'objet partagé , en mémoire, dans mon contexte Triton.
```c
from elftools.elf.elffile import ELFFile
from elftools.elf.relocation import RelocationSection
from triton import *
def load_binary(ctx):
with open("librusty_at_reversing.so", "rb") as f:
elf = ELFFile(f)
for sect in elf.iter_sections():
end_addr = sect["sh_addr"] + sect["sh_size"] - 1
ctx.setConcreteMemoryAreaValue(sect["sh_addr"], sect.data())
ctx.setConcreteRegisterValue(ctx.registers.rbp, 0x9fffffff)
ctx.setConcreteRegisterValue(ctx.registers.rsp, 0x9fffffff)
```
Pour ensuite exécuter uniquement les fonctions **get_flag, decrypt** afin d'obtenir le drapeau.
```py
def display_flag(ctx, flag_addr):
s = bytearray()
for off in range(0, 28):
s.append(ctx.getConcreteMemoryValue(flag_addr + off))
print(s)
def run(ctx, entry_point):
count = 0
pc = entry_point
while pc:
opcodes = ctx.getConcreteMemoryAreaValue(pc, 16)
instruction = Instruction()
instruction.setOpcode(opcodes)
instruction.setAddress(pc)
if pc == 0x13f5: # call qword [rel decrypt@GOT]
ctx.setConcreteRegisterValue(ctx.registers.rip, 0x1220);
if pc == 0x13fb: # After the call
rdi = ctx.getConcreteRegisterValue(ctx.registers.rsp) + 0x1c
display_flag(rdi);
exit(0)
count += 1
if instruction.getType() == OPCODE.X86.HLT:
break
pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
ctx = TritonContext()
ctx.setArchitecture(ARCH.X86_64)
ctx.concretizeAllMemory()
ctx.setMode(MODE.ALIGNED_MEMORY, True)
ctx.setMode(MODE.ONLY_ON_SYMBOLIZED, True)
load_binary(ctx)
run(ctx, 0x1350) # get_flag
```
J'obtiens le drapeau tant attendu `gigem{mr_stark_i_feel_rusty}`. Bien évidemment ce challenge peut - être résolu beaucoup plus rapidement.