반응형

본인은 BoB 프로젝트 도중 딥러닝을 이용하는 프로젝트를 진행하였다.

관련 지식을 공부하던 중 AI에 대한 흥미가 생겨 앞으로 관련된 포스팅을 여럿 해보고자 한다.


1. 개요

 다들 아시다 싶이 현재는 바야흐로 AI 전성시대이다.

많은 제품들에서 AI를 사용하고 있으며, 앞으로는 더 많아질 것이다.

일반 제품뿐만아니라 안랩, 이스트시큐리티에서도 AI 스타트 기업을 인수하거나

AI를 적용한 백신을 출시하는 등 관심도가 높아지고 있다.

 

컨퍼런스에서도 이를 알 수 있는데, 최근 기술, 보안 컨퍼런스만 보아도 AI에 대한 다양한 주제를 다루고 있다.

 

왼쪽부터 2019 삼성SDS컨퍼런스, 삼성테크토닉컨퍼런스, 데프콘 컨퍼런스

이처럼 많은 영역에선 인공지능에 대해 다루고, 연구하고 있다.

슬픈 이야기지만 앞으로 우리들이 AI를 공부하지 않으면 조금씩 뒤쳐질 것이라 생각한다.

이제부터 우리는 AI가 무엇이고, 인공지능과 딥러닝이 무엇인지 알고있어야한다.

본 포스팅에선 이를 '간략히' 설명하고자한다.

 

 

2. 인공지능

인공지능 용어 창안자(위키피디아)

"인공지능? 뭔가 먼 미래의 기술같아."

 

인공지능(Artificial Intellignece)은 사실 최근에 등장한 개념이 아니다. 1956년에 존매카시 교수가 개최한 다트머스 회의에서 처음 등장한 개념이며, 지금까지 계속된 연구활동 끝에 최근들어 주목받게 된것이다.

 

인공지능은 다양한 정의가 존재하며 '사람의 지능을 필요로하는 작업을 수행할 수 있는 컴퓨터 시스템'이라고 보면 된다.

이후 연구 활동이 지속되며 다양한 방법론이 등장하였고 이 과정에서 등장한 것이 머신러닝과 딥러닝이다.



3. 머신러닝

머신러닝은 '알고리즘을 이용하여 데이터를 분석하고, 학습하여 이러한 내용을 기반으로 판단, 예측하는 기술'을 뜻한다.

일반적인 프로그램은 직접 과정에 대한 코딩을 진행하여, 결과를 얻어내지만 머신러닝은 그렇지 않다.

 

그림으로 설명하면 다음과 같다.

기존 프로그래밍에선 데이터를 가지고 프로그래밍을 하여 답이란 결과를 얻어낸다.

하지만 머신러닝은 데이터와 답을 가지고 학습하고 교육하여 규칙을 도출해낸다.

그래서 이 규칙을 가지고 향후 새로운 데이터에 대해 규칙을 적용하여 예측하고 판단하는 결과를 낼 수 있다.


4. 딥러닝

딥러닝은 머신러닝연구자들이 만들어낸 하나의 개념이다.

딥러닝은 우리들 '뇌의 뉴련의 형태 정보 입출력 계층을 만들어 데이터를 학습하는 방식'이다.

인공신경망에서 발전한 형태라 보면 된다.

 

물론 처음에 엄청난 데이터량과 연산을 필요로하여 관련된 연구가 활발하지 않았다.

그러나 최근 하드웨어 성능과 알고리즘, 인터넷에 대한 빠른 발전으로 딥러닝 연구가 활기를 찾았다.

이러한 과정에서 CNN(Convolutional neural network), RNN(Recurrent Neural Network) 등 다양한 신경망 알고리즘이 등장하여 지금까지도 연구되고 있다.

 

 

심층적이고 논리적인 구조로 여러번의 과정과 학습을 거치는 기술인 만큼 높은 예측, 판단률(정확도)를 보이고 있다.

AI 기술의 핵심은 정확한 예측, 판단률이다. 그래서 현재 딥러닝이 가장 주목을 받고 있다.

 

5. 마치며

이제 우리는 딥러닝이 머신러닝에서 파생된 것이고, 머신러닝은 미래를 예측하고 판단하기 위한 기술이라는 것을 알았다. 현재 이러한 기술을 통해 글자인식, 목소리 인식이 가능하며 암을 예측하고, 심지어 폐 사진으로 코로나를 진단하고 있다.

 

 

이처럼 우리도 각자 이러한 기술을 어디에 사용할 수 있을지 고민해보는 시간을 가졌으면 좋겠다.


앞으로 계속해서 AI 관련 포스팅을 진행할 것이다.

반응형
반응형

드디어 PE(Portable Executable) 파일 포맷 포스팅을 하고자한다.

