abex' crackme #1 write up

 

 

처음에 Immunity Debugger로 문제 파일을 연 후 F8키를 눌러보면서 한줄씩 실행시켜보았다.

 

 

 

위와 같은 어셈블리 코드와 메세지창을 볼 수 있었고 CD-ROM이 맞다는 메세지 창이 뜨도록 만드는 것이 이 문제의 목표였기 때문에 아래 그림의 코드 부분을 유심히 보았다.

 

 

일단 00401026 주소에 있는 JE 명령어는 어셈블리의 분기 명령어 중 하나로 Jump if equal 즉, CMP로 비교한 결과 값이 같다면 참이 되고, 이때 해당 주소로 점프하는 기능을 가진 명령어이다.

 

 

수정하기 전의 코드에서 CMP 명령어를 처리해줄때 EAX 레지스터에는 00000001 값이 들어있고 ESI에는 00401003 값이 들어있기 때문에 두 결과값은 같지 않으므로 0040103D라는 주소로 넘어가지 않고 그냥 그 다음주소(00401028)로 넘어간다.

 

 

위와 같이 옆에 주석 부분에서 볼 수 있듯이 Ok, I really think that your HD is a CD-ROM이라는 메세지가 있는 MessageBox가 실행되도록 하려면 CMP를 해준 값이 같도록 하여 JE를 만났을때 해당 주소(0040103D)로 넘어갈 수 있게 만들어주면 될 것이라고 생각했다.

 

 

그래서 위와 같이 MOV EAX, ESI를 해주어 두 값이 같도록 수정했더니 제대로 메세지 창이 뜨는 것을 확인할 수 있었다.

MOV 명령어는 operend1값에 operend2 값을 넣어준다는 의미이다.

 

또 다른 풀이법으로는 JE 명령어를 JMP 명령어로 바꿔주는 방식이 있다.

JE 명령어는 CMP해준 두 값이 같아야만 점프를 하는 반면 JMP 명령어는 무조건 해당 주소값으로 점프를 한다. 

 

 

그렇기 때문에 위와 같이 JE를 JMP로 수정해주면 해당 주소로 잘 넘어가고 원하는 메세지 창이 뜨게 할 수 있다.

 

이외에도 레지스터의 값을 증가시키거나 감소시키는 코드로 수정하여 EAX와 ESI의 값을 같게 만들어주는 방법도 있을 것이다.

'2021-2 STUDY > Reversing Study' 카테고리의 다른 글

Week02_Dreamhack rev-basic-2, rev-basic-3  (0) 2021.09.25
Week02_PE 파일 구조  (0) 2021.09.25
Week01_Dreamhack rev-basic-0, rev-basic-1  (0) 2021.09.11
Week01_32bit/64bit 호출 규약  (0) 2021.09.11
Week01_64bit 레지스터  (0) 2021.09.11

Dreamhack rev-basic-0

 

x64dbg를 이용하여 문제에서 주어진 파일을 실행시켰다.

 

 

먼저 메인 함수를 찾기 위해 F8키로 계속 확인해보다가 cmd 창에 "input: "이라는 문구가 뜨는 함수 호출 부분에 breakpoint를 걸어주었다.

 

 

아까 걸어주었던 breakpoint 부분에서 F7키를 눌러 함수 내부로 들어온 후 위와 같은 부분을 확인할 수 있었다.

 

여기에서 test eax, eax를 통해 eax가 0인지 아닌지 판단하는 부분을 찾아볼 수 있다.

이때 eax 값이 0이면 Wrong을 출력하고, 아니면 Correct를 출력한다는 것을 예상할 수 있다.

 

이때 좀 더 자세히 확인해 보기 위해 test 명령어 전에 호출하는 함수 내부를 들어가 보았다.

 

 

test 명령어 전에 호출되는 함수 내부를 확인해보니 eax가 0이 아니면 ss:[rsp+20] 주소 부분에 1이 담기고,

