728x90

문제 코드를 살펴보면 scanf("%s",A)에서 입력된 문자열을 변수 'A'에 저장하는데, 이때 길이에 대한 제한이 없다. 따라서 지정된 문자열 길이인 8을 넘기게 되면 변수 'B'의 메모리 영역에 덮어써지게 된다. 

이제 어셈블리 코드를 살펴보자.


   <+0>:     endbr64

 

// int main()

   <+4>:     push   rbp
   <+5>:     mov    rbp,rsp
   <+8>:     sub    rsp,0x20

 

//canary 저장
   <+12>:    mov    rax,QWORD PTR fs:0x28
   <+21>:    mov    QWORD PTR [rbp-0x8],rax

 

// eax 0으로 초기화
   <+25>:    xor    eax,eax

 

// 지역 변수 'A'  초기화, 입력을 받아 'A'에 저장
   <+27>:    mov    QWORD PTR [rbp-0x10],0x0
   <+35>:    lea    rax,[rbp-0x18]
   <+39>:    mov    rsi,rax
   <+42>:    lea    rdi,[rip+0xe0e]        # 0x2008
   <+49>:    mov    eax,0x0
   <+54>:    call   0x10d0 <__isoc99_scanf@plt> // scanf 함수 호출

 

// 입력 받은 값 출력
   <+59>:    lea    rax,[rbp-0x10]
   <+63>:    mov    rsi,rax
  <+66>:    lea    rdi,[rip+0xdf9]        # 0x200b
  <+73>:    mov    eax,0x0
  <+78>:    call   0x10b0 <printf@plt>

 

// strcmp를 통해 A와 "I.Sly"를 비교
  <+83>:    lea    rax,[rbp-0x10]
   <+87>:    lea    rsi,[rip+0xdf0]        # 0x2017
   <+94>:    mov    rdi,rax
   <+97>:    call   0x10c0 <strcmp@plt>

 

// 비교 결과에 따라 분기
   <+102>:   test   eax,eax
   <+104>:   jne    0x124d <main+132>

 

// 문자열이 "I.Sly"인 경우 "Good!" 메시지와 플래그 문자열을 출력
   <+106>:   lea    rdi,[rip+0xde3]        # 0x201d
   <+113>:   call   0x1090 <puts@plt>

   <+118>:   lea    rdi,[rip+0xde2]        # 0x2028
   <+125>:   call   0x1090 <puts@plt>

 

// 문자열이 "I.Sly"가 아닌 경우 다음 단계로 분기
   <+130>:   jmp    0x1264 <main+155>

 

// 입력 값이 비어 있지 않은 경우 "You're doing it right!" 메시지를 출력
   <+132>:   movzx  eax,BYTE PTR [rbp-0x10]
   <+136>:   movzx  eax,al
   <+139>:   test   eax,eax
  <+141>:   je     0x1264 <main+155>
   <+143>:   lea    rdi,[rip+0xdfb]        # 0x205a
   <+150>:   call   0x1090 <puts@plt>

 

// 함수가 정상적으로 종료되는지 확인하고 스택 프레임을 정리, 스택 보호 검사 후 함수에서 반환
   <+155>:   mov    eax,0x0
   <+160>:   mov    rdx,QWORD PTR [rbp-0x8]
   <+164>:   xor    rdx,QWORD PTR fs:0x28
  <+173>:   je     0x127d <main+180>
   <+175>:   call   0x10a0 <__stack_chk_fail@plt>
   <+180>:   leave
   <+181>:   ret

 

스택 프레임 구조를 그려보면 위와 같다. 

문자열 길이인 8을 넘기게 되면 B에 데이터가 덮어써진다는 점을 이용하여 I.Sly 앞에 A 8개을 붙여 입력해주었다. [check] 뒤에 출력된 결과를 보아 변수 B에 I.Sly가 저장된 것을 확인할 수 있다. 필요한 조건을 충족하여 flag가 출력된 것을 볼 수 있다. 

728x90

1. Pwnable

Pwnable(시스템 해킹)

  • 운영체제 또는 프로그램 취약점을 찾아 공격하고, 관리자의 권한 등을 얻어 시스템을 장악하거나 의도치 않은 행동을 하게 하는 것
  • 컴퓨터 프로그램의 행위를 조작하여 공격자가 원하는 행동을 실행하도록 하는 공격
  • 궁극적으로 관리자 권한 탈취가 공격의 목표

취약점 종류

  • Memory Corruption : Buffer Overflow, Out-Of-Boundary, Off-by-one, Format String Bug, Double Free/User-After-Free
  • Logical Bug : Command Injection, Race Condition, Path Traversal(경로 탐색)

해킹 과정

  • 취약점 발견 -> 공격 시나리오 작성 -> 필요 정보 수집 및 공격 -> 권한 탈취