내용이 길것으로 예상되나 중요한 파트이므로 천천히 짚고 넘어가자.


1. PE 포맷?

PE 포맷이란 Portable Executable 약자로 윈도우 운영체제에서 사용되는 실행 가능한 파일 형태를 말한다. 

아래서 설명할 PE 파일은 말그대로 PE 포맷 구조를 가지는 파일이다.

 

대표적인 포맷인 만큼 다양한 분석툴에서 PE파일을 분석할 수 있다.

PE Viewer가 대표적이며 본 포스팅에선 개념 이해를 위해 HxD를 이용해 설명하도록 하겠다.

 

 

2. PE 파일 종류

PE 포맷을 가진다고 무조건 실행하는 계열의 확장자만 해당되는 것이 아니다.

다음과 같은 다양한 파일 포맷이 PE 구조를 가진다. 대표적으로 .exe와 .dll이 존재한다.

  • 실행 계열 : EXE, SCR
  • 드라이버 계열 : SYS, VXD
  • 라이브러리 계열 : DLL, OCX, CPL, DRV
  • 오브젝트 파일 계열 : OBJ


3. PE 파일 구조

PE 파일은 다음과 같이 구성되어있다.

  • Header + Section

Header란 실행 파일의 성격과 특징을 나타내며(프로그램 구동 정보 등등),

Section은 구체적이고 세부적인 기능을 나타낸다(코드, 전역변수 등등) .

 

위 구조를 그림으로 좀더 자세히 표현하자면 아래와 같다.

 

각 영역에 대해 살펴보자.

 

 

4. PE 파일 구조 - DOS 영역

MS에서 DOS 파일에 대한 하위 호환성을 고려하여 PE 파일 포맷을 만들었다.

DOS 영역의 두가지 영역을 살펴보자.

 

1) DOS Header

이제부터 몇몇 영역을 아래와 같이 소스코드로 나타낼 건데, 이는 MS에서 제공하는 winnt.h에서 가져온 내용이다.

typedef struct _IMAGE_DOS_HEADER {
    WORD   e_magic;                     // Magic number > DOS Signature : 4D5A("MZ")
    WORD   e_cblp;              
    WORD   e_cp;                     
    WORD   e_crlc;                    
    WORD   e_cparhdr;             
    WORD   e_minalloc;               
    WORD   e_maxalloc;          
    WORD   e_ss;               
    WORD   e_sp;                     
    WORD   e_csum;                   
    WORD   e_ip;                     
    WORD   e_cs;                    
    WORD   e_lfarlc;            
    WORD   e_ovno;                   
    WORD   e_res[4];                
    WORD   e_oemid;                  
    WORD   e_oeminfo;                  
    WORD   e_res2[10];                 
    LONG   e_lfanew;                    // Offset of NT Header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS Header 영역으로 중요한 정보는 두가지다.

첫번째 e_magic 값과 e_lfanew 값이다.

  • e_magic : DOS Signature 값으로 0x4D5A("MZ")라는 값을 가진다.
  • e_lfanew : NT Header의 오프셋 주소를 가진다. DOS Stub 영역을 점프하는 역할을 한다.

2) DOS Stub

에러메세지를 출력하는 부분이다.

가변적인 영역으로 옵션에 의해 존재여부를 결정한다.

없어도 파일은 잘 실행된다.

 

 

5. PE 파일 구조 - NT Header 영역

NT Header 영역은 3가지 영역이 존재한다.

 

1) PE File Signature

NT Header 시작부분에 위치하며 PE File Signature 값으로 0x5045("PE")라는 값을 가진다.

 

2) PE File Header(IMAGE_FILE_HEADER)

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 이 부분에서 중요한 정보는 Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics로 4가지 이다.

  • Machine : CPU별로 고유한 값을 가진다. 0x014C는 IA-32, 0x0200은 IA-64와 같은 값을 가지고 있다.
  • NumberOfSections : 섹션의 갯수를 나타내며, 1개 이상의 값을 가진다.
    *만약 정의된 섹션수 > 실제 섹션 : error

    *만약 정의된 섹션수 < 실제 섹션 : 정의된 섹션수만큼 인식
  • SizeOfOptionalHeader : 마지막 멤버 Optional Header32 구조체의 크기를 나타낸다.
  • Characteristics : 파일의 속성을 나타낸다. 실행 가능한 파일인지, dll 파일인지와 같은 정보 등을 나타낸다.

3) PE File Optional Header(IMAGE_OPTIONAL_HEADER)

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16
 
typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

PE Header 중 가장 크기가 큰 구조체다.

실행에 필수적인 값들이 존재하므로 잘못 값들이 세팅되면 프로그램이 실행되지 않는다.

