본문 바로가기
프로그래밍/해킹

시스템 해킹 퀴즈 모음

by 만디기 2024. 8. 30.

Quiz: Computer Architecture

- rax = 0x0123456789abcdef 일 때, ax의 값은? : 0xcdef

- rax = 0x0123456789abcdef 일 때, ah의 값은? : 0xcd

- rax = 0x0123456789abcdef 일 때, eax의 값은? : 0x89abcdef

- rax = 0x0123456789abcdef 일 때, al의 값은? : 0xef

- rax에서 rbx를 뺐을 때, ZF가 설정되었다. rax와 rbx의 대소를 비교하시오. : ==

(ZF(Zero Flag)는 결과가 0일 때 설정된다.)

Quiz: Linux Memory Layout

#include <stdlib.h>
int a = 0xa;
const char b[] = "d_str";
int c;
int foo(int arg) {
  int d = 0xd;
  return 0;
}
int main()
{
  int *e = malloc(sizeof(*e));
  return 0;
}

 

- d가 위치하는 세그먼트 : 스택

- e는 어느 세그먼트의 데이터를 가리키는가? : 힙

- foo(함수)가 위치하는 세그먼트는 어디인가? : 코드

- a(전역 변수)가 위치하는 세그먼트는 어디인가? : 데이터

- b(const)가 위치하는 세그먼트는 어디인가? : 읽기 전용 데이터 (rodata),

   ㄴ Rodata는 해석하면 Read only data로, 읽기전용인 data들이 들어간다. 주로 상수, 상수형 문자열, printf의 중괄호 부분이 들어가며, 오직 읽기 전용이다.

- c(초기화되지 않은 전역 변수)가 위치하는 세그먼트는 어디인가? : BSS

   ㄴ BSS 영역은 초기화 하기 전의 전역변수를 저장하고, 데이터 영역은 초기화 한 전역 변수를 저장한다.

- "d_str"이 위치하는 세그먼트는 어디인가? : 읽기 전용 데이터 (rodata)

 

Quiz: x86 Assembly 2

[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

- end로 점프하면 프로그램이 종료된다고 가정하자. 프로그램이 종료됐을 때, 0x400000 부터 0x400019까지의 데이터를 대응되는 아스키 문자로 변환하면 어느 문자열이 나오는가?

 

1) 첫번째 시도

[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]	; dl = 0x67 => rsi+rcx = 0x400000 주소의 값
2: xor dl, 0x30			; dl = 0x57 => dl 값과 0x30을 xor 한 값
3: mov BYTE PTR[rsi+rcx], dl	; BYTE PTR[0x400000] = 0x57 => 0x400000 주소의 값에 0x57 대입
4: inc rcx			; rcx = 0x1 => rcx 값 1 증가
5: cmp rcx, 0x19		; rcx < 0x19 (false) => 플래그 설정
6: jg end			; 직전에 비교한 두 피연산자 중 전자가 더 크면 end로 점프
7: jmp 1			; 1로 되돌아감

 

- 이런 식으로 반복하면 0x400019까지의 각 주소에 있는 값에 0x30을 xor한 값이 들어가며, 그 결과는 다음과 같다.

[Memory]
0x400000 | 0x57 0x65 0x6c 0x63 0x6f 0x6d 0x65 0x20
0x400008 | 0x74 0x6f 0x20 0x61 0x73 0x73 0x65 0x6d
0x400010 | 0x62 0x6c 0x79 0x20 0x77 0x6f 0x72 0x6c
0x400018 | 0x64 0x21 0x00 0x00 0x00 0x00 0x00 0x00

 

Quiz: x86 Assembly 3

[Code]
main:
    push rbp				// rbp를 스택 상단에 넣는다
    mov rbp, rsp			// rbp에 rsp 값을 대입
    mov esi, 0xf			// esi = 0xf
    mov rdi, 0x400500			// rdi = 0x400500
    call 0x400497 <write_n>		// write_n 함수를 실행	
    mov eax, 0x0			// eax = 0x0
    pop rbp				// 스택 상단값 제외
    ret					// 반환
    
