Dreamhack/Dreamhack Wargame (Challenge)

[42] IT 비전공자 [dreamhack]rev-basic-3 문제 풀기

imaginefuture-1 2024. 10. 21. 10:49

 

 

 

 

아 너 반갑다..?

 

 

 

 

하하하 42번째 문제 가보기전에

 

 

 

dreamhack Reverse Engineering Fundamental Roadmap 수강했다.

처음 문제 풀때는 뭔말인지 하나도 몰라서 포기하고 문제 풀었는데

그래도 이번에 한번 다시보면 이해되지않을까 수강했다.

 

그래도 60%는 이해된다!!(뿌듯)

 

아래에는 내가 혼자 공부하다가 필요한거랑 gpt한테 물어본거 정리했다

 

아 혹시나 드림핵 수업 정보를 여기다가 정리하면 저작권 문제되는지는 정확히 모르겠다

문제가 있다면 내리겠습니다!

 


 

범용 레지스터 🧺

범용 레지스터는 주용도는 있으나, 그 외 임의의 용도로도 사용될 수 있는 레지스터입니다. x86-64에서 각각의 범용 레지스터는 8바이트를 저장할 수 있으며, 부호 없는 정수를 기준으로 2^64 - 1까지 나타낼 수 있습니다.

자주 쓰이는 범용 레지스터들의 주용도는 다음과 같습니다. 여기 서술된 레지스터 외에도 x64에는 r8, r9, … , r15까지의 범용레지스터가 더 존재합니다.

이름주용도

rax (accumulator register) 함수의 반환 값
rbx (base register) x64에서는 주된 용도 없음
rcx (counter register) 반복문의 반복 횟수, 각종 연산의 시행 횟수
rdx (data register) x64에서는 주된 용도 없음
rsi (source index) 데이터를 옮길 때 원본을 가리키는 포인터
rdi (destination index) 데이터를 옮길 때 목적지를 가리키는 포인터
rsp (stack pointer) 사용중인 스택의 위치를 가리키는 포인터
rbp (stack base pointer) 스택의 바닥을 가리키는 포인터
 
Q1. rax = 0x0123456789abcdef 일 때, ah의 값은? 풀이도알려줘
 

ChatGPT의 말:

ChatGPT

rax 레지스터의 값은 64비트이며, 값은 0x0123456789abcdef입니다. 이 값에서 ah 레지스터의 값을 구하는 방법을 살펴보겠습니다.

레지스터와 비트 이해하기

  1. 레지스터 크기:
    • rax: 64비트 레지스터
    • eax: rax의 하위 32비트
    • ax: eax의 하위 16비트
    • ah: ax의 상위 8비트
    • al: ax의 하위 8비트
  2. 16진수 표현:
    • 0x0123456789abcdef는 64비트 값으로 각 16진수 자리수는 4비트에 해당합니다. 전체 64비트는 다음과 같이 구성됩니다:
    bash
    코드 복사
    0x 01 23 45 67 89 ab cd ef
  3. ax 레지스터:
    • ax는 eax의 하위 16비트이므로, 0x0123456789abcdef에서 하위 16비트는 cd ef입니다.
    • ax = 0xefcd (여기서 ef는 하위 8비트, cd는 상위 8비트).
  4. ah 레지스터:
    • ah는 ax의 상위 8비트이므로, ax = 0xefcd에서 ef의 상위 8비트는 cd에 해당합니다.

최종 결론

따라서 rax = 0x0123456789abcdef일 때, ah의 값은 0xcd입니다.

요약

  • rax = 0x0123456789abcdef에서 ah = 0xcd.

 

플래그 레지스터 🏳️🏴

플래그 레지스터는 프로세서의 현재 상태를 저장하고 있는 레지스터입니다. x64 아키텍처에서는 RFLAGS라고 불리는 64비트 크기의 플래그 레지스터가 존재하며, 과거 16비트 플래그 레지스터가 확장된 것입니다. 깃발을 올리고, 내리는 행위로 신호를 전달하듯, 플래그 레지스터는 자신을 구성하는 여러 비트들로 CPU의 현재 상태를 표현합니다.

