문제 : Name이 CodeEngn일때 Serial은 무엇인가

딱히 풀이를 기록할 필요가 없는 문제다. 브레이크포인트를 프로그램 실행 후 값들을 입력 받기 직전에 걸어 놓고 진행 하다 보면 그림 18처럼 임의로 입력한 Serial 값인 12345가 String1에 친절하게 나오고 String2에 적힌 값과 004011EF내부 함수에서 비교 판단하는 구조다. 답은 06162370056B6AC0 이다

18-1

18-2

문제: Key 값이 BEDA-2F56-BC4F4368-8A71-870B 일 때 Name은 무엇인가 힌트 : Name은 한자리인데.. 알파벳일수도 있고 숫자일수도 있고.. 정답인증은 Name의 MD5 해쉬값(대문자)

그냥 brute force하면 풀리긴 하지만 일단 분석을 해봤다. String Reference를 통해 진행되는 코드부분을 확인해서 브레이크포인트를 걸고 시작했다.

17-1

Name값엔 9를를 임의로 입력하고 실행해서 진행하다 보면 그림 13 처럼 Name값을 3글자이상 제한하는 분기점과 최대 1E = 30글자 미만으로 제한하는 부분이 보인다.

17-2 17-3

문제에서 Name값은 한자리라고 했기 때문에, 3자리 이상으로 설정한 부분을 1로 수정했다. 일단 여러 함수를 진행하면 0045BBA0에서 MOV한 EDX값이 9의 코드다. 그래서 0045BB9B함수로 들어가서 코드가 어떻게 생성되는지 확인해봤다.

17-4

진행하면 그림 16처럼 EDX에 키값의 앞부분인 C542가 보인다. 그 밑으로도 이런 식으로 키값을 생성하는 연산이 3번 더 나오지만, 앞부분만 확인해도 답을 판단 할 수 있을 꺼라 생각하고 C542가 어떻게 만들어 졌는지 확인하면 문제가 해결될 것 같다.

17-5 17-6

다시 처음부터 실행해서 연산을 보면 결국 EDX의 값은 ESI값에의해 결정되는데, ESI는 45B8A0에서 9(39)를 입력받고 ESI에 EDX(0)를 더한뒤, 772곱하기ESI 그리고 그값을 EDX에 복사한다. 그리고 ESI를 EDX에 곱하고 그 값을 다시 ESI에 더한다. 그후 다시 474를 곱하고 그후 자기자신을 더한다. 그리고 그 값을 EDX에 보낸다. 그러면 앞4비트가 키의 앞자리가 된다. 정리하면,

  1. ESI에 772를 곱한다.
  2. ESI의 제곱값을 EDX에 넣는다.
  3. ESI에 EDX를 곱한다.
  4. ESI에 474를 곱한다
  5. ESI+ESI를한다.

문제 키값인 BEDA-2F56-BC4F4368-8A71-870B 의 앞부분인 BEDA를 찾으면 된다. 뒷부분도 찾을 수는 있지만 굳이 찾을 필요는 없어서 생략했다. 45B8A0에서 받는 값이, 0~9는 30~39/ A~I는 41~49 / a~I는 61~69다. 아스키 코드값 같다 하나하나 17번 파일로 확인하면 오래 걸리기 때문에 위에 ESI와 EDX를 기준으로 연산하는 코드를 자바로 짜서 0x30~0x7A(0~z)까지 돌려봤다.

17-7

답은 70 16진수 46이다. 아스키코드 표를 찾아보니 영어 “F”다. 글자수를 수정한 17번 파일로 실행해서 확인해보니 맞다.

17-8

끝으로 R의 MD5 해쉬값은 ”e1e1d3d40573127e9ee0480caf1283d6” 다

문제 : Name이 CodeEngn일때 Serial을 구하시오

간단한문제라서 간략하게 설명