Pwntools

  • 리눅스 환경에서 Exploit(공격)을 보다 편리하게 할 수 있게 여러가지 함수와 기능을 제공하는 파이썬 라이브러리
  • 주요 함수
    • remote, process, ssh : 통신할 때 사용
    • send, sendline, sendafter, sendlineafter : 데이터 송신
    • recv, recvline, recvuntill: 데이터 수신
    • p32 : 32비트 리틀 엔디안으로 패킹

Shellcode

  • 시스템의 특징 명령(shell)을 실행하는 작은 사이즈의 프로그램
  • 기계어로 작성되며, 메모리에 올리면 바로 실행됨
  • 주로 BOF, FSB 공격에 사용

2. Buffer Overflow

Buffer Overflow(=Buffer Overrun)

  • 메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점
  • 지정된 버퍼를 벗어난 데이터를 다른 데이터로 덮어쓸 수 있음
  • Buffer : 지정된 크기의 메모리 공간
  • Stack Buffer Overflow : 스택에 위치한 버퍼에 할당된 것보다 많은 데이터를 쓸 때 발생, 함수의 return 주소를 조작하는 것이 일반적
  • Heap Buffer Overflow : 동적 메모리 할당 연결을 덮어써 프로그램의 함수 포인터 조작, 포인터가 가리키는 값을 조작하여 임의의 파일 또는 코드에 접근

3. Stack Frame

Stack Frame 레지스터 : 스택 오버플로우와 관련된 x86(32bit) 레지스터

  • 범용 레지스터 : EAX, EBX, ECX, EDX
  • 스택 포인터 레지스터 : 실제 메모리상의 주소를 참조할 때 SS(Stack Segmaent) 레지스터와 함께 사용
    • ESP : 현재 스택 영역에서 가장 하위 주소를 저장
    • EBP : 스택 세그먼트에서 현재 호출되어 사용되는 함수의 시작 주소 값 지정
  • 명령어 포인터 레지스터 : EIP 또는 PC, 유효주소를 계산하는데 필요한 주소 저장
  • 상태 레지스터 : EFLAGS, x86 프로레서의 상태를 저장하기 위해 사용
  • 세그먼트 레지스터 : 각 세그먼트의 위치를 가리킴, 물리 주소 변환에 사용
    • 코드 세그먼트 (CS) : 실행 가능한 명령어 코드가 존재하는 세그먼트의 시작 주소
    • 스택 세그먼트 (SS) : 스택이 존재하는 세그먼트의 시작 주소
    • 데이터 세그먼트 (DS) : 사용되는 데이터가 존재하는 세그먼트의 시작 주소
    • ES, FS, GS : 추가 세그먼트의 시작주소로, FS는 통상 스레드 관리용도로 사용

Stack Frame

  • 스택의 데이터 : 높은 주소에서 낮은 주소로
  • 데이터 작성 : 낮은 주소에서 높은 주소로
  • Buffer Overflow : 프로세스가 데이터를 버퍼에 저장할 때 프로그래머가 지정한 곳 바깥에 저장하는 것

728x90

풀이

main 함수이다. sub_140001000 함수를 살펴봐야할 것 같다.

a1에 -5를 곱한 값이 byte_140003000의 값과 일치하면 된다. 그런데 여기서 unsigned 값을 사용하기 때문에 쉽게 구할 수 는 없을 것 같다. 

byte_140003000 값을 얻어오자.

a = "AC F3 0C 25 A3 10 B7 25 16 C6 B7 BC 07 25 02 D5 C6 11 07 C5 00 00 00 00 00 00 00 00 00 00 00 00"
a = a.split()[:21]
a = [int(x, 16) for x in a]

tmp = []
for t in a:
     for x in range(256):  
         if (x * 251) % 256 == t:  
             tmp.append(x)
             break


for i in range(21):
    print(chr(tmp[i]),end='')

modular inverse 값을 얻어서 어찌저찌하면 될 것 같지만 일단 확실한 전수조사를 해보는 코드를 작성해봤다. 여기서 -5는 %256상에서 251이므로 그 값과 곱하여 t가 되는 x값을 구하는 코드를 작성했다.

파이썬 코드를 실행시켜보면 이와 같은 결과가 나온다. 

파일을 실행해 확인해보면 맞는 값인 것을 확인할 수 있다. 

 
 
** 모듈러 역수를 이용해서 구하는 코드 
tmp = []
inv = pow(-5, -1, 256)

for t in a:
    tmp.append((t*inv)%256)

for i in range(21):
    print(chr(tmp[i]),end='')

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] secure-mail  (0) 2024.05.20
[Dreamhack] rev-basic-9  (1) 2024.05.18
[Dreamhack] rev-basic-7  (0) 2024.05.14
[Dreamhack] rev-basic-6  (0) 2024.05.14
[Dreamhack] rev-basic-5  (0) 2024.05.14
728x90

