https://dreamhack.io/wargame/challenges/2650
로그인 | Dreamhack
최대 52만원 지원금 받고 진짜 해커로 레벨업 하세요!
dreamhack.io
1단계 리버싱 문제를 풀었다.
IDA에서 연 뒤에 F5를 눌러서 디컴파일 했더니 이런 코드가 나왔다.

scanf로 59글자짜리 문자열 s를 입력받은 후에, 제일 처음 if문이 s의 길이가 59가 맞는지 확인하는 것이다.
따라서 s는 59글자여야 한다.
그 다음 strcpy로 dest에 s를 복사한다.
다음 반복문이 나오고 나서 dest랑 "Please~problem" 문자열이랑 비교하는 조건문이 나온다.
dest[i] ^= *( (_BYTE *)v5 +i );
따라서 dest[0]부터 dest[58]에 대하여 특정 연산을 한 결과가 "Please~problem"이 되는 dest를 찾아야 한다.
먼저 (_BYTE *)가 무슨 뜻인지 검색해 보았다.
BYTE가 1바이트라는 뜻이고, *이 주소로 해석하라는 뜻이라고 한다.
따라서 (_BYTE *)는 어떤 메모리 주소나 포인터를 1바이트(8비트) 단위로 해석하라는 뜻이라고 한다.
그럼 위 식의 우변은 v5를 1바이트씩 끊어서 주소 값으로 보고, 그 주소 값에 i를 더한다. 그리고 그 전체에 *를 붙여서 다시 '주소'가 아닌 그냥 '숫자'로 만든 후 dest[i]와 XOR 연산을 하는 것이다.

주요 코드 위쪽에 여러 변수가 선언되는 부분 중에서, v5가 초기화되어 있는 것을 찾았다.
여기서 0x는 16진수를 나타내는 표현, LL은 8바이트짜리 long long 자료형임(v5 선언할 때도 8바이트 _QWORD라고 선언됨.)을 알려 주는 표현이라서 떼고 보면 된다.
이때 v5[0]은 글자 수가 1개 모자란데, 맨 앞 숫자가 0이라서 생략된 것이다. 따라서,
v5[0] = 0x0C313A0A11150D18
v5[1] =0x2C37426D44472F19
v5[2] = 0x1B1A3A1704000F00
v5[3] = 0x1301263B1904312A
v5[4] = 0x361F06041A0A3E17
그런데 8바이트씩 5개면 40바이트다. v5를 처음부터 끝까지 봤는데도 59바이트가 안 채워졌다.
도저히 모르겠어서 결국 이 부분에서 ai의 힘을 빌렸다....
알고 보니 *( (_BYTE *)v5 +i )가 무조건 v5 변수만을 써야 하는 것이 아니라, 그냥 v5에서 i만큼 떨어진 곳을 가리키는 거라고 한다.

먼저 크기를 보면,
v5는 8바이트짜리 _QWORD 5개이므로 40바이트.
v6는 1바이트짜리 _BYTE 11개이므로 11바이트.
v7은 __int64라는 8바이트 변수이므로 8바이트.
그리고 변수 선언 부분에서 메모리 주소를 보면,
v5는 rbp-120h에, v6은 rbp-F8h에, v7은 rbp-EDh에 위치하는 것을 알 수 있다.

뺄셈을 통해 확인해 보니 v5와 v6이 40바이트만큼, v6과 v7이 11바이트만큼 떨어져 있다.
즉, v5-v6-v7 스택 메모리상에서 빈틈 없이 이어져서 저장되어 있는 것이다.

그럼 여기서 dest와 연산할 우변 59바이트를 모두 찾았으므로, 알맞은 XOR 연산을 하면 dest를 구할 수 있다.
str="Please_input_the_correct_new_year_greetings_at_this_problem"이라 하자.
str과 우변을 XOR 연산 해서 dest를 구할 것이다.

코드를 짜 봤는데, 기가 막힌 문자열이 나왔다.

