DEF CON CTF Qualifier 2023 - Test Your Luck (LiveCTF)
Can you beat the hottest new skill-based game sweeping the nation? Is guessing 32 bits really possible within the span of the CTF?
- [30 solves]
very fun challenge XD
Analysis
IDA로 봤을 때보다 더 많은 로직이 있는 것 같았다. 그래서 나는 주로 GDB를 이용하여 분석했다. IDA에서 암호화된 함수를 찾을 수 있었다.
It seems to have a bit more logic than the function seen with IDA. So I usually analyzed with GDB. I could find encrypted function in IDA.
위 함수가 동적 디버깅할 때는 잘 해석이 되어서 바이트를 추출해서 패치를 해주기로 하였다.
The above function was interpreted well during dynamic debugging, so I decided to extract the bytes and patch them.
1 | gef> x/20i 0x555555556000 |
1 | gef> p/x 0x55555555615e - 0x555555556000 |
1 | buf = [0xf3, 0xf, 0x1e, 0xfa, 0x41, 0x54, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0xb9, 0x8, 0x0, 0x0, 0x0, 0x55, 0xbe, 0x1, 0x0, 0x0, 0x0, 0xbf, 0x1, 0x0, 0x0, 0x0, 0x53, 0x48, 0x83, 0xec, 0x20, 0x64, 0x48, 0x8b, 0x4, 0x25, 0x28, 0x0, 0x0, 0x0, 0x48, 0x89, 0x44, 0x24, 0x18, 0x48, 0x8d, 0x5, 0xca, 0xdf, 0xff, 0xff, 0x48, 0x8d, 0x54, 0x24, 0x8, 0x48, 0x8d, 0x6c, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x8, 0xe8, 0x9f, 0xf2, 0xff, 0xff, 0x4c, 0x8d, 0x25, 0xef, 0x22, 0x0, 0x0, 0xeb, 0x56, 0xf, 0x1f, 0x44, 0x0, 0x0, 0x48, 0x8b, 0x5c, 0x24, 0x10, 0x48, 0x89, 0x5c, 0x24, 0x8, 0x4c, 0x39, 0xe3, 0x72, 0xd, 0x48, 0x3b, 0x1d, 0x12, 0x25, 0x0, 0x0, 0xf, 0x86, 0x9c, 0x0, 0x0, 0x0, 0x48, 0x85, 0xdb, 0xf, 0x84, 0xc3, 0x0, 0x0, 0x0, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0x31, 0xf6, 0x31, 0xff, 0xb9, 0x8, 0x0, 0x0, 0x0, 0x48, 0x89, 0xea, 0x48, 0xc7, 0x44, 0x24, 0x10, 0x0, 0x0, 0x0, 0x0, 0xe8, 0x4c, 0xf2, 0xff, 0xff, 0x85, 0xc0, 0x7e, 0x4f, 0x48, 0x8b, 0x44, 0x24, 0x10, 0x48, 0x89, 0x3, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0x31, 0xf6, 0x31, 0xff, 0xb9, 0x8, 0x0, 0x0, 0x0, 0x48, 0x89, 0xea, 0x48, 0xc7, 0x44, 0x24, 0x10, 0x0, 0x0, 0x0, 0x0, 0xe8, 0x20, 0xf2, 0xff, 0xff, 0x85, 0xc0, 0x7f, 0x8b, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0x31, 0xc9, 0x31, 0xd2, 0xbe, 0x1, 0x0, 0x0, 0x0, 0xbf, 0x3c, 0x0, 0x0, 0x0, 0xe8, 0x3, 0xf2, 0xff, 0xff, 0xe9, 0x6d, 0xff, 0xff, 0xff, 0xf, 0x1f, 0x44, 0x0, 0x0, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0x31, 0xc9, 0x31, 0xd2, 0xbe, 0x1, 0x0, 0x0, 0x0, 0xbf, 0x3c, 0x0, 0x0, 0x0, 0xe8, 0xe0, 0xf1, 0xff, 0xff, 0xeb, 0x96, 0xf, 0x1f, 0x44, 0x0, 0x0, 0x45, 0x31, 0xc9, 0x45, 0x31, 0xc0, 0x31, 0xc9, 0x31, 0xd2, 0xbe, 0x4, 0x0, 0x0, 0x0, 0xbf, 0x3c, 0x0, 0x0, 0x0, 0xe8, 0xc0, 0xf1, 0xff, 0xff, 0x48, 0x8b, 0x5c, 0x24, 0x8, 0x48, 0x85, 0xdb, 0xf, 0x85, 0x46, 0xff, 0xff, 0xff, 0x66, 0xf, 0x1f, 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0x48, 0x8b, 0x44, 0x24, 0x18, 0x64, 0x48, 0x2b, 0x4, 0x25, 0x28, 0x0, 0x0, 0x0, 0x75, 0x9, 0x48, 0x83, 0xc4, 0x20, 0x5b, 0x5d, 0x41, 0x5c, 0xc3, 0xe8, 0xd2, 0xee, 0xff, 0xff] |
위 파이썬 스크립트를 실행해서 나온 결과를 IDA에서 스크립트로 실행하면 된다. 이때 중요한 점은 IDA에 반영하고 ‘reanalyze program’ 한 후 껐다 켜야 디컴파일이 되는데 끌 때 ‘DON’T SAVE the database’을 체크해야 한다.
You can execute the result of executing the above python script as a script in IDA. At this time, the important point is to reflect in IDA, ‘reanalyze program’, and guit IDA. When quiting it, You have to check ‘DON’T SAVE the database’. Now, you can see decompiled source code.
사실 나는 함수를 디코드 하기 전에 디버깅하면서 임의쓰기를 발견했다. while문을 이용해서 원하는 만큼 임의 쓰기가 가능하다. syscall을 이용해서 buf에 8바이트 입력을 받는데, 특정 주소를 거르고 있다. 이는 got 영역을 포함한다. 반복문을 끝내고 싶으면 0을 입력하면 된다. 30번째 라인에서 원하는 주소에 원하는 값을 쓰는 것을 확인할 수 있다.
Actually I found arbitrary writes before decoding the function. It is possible to write arbitrarily as many times as desired by using a while statement. The buf is received 8 bytes input by using a syscall. It’s filtering out a specific address including got space. If you want to end the loop, you can send 0. In the 30th line, you can find exactly arbitrary writes.
sub_2000 함수를 xref로 확인해보면 sub_2000 함수를 decode 해주는 함수를 찾을 수 있다. 그 함수에서 sub_2000을 decode 한 후, 특정한 프로그램 영역을 rwx로 권한을 바꿔주는 함수를 호출한다. 이 함수를 mprotect_rwx() 함수로 네이밍해주었다. 이 문제는 NX_bit 보호기법이 없어서 이 함수를 활용하면 좋겠다는 생각을 했다.
If you check the sub_2000 function by xref, you can find a function that decodes the sub_2000 function. After decode sub_2000 in that function, call a function that changes the authority of a specific program area to rwx. I named this function mprotect_rwx() function. Since there is no NX bit mitigation for this challenge, I thought it would be nice to use this function.
sub_2000 함수를 호출한 후 아래와 같은 로직의 generate_random() 함수를 호출한다. 사실상 완전한 random이 아니기 때문에 예측가능하다.
The generated_random() function is called after the sub_2000 function is called. The random_value isn’t definitly random. So It could be predictable.
1 | dword_45B0 = dword_45B8 + dword_45B0 * dword_45B4;// random_value 예측 가능 |
우리에겐 임의 쓰기가 있기 때문에 random_value를 예측할 수 있다. 이 함수가 종료되면 드디어 main 함수가 호출되는데, random_value는 main 함수에서 사용된다. random_value를 예측하면 Correct!을 출력하고 틀리면 Incorrect를 출력해준다. main 함수에서는 별다른 취약점이 보이지 않았다.
Because we have arbitrary writes, you can predict random_value. When this function ends, the main function is finally called, and random_value is used in the main function. If random_value is predicted, ‘Correct!’ is output, otherwise ‘Incorrect’ is output. I couldn’t find any vulnerabilities in the main function.
Exploit
처음에 pie 주소가 출력되어서 이것부터 릭해주었다.
At first, the pie address was printed, So I leaked it.
전략은 다음과 같다. 쉘코드 실행이 가능하기 때문에 쉘코드 실행을 목표로 삼았다. 그리고 임의 쓰기를 이용하여 w 권한이 주어진 곳에 쉘코드를 쓰기로 했다. 스택 주소를 알기가 힘들기 때문에 지금 현재 알고 있는 pie 주소를 최대한 활용하고자 했다.
The strategy is this. Because shellcode execution is possible, I targeted shellcode execution. And I decide to write shellcode at area with w authority by using arbitrary writes. It was difficult to know stack address, So I wanted to make the most of the pie address I currently know.
mprotect_rwx() 함수가 호출되면 위와 같이 저 영역이 rwx로 변하기 때문에 적당하다고 생각했다. 이를 위해 generate_random() 함수 대신 mprotect_rwx() 함수가 실행되도록 하기로 결심했다. 마지막으로 fini_array를 이용해서 sub_2000가 한 번 더 실행되게 했다.
When mprotect_rwx() function was called, the above area was changed to rwx autority. So I thought this area was suitable for writing shellcode. For this, I decided to have the mprotect_rwx() function call instead of the generate_random() function. Finally sub_2000 function is called one more time by using fini_array.
sub_2000가 한 번 더 실행되고, 이제 rwx 권한이 주어진 프로그램 영역에 임의 쓰기가 가능하다. sub_2000 함수에서 임의쓰기가 끝난 후 종료되는 아래 영역이 쉘코드를 작성하기에 가장 적당하다고 판단했다.
The sub_2000 is called one more, and now arbitrary writes are possible to the program area given the rwx permission. In the sub_2000 function, it was determined that the area below, which ends after arbitrary writing is finished, is the most suitable for writing shellcode.
Exploit Code
1 | #!/usr/bin/env python3 |