eax가 0이면 ss:[rsp+20] 주소 부분에 0이 담기고 함수를 빠져나가는 것을 확인할 수 있다. 

 

이때 "Correct"를 출력해주기 위해서는 eax가 결과적으로 0이 아니어야 하기 때문에 이 부분에서는 eax가 0이 되어야 한다.

 

여기서 call <JMP.&strcmp> 함수는 두 문자열을 비교하여 같으면 0, 다르면 1을 eax 레지스터에 저장하는 역할을 한다.

즉, eax가 0이 되도록 하기 위해서는 rcx와 rdx의 문자열이 서로 같으면 되기 때문에 compar3_the_string을 입력해주면 될 것이다.

 

 

위와 같이 Correct가 잘 출력되는 것을 확인할 수 있다.

 

 

Dreamhack rev-basic-1

 

x64dbg를 이용하여 문제에서 주어진 파일을 실행시켰다.

 

 

이 문제도 메인 함수를 찾기 위해 F8키로 계속 확인해보다가 cmd 창에 "input: "이라는 문구가 뜨는 함수 호출 부분에 breakpoint를 걸어주었다.

 

 

이 문제도 역시 Input값을 받아 test eax, eax를 해준 후  eax 값이 0이면 Wrong을 출력하고, 아니면 Correct를 출력한다는 것을 예상할 수 있다.

 

이 때 test 명령어 전 호출되는 함수에 breakpoint를 걸고 함수 내부로 들어가 보았다. 

 

 

함수 내부로 들어가 보면 위와 같이 cmp 명령어로 문자열을 하나씩 비교해주고 있는 것을 확인할 수 있다.

 

 

이때 하나씩 입력되는 문자열과 Input에 넣어주는 값이 같아야 하므로 Compar3_the_ch4ract3r를 입력해주면 된다.

 

 

위와 같이 Correct가 잘 출력되는 것을 확인할 수 있다.

'2021-2 STUDY > Reversing Study' 카테고리의 다른 글

Week02_Dreamhack rev-basic-2, rev-basic-3  (0) 2021.09.25
Week02_PE 파일 구조  (0) 2021.09.25
Week01_abex' crackme #1  (0) 2021.09.11
Week01_32bit/64bit 호출 규약  (0) 2021.09.11
Week01_64bit 레지스터  (0) 2021.09.11

▶ 함수 호출 규약 (Calling Convention)

 

→ 함수를 호출할 때 파라미터를 어떤 방식으로 전달할지 정해놓은 규약

 

함수 호출 시에는 유효하지 않은 파라미터를 삭제하는 과정이 필요하고, 유효하지 않은 파라미터를 가리키고 있는 ESP를 증가시켜 스택을 정리해주어야 한다. 이때 스택을 정리하는 방식에 대한 규약을 함수 호출 규약이라고 한다.

 

 

▶ 32bit 함수 호출 규약

 

▷ cdecl

 

- call(호출자)을 하는 쪽에서 스택을 정리하는 방식

- 인자를 오른쪽에서 왼쪽으로 넣음

- 가변인자 전달이 가능하다는 장점

- 함수 앞에 _를 추가하여 구분 가능

- ex) main( ) 함수에서 add( ) 함수를 호출하는 경우 add( ) 함수가 끝나고 main( ) 함수에서 스택에 저장되었던 파라미터를 정리하기 위해 esp 값을 증가시켜 스택 정리

 

▷ stdcall

 

- cdecl과 반대로 호출된 함수에서 스택을 정리하는 방식

- stdcall 형식으로 asm을 컴파일할 때 규약을 따름

- 호출된 함수의 마지막 부분에서 RETN 명령을 사용하여 함수 내에서 스택을 정리하고 함수를 빠져나옴

- 인자를 오른쪽에서 왼쪽으로 넣음

- main( ) 함수에 따로 스택을 정리하는 명령이 필요없기 때문에 코드 길이가 짧아진다는 장점

- 함수 앞에 _를 추가하고, 함수 이름 뒤에는 @를 추가

 

