OutDoorFrog의 리버싱 이야기

게싱 능력 상승 위해 easy_serial을 풀어보자. 본문

공부/CTF 문제 풀이

게싱 능력 상승 위해 easy_serial을 풀어보자.

OutDoorFrog 2019. 1. 12. 17:13

오늘은 게싱 능력 상승을 위해 Codegate 2018 예선에 출제되었던 easy_serial 문제를 풀어볼 것입니다.


그럼 문제를 풀면서 행복 회로를 활활 태워보러 갑시다!


행복 회로 풀 ㄱ동에 대한 이미지 검색결과





실행 환경 결정


Detect It Easy를 이용해서 한 번 확인해봅시다.




Elf64인 것을 확인할 수 있습니다. 돌려봅시다.



반응이 영 좋지 못하군요. IDA로 돌려봅시다.



hs_main이라 main에서 또 main 호출하는 패턴이면 c 말고 다른 언어일 확률이 높습니다.



shutdownHaskellAndExit... Haskell을 한 번 구글에 검색해봅시다.



네 함수형 프로그래밍 언어라는 점과 컴파일 과정을 나타내주는 사진을 구할 수 있었습니다.



복잡하긴 합니다만.. 요약하면 하스켈이라는 언어에서 C로 탈바꿈해서 나온거네요.


바이너리 내에 하드코딩 되어 있는 문자열을 이용해서 Ida로 계속해보았지만 분석 속도가 

상당히 느려서 생각을 해본 결과 디컴파일러가 있을지도 모른다는 희망을 가지게 되었습니다.


행복회로에 대한 이미지 검색결과


하스켈 디컴파일러를 구해봅시다!




하스켈 디컴파일러 구하기


어느 굉장히 박식하신 분께서 디컴파일러를 올려두셨기에 제 행복회로가 풀가동되었습니다.

(문제를 쉽게 풀 수도 있겠구나라는 생각에 말이죠)




python3을 이용하니까 Windows에서 설치하고 굴려봅시다.


이 모듈은 Capstone Engine을 기반으로 굴러갑니다.



그러니 pip를 이용해 Capstone Engine을 설치합시다.


pip install Capstone 



SetupTool도 있군요. 돌려줍시다.


python3 setup.py install 



인제 작동시켜주시면 정상적으로 작동이 됩니다.


python3 runner.py [파일이 있는 경로]




커맨드 프롬포트에 출력하는 것이 불편하시다면 파일에 출력해서 보관할 수도 있습니다.


python3 runner.py [파일이 있는 경로] > 이름.txt




디컴파일


이쁘게 디컴파일이 되었나 한 번 살펴봅시다.





가독성 상승을 위한 처리 과정



일단 아랫부분 부터 살펴보시면 특정 변수에 값이 입력되어져 있는 것을 확인 할 수 있습니다.


s1b4_info = unpackCString# "abcdefghijklmnopqrstuvwxyz"

loc_7172600 = I# 9

s1bb_info = !! s1b5_info loc_7172488

loc_7172488 = I# 2

