wk1 - tools

Theme: GDB, Python pwntools, calculating offsets

Basic Math (100)

Given

  • raw bytes address of totally_uninteresting_function written somewhere: I"E"?U

    • Note: Parse the raw bytes with a pwntools script

  • binary named basic_math

Solve

Our goal is to send raw bytes of the address of ADD for the flag. We will write a Python script to interact with the remote program.

Let's abbreviate totally_uninteresting_function to TUF. We can calculate the ADD address as follows:

  • addr(TUF) = base + offset(TUF)

  • Rearranged: base = addr(TUF) - offset(TUF)

  • addr(ADD) = base + offset(ADD) = (addr(TUF) - offset(TUF)) + offset(ADD)

From the equation, we need to find the address of TUF (given), TUF's offset and ADD's offset.

Offsets can be found using the objdump command on the given binary. Running objdump -d basic_math | grep -A 20 '<basic_math>', we see that ADD's offset is 0x1285. Using readelf on the binary, we find that TUF's offset is 0x1249.

Lastly, let's parse the raw bytes that represent TUF's address. Use the following python script. Note that bytes are sent and received in little-endian, which means the least-significant byte is handled first.

from pwn import *
context.log_level = "DEBUG"
p = remote("offsec-chalbroker.osiris.cyber.nyu.edu", 1245)
p.recvuntil("abc123): ".encode())
p.sendline("[ID]".encode())  # enter your Net ID
p.recvuntil("somewhere: ".encode())
# TUFaddr = p.recvuntil("\n".encode(), drop=True)
TUFaddr = p.recv(8) # receive 8 bytes
# print(TUFaddr)
p.recvuntil("> ".encode())

TUFoffset = 0x1249  # from symbol table gotten using readelf

baseAddr = int.from_bytes(TUFaddr, byteorder="little") - TUFoffset
baseAddrHex = hex(baseAddr)
print(f"base address in hex: {baseAddrHex}")    # check it does end in 0x000

ADDoffset = 0x1285  # from objdump, look at the assembly code within `basic_math` function

ADDaddr = baseAddr + ADDoffset

# p.sendline(hex(ADDaddr).encode())
p.sendline(ADDaddr.to_bytes(8, byteorder="little")) # send raw bytes

p.interactive()


# TUFAddrHex = hex(int.from_bytes(TUFaddr, byteorder="little"))
# print(f"TUF abs address in hex: {TUFAddrHex}")

Run this program and obtain the flag. Done!

GDB 0 (50)

Goal

  • Find the password by using GDB to debug and disassemble the code

Solve

When prompted for the password the first time, enter anything. Soon a prompt appears "pwndbg" which allows you to run the gdb debugger. We will use gdb to step through the code, understand where the password gets stored and read its value.

First, set a breakpoint on main using b main .Run the program by typing run.

Get the assembly code by running disass. We see this particular line of interest indicating the instruction to call a function named "get_password".

  • 0x0000561a096212c2 <+153>: call 0x561a09621333 <get_password>

We want to set a breakpoint on this function. To do this, notice "<+153>". This means relative to main, at an offset of +153. Therefore, we can run b *(main+153) to set this breakpoint. Then, run the program again. By calling convention, the returned value from a function that gets called gets stored in the rax register. It'd be helpful for us to see the value in rax at this point of the program to read the value of the password.

After reaching breakpoint main (1st breakpoint), run continue. This continues the program and you'll be prompted to enter the password. Enter anything. This will highly likely result in the wrong password. After this, we reach the second breakpoint where get_password is called. We run next to move on to the immediate next instruction after this function call. We notice that the RSI register contains the memory address 0x55ec8c7b5010, beside which gdb helpfully annotates its content, which is our password.

Run the program again and enter this password when prompted. Done.

GDB 1 (50)

Given

  • Prompt: What is the address of the buffer the FLAG is read into? (hint: it is zeroed out at the beginning of the function)

  • Binary executable

Solve

The gist of this challenge is to learn how to set breakpoints using gdb. We need to interact with the program by entering the flag's buffer address in exchange for the flag.

First, set a breakpoint on main using gdb and run disass to view the assembly code. There is a function called get_flag: it returns the flag's buffer address, which dereferences to the flag content. Our first strategy is to set a breakpoint at get_flag. By calling convention, whatever a function returns gets put into the rax register. We'll read the rax register for the flag's buffer address, then dump the contents at that adddress for the flag.

However, we can't just set a breakpoint at get_flag(), because this function gets called if we input the correct answer for the buffer address where flag loads into (from program logic). In other words, running the program on gdb and inputting the inevitably wrong address will not land us at this breakpoint.

Let's find another solution.

Observe the assembly code where user input is read:

We know that user input is compared with the buffer address to determine whether the flag gets displayed. To do this, there must be a line that compares user input and the buffer address. This is the line at <main+134>, where the cmp operation is used. By tracing instructions <main+126> to <main+134>, we see that the user input is first put into rax, then transferred to QWORD PTR [rbp-0x68]. Then, whatever is in [rbp-0x60] gets put into rax. And then, we compare rax with QWORD PTR [rbp-0x68]. If we know QWORD PTR [rbp-0x68] contains user input, then by elimination, we can work out rax must contain the buffer address, which was obtained from [rbp-0x60]. We conclude that the buffer address is [rbp-0x60]. To find what this is, we must find out what the rbp register contains at this point in time.

We find out rbp by setting a breakpoint just before user input is read, at <main+116>. Why? Because we know the program does not direct us to get_flag() after user input, which means we must set our breakpoint before user input (also such that after we find the buffer address, we can continue onto user input to input the answer). <main+116> is chosen because its rbp register contains the same thing as when user input is read. Its content has not changed.

Set breakpoint at <main+116> and observe what the RBP register (which contains the pointer to the stack base) contains. Subtract 0x60 from it to obtain the flag's buffer address. Step through the program instruction by instruction using next . When user input is prompted, enter the buffer address. Eventually that will get us to the line where the flag gets printed.

Done.

GDB 2 (100)

Given

  • Binary executable

Goal

Find the flag within the executable.

Solve

Using gdb, set a breakpoint at main and run disass to see the assembly code within main(). Observe the line where read_file function is called at <main+34>. Our hypothesis is that some file containing the flag is read here. The goal is to see the contents within the file.

Let's set a breakpoint at the read_file function: break *(main+34) and run until this breakpoint. It'll give us more info about what happens here if we disass read_file. Run disassemble /m read_file.

At read_file+84, we see <flag>. This seems like a point of interest to set a breakpoint at: b *(read_file+84).

Arriving at this breakpoint, begin stepping through the program instruction by instruction using next. When we arrive at <read_file+91> and inspect the registers, we notice that RSI contains the string "flag.txt". This confirms that the file that is being read indeed contains the flag. Continue stepping through the program because it seems like this file will get read soon.

Eventually, at line read_file+101, flag.txt's content gets loaded into RSI, which contains the flag.

chevron-rightFlaghashtag

lag{gl4d_y0u_f1gur3d_0ut_h0w_t0_f1nd_th3_fl4g!_96ff4618d6d22c03}

Last updated