모두 중요하지만 특히 중요한 정보들에 한하여 설명하겠다.

  • Magic : IMAGE_OPTIONAL_HEADER32이면 10Bh, IMAGE_OPTIONAL_HEADER64면 20Bh
  • AddressOfEntryPoint : EP(Entry Point)의 RVA(Relative Virtual Address)값을 가짐
  • ImageBase : 프로세스 가상 메모리(32bit) : 0 - FFFFFFFFh, 로딩(매핑)된는 시작 주소를 나타냄
  • SectionAlignment, FileAlignment : PE 파일은 섹션으로 나뉨, SectionAlignment는 메모리에서 섹션 최소단위, FileAlignment는 파일에서 섹션의 최소단위 나타냄
  • SizeOfImage : PE 파일 메모리 로딩 시 가상 메모리에서 PE Image가 차지하는 크기
  • SizeOfHeader : PE Header 전체 크기
  • Subsystem : 1이면 Driver 파일(sys, vxd), 2면 GUI 파일, 3이면 CUI 파일
  • NumberOfRvaAndSizes : 마지막 멤머 DataDirectory 배열 크기
  • DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열


6. PE 파일 구조 - Section Header 영역

Section Header 영역은 Section에 대한 구성 정보를 담고 있다.

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

5가지 정보에 대해 설명하겠다.

  • VirtualSize : 메모리에서 섹션이 차지하는 크기
  • VirtualAddress : 메모리에서 섹션 시작 주소(RVA), SectionAlignment에 맞춰 결정
  • SizeOfRawData : 파일에서 섹션이 차지하는 크기
  • PointerToRawData : 파일에서 섹션 시작 위치, FileAlignment에 맞춰 결정
  • Characteristics : 섹션의 속성

*RVA는 메모리에서 주소를 나타내며, RAW는 파일에서의 주소를 나타낸다.

*RVA를 RAW로 공식을 적용하여 계산할 수 있다.

  • RAW = RVA - VirtualAddress + PointerToRawData
  • 만약 RVA가 101400h, VirtualAddress가 101000h, PointerToRawData가 100000h이면 RAW 값은 100400h

7. PE 파일 구조 - Section 영역

Section 영역은 PE 파일의 실제 데이터를 담고 있다.
대표적인 몇몇 영역을 설명하겠다.

  • .text : 실행코드, 형식 제한 없음, 읽기 권한 지님, 리틀엔디안
  • .data : 초기화된 각종 변수, 형식 제한 없음, 읽기/쓰기 권한 지님
  • .rdata : 읽기 전용
  • .idata : import 섹션으로 DLL/함수 정보
  • .edata : export 섹션으로 - 본인함수 정보
  • .rsrc : 리소스, 비실행, 읽기 권한 지님, 파일 버전이나 아이콘 등 기타 데이터 정보
    ...

이외에도 다양한 영역이 존재한다. 여기까지 이론을 마치고 실습을 해보겠다.



8. notepad.exe 분석

HxD를 통해 위와 같은 구조로 notepad.exe를 분석할 수 있다.

NT Header의 PE File Header 부분까지 살펴보자.

1. DOS Header의 DOS Signature 값인 4D 5A를 확인할 수 있음

2. NT Header Offset을 나타내는 lfanew 값인 F8 00 00 00를 확인하여 F8 주소로 점프

3. 50 45라는 NT Header 시작부분인 PE File Signature를 볼 수 있음

4. NT File Header 부분 Magic값인 64 86를 통해 AMD64 CPU체제 확인 가능

5. 이어서 NumberOfSections이 07 00을 통해 7개의 섹션을 가진 것 확인

6. SizeOfOptionalHeader을 통해 IMAGE_OPTIONAL_HEADER32가 F0의 크기를 가진 것 확인

7. Charateristics값인 22 00을 통해 실행가능파일부터 다양한 정보 확인가능

 

각 헥스값에 대한 의미는 추가적인 검색을 통해 의미를 파악할 수 있으며

위와 같이 분석을 진행해 나가면 된다.

물론 실전에선 PE Viewer와 같은 전용 분석툴을 통해 분석하면

쉽게 구조를 파싱하여 확인 가능하므로 참고하자.

 


 

여기까지 PE 공부를 마친다. 다음시간엔 OLE 구조를 공부해보겠다.

반응형
반응형

C++을 이용해 소켓프로그래밍을 해보려한다.

소켓에 대한 개념은 너무 방대하여 간단하게 다루고

프로그램을 짜는데 중점을 두고 포스팅하려한다.

함께, 에코 서버와 에코 클라이언트를 프로그래밍해보자.


1. 소켓(Socket) 프로그래밍

소켓 : 네트워크에서 서버와 클라이언트가 서로 특정 포트를 이용하여 양방향 통신하도록 만들어주는 소프트웨어 장치