플래그 레지스터의 여러 플래그 비트들 중 앞으로 주로 접하게 될 것들은 다음과 같습니다.

플래그의미

CF(Carry Flag) 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 됩니다.
ZF(Zero Flag) 연산의 결과가 0일 경우 설정 됩니다.
SF(Sign Flag) 연산의 결과가 음수일 경우 설정 됩니다.
OF(Overflow Flag) 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 됩니다.

 

명령어🔫

인텔의 x64에는 매우 많은 명령어가 존재합니다. 본 커리큘럼에서는 이 코스와 다음 코스에 걸쳐, 이 중 중요한 21개의 명령어를 자세히 학습할 것입니다.

학습할 명령어들은 다음과 같이 분류할 수 있습니다.

명령 코드

데이터 이동(Data Transfer) mov, lea
산술 연산(Arithmetic) inc, dec, add, sub
논리 연산(Logical) and, or, xor, not
비교(Comparison) cmp, test
분기(Branch) jmp, je, jg
스택(Stack) push, pop
프로시져(Procedure) call, ret, leave
시스템 콜(System call) syscall

 

lea는 Load Effective Address의 약어입니다.

설명

  • Load Effective Address (LEA):
    • lea 명령어는 메모리 주소를 계산하여 해당 주소의 값을 로드하는 것이 아니라, 그 주소 자체를 레지스터에 로드합니다. 즉, 메모리의 데이터를 직접 로드하지 않고, 주소를 계산해 그 주소 값을 레지스터에 저장합니다.

사용 예시

lea 명령어의 일반적인 사용 예시는 다음과 같습니다:

assembly
코드 복사
lea rax, [rbx + rcx*4 + 8]

위의 명령어는 rbx와 rcx를 사용해 주소를 계산하고, 이 계산된 주소 값을 rax 레지스터에 저장합니다.

주요 용도

  1. 주소 계산: 배열이나 구조체에서 특정 요소의 주소를 계산할 때 주로 사용됩니다.
  2. 포인터 연산: 포인터 연산을 통해 메모리 주소를 계산하여 해당 주소를 다른 레지스터에 저장하는 데 유용합니다.
  3. 스택 프레임 관리: 함수 호출 시 스택에서의 주소를 계산하는 데 사용됩니다.

결론

lea는 메모리 주소 계산을 위한 중요한 명령어로, 주소를 로드할 때 매우 유용합니다. 이 명령어는 효율적인 메모리 접근과 계산을 가능하게 합니다.

 

나의 말:

jmp, je, jg jmp는 jump인거 알겠는데 나머지는 뭐 축약여야
 

ChatGPT의 말:

ChatGPT

jmp, je, jg는 모두 어셈블리 언어에서 사용하는 분기 명령어입니다. 각각의 약어는 다음과 같습니다:

1. jmp

  • Jump: 지정된 주소로 무조건 분기합니다. 즉, 프로그램의 실행 흐름을 특정 주소로 변경합니다.

2. je

  • Jump if Equal: 이전의 비교 명령어에서 두 값이 같을 경우 지정된 주소로 분기합니다. 주로 cmp 명령어와 함께 사용됩니다.

3. jg

  • Jump if Greater: 이전의 비교 명령어에서 첫 번째 값이 두 번째 값보다 클 경우 지정된 주소로 분기합니다. 이 명령어는 일반적으로 cmp와 함께 사용됩니다.

요약

  • jmp: Jump (무조건 분기)
  • je: Jump if Equal (같을 경우 분기)
  • jg: Jump if Greater (클 경우 분기)

이 명령어들은 프로그램의 흐름을 제어하는 데 매우 중요하며, 조건부 분기를 통해 다양한 로직을 구현할 수 있습니다.

 

stack에서 push pop이 뭔뜻이야 민다? 팝한다?
 

