この大会は2021/9/18 13:00(JST)~2021/9/19 13:00(JST)に開催されました。
今回もチームで参戦。結果は100点で81チーム中52位でした。
自分で解けた問題をWriteupとして書いておきます。
Reversing-I 100
ファイル名などからPythonの実行ファイル化したものと推測できる。Pythonコードにデコンパイルする。
$ python pyinstxtractor.py PyPuzzle.exe [*] Processing PyPuzzle.exe [*] Pyinstaller version: 2.1+ [*] Python version: 37 [*] Length of package: 8032677 bytes [*] Found 76 files in CArchive [*] Beginning extraction...please standby [!] Warning: The script is running in a different python version than the one used to build the executable Run this script in Python37 to prevent extraction errors(if any) during unmarshalling [!] Unmarshalling FAILED. Cannot extract PYZ-00.pyz. Extracting remaining files. [*] Successfully extracted pyinstaller archive: PyPuzzle.exe You can now use a python decompiler on the pyc files within the extracted directory
PyPuzzle.exe_extracted\PyPuzzleを編集し、ヘッダを付けpycにする。ヘッダは以下を16バイト付けた。
3e 0d 0d 0a 00 00 00 00 00 00 00 00 00 00 00 00
$ uncompyle6 PyPuzzle.pyc # uncompyle6 version 3.7.4 # Python bytecode 3.7 (3390) # Decompiled from: Python 3.6.9 (default, Jan 26 2021, 15:33:00) # [GCC 8.4.0] # Embedded file name: PyPuzzle.py import pygame, sys, random from pygame.locals import * BOARDWIDTH = 4 BOARDHEIGHT = 4 TILESIZE = 100 WINDOWWIDTH = 500 WINDOWHEIGHT = 550 FPS = 30 BLANK = None BLACK = (0, 0, 0) WHITE = (248, 240, 239) BRIGHTBLUE = (0, 50, 255) DARKTURQUOISE = (3, 54, 73) BLUE = (214, 25, 32) GREEN = (0, 128, 0) RED = (255, 0, 0) BGCOLOR = (248, 240, 239) TILECOLOR = BLUE TEXTCOLOR = WHITE BORDERCOLOR = RED BASICFONTSIZE = 20 TEXT = (214, 25, 32) BUTTONCOLOR = (214, 25, 32) BUTTONTEXTCOLOR = (214, 25, 32) MESSAGECOLOR = (214, 25, 32) XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 4) YMARGIN = int((WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2) UP = 'up' DOWN = 'down' LEFT = 'left' RIGHT = 'right' def main(): global BASICFONT global DISPLAYSURF global FPSCLOCK global NEW_RECT global NEW_SURF global RESET_RECT global RESET_SURF global SOLVE_RECT global SOLVE_SURF pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Slide Puzzle') BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE) RESET_SURF, RESET_RECT = makeText('Reset', TEXT, BGCOLOR, WINDOWWIDTH - 375, WINDOWHEIGHT - 40) NEW_SURF, NEW_RECT = makeText('New Game', TEXT, BGCOLOR, WINDOWWIDTH - 250, WINDOWHEIGHT - 40) SOLVE_SURF, SOLVE_RECT = makeText('Solve', WHITE, BGCOLOR, WINDOWWIDTH - 80, WINDOWHEIGHT - 40) mainBoard, solutionSeq = generateNewPuzzle(80) SOLVEDBOARD = getStartingBoard() allMoves = [] while True: slideTo = None msg = 'Click tile or press arrow keys to slide.' if mainBoard == SOLVEDBOARD: msg = 'Solved! But Wait you need to decrypt the flag *_^' flag = '3blKlvIKqQoG0D6B4XDcZxQjbLbg4KVJQzbm8b'★ drawBoard(mainBoard, msg) checkForQuit() for event in pygame.event.get(): if event.type == MOUSEBUTTONUP: spotx, spoty = getSpotClicked(mainBoard, event.pos[0], event.pos[1]) if ( spotx, spoty) == (None, None): if RESET_RECT.collidepoint(event.pos): resetAnimation(mainBoard, allMoves) allMoves = [] else: if NEW_RECT.collidepoint(event.pos): mainBoard, solutionSeq = generateNewPuzzle(80) allMoves = [] else: if SOLVE_RECT.collidepoint(event.pos): resetAnimation(mainBoard, solutionSeq + allMoves) allMoves = [] else: blankx, blanky = getBlankPosition(mainBoard) if spotx == blankx + 1 and spoty == blanky: slideTo = LEFT else: if spotx == blankx - 1 and spoty == blanky: slideTo = RIGHT else: if spotx == blankx and spoty == blanky + 1: slideTo = UP else: if spotx == blankx: if spoty == blanky - 1: slideTo = DOWN elif event.type == KEYUP: if event.key in (K_LEFT, K_a): if isValidMove(mainBoard, LEFT): slideTo = LEFT else: if event.key in (K_RIGHT, K_d): if isValidMove(mainBoard, RIGHT): slideTo = RIGHT if event.key in (K_UP, K_w): if isValidMove(mainBoard, UP): slideTo = UP if event.key in (K_DOWN, K_s): if isValidMove(mainBoard, DOWN): slideTo = DOWN if slideTo: slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) makeMove(mainBoard, slideTo) allMoves.append(slideTo) pygame.display.update() FPSCLOCK.tick(FPS) def terminate(): pygame.quit() sys.exit() def checkForQuit(): for event in pygame.event.get(QUIT): terminate() for event in pygame.event.get(KEYUP): if event.key == K_ESCAPE: terminate() pygame.event.post(event) def getStartingBoard(): counter = 1 board = [] for x in range(BOARDWIDTH): column = [] for y in range(BOARDHEIGHT): column.append(counter) counter += BOARDWIDTH board.append(column) counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1 board[(BOARDWIDTH - 1)][BOARDHEIGHT - 1] = BLANK return board def getBlankPosition(board): for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if board[x][y] == BLANK: return ( x, y) def makeMove(board, move): blankx, blanky = getBlankPosition(board) if move == UP: board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][(blanky + 1)], board[blankx][blanky] else: if move == DOWN: board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][(blanky - 1)], board[blankx][blanky] else: if move == LEFT: board[blankx][blanky], board[(blankx + 1)][blanky] = board[(blankx + 1)][blanky], board[blankx][blanky] else: if move == RIGHT: board[blankx][blanky], board[(blankx - 1)][blanky] = board[(blankx - 1)][blanky], board[blankx][blanky] def isValidMove(board, move): blankx, blanky = getBlankPosition(board) return move == UP and blanky != len(board[0]) - 1 or move == DOWN and blanky != 0 or move == LEFT and blankx != len(board) - 1 or move == RIGHT and blankx != 0 def getRandomMove(board, lastMove=None): validMoves = [ UP, DOWN, LEFT, RIGHT] if not (lastMove == UP or isValidMove(board, DOWN)): validMoves.remove(DOWN) if not (lastMove == DOWN or isValidMove(board, UP)): validMoves.remove(UP) if not (lastMove == LEFT or isValidMove(board, RIGHT)): validMoves.remove(RIGHT) if not (lastMove == RIGHT or isValidMove(board, LEFT)): validMoves.remove(LEFT) return random.choice(validMoves) def getLeftTopOfTile(tileX, tileY): left = XMARGIN + tileX * TILESIZE + (tileX - 1) top = YMARGIN + tileY * TILESIZE + (tileY - 1) return (left, top) def getSpotClicked(board, x, y): for tileX in range(len(board)): for tileY in range(len(board[0])): left, top = getLeftTopOfTile(tileX, tileY) tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE) if tileRect.collidepoint(x, y): return ( tileX, tileY) return (None, None) def drawTile(tilex, tiley, number, adjx=0, adjy=0): left, top = getLeftTopOfTile(tilex, tiley) pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE)) textSurf = BASICFONT.render(str(number), True, TEXTCOLOR) textRect = textSurf.get_rect() textRect.center = (left + int(TILESIZE / 2) + adjx, top + int(TILESIZE / 2) + adjy) DISPLAYSURF.blit(textSurf, textRect) def makeText(text, color, bgcolor, top, left): textSurf = BASICFONT.render(text, True, color, bgcolor) textRect = textSurf.get_rect() textRect.topleft = (top, left) return (textSurf, textRect) def drawBoard(board, message): DISPLAYSURF.fill(BGCOLOR) if message: textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5) DISPLAYSURF.blit(textSurf, textRect) for tilex in range(len(board)): for tiley in range(len(board[0])): if board[tilex][tiley]: drawTile(tilex, tiley, board[tilex][tiley]) left, top = getLeftTopOfTile(0, 0) width = BOARDWIDTH * TILESIZE height = BOARDHEIGHT * TILESIZE pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (left - 5, top - 5, width + 11, height + 11), 4) DISPLAYSURF.blit(RESET_SURF, RESET_RECT) DISPLAYSURF.blit(NEW_SURF, NEW_RECT) DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT) def slideAnimation(board, direction, message, animationSpeed): blankx, blanky = getBlankPosition(board) if direction == UP: movex = blankx movey = blanky + 1 else: if direction == DOWN: movex = blankx movey = blanky - 1 else: if direction == LEFT: movex = blankx + 1 movey = blanky else: if direction == RIGHT: movex = blankx - 1 movey = blanky drawBoard(board, message) baseSurf = DISPLAYSURF.copy() moveLeft, moveTop = getLeftTopOfTile(movex, movey) pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE)) for i in range(0, TILESIZE, animationSpeed): checkForQuit() DISPLAYSURF.blit(baseSurf, (0, 0)) if direction == UP: drawTile(movex, movey, board[movex][movey], 0, -i) if direction == DOWN: drawTile(movex, movey, board[movex][movey], 0, i) if direction == LEFT: drawTile(movex, movey, board[movex][movey], -i, 0) if direction == RIGHT: drawTile(movex, movey, board[movex][movey], i, 0) pygame.display.update() FPSCLOCK.tick(FPS) def generateNewPuzzle(numSlides): sequence = [] board = getStartingBoard() drawBoard(board, '') pygame.display.update() pygame.time.wait(500) lastMove = None for i in range(numSlides): move = getRandomMove(board, lastMove) slideAnimation(board, move, 'Generating new puzzle...', animationSpeed=(int(TILESIZE / 3))) makeMove(board, move) sequence.append(move) lastMove = move return ( board, sequence) def resetAnimation(board, allMoves): revAllMoves = allMoves[:] revAllMoves.reverse() for move in revAllMoves: if move == UP: oppositeMove = DOWN else: if move == DOWN: oppositeMove = UP else: if move == RIGHT: oppositeMove = LEFT else: if move == LEFT: oppositeMove = RIGHT slideAnimation(board, oppositeMove, '', animationSpeed=(int(TILESIZE / 2))) makeMove(board, oppositeMove) if __name__ == '__main__': main() # okay decompiling PyPuzzle.pyc
flagの値'3blKlvIKqQoG0D6B4XDcZxQjbLbg4KVJQzbm8b'を復号する必要がある。
base62と推測して、https://www.better-converter.com/Encoders-Decoders/Base62-Decodeで復号する。
GZPGS{EriRe$!at_Vf_@_Chmmyr}
さらにシーザー暗号と推測して、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。
Rotation 13: TMCTF{RevEr$!ng_Is_@_Puzzle}
TMCTF{RevEr$!ng_Is_@_Puzzle}