소켓 프로그래밍 : 소켓을 사용하여 네트워크를 구성해나가는 과정

 

2. 소켓 API 흐름

서버소켓과 클라이언트 소켓의 흐름은 다음과 같다.

 

서버 소켓 >

  1. 소켓 생성 : Create
  2. 서버가 사용할 주소 지정하여 결합(IP, Port) : Bind
  3. 연결 요청 대기 : Listen
  4. 요청 수신시 받기 : Accept
  5. 연결 수립시(Established) 데이터 송수신 : send/recv
  6. 송수신 완료, 소켓 닫기 : Close

클라이언트 소켓 >

  1. 소켓 생성 : Create
  2. 서버 연결요청 : Connect
  3. 연결 요청 받으면 데이터 송수신 : send/recv
  4. 송수신 완료, 소켓 닫기 : Close

그림으로 정리하면 다음과 같다.

3. Echo-Client

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

void usage(char *argv){
    cout << "Usage : " << argv << " [ip] [port]" << endl;
    cout << "Example) " << argv << " 192.168.0.12 1234" << endl;
}

int main(int argc, char *argv[]){
    if(argc != 3){ //인자가 3개가 아니면 usage를 출력
        usage(argv[0]);
        return -1;
    }

    struct sockaddr_in addr_server = {}; // 주소체계 구조체 선언

    memset(&addr_server, 0, sizeof(addr_server));
    addr_server.sin_family = AF_INET; // IPv4 통신, 기본 인터넷 프로토콜 지정
    addr_server.sin_addr.s_addr = inet_addr(argv[1]); // 첫번째 인자 IP 대입
    addr_server.sin_port = htons(atoi(argv[2])); // 두번째 인자 PORT 대입

    char w_buff[256]; // 쓰기용 버퍼 선언
    char r_buff[256]; // 읽기용 버퍼 선언

    int sock_client = socket(AF_INET, SOCK_STREAM, 0); // 소켓 생성(IPv4, TCP, 기본프로토콜)
    if(sock_client == -1){
        cout << "socket error" << endl;
        close(sock_client);
        exit(1);
    }

    if(connect(sock_client, (struct sockaddr*) &addr_server, sizeof(addr_server)) == -1){ // 연결 요청
        cout << "connect error" << endl;
        close(sock_client);
        exit(1);
    }

    while(1){ // 연결 수락시 반복문
        memset(r_buff, 0, 256); // 읽기 버퍼 초기화
        cin >> w_buff; // 쓰기 버퍼에 문자열 입력
        if(strlen(w_buff)>255) break; // 버퍼 오버플로그 방지
        int write_chk = write(sock_client, w_buff, strlen(w_buff)); // 작성 길이만큼 write(전송)
        if(write_chk == -1){
            cout << "write error" << endl;
            break;
        }
        int read_chk = read(sock_client, r_buff, sizeof(r_buff)-1); // 읽기 버퍼사이즈-1 만큼 read(읽기)
        if(read_chk == -1){
            cout << "read error" << endl;
            break;
        }else{
            r_buff[strlen(r_buff)] = '\n';
            cout << r_buff; // 버퍼 출력
        }
    }
    close(sock_client); // 연결 종료
    return 0;
}

 

 

4. Echo-Server

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

void usage(char *argv){
    cout << "Usage : " << argv << " [port]" << endl;
    cout << "Example) " << argv << " 1234" << endl;
}

int main(int argc, char *argv[]){
    if(argc != 2){ // 인자가 2개가 아니면 usage 출력
        usage(argv[0]);
        return -1;
    }

    char buff[256]; // 읽기, 쓰기용 버퍼 선언

    struct sockaddr_in addr_server = {}; // 주소체계 구조체 선언
    struct sockaddr_in addr_client = {};
    socklen_t addr_client_len = sizeof(addr_client_len); // 길이 계산

    memset(&addr_server, 0, sizeof(addr_server)); // 초기화
    addr_server.sin_family = AF_INET; // IPv4 인터넷 프로토콜
    addr_server.sin_port = htons(atoi(argv[1])); // 첫번째 인자 PORT 지정
    addr_server.sin_addr.s_addr = htonl(INADDR_ANY); // Anybody
 

    int sock_server = socket(AF_INET, SOCK_STREAM, 0); // 소켓 생성
    if(sock_server == -1){
        cout << "socket error" << endl;
        close(sock_server);
        exit(1);
    }

    if(bind(sock_server, (sockaddr*) &addr_server, sizeof(addr_server)) == -1){ // 주소 지정
        cout << "bind error" << endl;
        close(sock_server);
        exit(1);
    }

    if(listen(sock_server, 3) == -1){ // 연결 대기
        cout << "listen error" << endl;
        close(sock_server);
        exit(1);
    }

    int sock_client = accept(sock_server, (sockaddr*) &addr_client, &addr_client_len); // 연결 수락
    if(sock_client == -1){
        cout << "accept error" << endl;
        close(sock_server);
        exit(1);
    }

    while(1){
        memset(buff, 0, 256); // 버퍼 초기화
        int read_chk = read(sock_client, buff, sizeof(buff)-1); // 버퍼크기-1만큼 read(읽기)
        if(read_chk == -1){
            cout << "read error" << endl;
            break;
        }
        buff[strlen(buff)] = '\n';
        cout << buff; // 버퍼 출력
        int write_chk = write(sock_client, buff, strlen(buff)); // 버퍼 사이즈만큼 write(전송)
        if(write_chk == -1){
            cout << "write error" << endl;
            break;
        }
    }
    close(sock_server); // 연결 종료
    return 0;
}

 

 