ChatGPT의 말:

ChatGPT

스택(stack)에서의 pushpop은 데이터 구조에서 사용하는 기본적인 연산입니다. 이들 연산은 주로 후입선출(LIFO, Last In First Out) 원칙에 따라 작동합니다. 각 연산의 의미는 다음과 같습니다:

1. Push

  • 의미: 스택의 맨 위에 데이터를 추가하는 작업입니다.
  • 예시: 예를 들어, 스택이 비어 있을 때 push 연산으로 5를 추가하면, 스택의 내용은 [5]가 됩니다. 그 다음 push 10을 하면, 스택의 내용은 [5, 10]이 됩니다.

2. Pop

  • 의미: 스택의 맨 위에 있는 데이터를 제거하고, 그 값을 반환하는 작업입니다.
  • 예시: 위의 스택에서 pop을 수행하면, 10이 제거되고 반환됩니다. 그 후 스택의 내용은 [5]가 됩니다. 만약 다시 pop을 수행하면, 5가 제거되고 스택은 비게 됩니다.

비유적인 설명

  • 민다 (Push): 스택의 위에 물건을 쌓는 것과 비슷합니다. 새로운 물건이 위에 추가되므로, 아래에 있는 물건은 그대로 유지됩니다.
  • 팝한다 (Pop): 스택의 맨 위에 있는 물건을 꺼내는 것과 같습니다. 맨 위의 물건이 제거되고, 그 아래에 있는 물건이 위로 올라옵니다.

결론

  • Push: 데이터를 스택에 추가.
  • Pop: 스택에서 데이터를 제거하고 반환.

이러한 구조는 함수 호출, 리턴 값 저장, 재귀적 알고리즘 등 다양한 컴퓨터 프로그램의 데이터 처리에 매우 유용합니다.

 

ret 약어 알려줘
 

ChatGPT의 말:

ChatGPT

ret는 return의 약어입니다.

설명

  • return: 함수의 실행을 종료하고, 호출한 위치로 돌아가는 기능을 수행하는 명령어입니다.
  • ret 명령어는 호출된 함수의 스택에 저장된 반환 주소를 읽어 해당 주소로 점프하여 제어를 되돌립니다.

이 명령어는 프로그래밍에서 함수 호출 및 반환 메커니즘을 구현하는 데 중요한 역할을 합니다.

 

  Q12. end로 점프하면 프로그램이 종료된다고 가정하자.
프로그램이 종료됐을 때, 0x400000 부터 0x400019까지의 데이터를 대응되는 아스키 문자로 변환하면?
[Register]
rcx = 0
rdx = 0
rsi = 0x400000
=======================
[Memory]
0x400000 | 0x67 0x55 0x5c 0x53 0x5f 0x5d 0x55 0x10
0x400008 | 0x44 0x5f 0x10 0x51 0x43 0x43 0x55 0x5d
0x400010 | 0x52 0x5c 0x49 0x10 0x47 0x5f 0x42 0x5c
0x400018 | 0x54 0x11 0x00 0x00 0x00 0x00 0x00 0x00
=======================
[code] 1: mov dl, BYTE PTR[rsi+rcx]
2: xor dl, 0x30
3: mov BYTE PTR[rsi+rcx], dl
4: inc rcx
5: cmp rcx, 0x19
6: jg end
7: jmp 1

 

주어진 메모리와 코드를 바탕으로 "Welcome to assembly world!"라는 결과를 얻는 과정을 다시 분석하겠습니다.

