Written by
ZyphenSVC

Share this post!

Tweet

← ../

Learning Bin Exp from Scratch

May 25, 20245 min read

Forenote

I'm bored... Might as well become a god of binary exploitation.

This would not be possible if it wasnt for Nightmare by guyinatuxedo. This series is a compilation of my summarized notes and remarks done by me and me alone.

Assembly

#include <stdio.h>

void main(void)
{
    puts("Hello World!");
}

For this code we can get the following assembly code by running objdump -D a.out -M intel | less

0000000000001149 <main>:
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   rbp
    114e:       48 89 e5                mov    rbp,rsp
    1151:       48 8d 3d ac 0e 00 00    lea    rdi,[rip+0xeac]        # 2004 <_IO_stdin_used+0x4>
    1158:       e8 f3 fe ff ff          call   1050 <puts@plt>
    115d:       90                      nop
    115e:       5d                      pop    rbp
    115f:       c3                      ret

Note there are three pointers that the computer uses:

rbp : Base Pointer
rsp : Stack Pointer
rip : Instruction Pointer
rdi:    First Argument
rsi:    Second Argument
rdx:    Third Argument
rcx:    Fourth Argument
r8:     Fifth Argument
r9:     Sixth Argument

With this in mind we have that rbp is pushed into the stack with the infromation in the first argument which is our "Hello World!". Then executed as puts().

8 Byte RegisterLower 4 BytesLower 2 BytesLower Byte
rspebpbpbpl
rspespspspl
ripeipaxal
raxeaxbxbl
rbxebxcxcl
rcxecxdxdl
rdxedxsisil
rsiesididil
rdiedir8wr8b
r8r8dr9wr9b
r9r9dr10wr10b
r10r10dr11wr11b
r11r11dr12wr12b
r12r12dr13wr13b
r13r13dr14wr14b
r14r14dr15wr15b
r15r15d

With this in mind, we can use this to see how especially large variables of data are handled within the stack. For example a flag.

Stack

Look at the following piece of code. We are going to show how variables are injected and accessed from the stack.

#include <stdio.h>

void main(void)
{
    int x = 5;
    puts("hi");
}
0000000000001149 <main>:
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   rbp
    114e:       48 89 e5                mov    rbp,rsp
    1151:       48 83 ec 10             sub    rsp,0x10
    1155:       c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5
    115c:       48 8d 3d a1 0e 00 00    lea    rdi,[rip+0xea1]        # 2004 <_IO_stdin_used+0x4>
    1163:       e8 e8 fe ff ff          call   1050 <puts@plt>
    1168:       90                      nop
    1169:       c9                      leave
    116a:       c3                      ret
    116b:       0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

Note that a word is two bytes of data, dword is four, qword is eight.

Look and see that we moved four bytes of data onto the stack with a base pointer - 0x4 at position 0x5.

Note that the bounds of the stack is between rbp and rsp. Think of rbp as the base of the stack, meaning bottom, and the rsp as ceiling (seiling), or top.

Flags

00:     Carry Flag
01:     always 1
02:     Parity Flag
03:     always 0
04:     Adjust Flag
05:     always 0
06:     Zero Flag
07:     Sign Flag
08:     Trap Flag
09:     Interruption Flag     
10:     Direction Flag
11:     Overflow Flag
12:     I/O Privilege Field lower bit
13:     I/O Privilege Field higher bit
14:     Nested Task Flag
15:     Resume Flag

Instructions

mov

Moves one register to another.

mov rax, rdx

dereference

Dereferences the register within the brackets. Deals with pointers.

mov rax, [rdx]

lea

Calcuates the address of the second operand and moves that address into the first.

lea rdi, [rbx+0x10]

add

Adds two register values together in a sum.

add rax, rdx

sub

Subtracts two register values together.

sub rsp, 0x10

xor

Xors and stores the result in the first operation.

xor rdx, rax

push