▷ fastcall

 

- stdcall 방식과 같은데, 함수에 전달하는 파라미터 일부(최대 2개)를 레지스터를 사용하여 전달

- 이용하는 레지스터는 ECX, EDX

- 레지스터를 이용하기 때문에 빠르게 함수를 호출할 수 있는 장점

- 스택 정리는 호출된 함수 내에서 함

- 함수 앞에 @를 추가하고, 함수 이름 뒤에는 @가 추가됨

- 스택 이용 바이트 수 표현

 

 

▶ 64bit 함수 호출 규약

 

- 32bit 함수 호출 규약과 다르게 fastcall 방식만 이용

- 파라미터 전달

 

  정수 실수
리눅스 EDI, ESI, EDX, E8, CX, R8, R9 XMM0~XMM7
윈도우 ECX,EDX,R8,R9 XMM0~XMM4

 

- ex) add(int a, int b)는 파라미터가 정수형이므로 EDI, ESI 레지스터에 들어가고, 오른쪽에서 왼쪽 방향으로 파라미터가 들어가기 때문에 int b는 EDI 레지스터, int a는 ESI 레지스터에 들어간다.

 

 

※ ROP할 때

 

32bit : 함수 호출 → pr → 인자

64bit: pr → 인자 → 함수 호출

'2021-2 STUDY > Reversing Study' 카테고리의 다른 글

Week02_Dreamhack rev-basic-2, rev-basic-3  (0) 2021.09.25
Week02_PE 파일 구조  (0) 2021.09.25
Week01_abex' crackme #1  (0) 2021.09.11
Week01_Dreamhack rev-basic-0, rev-basic-1  (0) 2021.09.11
Week01_64bit 레지스터  (0) 2021.09.11

▶ 64bit 운영체제

 

64bit 운영체제는 2^64만큼의 비트를 사용한다.

→ 메모리를 0부터 18,446,744,073,709,551,616까지 저장할 수 있다.

 

64bit 컴퓨터에서는 최대 1tb(2^10gb)만큼의 RAM이 들어간다.

 

 

▶ 64bit 레지스터

 

※ 레지스터란?

→ CPU가 요청을 처리하는 데 필요한 데이터를 일시적으로 젖아하는 기억장치

→ 레지스터의 용량이 클수록 메모리에서 더 많은 데이터를 가져와 저장할 수 있으므로 처리속도가 빨라진다.

 

 

▷ 범용 레지스터

 

RAX 산술/논리 연산 수행 후 함수의 return 값 저장
RBX 메모리 주소 저장
RCX 반복문에서 카운터로 사용되는 레지스터
RDX I/O 주소 지정 시 사용, 다른 레지스터를 서포트하는 보조 레지스터
R8~R15 64bit 프로세서에서 추가된 범용 레지스터, 다양한 용도로 사용

 

▷ 인덱스 레지스터

 

RSI 복사할 데이터의 주소 저장
RDI 복사된 데이터의 주소 저장

 

▷ 포인터 레지스터

 

RSP 스택의 끝 지점 주소 (현재 스택 주소) 저장
스택의 가장 높은 곳을 가리킴
push, pop 명령을 통해 RSP 값이 위아래로 8바이트씩 이동
RBP 함수 호출 시 형성되는 스택프레임의 시작 지점 주소 (스택 복귀 주소) 저장

 

▷ 플래그 레지스터

 

RFLAGS register 시스템 제어 용도, 비교/조건문 처리 용도
CF(Carry Flag) 부호 없는 수끼리 연산 결과에서 자리올림/자리내림이 발생할 때 1,
unsigned int 값을 벗어날 때 1
OF(Overflow Flag) 부호 있는 수끼리 연산 결과에서 용량을 초과했을 때 1
SF(Sign Flag) 연산 결과 최상위 비트가 1인 경우 1
ZF(Zero Flag) 연산 결과가 0이면 1
AF(Auximiliary-carry Flag) 16bit 연산 시 자리올림/자리내림이 발생할 때 1
PF(Parity Flag) 연산 결과가 짝수면 1, 홀수면 0
DF(Direction Flag) 방향 플래그
0으로 설정 시 해당되는 어셈블리 명령에 제공되는 주소 증가
IF(Interrupt Flag) 1은 인터럽트 활성화, 0은 인터럽트 비활성화
TF(Trap Flag) single step mode에서 프로세스의 동작 허용

 

 