초기 상태

  1. 레지스터:
    • rcx = 0
    • rdx = 0
    • rsi = 0x400000
  2. 메모리:
  3. python
    코드 복사
    0x400000 | 0x67 // 'g' 0x400001 | 0x55 // 'U' 0x400002 | 0x5C // '\' 0x400003 | 0x53 // 'S' 0x400004 | 0x5F // '_' 0x400005 | 0x5D // ']' 0x400006 | 0x55 // 'U' 0x400007 | 0x10 // non-printable 0x400008 | 0x44 // 'D' 0x400009 | 0x5F // '_' 0x40000A | 0x10 // non-printable 0x40000B | 0x51 // 'Q' 0x40000C | 0x43 // 'C' 0x40000D | 0x43 // 'C' 0x40000E | 0x55 // 'U' 0x40000F | 0x5D // ']' 0x400010 | 0x52 // 'R' 0x400011 | 0x5C // '\' 0x400012 | 0x49 // 'I' 0x400013 | 0x10 // non-printable 0x400014 | 0x47 // 'G' 0x400015 | 0x5F // '_' 0x400016 | 0x42 // 'B' 0x400017 | 0x5C // '\' 0x400018 | 0x54 // 'T' 0x400019 | 0x11 // non-printable

코드 분석 및 실행

코드 실행 순서

  1. Code 1: mov dl, BYTE PTR[rsi + rcx]
    • rcx가 0이므로 rsi + rcx는 0x400000입니다.
    • 메모리에서 0x400000의 값을 읽어 dl에 저장:
      less
      코드 복사
      dl = 0x67 // 'g'
  2. Code 2: xor dl, 0x30
    • 0x67 XOR 0x30:
      scss
      코드 복사
      0x67 = 103 (10진수) 0x30 = 48 (10진수) 103 XOR 48 = 55 -> 0x37 (ASCII '7')
  3. Code 3: mov BYTE PTR[rsi + rcx], dl
    • 0x400000에 0x37 저장 (즉, '7')
  4. Code 4: inc rcx
    • rcx를 1로 증가시킵니다.
  5. Code 5: cmp rcx, 0x19
    • rcx는 1이므로 비교합니다.
  6. Code 6: jg end
    • rcx가 0x19보다 크지 않으므로, 다음 코드로 진행.
  7. Code 7: jmp 1
    • 첫 번째 줄로 점프하여 이 과정을 반복합니다.

각 메모리 주소에 대해 반복 수행

이 과정을 rcx가 0x19에 도달할 때까지 계속합니다. 각 주소에서 값을 읽고 변환하는 과정을 확인해 보겠습니다.

데이터 변환 과정

메모리 데이터를 읽어 아스키 문자로 변환합니다:

  1. 0x400000 (0x67) -> W
  2. 0x400001 (0x55) -> e
  3. 0x400002 (0x5C) -> l
  4. 0x400003 (0x53) -> c
  5. 0x400004 (0x5F) -> o
  6. 0x400005 (0x5D) -> m
  7. 0x400006 (0x55) -> e
  8. 0x400007 (0x10) -> (non-printable)
  9. 0x400008 (0x44) ->
  10. 0x400009 (0x5F) -> t
  11. 0x40000A (0x10) -> (non-printable)
  12. 0x40000B (0x51) -> o
  13. 0x40000C (0x43) ->
  14. 0x40000D (0x43) -> a
  15. 0x40000E (0x55) -> s
  16. 0x40000F (0x5D) -> s
  17. 0x400010 (0x52) -> e
  18. 0x400011 (0x5C) -> m
  19. 0x400012 (0x49) -> b
  20. 0x400013 (0x10) -> (non-printable)
  21. 0x400014 (0x47) -> l
  22. 0x400015 (0x5F) -> y
  23. 0x400016 (0x42) ->
  24. 0x400017 (0x5C) -> w
  25. 0x400018 (0x54) -> 0
  26. 0x400019 (0x11) -> (non-printable)

최종 결과

변환된 아스키 문자열은 "Welcome to assembly world!"입니다.

따라서, 프로그램이 종료될 때 메모리에 저장된 아스키 문자열은 Welcome to assembly world!가 맞습니다. 이전에 잘못된 변환이 있었던 점 사과드리며, 추가 질문이 있으시면 언제든지 말씀해 주세요!

 


 

 흐허..문제 풀러가자...

 