Pushes instruction to stack, will make stack grow by 8 bytes for x64, or 4 for x86.

push rax

pop

Pops the top instruction from the stack and shrinks stack by 8 bytes, or 4 for x86.

pop rax

The top 8 bytes of the stack will end up in the rax register.

jmp

Jumps to the instruction address, used to redirect code execution.

jmp 0x602010

call and ret

Call is similar to the jmp, but also pushes the values of rbp and rip onto the stack, then jumps to the addr. Return uses the pushed values of rbp and rip to continue execution as is.

call 0x0
ret 0x0

cmp

Similar to sub, but doesnt storte the result in the first argument. Similar to boolean comparision in CLang.

cmp edx, eax

jnz and jz

"Jump if not zero" and "Jump if zero".

xor rdx, rax

Ghidra

For this first part, let's look at the following crackme.

Windows

So first off, on a personal note always stay up to date on Ghidra. I learned from using a version 10 ghidra, it wouldn't recognize half as many ELF files as v11.

Once imported a file and opened it, always analyze the file.

By clicking on the window menu on top, we can open the bytes window, which let's us view the hex in the chance that this may be needed. We can drag any window anywhere for it to be merged and navigated using the panels at the bottom.

If you want a seperate window, but not a merger, then drag the title bar until you see an arrow of where you want to place this new window. Or drag it out to make a standalone window!

Note that the main asm window is called the listing window. The disassembly window is called decompile window. The .data, .bss and .text can be found in the program trees. And finally the symbol tree window contains the list of our functions and labels.

Decompilation Mechanics

We can rename the functions at the very top by right clicking and using Edit Function Signature.

If we use the following schematic for the function signature we can actually look at things in really similar CLang code.

int main (int argc, char * * argv)

Note that I had to make a double pointer for argv to be considered an array.

int main(int argc,char **argv)

{
  size_t sVar1;
  
  if (argc == 2) {
    sVar1 = strlen(argv[1]);
    if (sVar1 == 10) {
      if (argv[1][4] == '@') {
        puts("Nice Job!!");
        printf("flag{%s}\n",argv[1]);
      }
      else {
        usage(*argv);
      }
    }
    else {
      usage(*argv);
    }
  }
  else {
    usage(*argv);
  }
  return 0;
}

Our code in ghidra now looks like this! 😄

Comments

                             **************************************************************
                             * plate comment?                                             *
                             **************************************************************
                             precomment
        001011d3 83 7d fc 02     CMP        dword ptr [RBP + local_c],0x2                    eol comment
                             postcomment

We can add comments like this by right clicking a line of code and setting comments like this accordingly. We can only see the precomment in the actual decompile window though.

Inputting this flag into the crackme, we get the following flag

~$ ./rev50_linux64-bit abcd@efghi
Nice Job!!
flag{abcd@efghi}

Windows Reversing

Now let's analyze a windows file. Same old opening up files and then we click WindowsPE x86 Propogate External Parameters in the analyze menu.

At this point I realized this is so much more useful for generic ghidra as well.

We can view API References by opening up a Symbol References window which contains more references than the imports menu in the Symbol Tree.