write_n:
    push rbp				// rbp를 스택 상단에 넣는다
    mov rbp, rsp			// rbp = rsp
    mov QWORD PTR [rbp-0x8],rdi		// QWORD PTR [rbp-0x8] = 0x400500
    mov DWORD PTR [rbp-0xc],esi		// DWORD PTR [rbp-0xc] = 0xf
    xor rdx, rdx			// rdx = 0x0
    mov edx, DWORD PTR [rbp-0xc]	// edx = 0xf
    mov rsi,QWORD PTR [rbp-0x8]		// rsi = 0x400500
    mov rdi, 0x1			// rdi = 0x1
    mov rax, 0x1			// rax = 0x1
    syscall				// system call
    pop rbp				// 스택 상단값 제외
    ret					// 반환
    
==================================    
[Memory]
0x400500 | 0x3037207964343372
0x400508 | 0x003f367562336420

 

- 다음 어셈블리 코드를 실행했을 때 출력되는 결과로 올바른 것은?

1) rax에 0x1을 넣고 System Call을 하는 것으로 write System Call임을 알 수 있다.

2) 시스템 콜 테이블은 아래 표를 참조.

3) rdi = 0x1, rsi = 0x400500, rdx = 0x0 후 edx = 0xf (edx는 rdx의 하위 32비트이다.)

4) write(0x1(stdout), 0x400500(출력할 데이터의 주소값), 0xf(문자열의 길이))

5) x86 아키텍처는 리틀 엔디안 방식(낮은 주소에 데이터의 끝 부분을 저장하는 방식)이므로,

72 33 34 64 79 20 37 30 / 20 64 33 62 75 36 3f를 해석하면 답이 나온다.

 

 

Quiz: Calling Convention

// Name: callconv_quiz.c
// Compile: gcc -o callconv_quiz callconv_quiz.c -m32
int __attribute__((cdecl)) sum(int a1, int a2, int a3){
	return a1 + a2 + a3;
}
void main(){
	int total = 0;
	total = sum(1, 2, 3);
}
main:   
   0x080483ed <+0>:	push   ebp
   0x080483ee <+1>:	mov    ebp,esp
   0x080483f0 <+3>:	sub    esp,0x10					
   0x080483f3 <+6>:	mov    DWORD PTR [ebp-0x4],0x0		// total 변수 할당(esp 값만큼 공간 할당 - 스택 특성상 위로 자람(주소 값을 - 함))
   0x080483fa <+13>:	(a)					// push 0x3
   0x080483fc <+15>:	(b)					// push 0x2
   0x080483fe <+17>:	(c)					// push 0x1
   0x08048400 <+19>:	call   0x80483db <sum>			// sum 함수 호출
   0x08048405 <+24>:	(d)					// add esp, 0xc => sum 함수 호출 이후 스택 공간 정리 - esp 값에 할당한 공간 크기(0xc - 12 Byte)만큼 더해 스택 공간 정리
   0x08048408 <+27>:	mov    DWORD PTR [ebp-0x4],eax
   0x0804840b <+30>:	nop
   0x0804840c <+31>:	leave  
   0x0804840d <+32>:	ret

 

- 호출 규약으로 cdecl을 사용함을 확인할 수 있다. 즉, 이는 x86 바이너리를 컴파일한 것이다.

- ebp : 현재 스택 프레임의 베이스 주소(최하단 주소)를 저장하고 있는 레지스터

- esp : 현재 스택의 최상단 주소를 저장하고 있는 레지스터

- 함수의 인자들은 스택에 거꾸로 push 된다.

- x86 아키텍처의 cdecl에서는 호출자가 스택 프레임을 정리한다.

 

 