문제 풀이 블로그 글을 몇개 보고오고나니 역시나 수강할때부터 엄청나게 많은 정보들이 나왔기에..

문제 풀이가 어려울꺼라 예상은했지만

아주! 복잡!하다!

일단..그래 차근차근해보자

 

 

 

correct 검색

 

 

들어간다

 

 

correct입력받는 chal113.7FF729641000에 들어간ㄷㅏ

 

어셈블리어가 보인다

 

https://mokpo.tistory.com/261

 

[dreamhack] rev-basic-3 문제풀이

[Reversing] rev-basic-3 문제풀이 문제를 확인해보자. Correct를 출력하는 FLAG 값을 찾는 문제이다. 문제 파일을 x64dbg 프로그램으로 열어보자. correct를 출력하는 flag 값을 찾아야하기 때문에 문자열 참조

mokpo.tistory.com

https://velog.io/@ximin/revbasic3

 

Dreamhack | rev-basic-3 풀이

앞의 rev-basic 문제들과 비슷하나, 역연산을 요구하는 문제이다.

velog.io

 

이분들은 여기서 어셈블리어를 해석하셨다. 

나는 여기까지는..도저히...아직은 아니기에 IDA 디컴파일러로 이용해 C언어로 보기로했다

 

sub_140001000을 눌러본다

 

for문이 나온다

 

열심히 별 찍었던 for문....(아련)

 

gpt야 설명해줘,,

 

 

 

gpt:

 

 

주어진 C++ 스타일의 코드는 _int64 __fastcall sub_140001000(__int64 a1)라는 함수의 정의를 보여줍니다. 이 함수는 특정 조건을 확인하고, 그 결과에 따라 0 또는 1을 반환하는 구조입니다. 코드를 분석하여 각 부분이 무엇을 하는지 설명하겠습니다.

함수 정의 및 매개변수

c
코드 복사
_int64 __fastcall sub_140001000(__int64 a1)
  • sub_140001000: 함수 이름.
  • __fastcall: 호출 규약. 매개변수를 스택이 아닌 레지스터를 통해 전달할 수 있도록 최적화된 방식입니다.
  • __int64 a1: 64비트 정수를 매개변수로 받습니다.

변수 선언

c
코드 복사
int i; // [rsp+0h] [rbp-18h]
  • i: 루프 인덱스 변수를 선언합니다.

루프 구조

c
코드 복사
for (i = 0; (unsigned __int64)i < 0x18; ++i)
  • 0부터 0x18 (24)까지 반복하는 루프입니다. 즉, i의 값은 0에서 23까지 증가합니다.

24번 반복문이구나... 

조건 확인

c
코드 복사
if (byte_140003000[i] != (i ^ *(unsigned __int8 *)(a1 + i)) + 2 * i) return 0LL;
  • byte_140003000[i]: 특정 메모리 주소에서 i번째 바이트를 읽습니다.
  • *(unsigned __int8 *)(a1 + i): a1 포인터에서 i 바이트만큼 이동한 주소에서 1바이트를 읽습니다.
  • i ^ *(unsigned __int8 *)(a1 + i): i와 a1 + i의 값을 XOR 연산합니다.
  • + 2 * i: 위의 XOR 연산 결과에 2 * i를 더합니다.

이 두 값이 같지 않으면, 함수는 0LL을 반환합니다. 이 조건은 어떤 암호화된 체크 또는 데이터의 유효성을 검증하는 것으로 보입니다.

 

xor 연산이구나 너...

루프 종료 및 반환

c
코드 복사
return 1LL;
  • 루프가 끝날 때까지 조건이 충족되면 1LL을 반환합니다. 이는 모든 값이 조건을 만족함을 나타냅니다.

요약

  • 이 함수는 0x18 (24) 바이트를 검사하여 특정 조건을 만족하는지 확인합니다.
  • 만약 주어진 조건이 만족하지 않으면 0을 반환하고, 모든 조건이 만족되면 1을 반환합니다.
  • 이 코드의 목적은 주어진 데이터(a1)가 byte_140003000에 의해 정의된 특정 패턴과 일치하는지를 확인하는 것입니다.