5. 실행결과

1234번 포트로 server를 실행시키고 client에서 localhost와 1234번 포트로 접속을 시도했다.

client에서 "echotest"와 "hellworld" 문자열을 입력해보았고 실행결과는 다음과 같다.

 

echo-server-main.cpp

 

echo-client-main.cpp


잘못된 정보나 코드 제보 받으며, 여기까지 Echo-Client, Echo-Server 프로그램 개발 포스팅을 마친다.

 

 

반응형

'Programming > Network' 카테고리의 다른 글

[Network] pypcap 공부, 실습  (0) 2020.08.04
반응형

어셈블리어를 이용하여 파일을 읽고 출력하는 프로그램을 구현해보자.

필자는 Visual Studio 2017과 Irvine 라이브러리를 활용하여 개발할 예정이다.


1. 문제

  • 환경 : Visual Studio 2017, Irvine 라이브러리, x86 assembly
  • 디스크 파일을 읽어 읽은 결과를 메모리 덤프 및 화면 출력하는 프로그램을 작성
  • 디스크 파일은 c:\...\source\...\프로젝트\프로젝트\파일명에 위치

2. 문제 해결 방법

  1. 파일명과, 버퍼, 버퍼크기를 정의해준다(본 문제에선 버퍼 사이즈를 240이라 지정하였다.)
  2. 파일명 offset을 edx에 넣고, OpenInputFile을 call하여 파일 핸들값을 eax에 얻어온다.
  3. 정상적으로 파일이 열린경우 ReadFromFile을 이용하여 문자열을 읽어 버퍼에 넣는다.
  4. 정상적으로 읽었을 경우 버퍼 offset을 edx에 넣어 WriteString으로 문자열을 출력한다.
  5. ebx에 mov해둔 파일 핸들을 eax로 옮겨 CloseFile로 파일을 닫는다.

3. 코딩

TITLE Program asm4fileread(asm4fileread.asm)
; 프로그램 설명문 : 디스크의 파일을 읽고 출력하는 프로그램

INCLUDE c:\Irvine\Irvine32.inc
BUF_SIZE = 240

.data
filename		BYTE "sample.txt", 0
BUF			BYTE BUF_SIZE DUP(? )
good     		BYTE "Good processed !", 0dh, 0ah, 0
Nogood			BYTE "File open or write error !", 0dh, 0ah, 0

.code
main	PROC
	mov		edx, offset filename
	call		OpenInputFile			; 입력파일 열기
	mov		ebx, eax				; 핸들 ebx로 mov
	cmp		eax, 0					; 정상 오픈 확인
	jnz		good_open				; eax != 0 정상open
	call		WriteWindowsMsg			; open 오류
	jmp		terminate

good_open :
	mov		eax, ebx
	mov		edx, offset BUF
	mov		ecx, BUF_SIZE
	call		ReadFromFile			; 파일 내용 읽기
	jnc		good_read				; cf=0이면 jmp
	call		WriteWindowsMsg			; open 오류
	jmp		terminate

good_read :		
	mov		edx, offset BUF			; 버퍼내용 edx로 mov
	call		WriteString				; edx 출력
	call		Crlf
	jmp		good_end

good_end :								; 핸들 닫기
	mov		eax, ebx
	call		CloseFile
	mov		edx, offset good
	call		WriteString
	exit

terminate :
	mov		edx, offset nogood
	call		WriteString
	call		WaitMsg
	exit

main	ENDP
END	main

 

파일 내용과 실행 결과 사진은 다음과 같다.

 

 

 


계속해서 어셈블리를 통해 다양한 문제를 풀어볼 예정이다.

반응형

'Programming > Language' 카테고리의 다른 글

