TAMUctf 2020: Rusty At Reversing (Reverse)

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.** ![enter image description here](https://i.ibb.co/5MtLrc7/GET-flag.png) 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.