티스토리 뷰
지난번에 RGBsec
친구들과 본선권을 얻었던 2020 m0lecon ctf
를 진행했다. ( 2020-11-15 ~ 2020-11-16
)
2020 balsn ctf
도 겹쳐있어서 둘다 신경써봤는데 결국 문제를 푼건 m0lecon
밖에 없었다..
분명 본선이 이탈리아였던거로 기억하는데 코로나로 인해서 온라인으로 진행했다 ㅜㅜ
포너블을 많이 봤음에도 불구하고 립싱만 2문제 풀었다.... 지난번 레쎄 씨탭때도 진짜 못했었는데 요즘 자극을 많이 받는다.
The RickShank RuckDemption
포켓몬 비슷한 게임을 만들어서 맵을 돌아다니면서 결투하고, 수집하는 게임이다.
처음에는 다 깨야 되는줄 알고 내 몬스터의 캐릭터랑 체력을 올려서 맵을 돌아다녀봤다.
몬스터 다잡고 맵 다 돌아다녀봐도 플래그 없길래 문제분석을 다시했는데,
게임 로드할때 winFunc
이란 함수가 존재했고, 그냥 해당함수를 호출해주기만 하면 플래그를 얻을 수 있었다.
ptm{_P34ac3_4m0ng_wor1d5_b1c20a1a234a46e26dc7dcbfb69}
Virtual M0lecon
솔버가 적고 꽤 뇌지컬이 필요했던거 같은데 결국은 하라는대로만 하면 답이 나오는 문제였다 ㅎㅎ
VM
인데 입력을 받지 않았다... 무조건 루틴 분석해서 풀라는 뜻인가보다..
기본적인 컨셉은 다음과 같다.
STACK, MEMORY
STACK
은 그냥 값 push
, pop
하는 스택 값 할당하고 SP
로 관리했다.
좀 이상했던게 MEMORY
였는데, 바이너리에서는 MEMORY
를 4개만 할당해 줬었는데 opcode
에서는 0x70
까지 사용했기때문에
바이너리 패치를 해서 문제를 봤어야 했다.
Instruction
바이너리 분석해서 어셈들 쫙 뽑아보면 꽤 많이 나온다..
대충 한 400~500줄? 도저히 어셈만 보고 풀수는 없을거라 생각해서 좀 자세히 분석을 해봤다.
from pwn import *
with open("./opcode", 'rb') as f:
program = f.read(0x1000)
def fetch():
global program
global i
res = program[i]
i += 1
return u8(res)
print(hexdump(program))
i = 0
while True:
op = fetch()
if op == 0: # AND
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r1 & r2 )".format(i))
elif op == 1: # OR
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r1 | r2 )".format(i))
elif op == 2: # XOR
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r1 ^ r2 )".format(i))
elif op == 3: # NOT
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: push ( not r1 )".format(i))
elif op == 4: # ADD
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r1 + r2 )".format(i))
elif op == 5: # SUB
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r2 - r1 )".format(i))
elif op == 6: # MUL
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r1 * r2 )".format(i))
elif op == 7: # DIV
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r2 %% r1 )".format(i))
print("0x{:x}: push ( r2 / r1 )".format(i))
elif op == 8: # SHL
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r2 << r1 )".format(i))
elif op == 9: # SHR
print("0x{:x}: pop r1 ".format(i))
print("0x{:x}: pop r2 ".format(i))
print("0x{:x}: push ( r2 >> r1 )".format(i))
elif op == 10: # PRT
print("0x{:x}: pop r1".format(i))
print("0x{:x}: PutChar r1".format(i))
elif op == 11: # JMP
v1 = 0
for z in range(8):
v1 = fetch() + (v1 << 8)
print("0x{:x}: jmp {}: ".format(i, hex(i+v1)))
elif op == 12: # Jif
v1 = 0
for z in range(8):
v1 = fetch() + (v1 << 8)
print("0x{:x}: pop r1".format(i))
print("0x{:x}: if r1".format(i))
print("0x{:x}: jmp {}: ".format(i, hex(i+v1)))
elif op == 13: # SET
print("0x{:x}: pop r1".format(i))
print("0x{:x}: pop r2".format(i))
print("0x{:x}: MEMORY[r2] = r1".format(i))
elif op == 14: # GET
print("0x{:x}: pop r1".format(i))
print("0x{:x}: push MEMORY[r1]".format(i))
elif op == 15: # INT
print("0x{:x}: push {}: ".format(i, hex(fetch())))
elif op == 16: # DUP
print("0x{:x}: pop r1".format(i))
print("0x{:x}: push r1".format(i))
print("0x{:x}: r2 = DUP r1".format(i))
print("0x{:x}: push r2".format(i))
elif op == 17: # DEL
print("0x{:x}: pop r1".format(i))
print("0x{:x}: free(r1)".format(i))
elif op == 18: # NOP
pass
elif op == 19: # RET
print("0x{:x}: END".format(i))
break
elif op == 20: # RED
print("0x{:x}: GetChar r1".format(i))
print("0x{:x}: push r1".format(i))
else:
print('error')
디스어셈 돌리면 한 400~500
줄의 어셈이 나온다.
어셈만 보고 대충 풀기에는 무리가 있어서 직접 뇌디컴 돌려봐야한다....
Decompile
디컴 돌리면 아래와 같은 코드가 나온다.
for(int i = 0; i < 0x14; i++) {
MEMORY[0x59 + i] = 0;
}
MEMORY[0x1f] = 0;
ans_table = [3, 0xc4, 0x80, 0x16, 0xa2, 0xf8, 0x99, 0xca, 0xe6, 0x87, 0xe3, 0x49, 0x9f, 0x8b, 0xe7, 0xde, 0xe9, 4, 0x4c, 0x86];
for(int j = 0; j < 0x14; j++) {
MEMORY[0x10] = MEMORY[0x59+j];
MEMORY[0x11] = MEMORY[0x10] % 0x10
MEMORY[0x11] *= 0x30
MEMORY[0x12] = MEMORY[0x10] % 0x10 + ((MEMORY[0x10] / 0x10) * 5) % 0x10 - MEMORY[0x10] % 0x10
if(ans_table[j] != MEMORY[0x11] + MEMORY[0x12] + MEMORY[0x1f]) {
puts("Wrong");
exit(0)
}
MEMORY[0x1f] += 1;
}
puts("Correct");
dec.py
ans_table = [3, 0xc4, 0x80, 0x16, 0xa2, 0xf8, 0x99, 0xca, 0xe6, 0x87, 0xe3, 0x49, 0x9f, 0x8b, 0xe7, 0xde, 0xe9, 4, 0x4c, 0x86]
print(len(ans_table))
flag = ''
for z in range(19):
for i in range(0x20, 0x80):
a = i % 0x10
a *= 0x30
b = (i%0x10) + ((i / 0x10) * 5) % 0x10 - (i%0x10)
c = z
res = a + b + c
if (res & 0xff) == ans_table[z]:
flag += chr(i)
break
print(flag)
그대로 브포돌리면 된다.
ptm{custom_asm_4_u!}
Outro
외국인 친구들과 디코도 하면서 했었는데 그 친구들도 잘 받아줘서 재밌게 했었다 :)
성적은 좋지 않지만, 문제도 좀 풀고 과정이 재밌어서 매우 만족한 대회였다.
포너블이 몇문제가 어렵게 나오고, house of husk
문제가 하나 나왔는데 언솔빈 어택으로 로컬 땃는데 리모트 못따서 아쉬었다.