16번 문제를 디버거로 실행한하고 진행하면 5번째에서 바로 프로그램을 실행하는 함수를 발견했다. 그래서 함수내부로 들어가서 다시 진행하니 또 다른 함수내에서 프로그램을 실행시키고 있었다. 마찬가지로 그 함수내부로 들어가니 그림 8같은 16번 문제 실행화면과 같은 문자열들이 있는 곳을 확인했다. String Reference에서 확인해도 무관하다

16-1

16-2

C++로 작성된 파일이라 굉장히 깔끔하게 나온다. 프로그램 흐름에 따라 쭉 진행해서 Name과 Password를 입력하면 실패와 성공 분기점이 나온다. 15번문제와 마찬가지로 별다른 함정 없어 EAX와 CMP를 하는 부분이 나온다.

16-3

임의로 입력한 12345 즉16진수로 3039 를 입력했는데 대놓고 3039와 비교하는 값인 E4c60D97을 10진수로 바꾸면 “3838184855” 정답이다.

16-4

문제: Name이 CodeEngn일때 Serial을 구하시오

15-1

처음 실행하여 그림 1 15번 문제의 실행화면 처럼 진행하면 00458B14에서 프로그램을 실행시키는 CALL을 진행한다. 일단 Name에 CodeEngn을 입력하고 Serial부분에 임의로 12345를 입력하고, Chek it ! 을 클릭하니 그림 2 콘솔창에 입력한 화면같이 Try Again ! 이라는 메시지창이 출력됬다.

15-2 15-3

그래서 문자열을 확인해본 결과 “Try Again !” 이 있는 주소가 바로 나왔다. 해당 부분을 확인해보니 그림 4 Try Again 을 확인에서 확인할 수 있듯이 00458837에서 Serial이 맞는지 틀리는지에 대한 분기점을 확인했다. 이제 Name과 Serial을 입력 한 뒤를 확인하기위해 위에 00458800 에 Break Point를 걸고 다시 실행했다. 15-4 15-5 *** 분기점까지 가는 동안 총 3번의 Call이 있는데 일단 문제자체에 집중하기위해 함수내부를 확인하지는 않았다. 대략적으로 Name값을 확인하는 과정이다. 0045882C를 Call하면 EAX값이 12345의 아스키값에서 3039로 바뀐 것을 확인했다. 15-6

그리고 바로 다음부분에서 EAX값과 45B844의 값을 비교하고 그 결과로 Z플레그가 셋팅되서 JNZ로 성공과 실패가 분기된다. 즉 EAX값이 6160이 되면 성공하게 되고 즉 그 값이 CodeEngn의 Serial 값이다. 3039는 16진수 12345 이므로 6160을 10진수로 바꾸면 “24928”이 나온다 정답이다.

15-7 15-8 15-9

처음엔 Name과 Serial값을 비교하기전 함수들이 값을 변화시키거나 함수안에서 비교 할 줄 알고 Call들을 확인했는데, 생각보다 간단한 문제였다.

BYTE 8bit 부호 없는 정수

SBYTE 8bit 부호 있는 정수

WORD 16bit 부호 없는 정수

SWORD 16bit 부호 있는 정수

DWORD 33bit 부호 없는 정수

SDWORD 32bit 부호 있는 정수

FWORD 48bit 정수

QWORD 64bit 정수

TBYTE 80비트 정수

r8 8bit 범용 레지스터

r16 16bit 범용 레지스터

r32 32bit 범용 레지스터

Reg 임의의 범용 레지스터

Sreg 16bit 세그먼트 레지스터

imm 8, 16, 32bit 즉시값

imm8 8bit 즉시값

imm16 16bit 즉시값

imm32 32bit 즉시값

r/m8 8bt 범용 레지스터, 메모리

r/m16 16bit 범용 레지스터, 메모리

r/m32 32bit 범용 레지스터, 메모리

mem 8, 16, 32bit 메모리

어셈블리 명령어 INC(Increase) 피연산자에 1을 더한다. 연산 결과에 따라 ZF(Zero Flag)나 OF(Overflow Flag)가 세트될 수 있다. ex. INC reg, INC mem