풀이

main 함수이다. sub_140001000 함수를 살펴봐야할 것 같다.

__ROL1__(*(_BYTE *)(a1 + i), i & 7)는 읽은 바이트를 왼쪽으로 (i & 7) 비트만큼 회전하는 코드이다. 여기서 & 7 연산은 i 값을 8로 나눈 나머지를 구하는 것과 같으므로, 회전 수는 항상 0에서 7 사이가 된다. __ROL1__은 "Rotate Left" 연산을 의미한다. 해당 연산을 한 후 i와 xor한 값이 byte_140003000 값과 일치하면 조건에 맞는 값이다. 

byte_140003000을 얻어오자.

 

a = "52 DF B3 60 F1 8B 1C B5 57 D1 9F 38 4B 29 D9 26 7F C9 A3 E9 53 18 4F B8 6A CB 87 58 5B 39 1E"
a = a.split()
a = [int(x, 16) for x in a]

tmp = []
for i in range(31):
    k = a[i]^i
    r = i%8
    k = ((k >> r) | (k << (8 - r))) & 0xFF
    tmp.append(k)

for i in range(31):
    print(chr(tmp[i]),end='')

byte_140003000에 위치한 배열값과 i를 xor하고 그 값을 i%8 만큼 rotate right 연산을 해주면 원하는 값을 얻을 수 있다. 해당 연산을 파이썬 코드로 작성했다. 

파이썬 코드를 실행시켜보면 이와 같은 결과가 나온다. 

파일을 실행해 확인해보면 맞는 값인 것을 확인할 수 있다. 

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] rev-basic-9  (1) 2024.05.18
[Dreamhack] rev-basic-8  (0) 2024.05.14
[Dreamhack] rev-basic-6  (0) 2024.05.14
[Dreamhack] rev-basic-5  (0) 2024.05.14
[Dreamhack] rev-basic-4  (0) 2024.05.13
728x90

풀이

main 함수이다. 필요한 sub_140001000 함수를 살펴보자.

sub_140001000를 살펴보면 byte_140003020 배열의 a1[i]번째의 값이 byte_140003000[i] 값과 동일하면 correct가 출력된다는 것을 알 수 있다. 

 

 

byte_140003020 값과 byte_140003000 값을 가져온다

a = "63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A A0 52 3B D6 B3 29 E3 2F 84 53 D1 00 ED 20 FC B1 5B 6A CB BE 39 4A 4C 58 CF D0 EF AA FB 43 4D 33 85 45 F9 02 7F 50 3C 9F A8 51 A3 40 8F 92 9D 38 F5 BC B6 DA 21 10 FF F3 D2 CD 0C 13 EC 5F 97 44 17 C4 A7 7E 3D 64 5D 19 73 60 81 4F DC 22 2A 90 88 46 EE B8 14 DE 5E 0B DB E0 32 3A 0A 49 06 24 5C C2 D3 AC 62 91 95 E4 79 E7 C8 37 6D 8D D5 4E A9 6C 56 F4 EA 65 7A AE 08 BA 78 25 2E 1C A6 B4 C6 E8 DD 74 1F 4B BD 8B 8A 70 3E B5 66 48 03 F6 0E 61 35 57 B9 86 C1 1D 9E E1 F8 98 11 69 D9 8E 94 9B 1E 87 E9 CE 55 28 DF 8C A1 89 0D BF E6 42 68 41 99 2D 0F B0 54 BB 16"
a = a.split()


b = "00 4D 51 50 EF FB C3 CF 92 45 4D CF F5 04 40 50 43 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
b = b.split()[:18]

tmp = []

for i in range(18):
    k = a.index(b[i])
    tmp.append(k)

for i in range(18):
    print(chr(tmp[i]),end='')

b( byte_140003000 ) 값을 가져와서 a ( byte_140003020 ) 리스트에서 찾아 그 인덱스를 tmp에 저장하는 코드를 작성하였다. 

파이썬 코드를 작동시켜보면 이와 같은 결과가 나온다. 

파일을 실행해 확인해보면 맞는 값인 것을 확인할 수 있다. 

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] rev-basic-8  (0) 2024.05.14
[Dreamhack] rev-basic-7  (0) 2024.05.14
[Dreamhack] rev-basic-5  (0) 2024.05.14
[Dreamhack] rev-basic-4  (0) 2024.05.13
[Dreamhack] rev-basic-3  (0) 2024.05.13
728x90

풀이

main 함수를 보자. 이번에는 sub_140001000 함수를 봐야한다. 

함수를 살펴보면 a[i]+a[i+1] 이 byte_140003000의 배열값과 일치하면 된다.

 