※ 하나의 레지스터는 크기에 따라 적절히 쪼개 사용할 수 있다.

 

 

RAX(64 bits) - EAX(32 bits, Extended AX) - AX(16 bits) - AL(8 bits) - AH(8 bits)

 

'2021-2 STUDY > Reversing Study' 카테고리의 다른 글

Week02_Dreamhack rev-basic-2, rev-basic-3  (0) 2021.09.25
Week02_PE 파일 구조  (0) 2021.09.25
Week01_abex' crackme #1  (0) 2021.09.11
Week01_Dreamhack rev-basic-0, rev-basic-1  (0) 2021.09.11
Week01_32bit/64bit 호출 규약  (0) 2021.09.11

4.8 태스크 디스크립터(task_struct 구조체)

 

프로세스의 속성 정보를 표현하는 가장 중요한 자료구조는?

→ 태스크 디스크립터를 나타내는 task_struct 구조체

 

▶ ps -ely : 프로세스 목록

 

 

리눅스 시스템에서 구동 중인 프로세스 목록은 프로세스를 관리하는 태스크 디스크립터의 연결 리스트(init_task.tasks)에 접근하여 등록된 프로세스를 출력한다.

 

 

1) 프로세스를 식별하는 필드

 

 

 

→ comm은 TASK_COMM_LEN 크기의 배열이며 프로세스 이름을 저장한다.

→ 프로세스 이름들은 태스크 디스크립터를 나타내는 task_struct 구조체의 comm 필드에 접근해서 출력한다.

 

ex) 디버깅용 코드

 

 

현재 실행 중인 프로세스의 태스크 디스크립터 구조체 task_struct 구조체에 접근하는 current 매크로 + 프로세스 이름을 저장하는 comm 필드 -> 다양한 디버깅용 코드 작성 가능

 

 

task_struct *p가 깨우려는 프로세스의 태스크 디스크립터

프로세스 이름이 kthreadd인 경우 dump_stack( ) 함수를 호출해 함수 호출 흐름을 커널 로그로 출력

 

▶ pid : 프로세스를 식별하는 정수형 값

- 프로세스 아이디 : pid_t pid; 

- 스레드 그룹 아이디 : pid_t tgid;

 

 

2) 프로세스 상태 저장

 

▶ 태스크 디스크립터에서 프로세스 상태를 관리하는 두 가지 필드

 

① state : 프로세스 실행 상태

- TASK_RUNNING : CPU 에서 실행 중이거나 런큐에서 대기 상태에 있음 
- TASK_INTERRUPTIBLE : 휴면 상태 
- TASK_UNINTERRUPTIBLE : 특정 조건에서 깨어나기 위해 휴면 상태로 진입한 상태 

 

② flags : 프로세스 세부 동작 상태와 속성 정보

      → PF_*로 시작하는 매크로 필드를 OR 연산한 결과를 저장

- PF_IDLE : 아이들 프로세스
- PF_EXITING: 프로세스가 종료 중인 상태 
- PF_EXITPIDONE : 프로세스가 종료를 마무리한 상태 
- PF_WQ_WORKER : 프로세스가 워커 스레드인 경우 
- PF_KTHREAD : 프로세스가 커널 스레드인 경우 

 

▶ exit_state : 프로세스 종료 상태를 저장

- EXIT_DEAD

- EXIT_ZOMBIE

- EXIT_TRACE

 

▶ exit_code : 프로세스의 종료 코드를 저장

 

 

3) 프로세스 간의 관계

 

▶ 프로세스 간의 관계를 나타내는 필드

 