DEC(Decrease) 피연산자에 1을 뺀다. 연산 결과에 따라 ZF나 OF가 세트될 수 있다. ex. DEC reg, DEC mem

ADD(Add) Destination에 Source의 값을 더해서 Destination에 저장한다. 연산 결과에 따라 ZF, OF, CF(Carry Flag)가 세트될 수 있다. ex. ADD eax(Destination), 100(Source) : eax레지스터에 100을 더해서 eax레지스터에 저장

SUB(Subtract) Destination에 Source의 값을 빼서 Destination에 저장한다. 연산 결과에 따라 ZF, OF, CF가 세트될 수 있다. ex. Sub eax(Destination), 100(Source) : eax레지스터에 100을 빼서 eax레지스터에 저장

MUL(Unsigned Integer Multiply) 부호 없는 al, ax, eax의 값을 피연산자와 곱한다. 피연산자가 8비트이면 al과 곱해서 ax에 저장되고 16비트면 ax와 곱하고 dx(상위16비트):ax(하위16비트)에 저장된다. 연산 결과에 따라 OF, ZF 플래그가 세트될 수 있다. ex. MUL reg

IMUL(Integer Multiplication) 부호 있는 al, ax, eax의 값을 피연산자와 곱한다. 연산결과에 따라 CF, OF가 세트될 수 있다. ex. IMUL r/m8 : 단일 피연산자이고 피연산자를 al, ax, eax에 곱한다. IMUL r16(destination), r/m16(value) : value를 al, ax, eax와 곱해서 destination에 저장 IMUL r16(destination), r/m8(value), imm8(value) : value끼리 곱해서 destination에 저장 (연산 결과가 destination 레지스터의 크기보다 크다면 OF, CF가 세트된다.)

DIV(Unsigned Integer Divide) 8, 16, 32비트 부호 없는 정수의 나눗셈을 수행한다. 연산결과에 따라 CF, OF, ZF가 세트될 수 있다. ex. DIV reg

MOV(Move) Source에서 Destination으로 데이터를 복사한다. ex. MOV reg(Destination), mem(Source)

MOVS(Move String) Source에서 Destination으로 데이터를 복사한다. ex. MOVS Destination, Source

MOVSB, MOVSW, MOVSE(Move String) SI 또는 ESI 레지스터에 의해 지정된 메모리 주소의 내용을 DI 또는 EDI 레지스터에 의해 지정되는 메모리 주소로 복사한다. MOVSB는 BYTE 단위, MOVSW는 WORD 단위, MOVSD는 DWORD 단위로 복사한다. 방향 플래그(DF)가 1로 세트되어 있으면 ESI와 EDI는 복사 시에 감소하게 되고 DF가 0으로 세트되어 있으면 ESI와 EDI는 복사 시에 증가하게 된다. ex. MOVSB, MOVSW, MOVSD

MOVSX(Move with Sign-Extend) BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 부호는 그대로 유지. ex. MOVSX reg32, reg16

MOVZX(Move with Zero-Extend) BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 남은 비트는 0으로 채운다. ex. MOVZX reg32, reg16

INT(Interrupt) 소프트웨어 인터럽트를 발생시켜 운영체제의 서브루틴을 호출한다. ex. INT imm

AND(Logical AND) Destination과 Source 피연산자의 각 비트가 AND 연산된다. AND 연산은 각 비트가 모두 1일 때만 결과 값이 1이 된다. ex. Destination : 10011100 Source : 11001010 결과 : 10001000 AND reg(Destination), mem(Source) : reg와 mem을 AND 연산한 후 결과를 reg에 저장 AND 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

OR(Inclusive OR) Destination과 Source 피연산자의 각 비트가 OR 연산된다. OR 연산은 각 비트가 모두 0이면 결과는 0이고 모두 0이 아니면 결과는 1이 된다. ex. Destination : 10011100 Source : 11001010 결과 : 11011110 OR reg(Destination), mem(Source) : reg와 mem을 OR 연산한 후 결과를 reg에 저장 OR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