// Name: callconv_quiz2
// Compile: gcc -o callconv_quiz2 callconv_quiz2.c
int sum(int a1, int a2, int a3){
	return a1 + a2 + a3;
}
void main(){
	int total = 0;
	total = sum(1, 2, 3);
}
main:
   0x00000000004004f2 <+0>:	push   rbp
   0x00000000004004f3 <+1>:	mov    rbp,rsp
   0x00000000004004f6 <+4>:	sub    rsp,0x10
   0x00000000004004fa <+8>:	mov    DWORD PTR [rbp-0x4],0x0
   0x0000000000400501 <+15>:	(a)					// mov edx, 0x3
   0x0000000000400506 <+20>:	(b)					// mov esi, 0x2
   0x000000000040050b <+25>:	(c)					// mov edi, 0x1
   0x0000000000400510 <+30>:	call   0x4004d6 <sum>
   0x0000000000400515 <+35>:	mov    DWORD PTR [rbp-0x4],eax
   0x0000000000400518 <+38>:	nop
   0x0000000000400519 <+39>:	leave  
   0x000000000040051a <+40>:	ret

 

- 문제에서 SYSV를 적용하여 컴파일했다고 했으므로, 해당 바이너리는 x86-64 바이너리를 컴파일한 것이다.

- 여기서는 함수 인자를 rdi, rsi, rdx, rcx, r8, r9 에 차례로 저장하고 나머지 인자들은 스택에 push한다.

- 따라서 인자는 거꾸로 edx, esi, edi 순으로 push 된다. 인자의 크기가 작기 때문에 rdx, rsi, rdi의 하위 비트에 저장된다.

 

Quiz: Static Link vs. Dynamic Link

- runtime resolve를 하기 위한 코드는 plt에 들어 있다.

- PLT에서 GOT를 참조하여 실행 흐름을 옮기는 과정에는 어떠한 보호 기법도 존재하지 않는다.

- 정적 링크된 바이너리에서 처음 함수 func를 호출하면, runtime resolve를 거칠 필요가 없다. (모든 함수 코드가 이미 실행 파일에 포함되어 있다.)

- 동적 링크된 바이너리는 정적 링크된 바이너리에 비해 바이너리의 크기가 작다는 장점이 있다.

 

Quiz: RELRO

- No RELRO는 어떠한 RELRO 보호기법도 적용되지 않는 상태를 의미한다.

- Full RELRO에서는 .got 영역을 조작하여 실행 흐름을 조작할 수 없다.

- RELRO는 RELocation Read-Only의 줄임말이다.

- Partial RELRO에서는 .fini_array를 조작하여 실행 흐름을 조작할 수 없다. (Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 하기 어려워진다. 하지만 .got.plt 영역에는 쓰기 권한이 존재한다.)

 

Quiz: PIE

- Shared Object는 실행할 수 있다.

- PIE가 적용된 바이너리는 무엇인가?

/bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2f15ad836be3339dec0e2e6a3c637e08e48aacbd, for GNU/Linux 3.2.0, stripped

- PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재된다. 반대로, ASLR이 적용되지 않은 시스템에서는 PIE가 적용된 바이너리더라도 무작위 주소에 적재되지 않는다.

 

Quiz: Out of Bounds

#include <stdio.h>
int main() {
  int buf[0x10];
  unsigned int index;
  
  scanf("%d", &index);
  [A]				// if (index >= 0x10) {exit(-1);} : 인덱스가 0x10을 넘어가지 않도록 함
  printf("%d\n", buf[index]);
  return 0;
}

 

- OOB 취약점을 방어하기 위해 [A] 위치에 들어갈 올바른 검증 코드는?

 

Quiz: ptmalloc2

- smallbin의 청크를 재할당할 때, unlink과정을 수행해야 한다.

- 청크를 재할당 할 때, bin를 먼저 비운 뒤 tcache에서 청크를 꺼낸다. - X

- largebin에 보관할 수 있는 청크의 갯수는 10개로 제한된다. - X

- 메모리 파편화를 막기위해 연속된 fastbin의 청크는 병합된다. - X, smallbin

 

Quiz: Double Free Bug

- 다음 중 double free가 발생한 것으로 가장 의심되는 free list를 고르시오.

=> 0x55c2efc412c0 ← 0x55c2efc41200 ← 0x55c2efc41250 ← 0x55c2efc412c0 (중복된 주소가 free list에 들어가 있음)

 

Quiz: SECCOMP

- Strict Mode : read, write, exit, sigreturn 시스템 콜의 호출만을 허용한다.

- SECCOMP 의 ALLOW LIST

  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);

 

- BPF(Berkeley Packet Filter) :

