반응형

이름의 의미가 뭔지는 모르겠지만

이번 문제는 Moon 이다.


OpenGL을 사용하고 있었고 다른 팀원들 컴퓨터에서는 작동을 하지 않았기 때문에

나 혼자서 풀었어야 했었다.



We choose to go to the moon. We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win.

그럼 문제를 풀어보자



main 화면을 디스어셈 한 일부분이다.

main 내부가 크고 복잡하였기 때문에 일부분만 분석하였다.



main 부분에서 중요한 부분이다.

150 번째 줄에서 memcmp를 하는 것을 볼 수 있는데

이 부분을 x64 dbg로 참이 되게 바꾸니 Good 이라고 나오는 것을 확인했다.


Buf2 의 값은

30c7ead97107775969be4ba00cf5578f1048ab1375113631dbb6871dbe35162b1c62e982eb6a7512f3274743fb2e55c818912779ef7a34169a838666ff3994bb4d3c6e14ba2d732f14414f2c1cb5d3844935aebbbe3fb206343a004e18a092daba02e3c0969871548ed2c372eb68d1af41152cb3b61f300e3c1a8246108010d282e16df8ae7bff6cb6314d4ad38b5f9779ef23208efe3e1b699700429eae1fa93c036e5dcbe87d32be1ecfac2452ddfdc704a00ea24fbc2161b7824a968e9da1db756712be3e7b3d3420c8f33c37dba42072a941d799ba2eebbf86191cb59aa49a80ebe0b61a79741888cb62341259f62848aad44df2b809383e09437928980f

이었고 sub_401BF0 에서 주 연산이 이루어 진다는 것을 분석하였다.


문제는 분석하면서 qword_4CC528 라는 함수에서 갑자기 입력값에서 시리얼리 갑자기 툭 나온다는 것이었다.

라이브러리 끝까지 분석해 봤는데 syscall 나와서 멘붕했다는...

나중에 xref로 따라가 보니 GetProcAddress로 glClientWaitSync 이라는 함수 주소를 저장하는 걸 보고 냉장고에 머리를 찍었다.



위 사진은 sub_401BF0 내부이다.

여기서 glClientWaitSync를 실행하고 나면 시리얼이 만들어 지고 %.8x를 통해 문자열 화 될 것이다.



glClientWaitSync를 실행하기 전에 수행되는 작업인데 이 부분을 어셈으로 보면



이렇게 나오며 RAX를 hexdump로 아랫부분을 살펴보면



이렇게 입력값이 들어가 있다. 아마도 구조체인거 같은데 분석하기 귀찮고 직관적이어서 PASS 했다.



glClientWaitSync를 실행하게 되면 xmm0으로 넣었던 01 부분이 02가 되고 시리얼 값이 나오게 된다.



이제 glClientWaitSync이게 뭐 하는 함수인지 보자.



딱 보니 thread에서 wait 같이 기다리는 함수인거 같다.

sync을 찾아보니 glFenceSync의 반환값이라는 것을 알 수 있었고

인자 중 0x9117는 SYNC_GPU_COMMANDS_COMPLETE 였다.

아마도 GPU를 쓰는 거 같다. 


COMMANDS 라고 해서 sub_401BF0 함수 내부를 뜯어보던 중



이런 부분이 있길레 dword_4CA0A8를 xref로 찾아 보니



이런 부분이 나왔다.

62번째 줄 윗부분에는 Memory를 가지고 많은 연산을 하고 있었다.



sub_401770 내부이다. Compile 나오고 Source 나오고 난리났다.

뭔가 엄청나게 수상해 보인다.

따라서 디버거로 sub_401770 호출 부분에 BP를 걸고 인자 내부를 보았다.



????????



GLSL 이다.

소스