+i를 안 해서 다시 짜서 해 봤다. 왜 이렇게 바꿨던 거냐면 풀어야 하는 식이 dest[i] ^= *( (_BYTE *)v5 +i );이었기 때문이다.
그런데 생각해 보니 +i는 메모리 계산할 때 v5로부터 0~58(i)바이트 떨어진 거 계산하는 데 이미 썼던 거다. 연산할 때 또 index를 더할 필요 없었다.
진짜 모르겠어서 여기서 또 ai의 도움을 받았다. 컴퓨터는 Little endian 방식이라서 메모리에 뒤에서부터 저장되는 걸 알면서 또 까먹었다.

그래서 뒤에서부터 읽어 오는 걸로 바꿨다....
그리고 16^n으로 나누는 것보다 이럴 때는 bit shift 연산 (<<) 쓰는 게 더 좋다고 했다. 나도 그렇게 생각해서 그 부분도 바꿔 봤다.

여전히 이상하게 나와서 이상한 글자들의 인덱스를 세어 보았다.
Please_q -> v5[0]에서 마지막 글자
nput_th| -> v5[1]에서 마지막 글자
_correct
_new_yeK -> v5[3]에서 마지막 글자
r_greet~ -> v5[4]에서 마지막 글자
ngs] -> v6_1 다 이상함
at_thiss -> v6_2 마지막 글자
problem+ -> v7 마지막 글자
j 인덱스의 마지막 처리가 이상한 것 같았는데, 다시 보니까 그냥 로직 자체가 이상했다.
4*j(16진수 1글자=4비트니까, 16진수 기준 j개)만큼 왼쪽으로 쉬프트하는 게, 첫 글자(제일 오른쪽 비트)를 생각했을 때는 제일 오른쪽 두 개만 남으니까 괜찮은데, 갈수록 오른쪽에서부터 2, 4, 6, 8개 남게 되는 것이었다.
저만큼이나 맞게 나온 것도 신기하다.
그래서 쉬프트한 상태로 v5, v6 등 변수가 저장되도록 << 연산을 <<=로 고쳤다.

하
드디어 정상적인 문자열이 나왔다고 좋아했는데, str으로 설정한 문자열이 그대로 나왔다.
이제 진짜 울고 싶어져서 ai 도움을 받았다.
일단 str 문자열이 그대로 나온 건 XOR 연산이 제대로 안 되고 있다는 뜻이었고, str이랑 XOR하는 부분이 다 0이 되었다는 말이었다.
즉, 쉬프트 연산을 제대로 하지 못한 것이다.
쉬프트 연산을 이제 비트로 봐야 할지.... 2를 곱하고 나누는 걸로 봐야 할지.... 그럼 전체에서 2*n을 나눠야 하는지 곱해야 하는지.... 모든 것이 헷갈리기 시작해서 그냥 ai한테 물어봤더니 공식을 알려 줬다.
16진수에서 오른쪽에서 n번째에 있는 8비트만 추출하고 싶을 때.
([비트 문자열] << 8*n ) & 0xff
오른쪽에서 n 번째에 있는 8비트를 제일 오른쪽으로 민 다음에, 00000000...0000011111111과 and 연산을 해서 마지막 8비트만 남기는 거라고 한다.
하ㅠㅠㅠㅠ 그리고 알고 보니까 v6_1도 4바이트를 나온 그대로 쓰면 안 되는 거였다.
왜냐하면 초기화할 때

&v6[3]이 v6의 3번째(0부터 시작) 바이트째부터 저 값으로 덮어 쓰겠다는 뜻이라고 한다.
그래서 v6을 DWORD로 4바이트로 초기화한 코드에서, 마지막 바이트는 쓰지 않는 값을 그냥 함정으로 준 것이다.

고쳤다.

맞았다.
답이 Happy_new_2026_I_love_you_and_dreamhack_lets_happy_together였는데, 나는 전혀 행복하지 않은 문제였다.
너무 어렵다.... ㅠㅠㅠㅠ