이해가 잘 안 되는 부분이나 추가적으로 궁금한 점이 있다면 말씀해 주세요!

 

https://blog.naver.com/16_youth/222332761341

 

[Dreamhack] rev-basic-3

Reversing Basic Challenge #3 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검...

blog.naver.com

이 블로그분을 따라가면 xor개념을 잘 설명해주신다...

 

(byte_140003000[i] != (i ^ *(unsigned __int8 *)(a1 + i)) + 2 * i) 여기서

사용자 입력값 XOR i = byte_140003000일때, 
byte_140003000 ^ i = 사용자입력값도 가능하다는 말이다.. 역연산..

 

 

byte_140003000에는 무슨 값이 있는지 보자..!

.data:0000000140003000 byte_140003000  db 49h, 60h, 67h, 74h, 63h, 67h, 42h, 66h, 80h, 78h, 2 dup(69h)
.data:0000000140003000                                         ; DATA XREF: sub_140001000+28↑o
.data:000000014000300C                 db 7Bh, 99h, 6Dh, 88h, 68h, 94h, 9Fh, 8Dh, 4Dh, 0A5h, 9Dh
.data:0000000140003017                 db 45h, 8 dup(0)

 

0x49 0x60 0x67 0x74 0x63 0x67 0x42 0x66 0x80 0x78

0x69(두번중복) 0x7B 0x99 0x6D 0x88 0x68 0x94 0x9F 0x8D 0x4D

0xA5 0x9D 0x45 0x00 (8 중복)

 

(byte_140003000[i] != (i ^ *(unsigned __int8 *)(a1 + i)) + 2 * i)

 

1. byte_140003000[i] !=(i^(a1+i)+2*i)

2.  Byte_14003000[i] - 2*i != i ^ (a1+i)

 

3.(Byte_14003000[i] - 2*i) XOR i != (a1+i)

 

이제 거의 다왔다..역산 프로그램을 만들자..

c언어..가보자구

 

 

 

#include <stdio.h>

int main()
{
	unsigned char chars[] = { 0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78,

   0x69, 0x7B, 0x99, 0x6D, 0x88, 0x68, 0x94, 0x9F, 0x8D, 0x4D,

   0xA5, 0x9D, 0x45, 0x00 };
	for (int i = 0; i < 24; i++)
	{
		printf("%c", ((chars[i] - 2 * i) ^ i ));

	}
	return 0;
}
 

하고 출력을 누르면.,.. 두그둔근두느극ㄴ

 

에?

 

 

 

뭔가,..뭔가 잘못됐다

 

아 중복..중복을 위에 적어놓고 값을 두번씩 안적고 하나만적었다 이 멍청이!(깨달음)

 

0x00은 알아서 컴퓨터가 쭈욱 뒤에 나열해줘서 하나만 적어도 상관없는 것 같다 굳

 

#include <stdio.h>

int main()
{
	unsigned char chars[] = { 0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78,
		0x69, 0x69, 0x7B, 0x99, 0x6D, 0x88, 0x68, 0x94, 0x9F, 0x8D, 0x4D, 0xA5, 0x9D, 0x45, 0x00};

	for (int i = 0; i < 24; i++)
	{
		printf("%c", ((chars[i] - (2 * i)) ^ i));
	}

	return 0;

}
[출처] [Dreamhack] rev-basic-3|작성자 늘
 

 

다시 입력하고 출력을..출력을 누ㅡㄹ면...!!!!!

 
하아아아아아아반갑다!!!
 

 

하하하 푸었다고! ㅠㅜㅍ러어싸독ㅁㄴ

gpt한테 하다가 너무 힘들어서 응원의 메세지 달라했다..

 

 

 

01234
크흑..늘 너무 고마운 친구 우리 지피티..지피티야 오래가자