① struct task_struct *real_parent : 자신을 생성한 부모 프로세스의 태스크 디스크립터 주소 저장

 

② struct task_struct *parent : 부모 프로세스의 태스크 디스크립터 주소 저장

 

※ *real_parent와 *parent의 차이점

→ 부모 프로세스가 종료되지 않고 실행 중이면 같음

→ 프로세스 계층 구조에서 지정한 부모 프로세스가 없을 경우 init 프로세스를 부모 프로세스로 변경하면 다름

 

 

위와 같이 부모 프로세스가 종료되면 do_exit( ) 함수에서 화살표 방향으로 함수 호출

forget_original_parent( )와 find_new_reaper( )에서 새로운 부모 프로세스 지정

 

③ struct list_head children : 부모 프로세스가 자식 프로세스를 생성할 때 children 연결 리스트에 자식 프로세스 등록

 

④ struct list_head sibling : 같은 부모 프로세스로 생성된 프로세스의 연결 리스트 주소 저장

 

※ children과 sibling 필드의 연결 방식

 

▶ ps axjf : 부모와 자식 프로세스 관계 확인

 

 

rcu_gp, rcu_par_gp, kworker/0, mm_percpu_wq, ksoftirqd/0 프로세스들의 부몸 프로세스는 kthreadd임을 알 수 있다.

 

※ 부모 프로세스인 kthreadd 입장에서의 태스크 디스크립터

 

 

- kthreadd 프로세스 태스크 디스크립터의 children 필드는 연결리스트

- 연결리스트 헤드에 자식 프로세스의 task_struct 구조체의 sibling 필드 주소를 저장

- kworker/0:0H의 입장에서 mm_percpu_wq와 ksoftirpd/0 프로세스는 자신의 sibling 연결 리스트로 이어져 있음

 

 

4) 프로세스 연결 리스트

 

→ task_struct 구조체의 tasks 필드는 list_head 구조체로서 연결 리스트 타입

 

※ tasks 필드는 언제 init 프로세스의 태스크 디스크립터 필드 중 연결 리스트 타입이 tasks 필드에 등록될까?

 

▶ copy_process( ) 함수 : 프로세스를 생성할 때 호출

 

 

12번째 줄 실행 시 커널로부터 태스크 디스크립터 할당받음

13번째 줄 실ㄹ행 시 init_task.tasks 연결 리스트의 마지막 노드에 현재 프로세스의 task_struct 구조체의 tasks 주소 등록

 

▶ tasks 필드를 TRACE32로 확인해보기

 

 

6번째 줄에서 가리키는 주소의 의미는?

→ init_task.tasks 연결 리스트에 추가된 다음 프로세스의 task_struct 구조체의 tasks 필드 주소

 

 

즉, 0xA1618310 주소는 연결 리스트에 등록된 다음 프로세스 태스크 디스크립터의 next 필드 주소를 의미

→ 이 방식으로 커널에서 구동 중인 모든 프로세스의 태스크 디스크립터 주소를 알 수 있음

 

 

5. 프로세스 실행 시각 정보

 

▶ 태스크 디스크립터에서 프로세스의 실행 시각 정보를 알 수 있는 필드

 

① u64 utime : 유저 모드에서 프로세스가 실행한 시각

 

② u64 stime : 커널 모드에서 프로세스가 실행한 시각

 

③ struct sched_info sched_info.last_arrival : 프로세스 스케줄링 정보 저장

 

※ sched_info.last_arrival 필드는 언제 변경될까?

→ sched_info_arrive( ) 함수의 9번째 줄에서 업데이트됨

 

※ sched_info_arrive( ) 함수는 언제 호출될까?

context_switch( ) 함수 내에서 컨텍스트 스위칭을 수행하기 직전에 prepare_task_switch( ) 함수를 호출하는데, 이 함수를 따라가다 보면 호출됨

다음 순서로 함수 호출 후 실행