#version 430
layout(local_size_x=8,local_size_y=8)in;
layout(std430,binding=0) buffer shaderExchangeProtocol{uint state[64];uint hash[64];uint password[32];};
vec3 calc(uint p)
{
	float r=radians(p);
	float c=cos(r);
	float s=sin(r);
	mat3 m=mat3(c,-s,0.0,s,c,0.0,0.0,0.0,1.0);
	vec3 pt=vec3(1024.0,0.0,0.0);
	vec3 res=m*pt;
	res+=vec3(2048.0,2048.0,0.0);
	return res;
}
uint extend(uint e)
{
	uint i;uint r=e^0x5f208c26;
	for (i=15;i<31;i+=3)
	{
		uint f=e<<i;
		r^=f;
	}
	return r;
}
uint hash_alpha(uint p)
{
	vec3 res=calc(p);
	return extend(uint(res[0]));
}
uint hash_beta(uint p)
{
	vec3 res=calc(p);
	return extend(uint(res[1]));
}
void main()
{
	uint idx=gl_GlobalInvocationID.x+gl_GlobalInvocationID.y*8;
	uint final;
	if (state[idx]!=1)
	{
		return;
	}
	if ((idx&1)==0)
	{
		final=hash_alpha(password[idx/2]);
	}
	else
	{
		final=hash_beta(password[idx/2]);
	}
	uint i;
	for (i=0;i<32;i+=6)
	{
		final^=idx<<i;
	}
	uint h=0x5a;
	for (i=0;i<32;i++)
	{
		uint p=password[i];
		uint r=(i*3)&7;
		p=(p<<r)|(p>>(8-r));
		p&=0xff;
		h^=p;
	}
	final^=(h|(h<<8)|(h<<16)|(h<<24));
	hash[idx]=final;
	state[idx]=2;
	memoryBarrierShared();
}

뭔가 많이 복잡해 보이지만 쉽다.

입력한 한 글자마다 hash_alpha, hash_beta를 각각 만들어서 final로 저장하는 부분

충분히 계산 가능한 idx 가지고 연산하는 부분

마지막으로 역연산이 불가능 해 보이는 모든 password가지고 연산하는 부분 

3부분으로 이루어 진다.


첫 번째와 두 번째는 충분히 GLSL 코드를 변경하든지 해서 값을 뽑아올 수 있지만

마지막이 막막해 보이지만 2n 번째 hash와 2n+1 번째 hash 각각 2번째 부분을 해결 한뒤

서로 xor 하게 되면 마지막 부분이 xor의 성질에 따라 사라지고 hash_alpha ^ hash_beta를 한 값과 동일해 지게 된다.


따라서 hash_alpha와 hash_beta 가 출력하는 값을 가져오면 된다.

이부분은 GLSL이 C 기반이기도 하고 쉽기 때문에 PASS


from struct import unpack
f = open("ihash","rb")
hashtxt = f.read()
f.close()
f = open("alpha","rb")
alphatxt = f.read()
f.close()
f = open("beta","rb")
betatxt = f.read()
f.close()

hashs= []
for i in range(64):
    hashs.append(unpack(">I",hashtxt[i*4:(i+1)*4])[0])
alpha=[]
for i in range(0x60):
    alpha.append(unpack("<I",alphatxt[i*4:(i+1)*4])[0])
beta=[]
for i in range(0x60):
    beta.append(unpack("<I",betatxt[i*4:(i+1)*4])[0])
    
for i in range(64):
    fin = 0
    for j in range(0,32,6):
        fin ^= i << j
    hashs[i] ^= fin

where = []

for i in range(0x00,0x60):
    where.append(alpha[i] ^ beta[i])

txt = ""
for i in range(32):
    txt += chr(where.index(hashs[i*2] ^ hashs[i*2+1])+0x20)
print txt


이렇게 하면 답이 CTF{OpenGLMoonMoonG*esT*TheMoon} 이렇게 나왔다.

그러나 인증이 되지 않았고 실제 입력해 보니 답이 아니라고 나왔다.



삽질하던 도중 팀원분이 *를 0 으로 바꾸니 인증이 됬다고 하셨다.

원인을 알아보니 *와 0의 hash_alpha ^ hash_beta 값이 같았기 때문이다.



OpenGL를 강제적으로 배울 수 있는 계기였다.

FLAG: CTF{OpenGLMoonMoonG0esT0TheMoon}

'CTF' 카테고리의 다른 글

CodeGate 2022 NDEncryptor  (0) 2022.02.28
2017 DIMICTF problems & 후기  (0) 2017.07.22
Plaid CTF 2017 BB8  (0) 2017.06.02
SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24

+ Recent posts