[C++] 클래스(Class) 공부  (0) 2022.06.06
[Assembly] 문자열 입력과 출력  (0) 2020.06.05
[Assembly] 1부터 10까지의 합 계산  (0) 2020.06.04
[C++] STL - multimap 공부  (0) 2020.05.24
[C++] STL - map 공부  (0) 2020.05.14
반응형

어셈블리어를 이용하여 문자열 입출력을 구현해보자.

필자는 Visual Studio 2017과 Irvine 라이브러리를 활용하여 개발할 예정이다.


1. 문제

  • 환경 : Visual Studio 2017, Irvine 라이브러리, x86 assembly
  • 키보드에서 문자열을 입력받아 그 문자열을 출력하고, 길이와 메모리 덤프된 결과역시 출력하라.


2. 문제 해결 방법

  1. 안내문 문자열을 출력한다.
  2. 문자열을 입력받는다. > ReadString 사용(edx에 offset 지정 및 ecx로 글자수 지정)
  3. 문자열 길이 계산 > StrLength 사용(edx에 문자열 offset 지정)
  4. 문자열 길이 0과 비교하여 0이 아닐경우 1번으로 돌아감
  5. 0이면 종료
  6. 메모리 덤프 결과 출력 > DumpMem 사용(ecx에 출력할 길이, esi에 시작주소, ebx는 단위 크기)

3. 코딩

 

TITLE Program asm3inputoutput(asm3inputoutput.asm)
; 프로그램 설명문 :	문자열 입력받고 출력하는 프로그램

INCLUDE c:\Irvine\Irvine32.inc

.data
Ask			BYTE "INPUT : ", 0
Len			BYTE "Length : ", 0
Output		BYTE "OUTPUT : ", 0
Answer		BYTE 50 DUP(?)

.code
main			PROC

START:
	mov 		edx, 0
	mov 		edx, OFFSET Ask
	call		WriteString				; ASK 안내문 출력
	mov 		edx, OFFSET Answer
	mov 		esi, edx
	mov 		ecx, SIZEOF Answer - 1
	call		ReadString				; 문자열 읽기
	call		Crlf
	mov 		edx, 0
	mov 		edx, OFFSET Len
	call		WriteString				; Len 안내문 출력
	mov 		edx, 0
	mov 		edx, OFFSET Answer
	call		StrLength				; 문자열 길이 계산
	call		WriteDec				; 문자열 길이 eax 출력
	call		Crlf
	call		Crlf
	cmp 		eax, 0					; 문자열 길이 0 비교
	jz  		LOUT					; 길이 0일 경우 프로그램 종료
	mov 		edx, 0
	mov 		edx, OFFSET Output
	call		WriteString				; OUTPUT 안내문 출력
	mov 		edx, 0
	mov 		edx, OFFSET Answer
	call		WriteString				; 입력한 문자열 출력
	call		Crlf
	mov 		ecx, eax				; 메모리 덤프 출력 길이 지정
	call		DumpMem					; 메모리 덤프 출력
	call		Crlf
	call		START					; 반복

LOUT:		exit					; 빠져나가기

main			ENDP
END			main

 

실행 결과는 다음과 같다.

 


어셈블리를 통해 다양한 문제를 풀어볼 예정이다.

반응형

'Programming > Language' 카테고리의 다른 글

[C++] 클래스(Class) 공부  (0) 2022.06.06
[Assembly] 파일 읽고 출력해보기  (0) 2020.06.06
[Assembly] 1부터 10까지의 합 계산  (0) 2020.06.04
[C++] STL - multimap 공부  (0) 2020.05.24
[C++] STL - map 공부  (0) 2020.05.14
반응형

어셈블리어를 이용하여 1부터 10까지의 합을 구하는 프로그램을 개발해보자.

본 필자는 Visual Studio 2017과 Irvine 라이브러리를 활용하여 개발할 예정이다.


1. 문제

  • 환경 : Visual Studio 2017, Irvine 라이브러리, x86 assembly
  • 1부터 10까지 합산 값을 출력하고 레지스터를 덤프하는 프로그램을 개발하라.


2. 문제 해결 방법

  1. 문자열을 출력한다.
  2. eax를 xor로 초기화 시킨다.
  3. ecx에 10을 mov 한다.
  4. loop를 돌며 eax에 ecx 값을 더한다.(지정된 위치로 넘어가고 ecx가 1씩 감소하며, ecx가 0이되면 빠져나온다.)
  5. 결과 값을 출력한다.
  6. 덤프된 레지스터를 출력한다.


3. 코딩

TITLE Program asm2sum.asm(asm2sum.asm)
; 프로그램 설명문 : 1부터 10까지의 합을 구해 출력하는 프로그램

INCLUDE c:\Irvine\Irvine32.inc