여기서 생각해보면 (byte_140003000의 배열값이 data라고 할 때)

data[0] = a[0] + a[1]

data[1] = a[1] + a[2]

...

data[23] = a[23] + a[24]

이다.

 

data[23] = 0 인데 a는 unsigned integer이니까 a[23] = a[24] = 0 이어야 data[23] = 0일 수 있는 것을 알 수 있다.

 

byte_140003000에서의 값을 찾아오자. 

hex_string = "AD D8 CB CB 9D 97 CB C4 92 A1 D2 D7 D2 D6 A8 A5 DC C7 AD A3 A1 98 4C 00 00 00 00 00 00 00 00 00"

hex_values = hex_string.split()[:24]

decimal_values = [int(x, 16) for x in hex_values]

data = decimal_values[::-1]

tmp = [0]

for i in range(24):
    a = data[i]-tmp[i]
    tmp.append(a)

for i in range(24,0,-1):
    print(chr(tmp[i]),end='')

그리고 위의 연산을 반대로 하는 파이썬 코드를 작성했다. 

실행하면 위와 같은 값을 확인할 수 있다.

그리고 확인해보면 맞는 값인 것을 알 수 있다. 

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] rev-basic-7  (0) 2024.05.14
[Dreamhack] rev-basic-6  (0) 2024.05.14
[Dreamhack] rev-basic-4  (0) 2024.05.13
[Dreamhack] rev-basic-3  (0) 2024.05.13
[Dreamhack] rev-basic-2  (0) 2024.05.13
728x90

풀이

main 함수이다. sub_140001000 함수을 살펴보자.

함수를 살펴보면 byte_140003000에 위치한 배열과 a1의 상위 4비트, 하위 4비트의 위치를 바꾼 값과 비교해서 일치하면 되는 것을 확인할 수 있다. 

byte_140003000의 배열값을 복사하고, 파이썬 코드를 작성하자. 배열값의 상위 4비트, 하위4비트의 위치를 바꾸면 원하는 값을 얻을 수 있을 것이다. 

data = bytes.fromhex("24 27 13 C6 C6 13 16 E6 47 F5 26 96 47 F5 46 27 13 26 26 C6 56 F5 C3 C3 F5 E3 E3 00 00 00 00 00")

tmp = []

for i in range(28):
    a = (data[i]<<4|data[i]>>4)%256
    tmp.append(chr(a))
    
for i in range(28):
    print(tmp[i],end='')

여기서 ascii 코드 변환을 제대로 하기 위해서 %256도 해줘야한다.

결과는 이렇게 나왔다. 

파일을 실행하여 확인해보면 찾던 입력값인 것을 확인할 수 있다. 

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] rev-basic-6  (0) 2024.05.14
[Dreamhack] rev-basic-5  (0) 2024.05.14
[Dreamhack] rev-basic-3  (0) 2024.05.13
[Dreamhack] rev-basic-2  (0) 2024.05.13
[Dreamhack] simple-operation  (0) 2024.05.13
728x90

풀이

main 함수를 확인해보자. 이번에는 sub_140001000 함수를 확인해보면 correct가 출력되는 입력값을 알 수 있다. 

함수를 살펴보면 24까지 for문이 돌고 있고 byte_140003000에 위치한 배열과 (i^a1)+2*i 값을 비교하고 있다. 그러면 우리가 원하는 값은 주소값에 위치한 배열값을 x라고 할 때,  (x-2*i)^i 인 것을 알 수 있다. 그러면 이제 x를 구하고 연산하는  파이썬 코드를 작성해보자. 

byte_140003000로 이동해보면 배열 값을 확인할 수 있다. 

Hex View로 더 보기좋게 확인할 수 있다. 

이 바이트값을 복사해서 연산을 해주자.

 

data = bytes.fromhex("49 60 67 74 63 67 42 66 80 78 69 69 7B 99 6D 88 68 94 9F 8D 4D A5 9D 45 00 00 00 00 00 00 00 00")

tmp = []

for i in range(24):
    a = (data[i]-2*i)^i
    tmp.append(chr(a))
    
for i in range(24):
    print(tmp[i],end='')

이렇게 코드를 작성해서 실행해주면 아래와 같이 입력값을 확인할 수 있다. 

 

파일을 실행해서 확인해보면 맞는 입력값을 찾았음을 알 수 있다. 

'Write-up > Reversing' 카테고리의 다른 글

[Dreamhack] rev-basic-5  (0) 2024.05.14
[Dreamhack] rev-basic-4  (0) 2024.05.13
[Dreamhack] rev-basic-2  (0) 2024.05.13
[Dreamhack] simple-operation  (0) 2024.05.13
[Dreamhack] rev-basic-1  (0) 2024.05.13

+ Recent posts