If there is a symbolic constant, we can conver this to a more human-readable interpretation by using Set Equate and we can educatedly guess CREATE_ and it should autocomplete with given strings (since most of Microsoft's process creation flags are starting the same).

We can check defined strings by opening the Defined Strings menu. If we want to see all references to the address, we can right click in the listing window, click on references, and click Show References to Address.

We can even check a function call graph window!! Sooo many things I didnt know before that I feel like an amateur to my pro-mindset ego in HS.

If we click on the listing or decompile window with a function and press g key, then type the function name or addr.

We can see a visual representation of decision points similar to IDA, which pay to win sucks btw, by opening a Function Graph window.

At this point, I felt like I knew nothing compared to now from my old competition days. I was only blinding guessing as a sheep.

GDB

Now comes GDB, my favourite.

gef➤  r
Starting program: /home/zyphen/a.out
Hello World!
[Inferior 1 (process 292) exited with code 015]
gef➤  b main
Breakpoint 1 at 0x8001149
gef➤  r
Starting program: /home/zyphen/a.out

Breakpoint 1, 0x0000000008001149 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000008001149<main+0> endbr64
$rbx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rcx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rdx   : 0x00007ffffffed9780x00007ffffffedbba"SHELL=/bin/bash"
$rsp   : 0x00007ffffffed8780x00007fffff5c70b3<__libc_start_main+243> mov edi, eax
$rbp   : 0x0
$rsi   : 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
$rdi   : 0x1
$rip   : 0x0000000008001149<main+0> endbr64
$r8    : 0x0
$r9    : 0x00007fffff7c1d50  →   endbr64
$r10   : 0x00007fffff7ddf680x000000006ffffff0
$r11   : 0x00007fffff7ddf680x000000006ffffff0
$r12   : 0x0000000008001060<_start+0> endbr64
$r13   : 0x00007ffffffed9600x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffffed878+0x0000: 0x00007fffff5c70b3<__libc_start_main+243> mov edi, eax  ← $rsp
0x00007ffffffed880+0x0008: 0x0000000000000071 ("q"?)
0x00007ffffffed888+0x0010: 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
0x00007ffffffed890+0x0018: 0x00000001ff788618
0x00007ffffffed898+0x0020: 0x0000000008001149<main+0> endbr64
0x00007ffffffed8a0+0x0028: 0x0000000008001160<__libc_csu_init+0> endbr64
0x00007ffffffed8a8+0x0030: 0x6c1bbc69e92afb5d
0x00007ffffffed8b0+0x0038: 0x0000000008001060<_start+0> endbr64
─────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x8001139 <__do_global_dtors_aux+57> nop    DWORD PTR [rax+0x0]
    0x8001140 <frame_dummy+0>  endbr64
    0x8001144 <frame_dummy+4>  jmp    0x80010c0 <register_tm_clones>0x8001149 <main+0>         endbr64
    0x800114d <main+4>         push   rbp
    0x800114e <main+5>         mov    rbp, rsp
    0x8001151 <main+8>         lea    rdi, [rip+0xeac]        # 0x8002004
    0x8001158 <main+15>        call   0x8001050 <puts@plt>
    0x800115d <main+20>        nop
─────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "a.out", stopped 0x8001149 in main (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8001149 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000008001149<main+0> endbr64
$rbx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rcx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rdx   : 0x00007ffffffed9780x00007ffffffedbba"SHELL=/bin/bash"
$rsp   : 0x00007ffffffed8780x00007fffff5c70b3<__libc_start_main+243> mov edi, eax
$rbp   : 0x0
$rsi   : 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
$rdi   : 0x1
$rip   : 0x0000000008001149<main+0> endbr64
$r8    : 0x0
$r9    : 0x00007fffff7c1d50  →   endbr64
$r10   : 0x00007fffff7ddf680x000000006ffffff0
$r11   : 0x00007fffff7ddf680x000000006ffffff0
$r12   : 0x0000000008001060<_start+0> endbr64
$r13   : 0x00007ffffffed9600x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffffed878+0x0000: 0x00007fffff5c70b3<__libc_start_main+243> mov edi, eax  ← $rsp
0x00007ffffffed880+0x0008: 0x0000000000000071 ("q"?)
0x00007ffffffed888+0x0010: 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
0x00007ffffffed890+0x0018: 0x00000001ff788618
0x00007ffffffed898+0x0020: 0x0000000008001149<main+0> endbr64
0x00007ffffffed8a0+0x0028: 0x0000000008001160<__libc_csu_init+0> endbr64
0x00007ffffffed8a8+0x0030: 0x6c1bbc69e92afb5d
0x00007ffffffed8b0+0x0038: 0x0000000008001060<_start+0> endbr64
─────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x8001139 <__do_global_dtors_aux+57> nop    DWORD PTR [rax+0x0]
    0x8001140 <frame_dummy+0>  endbr64
    0x8001144 <frame_dummy+4>  jmp    0x80010c0 <register_tm_clones>0x8001149 <main+0>         endbr64
    0x800114d <main+4>         push   rbp
    0x800114e <main+5>         mov    rbp, rsp
    0x8001151 <main+8>         lea    rdi, [rip+0xeac]        # 0x8002004
    0x8001158 <main+15>        call   0x8001050 <puts@plt>
    0x800115d <main+20>        nop
─────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "a.out", stopped 0x8001149 in main (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8001149 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

This is how gef looks! Its been a while since I have seen this menu. Brings tears of joy.

  • next takes you to the next line of code but will step over function calls.
  • step will take you to the next line of code but will go into function calls.
  • stepi will take you through one instruction at a time and also stepping into function calls.
  • disas main will disassemble main similar to objdump.
  • info b will show all breakpoints.
  • delete 2 will delete the enumerated breakpoints.
  • b *main will breakpoint a function of any sort including puts.

Let us look at what outputs after a couple of nexti or ni's:

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000008001149<main+0> endbr64
$rbx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rcx   : 0x0000000008001160<__libc_csu_init+0> endbr64
$rdx   : 0x00007ffffffed9780x00007ffffffedbba"SHELL=/bin/bash"
$rsp   : 0x00007ffffffed8700x0000000000000000
$rbp   : 0x00007ffffffed8700x0000000000000000
$rsi   : 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
$rdi   : 0x0000000008002004"Hello World!"
$rip   : 0x0000000008001158<main+15> call 0x8001050 <puts@plt>
$r8    : 0x0
$r9    : 0x00007fffff7c1d50  →   endbr64
$r10   : 0x00007fffff7ddf680x000000006ffffff0
$r11   : 0x00007fffff7ddf680x000000006ffffff0
$r12   : 0x0000000008001060<_start+0> endbr64
$r13   : 0x00007ffffffed9600x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffffed870+0x0000: 0x0000000000000000$rsp, $rbp
0x00007ffffffed878+0x0008: 0x00007fffff5c70b3<__libc_start_main+243> mov edi, eax
0x00007ffffffed880+0x0010: 0x0000000000000071 ("q"?)
0x00007ffffffed888+0x0018: 0x00007ffffffed9680x00007ffffffedba7"/home/zyphen/a.out"
0x00007ffffffed890+0x0020: 0x00000001ff788618
0x00007ffffffed898+0x0028: 0x0000000008001149<main+0> endbr64
0x00007ffffffed8a0+0x0030: 0x0000000008001160<__libc_csu_init+0> endbr64
0x00007ffffffed8a8+0x0038: 0x6c1bbc69e92afb5d
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x800114d <main+4>         push   rbp
    0x800114e <main+5>         mov    rbp, rsp
    0x8001151 <main+8>         lea    rdi, [rip+0xeac]        # 0x80020040x8001158 <main+15>        call   0x8001050 <puts@plt>0x8001050 <puts@plt+0>     endbr64
       0x8001054 <puts@plt+4>     bnd    jmp QWORD PTR [rip+0x2f75]        # 0x8003fd0 <[email protected]>
       0x800105b <puts@plt+11>    nop    DWORD PTR [rax+rax*1+0x0]
       0x8001060 <_start+0>       endbr64
       0x8001064 <_start+4>       xor    ebp, ebp
       0x8001066 <_start+6>       mov    r9, rdx
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
puts@plt (
   $rdi = 0x0000000008002004"Hello World!"
)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "a.out", stopped 0x8001158 in main (), reason: SINGLE STEP
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8001158 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

Hey! Our Hello World! is there!

Let's look at the address at that lea:

gef➤  x/10c 0x8002004
0x8002004:      0x48    0x65    0x6c    0x6c    0x6f    0x20    0x57    0x6f
0x800200c:      0x72    0x6c
gef➤  x/s 0x8002004
0x8002004:      "Hello World!"
gef➤

We can do x/a for an address, x/10c for 10 characters, x/s for a string, x/g for a qword, and x/w for a dword.

Info Registers

gef➤  i r
rax            0x8001149           0x8001149
rbx            0x8001160           0x8001160
rcx            0x8001160           0x8001160
rdx            0x7ffffffed978      0x7ffffffed978
rsi            0x7ffffffed968      0x7ffffffed968
rdi            0x8002004           0x8002004
rbp            0x7ffffffed870      0x7ffffffed870
rsp            0x7ffffffed870      0x7ffffffed870
r8             0x0                 0x0
r9             0x7fffff7c1d50      0x7fffff7c1d50
r10            0x7fffff7ddf68      0x7fffff7ddf68
r11            0x7fffff7ddf68      0x7fffff7ddf68
r12            0x8001060           0x8001060
r13            0x7ffffffed960      0x7ffffffed960
r14            0x0                 0x0
r15            0x0                 0x0
rip            0x8001158           0x8001158 <main+15>
eflags         0x246               [ PF ZF IF ]
cs             0x33                0x33
ss             0x2b                0x2b
ds             0x0                 0x0
es             0x0                 0x0
fs             0x0                 0x0
gs             0x0                 0x0
gef➤

Info Frame

gef➤  i f
Stack level 0, frame at 0x7ffffffed880:
 rip = 0x8001158 in main; saved rip = 0x7fffff5c70b3
 Arglist at 0x7ffffffed868, args:
 Locals at 0x7ffffffed868, Previous frame's sp is 0x7ffffffed880
 Saved registers:
  rbp at 0x7ffffffed870, rip at 0x7ffffffed878

If we run disas main now, we can actually see where we are in the code now:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000008001149 <+0>:     endbr64
   0x000000000800114d <+4>:     push   rbp
   0x000000000800114e <+5>:     mov    rbp,rsp
   0x0000000008001151 <+8>:     lea    rdi,[rip+0xeac]        # 0x8002004
=> 0x0000000008001158 <+15>:    call   0x8001050 <puts@plt>
   0x000000000800115d <+20>:    nop
   0x000000000800115e <+21>:    pop    rbp
   0x000000000800115f <+22>:    ret
End of assembler dump.

Changing Values

As the name suggests, we can even change the name of variables and inputs. Suppose instead of hello world! we wanted womp womp. Let's go ahead and do that:

gef➤  x/s 0x8002004
0x8002004:      "Hello World!"
gef➤  set {char [10]} 0x8002004 = "womp womp"
gef➤  x/s 0x8002004
0x8002004:      "womp womp"

Note that we want the amount of characters plus one for the size of the char array. Remember how Clang works please.

Changing Addresses

We can also change addr by the following commands:

gef➤  x/g 0x08048451
0x8048451 <__libc_csu_init+33>:	0xff08838d
gef➤  set *0x08048451 = 0xfacade
gef➤  x/g 0x08048451
0x8048451 <__libc_csu_init+33>:	0xfacade

Finally we can jump to an address by using

gef➤  j *0x08048451
Continuing at 0x0x08048451.

Python Script Kiddie No More

from pwn import *

# target = remote("github.com", 9000)
target = process("./challenge")

gdb.attach(target)
gdb.attach(target, gdbscript='b *main')

target.send(x)
target.sendline(x)

print(target.recvline())
print(target.recvuntil(b"out"))

p64(x) # x64 qword
p32(x) # x32 dword
u64(x) # unpack qword
u32(x) # unpack dword

target.interactive()

Challenges

Helithumper RE

You can change data locations into strings in Ghidra by going to the pointed location and right clicking the listing window with Data > TerminatedCString.

This gets us this:

bool main(void)

{
  int check;
  char *ptr;
  
  ptr = (char *)calloc(0x32,1);
  puts(s_Welcome_to_the_Salty_Spitoon_,_H_00102008);
  scanf();
  check = validate(ptr);
  if (check == 0) {
    puts(s_Yeah_right._Back_to_Weenie_Hut_J_00102050);
  }
  else {
    puts("Right this way...");
  }
  return check == 0;

If we check the validate function, we get the following:

int validate(char *string)

{
  int iVar1;
  size_t sVar1;
  long in_FS_OFFSET;
  int local_50;
  int check [4];
  undefined4 local_38;
  undefined4 local_34;
  undefined4 local_30;
  undefined4 local_2c;
  undefined4 local_28;
  undefined4 local_24;
  undefined4 local_20;
  undefined4 local_1c;
  undefined4 local_18;
  undefined4 local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  check[0] = 0x66;
  check[1] = 0x6c;
  check[2] = 0x61;
  check[3] = 0x67;
  local_38 = 0x7b;
  local_34 = 0x48;
  local_30 = 0x75;
  local_2c = 0x43;
  local_28 = 0x66;
  local_24 = 0x5f;
  local_20 = 0x6c;
  local_1c = 0x41;
  local_18 = 0x62;
  local_14 = 0x7d;
  sVar1 = strlen(string);
  local_50 = 0;
  do {
    if ((int)sVar1 <= local_50) {
      iVar1 = 1;
LAB_001012b7:
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return iVar1;
    }
    if ((int)string[local_50] != check[local_50]) {
      iVar1 = 0;
      goto LAB_001012b7;
    }
    local_50 = local_50 + 1;
  } while( true );
}

Convert the variable's hex to ascii:

0x66 0x6C 0x61 0x67 0x7B 0x48 0x75 0x43 0x66 0x5F 0x6C 0x41 0x62 0x7D

We can now input the following

gef➤  r
Starting program: /mnt/e/downloads/rev ]
Welcome to the Salty Spitoon™, How tough are ya?
flag{HuCf_lAb}
Right this way...
[Inferior 1 (process 695) exited normally]
gef➤

CSAW 2019: Beleaf

int FUN_001008a1(void)

{
  size_t len;
  long output;
  long in_FS_OFFSET;
  ulong i;
  char check [136];
  long stack;
  
  stack = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter the flag\n>>> ");
  scanf();
  len = strlen(check);
  if (len < 0x21) {
    puts("Incorrect!");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  for (i = 0; i < len; i = i + 1) {
    output = validate(check[i]);
    if (output != *(long *)(s__003014e0 + i * 8)) {
      puts("Incorrect!");
                    /* WARNING: Subroutine does not return */
      exit(1);
    }
  }
  puts("Correct!");
  if (stack != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
long validate(char input)

{
  long i;
  
  i = 0;
  while ((i != -1 && ((int)input != *(int *)(alphabet + i * 4)))) {
    if ((int)input < *(int *)(alphabet + i * 4)) {
      i = i * 2 + 1;
    }
    else if (*(int *)(alphabet + i * 4) < (int)input) {
      i = (i + 1) * 2;
    }
  }
  return i;
}

Then we know that we have to take each hex value of the input and multiply to the location of the alphabet times 4. Which means hex((0x00301020 + (4*9))) = '0x301044'.

Which is the location of l. Of course continuing down this list, we get the following:

$ ./beleaf
Enter the flag
>>> flag{we_beleaf_in_your_re_future}
Correct!

It's crazy for how unsuspectingly short alphabet can create a full flag.


Published May 25, 2024, by ZyphenSVC.

If you enjoyed the post, consider sharing it!

Tweet


Copyright © 2024 Sriaditya Vedantam. Site source on GitHub.