Stop the mad titan Thanos and save the world!
In a linux environment, within the directory containing InfinityWar, please make sure to run the command:
chmod +x InfinityWar
to enable write permissions for the executable.
In the directory where InfinityWar
is contained, type:
gdb InfinityWar
This will start running gdb so we can start running gdb commands! The following steps will go through an example of commands you can use to solve the first phase of InfinityWar. Only type in the commands that come after "(gdb)".
(gdb) run
Starting program: /w/home.01/cs/ugrad/kristiel/InfinityWar
Please put in the first phrase:
The program is running. If you ever want to interrupt the program and get back to the gdb prompt, type Ctrl-C. Once you're back in the gdb prompt, you can use the run
command again to run the program from the start.
(gdb) quit
This exits out of gdb.
We use gdb to stop our program at certain points with breakpoints. We can either break on an address or a function name. To find names of functions we can break on, print out the disassembled version of the program with the following command (do this in your terminal and not in gdb):
objdump -d InfinityWar
The first few lines of output:
InfinityWar: file format elf64-x86-64
Disassembly of section .init:
00000000004004d8 <_init>:
4004d8: 48 83 ec 08 sub $0x8,%rsp
4004dc: 48 8b 05 15 0b 20 00 mov 0x200b15(%rip),%rax # 600ff8 <__gmon_start__>
4004e3: 48 85 c0 test %rax,%rax
4004e6: 74 05 je 4004ed <_init+0x15>
4004e8: e8 83 00 00 00 callq 400570 <.plt.got>
4004ed: 48 83 c4 08 add $0x8,%rsp
4004f1: c3 retq
...
If we inspect the output of objdump
, we will see many function names enclosed in angle brackets <> Take particular note of main, which is the first function that is run.
Start up gdb again with gdb InfinityWar
.
(gdb) break main
Breakpoint 1 at 0x400771: file Stones.c, line 63.
This sets a break point at the main function
(gdb) run
Starting program: /w/home.24/cs/ugrad/.../cs33/CS33-AVENGERS-e2e5cfa7c26e74cdd23365d1ab1bf768967c7a92/InfinityWar
Breakpoint 1, main () at Stones.c:63
63 Stones.c: No such file or directory.
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.3.x86_64
You'll stop execution just before main. If you see the "Stones.c:..." error, just ignore it. It comes up because you don't have the source file. If you want to see what instructions are in main, you can use the disassemble command.
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400769 <+0>: push %rbp
0x000000000040076a <+1>: mov %rsp,%rbp
0x000000000040076d <+4>: sub $0x10,%rsp
=> 0x0000000000400771 <+8>: mov $0x4008e0,%edi
0x0000000000400776 <+13>: mov $0x0,%eax
...
This is the same output as objdump, but we can use it to run on a specific function (which in this case is main) and it also calculates offsets for you.
The arrow on the left tells you which instruction you are about to run.
(gdb) print $rdi
$1 = 1
Note the dollar sign before the register. This will print out what's inside rdi. Recall that rdi is the register that contains the first parameter, so it is particularly useful to print its value right before calling another function. In this case, we are about to run mov $0x4008e0,%edi
, so the value 1 is in rdi before we move 0x4008e0 into the register.
(gdb) stepi
0x0000000000400776 63 in Stones.c
This runs the current assembly instruction. The output gives you the address of the following assembly instruction. Since we were previously at address 0x000000000040076d
, the next assembly address is at 0x0000000000400776
.
Notice that 0x0000000000400771 <+8>: mov $0x4008e0,%edi
has been run, which moved a new value into rdi. Let's print that value:
(gdb) print $rdi
$2 = 4196576
This doesn't look like the value $0x4008e0
, so what is going on? We didn't specify the print format, so the default was in decimal. To set the format to hexadecimal, we add "/x" to the print command. Also note that the print command can be shortened to p.
(gdb) p/x $rdi
$3 = 0x4008e0
x is not the only formatting character. Some other useful ones are d and c, which print in decimal and character format. For a full list of format characters, see here.
(gdb) p/d 0xf
$4 = 15
(gdb) p/c 65
$5 = 65 'A'
We will also want to look at the contents of memory addresses. These might be in the form of literals you see in the assembly instructions or values stored in registers. If we notice in the main function:
Dump of assembler code for function main:
0x0000000000400769 <+0>: push %rbp
0x000000000040076a <+1>: mov %rsp,%rbp
0x000000000040076d <+4>: sub $0x10,%rsp
=> 0x0000000000400771 <+8>: mov $0x4008e0,%edi
0x0000000000400776 <+13>: mov $0x0,%eax
0x000000000040077b <+18>: callq 0x400520 <printf@plt>
...
The address $0x4008e0
is moved to rdi right before the function printf. If we take a look at the documentation for printf here, we will notice that printf takes in a char pointer as its first parameter. The bomb lab will also use many standard C functions, so if you find something unfamiliar, you should definitely check out the documentation. Since printf takes in a char pointer, we can assume that 0x4008e0
points to a C string in memory. To examine memory, we use the gdb x command. Like p, you can also specify the format after the slash. In this case, we will use the s format, which stands for string.
(gdb) x/s 0x4008e0
0x4008e0: "Please put in the first phrase: "
Oh! So what we're passing into printf is the prompt string for the first phase. That makes sense for the first thing to run in main. The following is a demonstration of other formats for the x command
(gdb) x/20xb 0x4008e0
0x4008e0: 0x50 0x6c 0x65 0x61 0x73 0x65 0x20 0x70
0x4008e8: 0x75 0x74 0x20 0x69 0x6e 0x20 0x74 0x68
0x4008f0: 0x65 0x20 0x66 0x69
20xb means to print 20 bytes in hexadecimal.
Notice that this is the exact same data that was shown previously for 0x4008e0
, just in hexadecimal format instead of string format. To double check that it's the same, let's print out the first hexadecimal byte as a character:
(gdb) p/c 0x50
$6 = 80 'P'
Sure enough, this matches the first letter in the string (capital P in Please).
Let's move on to the first phase in main by breaking at the CapAmerica function.
(gdb) break CapAmerica
Breakpoint 2 at 0x4006fe: file Stones.c, line 40
Note that if we want to delete a breakpoint, we can use the delete command with the number of the breakpoint
(gdb) delete 1
To continue running until you hit the next breakpoint:
(gdb) continue
Continuing.
Please put in the first phrase: hi
Breakpoint 2, CapAmerica (speech=0x601080 <input> "hi") at Stones.c:40
40 in Stones.c
Enter a random phrase when the program prompts you. Running the program in gdb is just as if you ran the program in your terminal (meaning that the bomb can explode).
Now we've stopped at the beginning of the CapAmerica function. We could use the disas
command to get the instructions for this function, but we'll also teach you a second way. Recall that rip is the register that contains the instruction pointer. That means the address of the current instruction is stored in this register. Let's print it out.
(gdb) p $rip
$8 = (void (*)()) 0x4006fe <CapAmerica+12>
Like any other contents in memory, we can look at it with the x command. The particularly useful format to use here is i, which interprets bytes as instructions.
(gdb) x/i $rip
=> 0x4006fe <CapAmerica+12>: mov -0x8(%rbp),%rax
To print more than one upcoming instruction, add a number:
(gdb) x/6i $rip
=> 0x4006fe <CapAmerica+12>: mov -0x8(%rbp),%rax
0x400702 <CapAmerica+16>: mov $0x4008c8,%esi
0x400707 <CapAmerica+21>: mov %rax,%rdi
0x40070a <CapAmerica+24>: callq 0x400550 <strcmp@plt>
0x40070f <CapAmerica+29>: test %eax,%eax
0x400711 <CapAmerica+31>: jne 0x40071a <CapAmerica+40>
Recall that strcmp is a C function that takes in two char pointers and returns 0 if they are equal. Also recall that rdi is the register of the first parameter and rsi is the register of the second parameter. Notice in the assembly instructions of CapAmerica that $0x4008c8
and some register value (presumably the user input) are moved into rsi and rdi. We can deduce that the function is trying to compare the two strings to see if they're equal. Let's check out what the string stored in $0x4008c8
is!
(gdb) x/s 0x4008c8
And that's the phrase that solves the first phase! Now try the second one on your own or start the bomb lab! If you're really stuck, check out the Spoilers folder in this repo for the source code (you won't get the source code in the bomb lab though). Good luck!