XOR(Exclusive OR) Destination과 Source 피연산자의 각 비트가 XOR 연산된다. XOR 연산은 각 비트가 서로 다른 값일 때만 결과가 1이다. 같은 값이라면 결과는 0이 된다. ex. Destination : 10011100 Source : 11001010 결과 : 01010110 XOR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다. 피연산자의 두 값이 같은 값이라면 결과는 항상 0이 된다. 레지스터를 0으로 초기화시킬때 MOV 명령어를 사용하기보다는 XOR reg, reg으로 많이 사용한다.

TEST(Test) 두 피연산자 사이에 논리적인 AND 연산을 수행하여 플래그 레지스터에 영향을 주지만 결과값은 저장하지 않는다. OF, CF는 항상 0으로 세트되고 TEST 연산 결과값이 0이면 ZF가 1로 세트, 0이 아니면 ZF가 0으로 세트된다. ex. TEST reg, reg

STC(Set Carry Flag) 캐리 플래그(CF)를 1로 세트한다. ex. STC

CLC(Clear Carry Flag) 캐리 플래그(CF)를 0으로 세트한다. ex. CLC

STD(Set Direction Flag) 방향 플래그(DF)를 1로 세트한다. ex. STD

CLD(Clear Direction Flag) 방향 플래그(DF)를 0으로 세트한다. ex. CLD

STI(Set Interrupt Flag) 인터럽트 플래그(IF)를 1로 세트한다. ex. STI

CLI(Clear Interrupt Flag) 인터럽트 플래그(IF)를 0으로 세트한다. ex. CLI

SHR(Shift Right) Destination 피연산자를 Source 피연산자의 크기만큼 오른쪽으로 각 비트를 시프트시킨다. 최상위 비트는 0으로 채워지고 최하위 비트는 캐리 플래그(CF)로 복사된다. ex. SHR reg, imm16

SHL(Shift Left) Destination 피연산자를 Source 피연산자의 크기만큼 왼쪽으로 각 비트를 시프트시킨다. 최상위 비트는 캐리 플래그(CF)로 복사되고 최하위 비트는 0으로 채워진다. ex. SHL reg, imm16

PUSH(Push on Stack) 스택에 값을 넣는다. ESP의 값이 4만큼 줄어들고 이 위치에 새로운 값이 채워진다. ex. PUSH reg8

PUSHAD(Push All) EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터의 값을 스택에 PUSH한다. 레지스터들의 값을 보관해야 할 때 사용한다. ex. PUSHAD

PUSHFD(Push Flags) 플래그 레지스터를 스택에 PUSH한다. 플래그 레지스터의 값을 보관해야 할 때 사용한다. ex. PUSHFD

POP(Pop from Stack) ESP 레지스터가 가리키고 있는 위치의 스택 공간에서 4byte 만큼을 Destination 피연산자에 복사하고 ESP 레지스터의 값에 4를 더한다. ex. POP reg16(Destination)

POPAD(Pop All Flags from Stack) 스택에 존재하는 값을 EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터로 POP한다. PUSHAD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다. ex. POPAD

POPFD(Pop Flags from Stack) 스택에 존재하는 값을 플래그 레지스터로 POP한다. PUSHFD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다. ex. POPFD

XCHG(Exchange) 두 피연산자의 내용이 서로 교환된다. XCHG 명령은 imm 값이 피연산자로 올 수 없다. ex. XCHG reg, mem NEG(Negate) 피연산자의 2의 보수를 계산하여 결과를 피연산자에 저장한다. ex. NEG reg

PTR 피연산자의 크기를 재설정한다. ex. WORD PTR value : value의 크기를 WORD의 크기로 재설정한다.

OFFSET 세그먼트의 시작으로부터 변수가 위치한 거리까지의 상대적 거리를 리턴한다. ex. OFFSET value : value가 존재하는 위치를 세그먼트 시작 지점으로부터의 상대적 거리를 구한다.