BPF_LD, BPF_JMP, BPF_JEQ, BPF_RET, BPF_STMT, BPF_JUMP

 

Quiz: Master Canary

- TLS : Thread Local Storage

- TLS의 0x28 오프셋에 마스터 카나리가 저장된다.

- gdb에서 FS에 주소를 참조하기 위한 키워드는? : $fs_base

- FS를 초기화하기 위해 사용하는 시스템 콜 : arch_prctl

 

Quiz: _rtld_global

- 프로그램이 종료될 때 호출되는 함수 : __GI_exit, _dl_fini, __rtld_lock_lock_recursive

- _rtld_global 구조체가 위치하는 영역 : /lib/x86_64-linux-gnu/ld-2.27.so

 

Quiz: SigReturn-Oriented Programming

- 다음 중 올바르지 않은 시그널 종류는? : SIGEXIT

include/linux/signal.h 일부

*	+--------------------+------------------+
 *	|  POSIX signal      |  default action  |
 *	+--------------------+------------------+
 *	|  SIGHUP            |  terminate	|
 *	|  SIGINT            |	terminate	|
 *	|  SIGQUIT           |	coredump 	|
 *	|  SIGILL            |	coredump 	|
 *	|  SIGTRAP           |	coredump 	|
 *	|  SIGABRT/SIGIOT    |	coredump 	|
 *	|  SIGBUS            |	coredump 	|
 *	|  SIGFPE            |	coredump 	|
 *	|  SIGKILL           |	terminate(+)	|
 *	|  SIGUSR1           |	terminate	|
 *	|  SIGSEGV           |	coredump 	|
 *	|  SIGUSR2           |	terminate	|
 *	|  SIGPIPE           |	terminate	|
 *	|  SIGALRM           |	terminate	|
 *	|  SIGTERM           |	terminate	|
 *	|  SIGCHLD           |	ignore   	|
 *	|  SIGCONT           |	ignore(*)	|
 *	|  SIGSTOP           |	stop(*)(+)  	|
 *	|  SIGTSTP           |	stop(*)  	|
 *	|  SIGTTIN           |	stop(*)  	|
 *	|  SIGTTOU           |	stop(*)  	|
 *	|  SIGURG            |	ignore   	|
 *	|  SIGXCPU           |	coredump 	|
 *	|  SIGXFSZ           |	coredump 	|
 *	|  SIGVTALRM         |	terminate	|
 *	|  SIGPROF           |	terminate	|
 *	|  SIGPOLL/SIGIO     |	terminate	|
 *	|  SIGSYS/SIGUNUSED  |	coredump 	|
 *	|  SIGSTKFLT         |	terminate	|
 *	|  SIGWINCH          |	ignore   	|
 *	|  SIGPWR            |	terminate	|
 *	|  SIGRTMIN-SIGRTMAX |	terminate       |
 *	+--------------------+------------------+
 *	|  non-POSIX signal  |  default action  |
 *	+--------------------+------------------+
 *	|  SIGEMT            |  coredump	|
 *	+--------------------+------------------+

 

- 컨텍스트 스위칭이 일어날 때 커널 코드의 실행을 마친 후, 기억한 정보를 되돌려 복귀할 때 사용하는 syscall 이름은 무엇인가? : sigreturn

- restore_sigcontext 함수에서 복귀할 때 컨텍스트를 복구하기 위해 사용되는 구조체는 무엇인가? : sigcontext

 

Quiz: _IO_FILE

- 다음 중 _IO_jump_t 구조체의 함수 테이블 멤버 함수가 아닌 것은? : _IO_flush_t

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

 

- 리눅스 시스템의 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체 : _IO_FILE

- _IO_FILE 구조체에서 _flags 필드 중 _IO_MAGIC 의 값은 무엇인가? : 0xFBAD0000

'프로그래밍 > 해킹' 카테고리의 다른 글

간단한 해킹 문제 모음  (2) 2024.09.04
리버싱 퀴즈 모음  (0) 2024.08.31
웹 해킹 퀴즈 모음  (0) 2024.08.30
Format String Bug  (0) 2024.08.18
PLT / GOT / libc database  (0) 2024.08.15