s1b5_info = splitOn $fEqChar (unpackCString# "#") s1dZ_info_arg_0

loc_7172584 = I# 8

loc_7172504 = I# 3

s1b2_info = unpackCString# "1234567890"

loc_7172568 = I# 7

loc_7172552 = I# 6

s1b3_info = unpackCString# "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

loc_7172536 = I# 5

loc_7172520 = I# 4

loc_7172472 = I# 1

loc_7172456 = I# 0

loc_7168872 = C# 48

s1b9_info = !! s1b5_info loc_7172472

s1b7_info = !! s1b5_info loc_7172456


숫자가 조금 어지럽게 느껴져서 오름차순으로 정렬해줬습니다.


정수형 숫자들과 오름차순으로 정렬된 알파벳들.. 한 번 더 깔끔하게 처리해봅시다.


loc_7172456 = I# 0

loc_7172472 = I# 1

loc_7172488 = I# 2

loc_7172504 = I# 3

loc_7172520 = I# 4

loc_7172536 = I# 5

loc_7172552 = I# 6

loc_7172568 = I# 7

loc_7172584 = I# 8

loc_7172600 = I# 9

loc_7168872 = C# 48


s1b2_info = unpackCString# "1234567890"

s1b3_info = unpackCString# "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

s1b4_info = unpackCString# "abcdefghijklmnopqrstuvwxyz"

s1b5_info = splitOn $fEqChar (unpackCString# "#") s1dZ_info_arg_0


s1b7_info = !! s1b5_info loc_7172456

s1b9_info = !! s1b5_info loc_7172472


조금 직관적이도록 수정해봤습니다.


문자열 몇 개를 빼주고 변수 값을 참조해서 코드를 치환했더니 훨씬 직관적이네요.


loc_7172456 =  0

loc_7172472 =  1

loc_7172488 =  2

loc_7172504 =  3

loc_7172520 =  4

loc_7172536 =  5

loc_7172552 =  6

loc_7172568 =  7

loc_7172584 =  8

loc_7172600 =  9

loc_7168872 =  48


s1b2_info = "1234567890"

s1b3_info = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

s1b4_info = "abcdefghijklmnopqrstuvwxyz"

s1b5_info = splitOn $fEqChar ("#") s1dZ_info_arg_0


s1b7_info = !! s1b5_info 0

s1b9_info = !! s1b5_info 1

s1bb_info = !! s1b5_info 2




앞 부분을 한 번 살펴보겠습니다. 


Main_main_closure = >> $fMonadIO

    (putStrLn (unpackCString# "Input Serial Key >>> "))

    (>>= $fMonadIO

        getLine

        (\s1dZ_info_arg_0 ->

            >> $fMonadIO

                (putStrLn (++ (unpackCString# "your serial key >>> ") (++ s1b7_info (++ (unpackCString# "_") (++ s1b9_info (++ (unpackCString# "_") s1bb_info))))))

                (case && (== $fEqInt (ord (!! s1b7_info loc_7172456)) (I# 70)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172472)) (I# 108)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172488)) (I# 97)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172504)) (I# 103)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172520)) (I# 123)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172536)) (I# 83)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172552)) (I# 48)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172568)) (I# 109)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172584)) (I# 101)) (&& (== $fEqInt (ord (!! s1b7_info loc_7172600)) (I# 48)) (&& (== $fEqInt (ord (!! s1b7_info (I# 10))) (I# 102)) (&& (== $fEqInt (ord (!! s1b7_info (I# 11))) (I# 85)) (== $fEqInt (ord (!! s1b7_info (I# 12))) (I# 53))))))))))))) of

                    <tag 1> -> putStrLn (unpackCString# ":p"),

                    c1ni_info_case_tag_DEFAULT_arg_0@_DEFAULT -> case == ($fEq[] $fEqChar) (reverse s1b9_info) (: (C# 103) (: (C# 110) (: (C# 105) (: (C# 107) (: loc_7168872 (: loc_7168872 (: (C# 76) (: (C# 51) (: (C# 114) (: (C# 52) [])))))))))) of

                        False -> putStrLn (unpackCString# ":p"),

                        True -> case && (== $fEqChar (!! s1bb_info loc_7172456) (!! s1b3_info loc_7172456)) (&& (== $fEqChar (!! s1bb_info loc_7172472) (!! s1b4_info (I# 19))) (&& (== $fEqChar (!! s1bb_info loc_7172488) (!! s1b3_info (I# 19))) (&& (== $fEqChar (!! s1bb_info loc_7172504) (!! s1b4_info loc_7172568)) (&& (== $fEqChar (!! s1bb_info loc_7172520) (!! s1b2_info loc_7172488)) (&& (== $fEqChar (!! s1bb_info loc_7172536) (!! s1b3_info (I# 18))) (&& (== $fEqChar (!! s1bb_info loc_7172552) (!! s1b4_info (I# 19))) (&& (== $fEqChar (!! s1bb_info loc_7172568) (!! s1b2_info loc_7172504)) (&& (== $fEqChar (!! s1bb_info loc_7172584) (!! s1b4_info (I# 17))) (== $fEqChar (!! s1bb_info loc_7172600) (!! s1b4_info (I# 18))))))))))) of

                            <tag 1> -> putStrLn (unpackCString# ":p"),

                            c1tb_info_case_tag_DEFAULT_arg_0@_DEFAULT -> putStrLn (unpackCString# "Correct Serial Key! Auth Flag!")

                )

        )

    )


오우... 가독성이 상당히 떨어집니다.


히익 짤에 대한 이미지 검색결과



연산자들을 기준으로 해서 개행해봅시다.


Main_main_closure = >> $fMonadIO

    (putStrLn (unpackCString# "Input Serial Key >>> "))

    (>>= $fMonadIO

        getLine

        (\s1dZ_info_arg_0 ->

            >> $fMonadIO

                (putStrLn 

(++ (unpackCString# "your serial key >>> ") 

(++ s1b7_info (++ (unpackCString# "_") 

(++ s1b9_info (++ (unpackCString# "_") 

s1bb_info))))))

                (case && 

(== $fEqInt (ord (!! s1b7_info loc_7172456)) (I# 70)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172472)) (I# 108)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172488)) (I# 97)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172504)) (I# 103)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172520)) (I# 123)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172536)) (I# 83)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172552)) (I# 48)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172568)) (I# 109)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172584)) (I# 101)) (&& 

(== $fEqInt (ord (!! s1b7_info loc_7172600)) (I# 48)) (&& 

(== $fEqInt (ord (!! s1b7_info (I# 10))) (I# 102)) (&& 

(== $fEqInt (ord (!! s1b7_info (I# 11))) (I# 85)) 

(== $fEqInt (ord (!! s1b7_info (I# 12))) (I# 53))))))))))))) of

                    <tag 1> -> putStrLn (unpackCString# ":p"),

                    c1ni_info_case_tag_DEFAULT_arg_0@_DEFAULT 

-> case == ($fEq[] $fEqChar) (reverse s1b9_info) 

(: (C# 103) 

(: (C# 110) 

(: (C# 105) 

(: (C# 107) 

(: loc_7168872 

(: loc_7168872 

(: (C# 76) 

(: (C# 51) 

(: (C# 114) 

(: (C# 52) [])))))))))) of

                        False -> putStrLn (unpackCString# ":p"),

                        True -> case && 

(== $fEqChar (!! s1bb_info loc_7172456) (!! s1b3_info loc_7172456)) (&& 

(== $fEqChar (!! s1bb_info loc_7172472) (!! s1b4_info (I# 19))) (&& 

(== $fEqChar (!! s1bb_info loc_7172488) (!! s1b3_info (I# 19))) (&& 

(== $fEqChar (!! s1bb_info loc_7172504) (!! s1b4_info loc_7172568)) (&& 

(== $fEqChar (!! s1bb_info loc_7172520) (!! s1b2_info loc_7172488)) (&& 

(== $fEqChar (!! s1bb_info loc_7172536) (!! s1b3_info (I# 18))) (&& 

(== $fEqChar (!! s1bb_info loc_7172552) (!! s1b4_info (I# 19))) (&& 

(== $fEqChar (!! s1bb_info loc_7172568) (!! s1b2_info loc_7172504)) (&& 

(== $fEqChar (!! s1bb_info loc_7172584) (!! s1b4_info (I# 17))) 

(== $fEqChar (!! s1bb_info loc_7172600) (!! s1b4_info (I# 18))))))))))) of

                            <tag 1> -> putStrLn (unpackCString# ":p"),

                            c1tb_info_case_tag_DEFAULT_arg_0@_DEFAULT 

 -> putStrLn (unpackCString# "Correct Serial Key! Auth Flag!")

                )

        )

    )


조금 깔끔해지긴 했지만 좀 더 가독성을 끌어올려봅시다.


Main_main_closure = >> $fMonadIO

    (putStrLn ( "Input Serial Key >>> "))

    (>>= $fMonadIO

        getLine

        (\s1dZ_info_arg_0 ->

            >> $fMonadIO

                (putStrLn 

(++ ( "your serial key >>> ") 

(++ s1b7_info (++ ( "_") 

(++ s1b9_info (++ ( "_") 

s1bb_info))))))

                (case && 

(== $fEqInt (ord (!! s1b7_info 0)) (70)) (&& 

(== $fEqInt (ord (!! s1b7_info 1)) (108)) (&& 

(== $fEqInt (ord (!! s1b7_info 2)) (97)) (&& 

(== $fEqInt (ord (!! s1b7_info 3)) (103)) (&& 

(== $fEqInt (ord (!! s1b7_info 4)) (123)) (&& 

(== $fEqInt (ord (!! s1b7_info 5)) (83)) (&& 

(== $fEqInt (ord (!! s1b7_info 6)) (48)) (&& 

(== $fEqInt (ord (!! s1b7_info 7)) (109)) (&& 

(== $fEqInt (ord (!! s1b7_info 8)) (101)) (&& 

(== $fEqInt (ord (!! s1b7_info 9)) (48)) (&& 

(== $fEqInt (ord (!! s1b7_info 10)) (102)) (&& 

(== $fEqInt (ord (!! s1b7_info 11)) (85)) 

(== $fEqInt (ord (!! s1b7_info 12)) (53))))))))))))) of

                    <tag 1> -> putStrLn (":p"),

                    c1ni_info_case_tag_DEFAULT_arg_0@_DEFAULT 

-> case == ($fEq[] $fEqChar) (reverse s1b9_info) 

(: (103) 

(: (110) 

(: (105) 

(: (107) 

(: (48)

(: (48)

(: (76) 

(: (51) 

(: (114) 

(: (52) [])))))))))) of

                        False -> putStrLn (":p"),

                        True -> case && 

(== $fEqChar (!! s1bb_info 0) (!! s1b3_info 0)) (&& 

(== $fEqChar (!! s1bb_info 1) (!! s1b4_info (19))) (&& 

(== $fEqChar (!! s1bb_info 2) (!! s1b3_info (19))) (&& 

(== $fEqChar (!! s1bb_info 3) (!! s1b4_info 7)) (&& 

(== $fEqChar (!! s1bb_info 4) (!! s1b2_info 2)) (&& 

(== $fEqChar (!! s1bb_info 5) (!! s1b3_info 18)) (&& 

(== $fEqChar (!! s1bb_info 6) (!! s1b4_info 19)) (&& 

(== $fEqChar (!! s1bb_info 7) (!! s1b2_info 3)) (&& 

(== $fEqChar (!! s1bb_info 8) (!! s1b4_info 17)) 

(== $fEqChar (!! s1bb_info 9) (!! s1b4_info 18)))))))))) of

                            <tag 1> -> putStrLn (":p"),

                            c1tb_info_case_tag_DEFAULT_arg_0@_DEFAULT 

 -> putStrLn ("Correct Serial Key! Auth Flag!")

                )

        )

    )



완벽합니다! 상당히 가독성이 상승된 것 같습니다!




코드 설명



처음에 시리얼 키를 입력하라고 출력한 후 문자열을 입력받는 것 같습니다.


문자열을 출력하는데 

your serial key >>> s1b7_info+"_"+s1b9_info+"_"+s1bb_info 형식으로 출력될 것 같네요. 


Main_main_closure = >> $fMonadIO

    (putStrLn ( "Input Serial Key >>> "))

    (>>= $fMonadIO

        getLine

        (\s1dZ_info_arg_0 ->

            >> $fMonadIO

                (putStrLn 

(++ ( "your serial key >>> ") 

(++ s1b7_info (++ ( "_") 

(++ s1b9_info (++ ( "_") 

s1bb_info))))))



ord 함수가 사용되는 것 봐서 어떤 문자열에 아스키 코드를 구하는 것 같습니다.


Python으로 억지로 표기를 해보자면 이렇게 표현이 가능하겠네요.


for i in range(len(s1b7_info)):

if(s1b7_info[i] == HardCodedList[i]): # HardCodedList = [70,108,97,...]

continue

else :

print(":p")



                (case && 

(== $fEqInt (ord (!! s1b7_info 0)) (70)) (&& 

(== $fEqInt (ord (!! s1b7_info 1)) (108)) (&& 

(== $fEqInt (ord (!! s1b7_info 2)) (97)) (&& 

(== $fEqInt (ord (!! s1b7_info 3)) (103)) (&& 

(== $fEqInt (ord (!! s1b7_info 4)) (123)) (&& 

(== $fEqInt (ord (!! s1b7_info 5)) (83)) (&& 

(== $fEqInt (ord (!! s1b7_info 6)) (48)) (&& 

(== $fEqInt (ord (!! s1b7_info 7)) (109)) (&& 

(== $fEqInt (ord (!! s1b7_info 8)) (101)) (&& 

(== $fEqInt (ord (!! s1b7_info 9)) (48)) (&& 

(== $fEqInt (ord (!! s1b7_info 10)) (102)) (&& 

(== $fEqInt (ord (!! s1b7_info 11)) (85)) 

(== $fEqInt (ord (!! s1b7_info 12)) (53))))))))))))) of

                    <tag 1> -> putStrLn (":p"),




reverse라는 단어가 나왔습니다. s1b9_info의 내용을 뒤집는 것 같습니다.


요것도 굳이 python으로 억지로 표현하자면...


s1b9_info.reverse()

for i in range(len(s1b9_info)):

if(s1b9_info[i] == HardCodedList[i]): # HardCodedList = [103,110,105,107, ...]

continue

else:

print(":p")



                    c1ni_info_case_tag_DEFAULT_arg_0@_DEFAULT 

-> case == ($fEq[] $fEqChar) (reverse s1b9_info) 

(: (103) 

(: (110) 

(: (105) 

(: (107) 

(: (48)

(: (48)

(: (76) 

(: (51) 

(: (114) 

(: (52) [])))))))))) of

                        False -> putStrLn (":p"),




s1b2_info, s1b3_info, s1b4_info의 문자열에서 특정 문자를 참조하는 것 같습니다!


Python으로 표현해보겠습니다.



if( (s1bb_info[0] == s1b3_info[0]) and (s1bb_info[1] == s1b4_info[19]) and

(s1bb_info[2] == s1b3_info[19]) ....):

print("Correct Serial Key! Auth Flag!")




                        True -> case && 

(== $fEqChar (!! s1bb_info 0) (!! s1b3_info 0)) (&& 

(== $fEqChar (!! s1bb_info 1) (!! s1b4_info (19))) (&& 

(== $fEqChar (!! s1bb_info 2) (!! s1b3_info (19))) (&& 

(== $fEqChar (!! s1bb_info 3) (!! s1b4_info 7)) (&& 

(== $fEqChar (!! s1bb_info 4) (!! s1b2_info 2)) (&& 

(== $fEqChar (!! s1bb_info 5) (!! s1b3_info 18)) (&& 

(== $fEqChar (!! s1bb_info 6) (!! s1b4_info 19)) (&& 

(== $fEqChar (!! s1bb_info 7) (!! s1b2_info 3)) (&& 

(== $fEqChar (!! s1bb_info 8) (!! s1b4_info 17)) 

(== $fEqChar (!! s1bb_info 9) (!! s1b4_info 18)))))))))) of

                            <tag 1> -> putStrLn (":p"),

                            c1tb_info_case_tag_DEFAULT_arg_0@_DEFAULT 

 -> putStrLn ("Correct Serial Key! Auth Flag!")

                )

        )

    )




s1b5_info 함수를 보면은 split, 즉 #을 기준으로 입력받은 문자열을 나누는 것 같습니다.


그리고 s1b7_info, s1b9_info, s1bb_info이라는 변수에  입력받은 문자열을 3갈래로 나눈 문자열들이 들어가는 것 같습니다.


나머지 변수들은.. 위쪽 부분에 설명해두었으니 생략하겠습니다.


loc_7172456 =  0

loc_7172472 =  1

loc_7172488 =  2

loc_7172504 =  3

loc_7172520 =  4

loc_7172536 =  5

loc_7172552 =  6

loc_7172568 =  7

loc_7172584 =  8

loc_7172600 =  9

loc_7168872 =  48


s1b2_info = "1234567890"

s1b3_info = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

s1b4_info = "abcdefghijklmnopqrstuvwxyz"

s1b5_info = splitOn $fEqChar ("#") s1dZ_info_arg_0


s1b7_info = !! s1b5_info 0

s1b9_info = !! s1b5_info 1

s1bb_info = !! s1b5_info 2




결론적으로 플래그는 s1b7_info+"_"+s1b9_info.reverse()+"_"+s1bb_info가 될 것 같습니다.


파이썬으로 한 번 구해봅시다.


from sys import *


def ListWrite(s1b):

    for i in range(len(s1b)):

        stdout.write(chr(s1b[i]))


s1b7_info = [70,108,97,103,123,83,48,109,101,48,102,85,53]


s1b9_info = [103, 110, 105, 107, 48, 48, 76, 51, 114, 52]

s1b9_info.reverse()


s1b2_info = "1234567890"

s1b3_info = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

s1b4_info = "abcdefghijklmnopqrstuvwxyz"


s1bb_info = [ord(s1b3_info[0]),ord(s1b4_info[19]),ord(s1b3_info[19]),ord(s1b4_info[7]),ord(s1b2_info[2]),ord(s1b3_info[18]),ord(s1b4_info[19]),ord(s1b2_info[3]),ord(s1b4_info[17]),ord(s1b4_info[18])]




ListWrite(s1b7_info)

stdout.write('#')

ListWrite(s1b9_info)

stdout.write('#')

ListWrite(s1bb_info)






문제를 풀어보고 나서 다른 언어들도 한 번 디컴파일 대책을 한 번 세워봐야겠다는 생각이 들었습니다.


간략하게 문법이라도 경험을 해둬서 블로그에 정리도 해두고요.


Pycham을 사용하려는데 공간이 없습니다.

일주일 후에 노트북이 오는데 그 때 환경 구현도 같이 해놔야겠네요.


Comments