.data
str_data db "1 + 2 + 3 ... + 10 = ", 0	; 문자열 str_data db(1바이트)크기 선언

.code
main	PROC

mov 	ecx, 10					; ecx에 10을 mov
xor 	eax, eax				; eax는 0

LP:
add 	eax, ecx				; eax에 ecx를 더해 eax에 결과값 저장
loop	LP						; ecx가 0보다 크면 LP로 다시 돌아감, ecx 1 감소

mov 	edx, offset str_data	; str_data offset 주소 edx에 mov
call	writeString				; edx에 있는 값 문자열 출력

call	WriteDec				; eax에 있는 값 10진수 출력
call	Crlf					; 줄 바꿈
call	DumpRegs				; 덤프된 레지스터 출력

exit							; 종료
main	ENDP
END main

 

실행 결과는 다음과 같다.


어셈블리를 통해 다양한 문제를 풀어볼 예정이다.

반응형

'Programming > Language' 카테고리의 다른 글

[Assembly] 파일 읽고 출력해보기  (0) 2020.06.06
[Assembly] 문자열 입력과 출력  (0) 2020.06.05
[C++] STL - multimap 공부  (0) 2020.05.24
[C++] STL - map 공부  (0) 2020.05.14
[C++] STL - set 공부  (0) 2020.05.14
반응형

2020 카카오 하계 인턴 코딩테스트에서 처참한 결과를 보며 느낀점이 많다.

아직 기초를 한참 더 쌓아야 가능할 것같다고 생각하였다.

이번 시간엔 파이썬과 스택 구조를 활용하여

수식의 후위 표기, 계산에 대해 공부해보도록 하자.


1. 수식의 후위 표기법

후위 표기법을 이야기하기 전에 수식의 여러가지 표기법에 대해 짚고 넘어가야한다.

수식엔 다양한 표기법이 존재한다.

 

  • 전위(prefix) 표기법 : 연산자 먼저 표시, 피연산자를 나중에 표시
    • * + A B - C D
  • 중위(infix) 표기법 : 피연산자 사이에 연산자를 표기(일반적으로 사용)
    • (A + B) * (C - D)
  • 후위(postfix) 표기법 : 피연산자를 먼저 표시, 연산자를 나중에 표시
    • A B + C D - *

3개 예제는 모두 같은 공식이다. 중위 표기법을 일반적으로 우리가 사용하지만

프로그래밍으로 괄호가 포함된 중위 표기 연산 수식을 계산하긴 힘들다.

 

컴퓨터 프로그래밍시 괄호표기가 필요없는 후위 표기법을 이용하여

다양한 수식을 계산하는 것이 속도가 빠르다.

 

*계산기나 컴퓨터 내부에선 원래 스택을 활용해 중위표기법을 후위표기법으로 변환하여 연산한다.



2. 중위 표기법 -> 후위 표기법 변환

후위 표기법을 연산하기 위해선 일반적인 중위 표기법을 후위 표기법으로 변환할 필요가 있다.

변환 방법은 다음과 같다.

 

  1. 수식의 각 연산자에 대해 우선순위에 따라 괄호를 사용하여 다시 표현한다.
  2. 각 연산자를 그에 대응하는 오른쪽 괄호의 뒤로 이동시킨다.
  3. 괄호를 제거한다.

예제를 통해 살펴보자. 위 공식을 이용하여

  • A * C + B(중위 표기법)를 후위 표기법으로 변환해보자.
  1. 우선순위에 따라 괄호를 다시 표현하면 ((A * B) + C)
  2. 각 연산자를 오른쪽 괄호 뒤로 이동시키면 ((A B) * C) +
  3. 괄호제거하면 마무리, A B * C +


3. 후위 표기법 계산

이제는 후위 표기법을 계산해보자.

이제부터 스택 개념을 적용할 것이며 계산 방법은 다음과 같다.

*스택 : https://saynot.tistory.com/28

  1. 처음부터 차례대로 읽으며 피연산자는 스택에 쌓는다.
  2. 연산자를 만나면 스택에서 피연산자 2개를 꺼내 연산을 수행하고 다시 스택에 쌓는다.
  3. 모두 읽을 때 까지 위 두 공식을 반복한다.

역시 예제를 통해 알아보자.

위 공식을 이용하여 13 5 + 3 -(후위 표현식) 수식을 계산해보자.

  1. 13은 피연산자, 스택에 PUSH
  2. 5는 피연산자, 스택에 PUSH
  3. +는 연산자이므로 5 POP, 13 POP하여 연산 수행
  4. 13 + 5 = 18, 결과값을 스택에 PUSH
  5. 3은 피연산자, 스택에 PUSH
  6. -는 연산자이므로 3 POP, 18 POP하여 연산 수행
  7. 18 - 3 = 15, 계산 끝
  8. 13 5 + 3 - = 15