• context_switch( ) 
• prepare_task_switch( ) 
• sched_info_switch( ) 
• __sched_info_switch( ) 
• sched_info_arrive( ) 

→ 커널은 sched_info_arrive( ) 함수에서 프로세스의 실행 시간을 업데이트함

 

★ 크래시 유틸리티 프로그램 "ps -l"

→ 가장 마지막에 실행된 순서대로 프로세스 목록 출력

→ 크래시 유틸리티 프로그램 : 커널 크래시를 디버깅할 수 있는 유틸리티 프로그램

- URL: https://people.redhat.com/anderson/crash_whitepaper/ 

이는 리눅스 커널 개발자들이 자주 활용하는 프로그램이니 잘 알아두기!

 

 

4.9 스레드 정보 : thread_info 구조체

 

1) thread_info 구조체란?

 

→ 커널에서는 프로세스의 세부 실행 정보를 저장하거나 로딩하는 자료구조가 필요한데, 이를 관리함

 

▶ 프로세스의 핵심 실행 정보 저장

- 선점 스케줄링 실행 여부

- 시그널 전달 여부

- 인터럽트 컨텍스트와 Soft IRQ 컨텍스트 상태

- 휴면 상태로 진입학 직전 레지스터 세트를 로딩 및 백업

 

※ thread_info 구조체는 어디에 있을까?

→ 프로세스 스택의 최상단 주소에 위치, 프로세스마다 1개의 구조체가 있음

 

▶ 프로세스의 세부 실행 정보 저장

- 컨텍스트 정보

- 스케줄링 직전 실행했던 레지스터 세트

- 프로세스 세부 실행 정보

 

★ task_struct 구조체와 thread_info 구조체의 차이점

- task_struct : CPU 아키텍처에 독립적인 프로세스 관리용 속성을 저장

- thread_info : CPU 아키텍처에 종속적인 프로세스의 세부 속성을 저장

 

★ thread_info 구조체에서 관리하는 커널의 핵심 동작

- 현재 실행 중인 코드가 인터럽트 컨텍스트인지 여부

- 현재 프로세스가 선점 가능한 조건인지 점검

- 프로세스가 시그널을 받았는지 여부

- 컨텍스트 스케줄링 전후로 실행했던 레지스터 세트를 저장하거나 로딩

 

 

 

 

 

 

 

 

'2021 SUMMER STUDY > LINUX KERNEL' 카테고리의 다른 글

Week04_프로세스2  (0) 2021.08.15
Week03_프로세스  (0) 2021.08.09
Week02_커널 디버깅과 코드 학습  (0) 2021.07.25
Week01_리눅스 소개와 전망  (0) 2021.07.18

4.5 커널 스레드

 

1) 커널 스레드란?

 

→ 커널 프로세스는 커널 공간에서만 실행되는 프로세스로 대부분 커널 스레드 형태로 동작한다.

 

- 데몬 vs 커널 스레드

공통점 : 백그라운드 작업으로 실행되면서 시스템 메모리나 전원을 제어하는 동작 수행

차이점 : 커널 스레드는 유저 영역과 시스템 콜을 받지 않고 동작

 

★ 커널 스레드의 특징

- 커널 공간에서만 실행되며 유저 공간과 상호작용하지 않는다.

- 실행, 휴면 등 모든 동작을 커널에서 직접 제어 관리한다.

- 대부분 시스템이 부팅할 때 생성되고 종료할 때까지 백그라운드로 실행된다.

 

 

2) 커널 스레드의 종류

 

▶ ps axjf

 

커널 스레드 목록

 

① kthreadd 프로세스

→ 모든 커널의 부모 프로세스, 커널 스레드를 생성하는 역할 수행

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'2021 SUMMER STUDY > LINUX KERNEL' 카테고리의 다른 글

Week05_프로세스3  (0) 2021.08.21
Week03_프로세스  (0) 2021.08.09
Week02_커널 디버깅과 코드 학습  (0) 2021.07.25
Week01_리눅스 소개와 전망  (0) 2021.07.18

+ Recent posts