Sechack
Codegate2022 본선 후기 + 문제 풀이 본문
이번 코게는 뭐.. 어차피 1, 2, 3등이 정해져 있어서 딱히 스코어나 순위에 신경쓰지 않고 재밌게 즐겼다. 점수 올리려면 삽질하면 무조건 풀리는 웹을 했어야 했지만 리버싱에 할만해보이는 vm문제가 있길래 삽질하면 충분히 풀 수 있는 웹 2 ~ 3개를 던지고 메이저 대회에서 처음으로 리버싱을 잡아봤다. 덕분에 등수는 10위권에도 못드는 엄청난 나락으로 갔지만 어차피 이번년도에는 수상 못하는 대회라서 미련은 없다. 뭐 즐겜한 대회인 만큼 수상보다는 어떤 재밌는 문제가 나올까에 더 초점을 맞추고 있었어서 전날에도 별로 신경을 안쓴 탓인지 대회장에도 1시간 늦게 가서 사진은 많이 못찍었다.
이번에는 코엑스에서 본선을 진행했는데 대충 본선장 사진이다. 밥도 Wacon이나 CCE랑은 다르게 김밥을 줬는데 내가 가장 좋아하는 김밥인 참치김밥을 줘서 맛있게 먹었는데 김밥이 남아서 더 먹었다. 솔직히 맛있는 반찬 한두개밖에 없는 어중간한 도시락보다 참치김밥이 훨씬 낫다. 대회 후반부에는 100ml였나 150ml였나 200ml였나 조그만 팩에 담긴 주스들도 들어왔는데 포도주스를 7팩이나 마셨다.
그리고 이번 코게에서 가장 의미있었던 시간이다. 바로 그 유명하신 갓해커 박세준님과 사진을 찍었다. 매우 영광스러운 순간이었다. 박세준님은 내가 그렇게 들어가고 싶어하는 회사인 티오리의 대표님이시다. 후기는 뭐 이정도고 이제 문제 얘기로 넘어가야겠다.
생각나는 문제 얘기 + 풀이
이번에는 포너블이 너무 쉽게 나와서 하나 빼고는 시작한지 3시간안에 다 푼것 같다. 간단한 스택오버랑 strncmp이용해서 1byte씩 brute force해서 canary랑 libc leak하고 rtl하는거랑 힙오버로 함수포인터만 덮으면 풀리는거랑 힙오버로 fake chunk만들어서 힙익스 하는거랑 C++ pwn이었는데 edit기능에서 대놓고 oob터져서 권한 관리하는 구조체 덮어서 admin따고 셸따는 문제정도가 기억난다. 전부다 순한맛이었다. 마지막건 go언어 포너블이길래 그냥 하기 싫어서 분석도 안했다. 포너블 풀이랑 익스는 딱히 올릴 가치가 없는것 같아서 안올리겠다.
크립토에는 RSA에서 e값만 다르게 해서 2번 암호화를 한걸 주길래 대충 rsa different e이런식으로 구글링 했는데 대충 확장 유클리드 알고리즘으로 깰 수 있다는 얘기가 나왔다. RSA에 대한 배경지식이 탄탄하지 않았기에 이해는 못했고 소스코드 복붙해서 풀었다.
https://asecuritysite.com/rsa/rsa_e2
대충 요 사이트에서 맨 아래 샘플로 나와있는 소스코드 가져다가 수정했더니 플래그 나왔다.
from Crypto.Util.number import *
from Crypto import Random
import Crypto.Util.number
from pwn import *
import libnum
import sys
def attack(c1, c2, e1, e2, N):
if libnum.gcd(e1, e2) != 1:
instructions.append ("Exponents e1 and e2 cannot be coprime")
sys.exit(0)
x,y,_=libnum.xgcd(e1,e2)
val = (pow(c1,x,N) * pow(c2,y,N)) % N
return (val) % N
e1=65537
e2=11731
n = 530608211275513140366213138576723793063425255614923916339818296370965195790170509377272816425755124935117505883803711912448089542645382900611950157918984019577954045101766666641238782097999510625875880969945471060158125437911539276388013871362986018325401113892681619320340704004127463091857351199317633994571579952934196595940153186110524883992930224361870545884119798418793773606942649578976020070310101589489301325141130049757844603712484465320142622014398855573242395860195081413402454165102037618128916290867959544566706382208694897560692739599385906736064223144970858620904311410004277627671783
ct1 = 36438785448993034686319725528189192854463151662047410787227922472649376988517578574739948014360868712767703814105348128280340128939238535870532598929770331230858903476818025917271405175287372062625882551287048889017803584186380153095109131743562028934469362853792211973654740907312518795378145540926223924479122691199553850552127169415722835585231774507859734000109872494993889601024066569604368136614240776022505481719558228444943141026367964278184575717257240100749291352017125500119659786838130694989077975539301274525538419859900000660538238458822546354653698743617030591065087263349564991665849
ct2 = 454045540371169334472351754484136780129066219703489904165202842522515664378538610447624560284169053268730975791349131234222808083488597486803727304405099404081868654631796931174509511691927858633690661209034072047272183265894764752839929020695260728240428345390829399265245232898345572532352424374555423507654005941229433225611956943133363918291490204626186412647847220114809916647068100876659371702917383788830054608420673772563553420797641488934352031754512664158636253136857155982303012426019741495093518469924228444208995681120899429608361648412150864338383128649019067200581109180307642009088751
message = attack(ct1, ct2, e1, e2, n)
success((int(message)).to_bytes(len(hex(int(message)))//2-1, "big"))
맨날 노베이스로 크립토 구글링해서 풀다보니까 흥미가 생긴다. 조만간 RSA의 자세한 원리부터 제대로 공부해봐야겠다.
misc에도 풀만한 문제가 하나 있었다. exe파일이었고 대충 방향키랑 스페이스 가지고 게임을 하는건데 화면에 나온 그림대로 방향키와 스페이스바를 차례로 누르는 게임이다.
설명을 보면 이해가 갈것이다. 화면에 → ← ← ↑ ↓ 대충 요런식으로 출력이 되면 저기에 맞게 방향키를 눌러야 하는 게임이다. 처음에는 보자마자 API hooking걸어서 풀려고 생각했고 프로그램을 뜯어봤는데 루틴이 뭔가 되게 많아서 후킹할 API를 찾기가 힘들었다. 근데 정답이 아닌 다른 키를 눌러도 아무런 처리 없이 정답인 키를 누를때까지 가만히 있는걸 보고 그냥 Windows API로 방향키랑 스페이스바 순서대로 누르는 매크로를 만들기로 했다.
#define _CRT_OBSOLETE_NO_WARNINGS
#include <Windows.h>
#include <stdbool.h>
#include <stdlib.h>
WNDCLASSA WndClass;
MSG msg;
HINSTANCE g_hInst;
POINT ptMouse;
bool automousetoogle = false;
HANDLE autoclickthread;
#define STARTBTN 1
#define STOPBTN 2
LRESULT CALLBACK WinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI autoclick(void* lpVoid)
{
while (true)
{
if (automousetoogle)
{
keybd_event(VK_LEFT, 0, 0, 0);
_sleep(10);
keybd_event(VK_RIGHT, 0, 0, 0);
_sleep(10);
keybd_event(VK_UP, 0, 0, 0);
_sleep(10);
keybd_event(VK_DOWN, 0, 0, 0);
_sleep(10);
keybd_event(VK_SPACE, 0, 0, 0);
_sleep(10); //과부화로 인한 렉 방지를 위한 sleep
}
}
}
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WndClass.lpszClassName = "mainwindow";
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc = WinProc;
WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.lpszMenuName = NULL;
RegisterClassA(&WndClass);
CreateWindowA("mainwindow", "auto mouse", WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_BORDER, 300, 200, 400, 100, NULL, NULL, g_hInst, NULL);
while (GetMessageA(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
return 0;
}
LRESULT CALLBACK WinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_HOTKEY)
{
if (wParam == 1)
{
automousetoogle = true;
}
else if (wParam == 0)
{
automousetoogle = false;
}
}
switch (Msg)
{
case WM_CLOSE:
CloseHandle(autoclickthread);
PostQuitMessage(0);
break;
case WM_CREATE:
autoclickthread = CreateThread(NULL, 0, autoclick, &automousetoogle, 0, NULL);
RegisterHotKey(hWnd, 1, 0, VK_F5);
RegisterHotKey(hWnd, 0, 0, VK_F6);
CreateWindowA("button", "시작(F5)", WS_VISIBLE | WS_CHILD, 70, 15, 100, 30, hWnd, (HMENU)STARTBTN, g_hInst, NULL);
CreateWindowA("button", "중지(F6)", WS_VISIBLE | WS_CHILD, 200, 15, 100, 30, hWnd, (HMENU)STOPBTN, g_hInst, NULL);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case STARTBTN:
automousetoogle = true;
break;
case STOPBTN:
automousetoogle = false;
break;
default:
break;
}
default:
break;
}
return DefWindowProcA(hWnd, Msg, wParam, lParam);
}
예전에 Windows API로 오토마우스 만들어서 깃허브에 올린적 있는데 거기서 마우스 이벤트를 키보드 이벤트로만 갈아치운거다. 이걸로 게임을 하면 엄청난 속도로 슉슉 지나가고 게임이 클리어 된다. 게임이 클리어 되고 나서 플래그 출력되고 system("pause")를 하고 그다음 종료가 된다. 프로그램을 그냥 실행하면 끝나도 계속 입력을 해서 pause가 스킵되고 프로그램이 꺼져서 플래그를 못보니까 디버거 위에서 system("pause")에다가 breakpoint걸고 돌리면 플래그를 볼 수 있다. 그리고 이건 들은 얘긴데 직접 손으로 게임을 해서 피지컬로 깬 사람도 있다고 한다.
VM
이 문제는 가장 기억에 남은 문제이다. 기존에 리버서였던 사람한테는 쉬운 문제겠지만 포너블과 웹만 하다가 코게같은 메이저 CTF에서 처음으로 잡아본 리버싱 문제이다. 삽질하면 풀리는 웹도 던지고 이 문제만 잡았다.
main함수를 보면 그냥 인자로 준 파일 열고 malloc을 2개 받는다. 인자로 주는 파일은 문제에서 제공해줬다. 첫번째 힙은 파일 내용을 저장하고 두번째 힙은 나중에 분석하면서 알게 되겠지만 인풋값과 테이블값을 저장한다. 결과적으론 VM함수의 리턴값이 0이 아니게 하는 인풋이 플래그이다.
__int64 VM()
{
char *v1; // [rsp+0h] [rbp-10h]
int v2; // [rsp+Ch] [rbp-4h]
dword_40BC = 4096;
v2 = 1;
while ( v2 )
{
v1 = (char *)ptr + 8 * (unsigned int)dword_40C4;
switch ( *v1 )
{
case 0:
context[(unsigned __int8)v1[1]] = *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 1:
*(_DWORD *)((char *)s + (unsigned int)(context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1))) = context[(unsigned __int8)v1[1]];
break;
case 2:
context[(unsigned __int8)v1[1]] = *(_DWORD *)((char *)s
+ (unsigned int)(context[(unsigned __int8)v1[2]]
+ *((_DWORD *)v1 + 1)));
break;
case 3:
context[(unsigned __int8)v1[1]] += *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 4:
context[(unsigned __int8)v1[1]] -= context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1);
break;
case 5:
context[(unsigned __int8)v1[1]] ^= *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 6:
context[(unsigned __int8)v1[1]] &= *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 7:
dword_40C8 = context[(unsigned __int8)v1[1]] == context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1);
break;
case 8:
if ( dword_40C8 )
dword_40C4 += context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1);
break;
case 9:
if ( !dword_40C8 )
dword_40C4 += context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1);
break;
case 10:
dword_40C4 += context[(unsigned __int8)v1[2]] + *((_DWORD *)v1 + 1);
break;
case 11:
context[(unsigned __int8)v1[1]] <<= *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 12:
context[(unsigned __int8)v1[1]] >>= *((_DWORD *)v1 + 1) + context[(unsigned __int8)v1[2]];
break;
case 13:
if ( *((_DWORD *)v1 + 1) )
{
if ( *((_DWORD *)v1 + 1) == 1 )
dword_40A4 = write(fd, (char *)s + (unsigned int)dword_40B8, (unsigned int)n);
}
else
{
dword_40A4 = read(fd, (char *)s + (unsigned int)dword_40B8, (unsigned int)n);
}
break;
case 14:
v2 = 0;
break;
default:
break;
}
++dword_40C4;
}
return (unsigned int)dword_40A4;
}
VM함수를 보면 전형적인 가상화 문제이다. 포너블에서도 비슷한 유형이 있는데 포너블 같은 경우에는 각 instruction의 기능을 이해하고 취약점이 터지는 부분을 잘 활용해서 직접 vm에서 실행시킬 instruction들을 창조해나가는 방식으로 익스를 하는데 리버싱 같은 경우에는 실행시킬 instruction들을 제공해준다. main함수에서 인자로 받는 파일이 vm에서 실행시킬 instruction들이 모여있는 파일이다. 그렇다면 결국에는 주어진 instruction들이 어떤 행위를 하는지 그 행위들은 무엇을 의미하는지 분석을 해야한다.
분석을 하기 위해선 그냥 동적분석으로 뜯어봐도 되지만 좀 더 편하고 명확하게 파악하기 위해서 행위 하나하나를 출력하는 디스어셈블러를 만들어야 한다.
from ctypes import *
vm = open("./VM", "rb").read()
v2 = 1
pc = 0
opcode = b""
box = [0 for i in range(0x100)]
s = [0 for i in range(0x2000)]
flag = False
box[7] = 0x1000
while v2:
opcode = vm[pc*8+4:pc*8+8+4]
if opcode[0] == 14:
print("exit")
break
elif opcode[0] == 0:
box[opcode[1]] = c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] = BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 1:
s[c_int32(box[opcode[2]]).value + c_int32(int.from_bytes(opcode[4:8], "little")).value] = c_int32(box[opcode[1]]).value
print("S[%d] = BOX[%d]" % (c_int32(box[opcode[2]]).value + c_int32(int.from_bytes(opcode[4:8], "little")).value, opcode[1]))
elif opcode[0] == 2:
print("BOX[%d] = S[%d]" % (opcode[1], c_int32(box[opcode[2]]).value + c_int32(int.from_bytes(opcode[4:8], "little")).value))
box[opcode[1]] = s[c_int32(box[opcode[2]]).value + c_int32(int.from_bytes(opcode[4:8], "little")).value]
elif opcode[0] == 3:
box[opcode[1]] += c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] += BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 4:
box[opcode[1]] -= c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] -= BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 5:
box[opcode[1]] ^= c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] ^= BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 6:
box[opcode[1]] &= c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] &= BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 7:
flag = (box[opcode[1]] == c_int32(box[opcode[2]]).value + c_int32(int.from_bytes(opcode[4:8], "little")).value)
print("flag = BOX[%d] == BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 8:
if flag:
pc += c_int32(c_int32(box[opcode[2]]).value + int.from_bytes(opcode[4:8], "little")).value
elif opcode[0] == 9:
if not flag:
pc += c_int32(c_int32(box[opcode[2]]).value + int.from_bytes(opcode[4:8], "little")).value
elif opcode[0] == 10:
pc += c_int32(c_int32(box[opcode[2]]).value + int.from_bytes(opcode[4:8], "little")).value
elif opcode[0] == 11:
box[opcode[1]] <<= c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] <<= BOX[%d] + %x" % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 12:
box[opcode[1]] >>= c_int32(int.from_bytes(opcode[4:8], "little")).value + c_int32(box[opcode[2]]).value
print("BOX[%d] >>= BOX[%d] + %x " % (opcode[1], opcode[2], c_int32(int.from_bytes(opcode[4:8], "little")).value))
elif opcode[0] == 13:
if c_int32(int.from_bytes(opcode[4:8], "little")).value == 1:
for i in range((box[4]//4)+1):
print("write %x" % s[box[6]+i*4])
else:
print("read S[%d]" % (box[6]))
pc += 1
이건 대회 당시에 짠 디스어셈블러이다. 진짜 무지성으로 분기문 반복문 이런거 신경 안쓰고 무슨 행위를 하는지만 하나하나 출력했다. 어차피 반복문도 여러번 도는게 다 출력이 되니까 분기문을 처리 안해도 되지 않을까 하는 안일한 생각을 했었는데 이게 매우 잘못된 생각이었다.
BOX[1] ^= BOX[1] + 0
BOX[3] = BOX[0] + f
BOX[2] = BOX[7] + -54
BOX[5] = BOX[2] + 0
BOX[8] = BOX[0] + 55504e49
S[3968] = BOX[8]
BOX[8] = BOX[0] + 203a54
S[3972] = BOX[8]
S[4012] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4016] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4020] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4024] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4028] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4032] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4036] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4040] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4044] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4048] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4052] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4056] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4060] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4064] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
S[4068] = BOX[1]
BOX[5] += BOX[0] + 4
BOX[3] -= BOX[0] + 1
flag = BOX[3] == BOX[0] + 0
BOX[1] = BOX[7] + -80
BOX[4] = BOX[0] + 7
BOX[6] = BOX[1] + 0
BOX[5] = BOX[0] + 1
write 55504e49
write 203a54
BOX[4] = BOX[0] + 3c
BOX[6] = BOX[2] + 0
BOX[5] = BOX[0] + 0
read S[4012]
BOX[6] = BOX[7] + -54
BOX[6] += BOX[1] + 0
BOX[6] -= BOX[0] + 1
S[7979] = BOX[0]
BOX[4] ^= BOX[4] + 0
BOX[8] = BOX[0] + 42ca32da
S[3948] = BOX[8]
BOX[8] = BOX[0] + 7f09207f
S[3952] = BOX[8]
BOX[8] = BOX[0] + 7b291908
S[3956] = BOX[8]
BOX[8] = BOX[0] + -1fef28b
S[3960] = BOX[8]
BOX[8] = BOX[0] + -6d5114e1
S[3964] = BOX[8]
BOX[8] = BOX[0] + -6aa8a2a5
S[3968] = BOX[8]
BOX[8] = BOX[0] + 4d4fe62
S[3972] = BOX[8]
BOX[8] = BOX[0] + -1fe5784
S[3976] = BOX[8]
BOX[8] = BOX[0] + -87ed21f
S[3980] = BOX[8]
BOX[8] = BOX[0] + -427548fb
S[3984] = BOX[8]
BOX[8] = BOX[0] + 42cadd6d
S[3988] = BOX[8]
BOX[8] = BOX[0] + 12fb883e
S[3992] = BOX[8]
BOX[8] = BOX[0] + 343c8ebb
S[3996] = BOX[8]
BOX[8] = BOX[0] + 1d0fa7c5
S[4000] = BOX[8]
BOX[8] = BOX[0] + 1d0f1d0f
S[4004] = BOX[8]
BOX[6] = BOX[7] + -94
BOX[5] = BOX[2] + 0
BOX[5] += BOX[4] + 0
BOX[3] ^= BOX[3] + 0
BOX[1] = BOX[0] + ffff
S[3940] = BOX[5]
BOX[8] = BOX[0] + 8
S[3944] = BOX[8]
BOX[5] = S[3940]
BOX[8] = BOX[5] + 0
BOX[8] += BOX[3] + 0
BOX[5] = S[4012]
BOX[5] &= BOX[0] + ff
BOX[5] <<= BOX[0] + 8
BOX[1] ^= BOX[5] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[3] += BOX[0] + 1
flag = BOX[3] == BOX[0] + 2
BOX[8] = BOX[0] + 8
S[3944] = BOX[8]
BOX[5] = S[3940]
BOX[8] = BOX[5] + 0
BOX[8] += BOX[3] + 0
BOX[5] = S[4013]
BOX[5] &= BOX[0] + ff
BOX[5] <<= BOX[0] + 8
BOX[1] ^= BOX[5] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[8] = BOX[1] + 0
BOX[1] += BOX[1] + 0
BOX[1] &= BOX[0] + ffff
BOX[8] >>= BOX[0] + f
flag = BOX[8] == BOX[0] + 1
BOX[1] ^= BOX[0] + 1021
BOX[5] = S[3944]
BOX[5] -= BOX[0] + 1
S[3944] = BOX[5]
flag = BOX[5] == BOX[0] + 0
BOX[3] += BOX[0] + 1
flag = BOX[3] == BOX[0] + 2
BOX[3] = S[3948]
BOX[3] &= BOX[0] + ffff
flag = BOX[3] == BOX[1] + 0
BOX[1] ^= BOX[1] + 0
exit
결과를 보면 무슨 행위를 하는지는 잘 나오긴 하는데 진짜 무지성으로 출력되어 있는걸 알 수 있다. 반복도는것도 그냥 무지성으로 전부 출력되어있다. 그래서 루틴은 대충 알 수 있었지만 어디서 테이블과 비교롤 하고 정확히 어떤식으로 연산을 수행하는지 알기가 힘들어서 대회 도중에 못풀었다.
끝나고 나서 태양님과 승환님에게 jmp, jne같은 instruction도 실제 어셈블리어처럼 라벨링 해서 처리하고 저런 형태로 출력하지 말고 add, sub, mov같은 어셈블리 형태로 출력하면 더 알아먹기 쉽다는 조언을 받았다. 근데 확실히 분기문 라벨링의 필요성은 느꼈지만 어셈블리 형태로 출력하던 연산 기호로 출력하던 그건 본인이 알아먹기 쉬운걸로 하면 될듯 하다. 승환님이 본인이 짰던 디스어셈블러를 보여줬는데 진짜 깔끔하게 정리되어서 출력이 되었다. 코드도 훨씬 깔끔하다. 그래서 그거 보고 참고해서 나도 다시 짜봤다.
from pwn import *
from ctypes import c_int32
vm = open("./VM", "rb").read()
pc = 0
labels = []
cmds = []
while True:
try:
opcode = vm[pc*8+4]
arg1 = vm[pc*8+5]
arg2 = vm[pc*8+6]
arg3 = u32(vm[pc*8+8:pc*8+12])
cmds.append([opcode, arg1, arg2, arg3])
pc += 1
except:
break
for i, (opcode, arg1, arg2, arg3) in enumerate(cmds):
if 8 <= opcode <= 10:
labels.append(i+c_int32(arg3).value + 1)
for i, (opcode, arg1, arg2, arg3) in enumerate(cmds):
if i in labels:
print(f"lable_{i}")
print("\t", end="")
if opcode == 0:
print(f"mov ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 1:
print(f"mov memory[ctx[{arg2}] + {hex(c_int32(arg3).value)}], ctx[{arg1}]")
elif opcode == 2:
print(f"mov ctx[{arg1}], memory[ctx[{arg2}] + {hex(c_int32(arg3).value)}]")
elif opcode == 3:
print(f"add ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 4:
print(f"sub ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 5:
print(f"xor ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 6:
print(f"and ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 7:
print(f"cmp ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 8:
print(f"je label_{i + c_int32(arg3).value}")
elif opcode == 9:
print(f"jne label_{i + c_int32(arg3).value}")
elif opcode == 10:
print(f"jmp label_{i + c_int32(arg3).value}")
elif opcode == 11:
print(f"shl ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 12:
print(f"shr ctx[{arg1}], ctx[{arg2}] + {hex(c_int32(arg3).value)}")
elif opcode == 13:
if arg3 == 1:
print("write(1, ctx[14] + ctx[6], ctx[4])")
else:
print("read(1, ctx[14] + ctx[6], ctx[4])")
elif opcode == 14:
print("exit")
break
이런식으로 라벨링 하고 가독성도 좋게 해주니까
xor ctx[1], ctx[1] + 0x0
mov ctx[3], ctx[0] + 0xf
mov ctx[2], ctx[7] + -0x54
mov ctx[5], ctx[2] + 0x0
mov ctx[8], ctx[0] + 0x55504e49
mov memory[ctx[7] + -0x80], ctx[8]
mov ctx[8], ctx[0] + 0x203a54
mov memory[ctx[7] + -0x7c], ctx[8]
lable_8
mov memory[ctx[5] + 0x0], ctx[1]
add ctx[5], ctx[0] + 0x4
sub ctx[3], ctx[0] + 0x1
cmp ctx[3], ctx[0] + 0x0
jne label_7
mov ctx[1], ctx[7] + -0x80
mov ctx[4], ctx[0] + 0x7
mov ctx[6], ctx[1] + 0x0
mov ctx[5], ctx[0] + 0x1
write(1, ctx[14] + ctx[6], ctx[4])
mov ctx[4], ctx[0] + 0x3c
mov ctx[6], ctx[2] + 0x0
mov ctx[5], ctx[0] + 0x0
read(1, ctx[14] + ctx[6], ctx[4])
mov ctx[6], ctx[7] + -0x54
add ctx[6], ctx[1] + 0x0
sub ctx[6], ctx[0] + 0x1
mov memory[ctx[6] + 0x0], ctx[0]
xor ctx[4], ctx[4] + 0x0
mov ctx[8], ctx[0] + 0x42ca32da
mov memory[ctx[7] + -0x94], ctx[8]
mov ctx[8], ctx[0] + 0x7f09207f
mov memory[ctx[7] + -0x90], ctx[8]
mov ctx[8], ctx[0] + 0x7b291908
mov memory[ctx[7] + -0x8c], ctx[8]
mov ctx[8], ctx[0] + -0x1fef28b
mov memory[ctx[7] + -0x88], ctx[8]
mov ctx[8], ctx[0] + -0x6d5114e1
mov memory[ctx[7] + -0x84], ctx[8]
mov ctx[8], ctx[0] + -0x6aa8a2a5
mov memory[ctx[7] + -0x80], ctx[8]
mov ctx[8], ctx[0] + 0x4d4fe62
mov memory[ctx[7] + -0x7c], ctx[8]
mov ctx[8], ctx[0] + -0x1fe5784
mov memory[ctx[7] + -0x78], ctx[8]
mov ctx[8], ctx[0] + -0x87ed21f
mov memory[ctx[7] + -0x74], ctx[8]
mov ctx[8], ctx[0] + -0x427548fb
mov memory[ctx[7] + -0x70], ctx[8]
mov ctx[8], ctx[0] + 0x42cadd6d
mov memory[ctx[7] + -0x6c], ctx[8]
mov ctx[8], ctx[0] + 0x12fb883e
mov memory[ctx[7] + -0x68], ctx[8]
mov ctx[8], ctx[0] + 0x343c8ebb
mov memory[ctx[7] + -0x64], ctx[8]
mov ctx[8], ctx[0] + 0x1d0fa7c5
mov memory[ctx[7] + -0x60], ctx[8]
mov ctx[8], ctx[0] + 0x1d0f1d0f
mov memory[ctx[7] + -0x5c], ctx[8]
mov ctx[6], ctx[7] + -0x94
lable_58
mov ctx[5], ctx[2] + 0x0
add ctx[5], ctx[4] + 0x0
xor ctx[3], ctx[3] + 0x0
mov ctx[1], ctx[0] + 0xffff
mov memory[ctx[7] + -0x9c], ctx[5]
lable_63
mov ctx[8], ctx[0] + 0x8
mov memory[ctx[7] + -0x98], ctx[8]
mov ctx[5], memory[ctx[7] + -0x9c]
mov ctx[8], ctx[5] + 0x0
add ctx[8], ctx[3] + 0x0
mov ctx[5], memory[ctx[8] + 0x0]
and ctx[5], ctx[0] + 0xff
shl ctx[5], ctx[0] + 0x8
xor ctx[1], ctx[5] + 0x0
lable_72
mov ctx[8], ctx[1] + 0x0
add ctx[1], ctx[1] + 0x0
and ctx[1], ctx[0] + 0xffff
shr ctx[8], ctx[0] + 0xf
cmp ctx[8], ctx[0] + 0x1
jne label_78
xor ctx[1], ctx[0] + 0x1021
lable_79
mov ctx[5], memory[ctx[7] + -0x98]
sub ctx[5], ctx[0] + 0x1
mov memory[ctx[7] + -0x98], ctx[5]
cmp ctx[5], ctx[0] + 0x0
jne label_71
add ctx[3], ctx[0] + 0x1
cmp ctx[3], ctx[0] + 0x2
jne label_62
mov ctx[3], memory[ctx[6] + 0x0]
and ctx[3], ctx[0] + 0xffff
cmp ctx[3], ctx[1] + 0x0
jne label_96
add ctx[4], ctx[0] + 0x2
add ctx[6], ctx[0] + 0x2
cmp ctx[4], ctx[0] + 0x3c
jne label_57
mov ctx[1], ctx[0] + 0x1
jmp label_97
lable_97
xor ctx[1], ctx[1] + 0x0
lable_98
exit
진짜 직관적이고 깔끔한 출력물이 완성된다. 이제 이것만 핸드레이 해주면 되는데 위에는 입력받고 테이블 만드는 루틴이니까 분석할 필요 없고 실질적으로 분석해야 하는건 lable_58부터다.
#a = ctx[2]
#b = ctx[4]
#c = ctx[6]
#x = ctx[1]
#y = ctx[8]
#z = ctx[3]
#i = ctx[5]
#s = memory[ctx[7] + -0x98]
#s1 = memory[ctx[7] + -0x9c]
:58
i = a
i += b
z = 0
x = 0xffff
s1 = i
:63
s = 8
y = s1
y += z
i = input[y] & 0xff
x ^= (i << 8)
:72
y = x
x *= 2
y >> 0xf
if(y == 1):
x ^= 0x1021
s -= 1
if(s != 0) jmp :72
z += 1
if(z != 2) jmp :63
if(x == table[c]) success
b += 2
c += 2
if(b == 0x3c) end
핸드레이 해보면 루틴이 이렇다. 하나하나 분석하면서 루틴을 파악하다보면 위에서 정의해둔 s와 s1은 반복문 카운트 변수를 저장하기 위한 공간이라는걸 알 수 있다.
table = [0x32DA, 0x42CA, 0x207F, 0x7F09, 0x1908, 0x7B29, 0x0D75, 0xFE01, 0xEB1F, 0x92AE, 0x5D5B, 0x9557, 0xFE62, 0x04D4, 0xA87C, 0xFE01, 0x2DE1, 0xF781, 0xB705, 0xBD8A, 0xDD6D, 0x42CA, 0x883E, 0x12FB, 0x8EBB, 0x343C, 0xA7C5, 0x1D0F, 0x1D0F, 0x1D0F]
for i in range(0x3c//2):
x = 0xffff
for j in range(2):
i = input[j] & 0xff
x ^= (i << 8)
for k in range(8):
y = x
x *= 2
x &= 0xffff
y >>= 0xf
if(y == 1):
x ^= 0x1021
if x == table[i]:
print("success")
else:
print("fail")
정리해서 정연산을 짜보면 이렇다. 2byte씩 연산을 해주는데 그럼 한번에 미지수가 2개가 생긴다. 2byte면 brute force날려도 2글자 구할때마다 $ 255^2 \times 16 $ 번의 반복문을 돈다. 그렇게 큰 수치는 아니니까 2byte brute force로 해를 구할 수 있다.
table = [0x32DA, 0x42CA, 0x207F, 0x7F09, 0x1908, 0x7B29, 0x0D75, 0xFE01, 0xEB1F, 0x92AE, 0x5D5B, 0x9557, 0xFE62, 0x04D4, 0xA87C, 0xFE01, 0x2DE1, 0xF781, 0xB705, 0xBD8A, 0xDD6D, 0x42CA, 0x883E, 0x12FB, 0x8EBB, 0x343C, 0xA7C5, 0x1D0F, 0x1D0F, 0x1D0F]
flag = ""
for i in range(0x3c//2):
find = False
for j in range(0x20, 0x7f):
if find:
break
for k in range(0x20, 0x7f):
if find:
break
dec = [j, k]
x = 0xffff
for l in range(2):
v = dec[l] & 0xff
x ^= (v << 8)
for m in range(8):
y = x
x *= 2
x &= 0xffff
y >>= 0xf
if(y == 1):
x ^= 0x1021
if(x == table[i]):
flag += chr(j)+chr(k)
find = True
print(flag)
정확히 구해진다. 플래그 보니까 CRC루틴을 그대로 가져온것 같다.
flag : codegate2022{The_VM_and_CRC_is_it_really_an_irreversible_algorithm?}
'CTF' 카테고리의 다른 글
2022 Layer7 CTF write up + 후기 (0) | 2022.12.19 |
---|---|
TUCTF 2022 - Write up (0) | 2022.12.04 |
CCE 2022 학생부 준우승 후기 (3) | 2022.10.31 |
Dice CTF 2022 @ HOPE Write up (0) | 2022.07.25 |
WACON CTF 2022 본선 Pwnable Write up + 후기 (0) | 2022.07.18 |