4. 알고리즘 설계

중위 표현식 > 후위 표현식 변환 알고리즘

  1. 연산자 우선순위 지정(이걸 바꾸는 코딩테스트 문제 존재)
  2. 후위 표현식으로 만들어져 반환될 리스트 생성
  3. 중위 표현식을 왼쪽부터 한 글짜씩 읽음
  4. 피연산자면 리스트에 append
  5. '('이면 스택에 PUSH, ')'이면 '('가 나올때까지 스택에서 POP, 리스트에 append
  6. 연산자면 스택에서 이보다 높은 우선순위들 POP, 리스트에 append
  7. 그리고 이 연산자를 스택에 PUSH
  8. 스택에 남아있는 연산자는 모두 POP, 리스트에 append

후위 표현식 계산 알고리즘

  1. 후위 표현식을 왼쪽부터 한 글짜씩 읽음
  2. 피연산자면 스택에 PUSH
  3. 연산자면 스택에서 POP해서 op1, 다시 POP해서 op2 지정
  4. op2와 op1을 연산자로 계산하여, 결과값 스택에 PUSH
  5. 수식의 끝이면 스택에서 POP하여 계산 결과 도출


4. 코딩 - Python

위 두가지를 이용하여 중위 표현식을 입력받으면 후위 표현식으로 변환하여

결과값을 도출하는 알고리즘을 구현해보았다.

*splitTokens함수와 Stack 클래스는 해당 주제에서 제외되는 부분이라 생략하였다.

def infixTopostfix(tokenList): #중위 표현식을 후위 표현식으로 변환
    prec = {
        '*': 3,
        '/': 3,
        '+': 2,
        '-': 2,
        '(': 1,
    }

    opStack = ArrayStack()
    postfixList = []

    for token in tokenList:
        if type(token) is int:
            postfixList.append(token)         
        elif token == ')':
            if token == ')':
                while opStack.peek() != '(':
                #print(opStack.peek())
                    postfixList.append(opStack.pop())
                opStack.pop()
        else:
            if opStack.isEmpty() == False:  
                if prec[opStack.peek()] >= prec[token] and token != '(':
                    postfixList.append(opStack.pop())
                    opStack.push(token)
                elif prec[opStack.peek()] >= prec[token] and token == '(':
                    opStack.push(token)
                else:
                    opStack.push(token)
            elif opStack.isEmpty() == True:
                opStack.push(token)

    while not opStack.isEmpty():
        postfixList.append(opStack.pop())

    return postfixList


def postfixEval(tokenList): #후위 표현식 계산
    opStack = ArrayStack()
    for token in tokenList:
        if type(token) is int:
            opStack.push(token)
        elif token == '*':
            tmp1 = opStack.pop()
            tmp2 = opStack.pop()
            opStack.push(tmp2*tmp1)
        elif token == '/':
            tmp1 = opStack.pop()
            tmp2 = opStack.pop()
            opStack.push(tmp2/tmp1)
        elif token == '+':
            tmp1 = opStack.pop()
            tmp2 = opStack.pop()
            opStack.push(tmp2+tmp1)
        elif token == '-':
            tmp1 = opStack.pop()
            tmp2 = opStack.pop()
            opStack.push(tmp2-tmp1)
    return opStack.pop()



def solution(expr):
    tokens = splitTokens(expr) #문자열을 토큰화 시키는 함수
    print("tokens : ", tokens)
    postfix = infixTopostfix(tokens) #중위 표현식 > 후위 표현식
    print("postfix : ", postfix)
    res = postfixEval(postfix) #후위 표현식 계산
    return res

def main():
    quest = '( 13 + 15 ) * ( 16 + 23 )'
    ans = solution(quest)
    print(quest, " = ",ans)

if __name__ == '__main__':
    main()


결과는 다음과 같다.


일반적으로 수식 순서는 * /가 + -에 비해 먼저이다. 하지만 이를 바꿔서 계산해야하는

코딩 테스트 문제가 존재한다. 이럴때 위 코드에서 사전을 수정하여 우선순위를 조절해 코드를 바꾸면 될것이다.

반응형
반응형

Canon 100D + EF-S 18-55mm


 

Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 프라하 페트린타워



Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 잘츠부르크



Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 잘츠부르크 호엔잘츠부르크성



Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 잘츠부르크 호엔잘츠부르크성



Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 할슈타트 호수

 

Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 잘츠부르크 운터스베르크산

 

Canon 100D, EF-S 18-55mm F3.5-5.6 IS STM WHITE, 프라하 거리

반응형

'Interesting > Pictures' 카테고리의 다른 글

Canon 100D + EF-S 18-55mm - 1  (0) 2020.05.21

+ Recent posts