LEA(Load Effective Address) Source 피연산자의 유효 주소를 계산하여 Destination 피연산자에 복사한다. 간단히 주소를 알아내서 복사하는 명령어다. ex. LEA reg(Destination), mem(Source)

REP(Repeat String) ECX 레지스터를 카운터로 사용해서 문자열 관련 명령을 ECX>0인 동안 반복한다. 한번 진행될 때마다 ECX 레지스터값이 -1 된다. ex. REP MOVS destination, source

JMP(Jump Unconditionally to Lable) 피연산자의 위치로 실행 흐름이 변경된다. 피연산자가 가리키는 코드로 점프 뛰어서 실행한다고 생각하면 된다. 피연산자에는 레이블이나 레지스터, 메모리 값이 올 수 있다. short점프는 -127 ~ 127 byte범위 안에서, near점프는 같은 세그먼트 내부에서, far점프는 현재 세그먼트를 벗어날 때 사용된다. JMP 명령어는 되돌아올 리턴 어드레스 값을 저장하지 않는다. ex. JMP shortlabel, JMP nearlabel, JMP farlabel

CALL(Call a Procedure) 함수 호출시 사용된다. JMP명렁어 같이 프로그램의 실행 흐름이 변경되지만 JMP명령와 다른 점은 되돌아올 리턴 어드레스(CALL 다음 명령)를 스택에 저장한다는 것이다. 되돌아올 주소를 저장하기 떄문에 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있다. 호출한 함수가 일을 다 마치면 원래 위치에서 다시 프로그램이 실행될 수 있음을 의미한다. ex. CALL nearlabel, CALL farlabel, CALL mem16, CALL 함수주소, CALL DWORD PTR[EAX+8], CALL : 특정 api 지목

CMP(Compare) 두 피연산자를 비교하는 작업을 한다. Destination 피연산자에서 Source 피연산자를 묵시적으로 빼서 값을 비교한다. 두 피연산자의 값이 같다면 결과는 0이 되고 제로 플래그(ZF)가 1로 세트된다. 다르다면 제로 플래그(ZF)는 0으로 세트된다. ex. CMP reg, reg

조건 점프 명령 JMP 명령어와는 다르게 특정 조건이 만족하게 된다면 점프를 수행하게 되는 명령어이다.

JA Jump if (unsigned) above CF=0 and ZF=0

JAE Jump if (unsigned) above or equal CF=0

JB Jump if (unsigned) below CF=1

JBE Jump if (unsigned) below

or equal CF=1 or ZF=1

JC Jump if carry flag set CF=1

JCXZ Jump if CX is 0 CX=0

JE Jump if equal ZF=1 JECXZ Jump if ECX is 0 ECX=0

JG Jump if (signed) greater ZF=0 and SF=0

JGE Jump if (signed) greater or equal SF=OF

JL Jump if (signed) less SF!=OF

JLE Jump if (signed) less or equal ZF=1 and OF!=OF

JNA Jump if (unsigned) not above CF=1 or ZF=1

JNAE Jump if (unsigned) not above or equal CF=1

JNB Jump if (unsigned) not below CF=0

JNBE Jump if (unsigned) not below or equal CF=0 and ZF=0

JNC Jump if carry flag not set CF=0

JNE Jump if not equal ZF=0

JNG Jump if (signed) not greater ZF=1 or SF!=OF

JNGE Jump if (signed) not greater or equal SF!=OF

JNL Jump if (signed) not less SF=OF

JNLE Jump if (signed) not les or equal ZF=0 and SF=OF

JNO Jump if overflow flag not set OF=0

JNP Jump if parity flag not set PF=0

JNS Jump if sign flag not set SF=0

JNZ Jump if not zero ZF=0

JO Jump if overflow flag is set OF=1

JP Jump if parity flag set PF=1

JPE Jump if parity is equal PF=1

JPO Jump if parity is odd PF=0

JS Jump if sign flag is set SF=1

JZ Jump is zero ZF=1