jmeter를 사용한 가변길이 TCP Binary 패킷 처리 방법

Use Dynamic Binary Packet for Jmeter

jmeter를 사용한 가변길이 TCP Binary 패킷 처리를 설명합니다.

Test 시나리오

  • 테스트할 서버는 로컬에 1234 port를 listen하고 있습니다.
  • 요청 패킷에 요청 seq가 포함됩니다.
  • 요청/응답 header 패킷은 동일합니다.
struct header_ {
    unsigned char version;      // 1 byte
    short int seq;              // 2 byte
    unsigned char str;          // 1 byte
    short int type;             // 2 byte
    short int bodyLen;          // 2 byte
}
  • body는 일반 문자열입니다.
    • 요청 패킷 전문은 CSV 파일을 읽어 내용을 만듭니다.
  • thread(socket connection) 는 10개로 합니다.

apache jmeter 소스 받기

jmeter의 sampler에서 tcp 가변길이 패킷을 처리할 수 있는 방법이 없으므로, plug-in을 만들어야 합니다. Java Request plug-in을 만들기 위해 jmeter 소스를 받습니다.

1. github 에서 fork

https://github.com/apache/jmeter

2. 내 PC에 checkout

git clone https://github.com/lmk/jmeter

3. branch 생성

git branch DynamicLengthTCP

4. eclipse import 준비

ant setup-eclipse-project

5. eclipse > File > Import > Existing Projects into Workspace

코드 추가

1. org.apache.jmeter.protocol.java.test 아래 DynamicLengthTCP, SingletonSocketMap class 추가

  • DynamicLengthTCP
    • AbstractJavaSamplerClient 상속받아 구현했습니다.
    • 자세한 내용은 JavaTest class 참고했습니다. (JavaTest를 그대로 복사해서 생성)
    • Jmeter에서 생성한 Binary 패킷을 plug-in으로 가져오기 위해서는 hex string으로 변환해서 넘어옵니다. (BinaryTCPClientImpl 참고) "RequestData" 저장된 요청 패킷을 byte[]로 변환합니다.(여기)
    • 요청 패킷을 전송합니다.
    • 응답 패킷을 header 먼저 받고, body 길이만큼 응답 패킷을 받습니다.
    • 받은 Binary 패킷과 상태 정보를, jmeter에서 받을 수 있도록 SampleResult에 담습니다.
  • SingletonSocketMap
    • Thread별 Socket 재사용을 위한 class 입니다.

2. git add 및 commit, push

Export jar

1. Package Explorer 에서 추가한 파일 선택

  • DynamicLengthTCP.java
  • SingletonSocketMap.java

2. Export > JAR File

3. \lib\ext 경로 아래 저장할 jar 파일명 입력

  • \lib\ext에 jar 파일을 넣어두면, jmeter 실행시 plugin으로 인식해서 읽어들입니다.
  • github에서 받은 소스의 \lib\ext 경로가 아니라, jmeter 실행파일이 위치한 경로입니다.
  • 제 경우는 소스는 "D:\src\jmeter"에 받았고, jmeter 실행파일은 "D:\Tools\apache-jmeter-5.0"에 받았으므로, jar 파일을 저장할 경로는 "D:\Tools\apache-jmeter-5.0\lib\ext" 였습니다.

Test

1. bin/jmeter.bat 실행

2. "Test Plan" 우클릭 > Add > Thread Group추가

- Number of Threads를 10 으로 수정합니다.
- Loop Count는 Forver 체크하면 Stop 할때까지 반복하지만, 테스트니까 기본값 1로 유지합니다.

3. "Thread Group" 우클릭 > Add > Config Element > Counter 추가.

  • 요청 패킷 seq 증가를 위한 것입니다.
  • 시작값, 증가값은 1로 설정합니다.
  • seq 는 패킷의 자료형 크기보다 작게 max 값을 정합니다.
    • 여기서는 short 형이므로 30000으로 잡았습니다.
  • Exported Variable Name에 jmeter 내부에서 사용할 변수명 "reqCount"를 입력합니다.

4. "Thread Group" 우클릭 > Add > Config Element > CSV Data Set Config 추가.

  • Filename에 "msg.txt"로 지정합니다.
    • msg.txt 파일 내용은 아와 같습니다.
    메시지1
    메시지2
    메시지3
    
    • 여기서는 row전체를 하나의 field를 사용합니다.
  • Variable NAmes에 jmeter 내부에서 사용할 변수명 "msg"를 입력합니다.
  • Recyle on EOF: row가 끝나면 처음부터 다시 읽도록 True로 설정합니다.
  • Stop thread on EOF: row가 끝나도 테스트를 계속 할것이므로 False로 설정합니다.

5. 테스트 결과를 쉽게 보기 위해 Listener 추가

  • "Thread Group" 우클릭 > Add > Listener > View Results Tree
  • "Thread Group" 우클릭 > Add > Listener > Summary Report
  • "Thread Group" 우클릭 > Add > Listener > Response Time Graph

6. "Thread Group" 우클릭 > Add > Sampler > Java Request

  • Classname은 이번에 만든 DynamicLengthTCP 선택합니다.
  • HostIP: 접속할 서버 IP 127.0.0.1 입력합니다.
  • Port: 접속할 서버 Port 1234 입력합니다.
  • Connect Timeout: 접속할때 처리할 timeout 시간은 대략 200 입력합니다.
  • Response Timeout: 응답 패킷을 받을때 timeout 시간은 대략 200 입력합니다.
  • Re-use connect: 하나의 thread에서 connect socket을 재사용할지 여부인데, True를 입력합니다.
  • Set NoDelay, SO_LINGER는 socket 옵션 참고합니다. (모르겠으면 flase, 0)
  • Size of response header: 응답 패킷의 해더 사이즈를 입력합니다 (여기서는 8)
  • Offset of response body length: 응답 해더에서 body 사이즈가 포함된 위치를 입력합니다 (여기서는 6)
  • Size of response body length: body 사이즈가 저장된 크기를 입력합니다 (여기서는 2)
    • DynamicLengthTCP 에서는 사이즈가 1, 2, 4 일때만 처리 가능합니다.

7. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor

  • 요청 패킷을 만들기 위해 BeanShell을 작성합니다.
import java.io.*;
import org.apache.jmeter.protocol.tcp.sampler.*;

try {
  // msg from CSV Data Set Config
  String str = vars.get("msg");
  byte[] msg = str.getBytes();

  byte[] pkt = new byte[8 + msg.length];
  
  // version
  pkt[0] = (byte)0x01;
  
  // seq from Counter
  short seq = Short.parseShort(vars.get("reqCount"));
  pkt[1] = (byte)(seq>>>8);
  pkt[2] = (byte)seq;
  
  // constString
  pkt[3] = (byte)0x00;
  
  // type
  pkt[4] = (byte)0x00;
  pkt[5] = (byte)0x01;
  
  // len
  pkt[6] = (byte)(msg.length>>>8);
  pkt[7] = (byte)msg.length;
  
  // msg
  System.arraycopy(msg, 0, pkt, 8, msg.length);
  
  // byte array to hex string
  char[] hexArray = "0123456789ABCDEF".toCharArray();
  char[] hexChars = new char[pkt.length *2];
  for(int i=0; i<pkt.length; i++) {
  	int v = pkt[i] & 0xff;
  	hexChars[i * 2] = hexArray[v>>>4];
  	hexChars[i * 2 + 1] = hexArray[v & 0x0f];
  }
  
  str = new String(hexChars);
  vars.put("RequestData", str);
  
  log.info("Request data size: " + str.length());
}catch(Exception e) {
  log.info("Detect BeamShell PreProcessor Exception"+ e);
  prev.setTopThread(true);
}

8. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor

  • 응답패킷 파싱을 위해 BeanShell을 작성합니다.
import java.nio.ByteBuffer;
import org.apache.jmeter.samplers.SampleResult;

byte[] resData = prev.getResponseData();
int resSize = resData.length;

byte[] buf1 = {resData[4], resData[5]};
byte[] buf2 = {resData[6], resData[7]};

short type = ByteBuffer.wrap(buf1).getShort();
short bodySize = ByteBuffer.wrap(buf2).getShort();

log.info("Rsponse Size: " + resSize +  ", type: " + type+ ", bodySize: " + bodySize);

9. Run > Start

  • 테스트 및 결과 확인합니다.


Windows 에서 포트 포워딩 기능 사용하기.

VMWare등을 사용하는데, Bridged 모드로 설정하지 않고 (IP 여유가 없거나, 설정하기 귀찮을때..)

기본값인 NAT 모드로 설정해두고 아래 명령으로 하시면 됩니다.


CMD 창을 관리자 모드로 열고,


* Forwarding 설정


> netsh interface portproxy add v4tov4 listenport=80 listenaddress=192.168.0.100 connectport=80 connectaddress=192.168.229.100

 


* Forwarding 해제


> netsh interface portproxy delete v4tov4 listenport=80 listenaddress=192.168.0.100

 


* 설명

    - listenport: 내 PC에서 Listen할 Port

    - listenaddress: 내 PC에서 Listen할 IP

    - connectport: VM에 포워딩할 포트

    - connectaddress: VM의 IP

FindMSSQL

github에 올린 내용 정리해서 올립니다.

https://github.com/lmk/FindMSSQL



FindMSSQL

Find all MSSQL Server

같은 네트워크망에 설치된 MSSQL의 정보를 조회 합니다.

Visual Studio 2015 MFC 기반에서 코딩해 봤습니다.

Enveroment

  • Virsual Studio 2015
  • MFC (v140)

How to use

  • 함수 원형은 아래와 같습니다.
  • 3번째 파라미터를 생략하면 같은 네트워크망의 모든 서버를 검색합니다. (Broadcast)
bool GetMSSQLInfo(CString &info, int timeout_sec, CString ip = _T(""));

How to implement

  • MSSQL을 설치하면 SQL Server Browser가 함께 설치됩니다.
  • SQL Server Browser 접속해서 DBMS 정보를 가져옵니다.
    • UDP 1434 포트에 접속
    • Send 0x02
    • Receive Data


월간 newtype개발

월간 newtype개발

가수 윤종신님의 '월간 윤종신' 처럼, 매월 개인 프로젝트 하나씩을 해보려고 합니다.


MFC 프로젝트 버전 일괄 변경 툴

MFC 프로젝트가 여러개 일때, 버전 변경을 일일이 수정하는 것이 번거롭습니다.

이럴때, 버전일 일괄적으로 변경해 주는 툴입니다.


원리는, 설정 파일에 리소스파일 경로를 넣어둔 목록을 만들고

툴에서 파일 목록을 읽어서 아규먼트로 받은 버전으로 일괄 치환 합니다.

Visual Studio 2015 C# ( .NET Framework 4.5.2 ) 기반에서 코딩해 봤습니다.


자세한 내용은 github에 올린 내용으로 대체 합니다.

( https://github.com/lmk/changeVer )


changeVer

All change FileVersion/ProductVersion on MFC resource files

Enveroment

  • Virsual Studio 2015
  • C#

Help

Usage>
$ changeVer.exe -f FileVersion -p ProductVersion -c ConfigFilename
ex) $ changeVer.exe -f 2.1.6.33 -p 2.1.0.0 -c resources.txt
ex) $ changeVer.exe -s -c resources.txt

Options>
        -f[F]: After File version
        -p[P]: After Product version
        -c[C]: Config filename
        -s[S]: Show Current version

How to use

  1. Make list file
$ type resources.txt
D:\Source\server\server.rc
D:\Source\client\client.rc
D:\Source\client2\client2.rc
D:\Source\client3\client3.rc
  1. Execute tool
$ changeVer -f 2.1.6.34 -p 2.1.0.0 -c resources.txt
OK:     D:\Source\server\server.rc
OK:     D:\Source\client\client.rc
OK:     D:\Source\client2\client2.rc
OK:     D:\Source\client3\client3.rc

$ changeVer -s -c resources.txt
D:\Source\server\server.rc:
         FILEVERSION 2,1,6,34
         PRODUCTVERSION 2,1,0,0
                    VALUE "FileVersion", "2, 1, 6, 34"
                    VALUE "ProductVersion", "2, 1, 0, 0"
D:\Source\client\client.rc:
         FILEVERSION 2,1,6,34
         PRODUCTVERSION 2,1,0,0
                    VALUE "FileVersion", "2, 1, 6, 34"
                    VALUE "ProductVersion", "2, 1, 0, 0"
D:\Source\client2\client2.rc:
         FILEVERSION 2,1,6,34
         PRODUCTVERSION 2,1,0,0
                    VALUE "FileVersion", "2, 1, 6, 34"
                    VALUE "ProductVersion", "2, 1, 0, 0"
D:\Source\client3\client3.rc:
         FILEVERSION 2,1,6,34
         PRODUCTVERSION 2,1,0,0
                    VALUE "FileVersion", "2, 1, 6, 34"
                    VALUE "ProductVersion", "2, 1, 0, 0"


Simple Http Server

자세한 소스는 github을 참고하세요.

https://github.com/lmk/SimpleHttpServer


SimpleHttpServer

  • very simple http server
  • default port: 8080
  • support: linux g++ 4.9.2

How to use

  • block server
HttpServer httpServer;

httpServer.Init(5, NULL);
httpServer.Run();
  • non-block
NBHttpServer* httpServer = NBHttpServer::getInstance();
httpServer->Init(5);
httpServer->Start();
sleep(60);
httpServer->Stop();


OpenSSL 을 사용한 RSA

RSA

RSA 생성

  • PEM public 키로 RSA 생성하기
unsigned char *key = "PEM 형식의 public 키";
BIO *bio = BIO_new_mem_buf(key, -1);
RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, &rsa, NULL, NULL);
  • PEM private 키로 RSA 생성하기
unsigned char *key = "PEM 형식의 private 키";
BIO *bio = BIO_new_mem_buf(key, -1);
RSA *rsa = PEM_read_bio_RSAPrivateKey(bio, &rsa, NULL, NULL);
  • RSA 생성
int bits = 2048;

BIGNUM *bn = BN_new();

if ( BN_set_word(bn, RSA_F4) != 1 ) throw "BN_set_word fail";

RSA *rsa = RSA_new();

if ( RSA_generate_key_ex(rsa, bits, bn, NULL) != 1 ) throw "RSA_generate_key_ex fail";
  • 인증서(.cer) 파일로 RSA 생성하기
char path[256] = ".cer 경로";

FILE* fp = fopen(path, "rb");
if(fp == NULL) throw "File open fail";

X509 *cert = d2i_X509_fp(fp, NULL);
if(cert == NULL) throw "X509 parsing fail";

EVP_PKEY *pkey = X509_get_pubkey(cert);
if (pkey == NULL) throw "public key getting fail";

int id = EVP_PKEY_id(pkey);
if ( id != EVP_PKEY_RSA ) throw "is not RAS Encryption file";

RSA *rsa = EVP_PKEY_get1_RSA(pkey);
if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
  • PCKS12(.p12) 파일로 RSA 생성하기
char path[256] = ".p12 경로";
char passwd[128] = ".p12 파일 암호"

FILE* fp = fopen(path, "rb");
if(fp == NULL) throw "File open fail";

PKCS12 *pkcs12 = d2i_PKCS12_fp(fp, NULL);
if(pkcs12 == NULL) throw "PKCS12 load fail";

EVP_PKEY *pkey = NULL;
X509 *cert = NULL;
int result = PKCS12_parse(pkcs12, passwd, &pkey, &cert, NULL);
if ( result != 1 ) throw "PKCS12 parsing fail";

RSA *rsa = EVP_PKEY_get1_RSA(pkey);
    if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
  • DER 파일 읽기
int size = 0;
FILE* fp = NULL;

try {
    fp = fopen(filename, "rb");
    if ( fp == NULL ) throw L"File open error";

    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    if ( size <= 0 ) throw L"File size is zero";

    fseek(fp, 0, SEEK_SET);

    if ( *out_len < size ) throw L"Out of memeory";

    if ( fread(out, sizeof(unsigned char), size, fp) != size ) throw L"File read error";

    *out_len = size;
}
catch(TCHAR *msg)    {
    MessageBox(msg);
    isSuccess = false;
}

if ( fp ) fclose(fp);
  • DER 형식의 private key로 RSA 생성

    • DER 형식의 private key 생성 명령
    openssl rsa -inform PEM -outform DER -in privatekey.pem -out privatekey.der
    • RSA 생성 코드
    const unsigned char *key = { /*DER 형식의 private*/ };
    const int key_len = 1192; // key의 길이
    
    EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &key, key_len);
    if ( pkey == NULL ) throw "RSA private Key read fail";
    
    RSA *rsa = EVP_PKEY_get1_RSA(pkey);
    if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
  • DER 형식의 public key로 RSA 생성(294byte)

    • DER 형식의 public key 생성 명령
    openssl rsa -pubin -in publickey.pem -inform PEM -pubout -out publickey.der -outform DER
    • RSA 생성 코드
    RSA *rsa = d2i_RSA_PUBKEY(NULL, &key, key_len);
  • DER 형식의 public key로 RSA 생성(993byte)

    • DER 형식의 public key 생성 명령
    openssl x509 -in ca.crt -pubkey -out ca_publickey.der -outform DER
    • RSA 생성 코드
    X509 *cert = d2i_X509(NULL, &key, key_len);
    if ( cert == NULL ) throw "RSA public key read fail";
    
    EVP_PKEY *pkey = X509_get_pubkey(cert);
    if (pkey == NULL) throw "public key getting fail";
    
    int id = EVP_PKEY_id(pkey);
    if ( id != EVP_PKEY_RSA ) throw "is not RAS Encryption file";
    
    RSA *rsa = EVP_PKEY_get1_RSA(pkey);
  • 순수한 public key(256byte) 로 RSA 생성하기

/**
 * @brief ASN1 포로트콜용 길이 문자열을 HEX STRING으로 만든다.
 * @param [in] size byte data 길이
 * @param [out] out HEX STRING
 * @return out
 */
inline char *ASN1_MAKE_HEX_LENGTH(int size, char *out)
{
    if( size < 0x81 ) sprintf(out, "%02X", size);
    else if ( size == 0x81 ) sprintf(out, "81%02X", size);
    else if ( size > 0x81 ) sprintf(out, "82%04X", size);

    return out;
}


const unsigned char *key = "HEX String의 256byte public 키";

char hex[540+1] = "";
unsigned char raw_buf[4096], *p;
int raw_size = 0;
char seq_length[6+1]="", int_length[6+1]="";
int seq_size_of_byte, int_size_of_byte = (strlen((const char*)key) +2) / 2; // include first "00"

ASN1_MAKE_HEX_LENGTH(int_size_of_byte, int_length);

seq_size_of_byte = 1 + (strlen(int_length)/2) + int_size_of_byte    // public key block
                    + 5;                                            // exponent block

ASN1_MAKE_HEX_LENGTH(seq_size_of_byte, seq_length);

sprintf(hex, "30%s02%s00%s0203010001", seq_length, int_length, key);

// public key size를 256byte로 고정하는 경우
//sprintf(hex, "3082010A0282010100%s0203010001", key);

p = raw_buf;

hex2binary(raw_buf, &raw_size, hex);

RSA *rsa = d2i_RSAPublicKey(NULL, (const unsigned char**)&p, raw_size);
if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";

RSA 에서 추출

  • RSA에서 PEM public 키 추출
BIO *bio = BIO_new(BIO_s_mem());

// -----BEGIN RSA PUBLIC KEY----- 이런 해더로 생성됨
//if ( PEM_write_bio_RSAPublicKey(bio, rsa) != 1 )  throw "PEM_write_bio_RSAPublicKey fail";

// -----BEGIN PUBLIC KEY----- 이런 해더로 생성됨
if ( PEM_write_bio_RSA_PUBKEY(bio, rsa) != 1 )  throw "PEM_write_bio_RSAPublicKey fail";

int size = BIO_get_mem_data(bio, &p);
if ( size <= 0 ) throw "Public size is zero";
size++; // include null

char *buffer = new char[size];

int read_size = BIO_read(bio, buffer, size-1);
if ( read_size != size-1 ) throw "Public read fail";
  • RSA에서 PEM private 키 추출
BIO *bio = BIO_new(BIO_s_mem());

if ( PEM_write_bio_RSAPrivateKey(bio, _rsa, NULL, NULL, 0, NULL, NULL) != 1) throw "PEM_write_bio_RSAPrivateKey fail";

int size = BIO_get_mem_data(bio, &p);
if ( size <= 0 ) throw "Private size is zero";
size++; // include null

char *buffer = new char[size];

int read_size = BIO_read(bio, buffer, size-1);
if ( read_size != size-1 ) throw "Private read fail";
  • RSA에서 순수한 Public 키(256byte) 추출 하기(DER 파싱)
/**
  @breif der를 한번 파싱한다.
  @param [in] in 파싱 시작위치
  @param [out] tag 태그 type
  @param [out] length data length
  @param [out] data data 시작위치
  @return true 성공
*/
bool parsingDer(const unsigned char *in, unsigned char *tag, int *length, unsigned char **data)
{
    int offset = 0;
    *tag = in[0];

    if ( in[++offset] == 0x82 ) {
        *length = (int)(in[offset+1] << 8 | in[offset+2]);
        offset += 2;
    } else {
        *length = (int)(in[++offset]);
    }

    *data = ( unsigned char *)&in[++offset];

    return true;
}

unsigned char out[256];
unsigned char tag, *buf, *start_pos, *data_pos;

int data_len = i2d_RSAPublicKey( rsa, &buf );
if ( data_len < 0 ) throw "Fail get public key from rsa";

start_pos = buf;

parsingDer(start_pos, &tag, &data_len, &data_pos);
if ( tag != 0x30 ) throw "Fail parsing at SEQUENCE";

start_pos = data_pos;
parsingDer(start_pos, &tag, &data_len, &data_pos);
if ( tag != V_ASN1_INTEGER ) throw "Fail parsing at INTEGER";

memcpy(out, &(data_pos[1]), data_len-1);
  • RSA에서 시작일 추출
ASN1_TIME* atime_before = NULL;

atime_before = X509_get_notBefore(_cert);
if ( atime_before == NULL ) throw "X509_get_notBefore fail";
  • RSA에서 만료일 추출
ASN1_TIME* atime_after = NULL;

atime_after = X509_get_notAfter(_cert);

if ( atime_after == NULL ) throw "X509_get_notAfter fail";

ASN1_TIME을 문자열로 변환

char* ASN1_TIME_to_string(const ASN1_TIME* time, char out[DT_STRING_LENGTH])
{
    struct tm t;
    const char* str = (const char*) time->data;
    size_t i = 0;

    memset(&t, 0, sizeof(t));

    if (time->type == V_ASN1_UTCTIME) {/* two digit year */
        t.tm_year = (str[i++] - '0') * 10;
        t.tm_year += (str[i++] - '0');
        if (t.tm_year < 70)
            t.tm_year += 100;
    } else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
        t.tm_year = (str[i++] - '0') * 1000;
        t.tm_year+= (str[i++] - '0') * 100;
        t.tm_year+= (str[i++] - '0') * 10;
        t.tm_year+= (str[i++] - '0');
        t.tm_year -= 1900;
    }
    t.tm_mon  = (str[i++] - '0') * 10;
    t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1.
    t.tm_mday = (str[i++] - '0') * 10;
    t.tm_mday+= (str[i++] - '0');
    t.tm_hour = (str[i++] - '0') * 10;
    t.tm_hour+= (str[i++] - '0');
    t.tm_min  = (str[i++] - '0') * 10;
    t.tm_min += (str[i++] - '0');
    t.tm_sec  = (str[i++] - '0') * 10;
    t.tm_sec += (str[i++] - '0');

    /* Note: we did not adjust the time based on time zone information */
    time_t tt = mktime(&t);
    //strftime(out, DT_STRING_LENGTH, "%Y-%m-%d %H:%M:%S", localtime(&tt));
    strftime(out, DT_STRING_LENGTH, "%Y%m%d", localtime(&tt));

    return out;
}

암호화

  • public키로 생성한 RSA로 암호화
RSA_public_encrypt(flen, from, to, rsa, padding);
  • private키로 생성한 RSA로 암호화
RSA_private_encrypt(flen, from, to, rsa, padding);

복호화

  • public키로 생성한 RSA로 복호화
RSA_public_decrypt(flen, from, to, rsa, padding);
  • private키로 생성한 RSA로 복호화
RSA_private_decrypt(flen, from, to, rsa, padding);

base64

  • 인코딩
int raw_size = 256;
unsigned char raw[256] ={/* RAWDATA */};

int bas64_size = 1.5 * raw_size;
char *out = new char[bas64_size];

bas64_size = EVP_EncodeBlock((unsigned char*)out, (const unsigned char*)raw, raw_size);
out[bas64_size++] = 0;

기타 함수

  • hex string을 binary(RAWDATA)로 만들기
void hex2binary(unsigned char *dst, int *dst_len, const char *src)
{
    int src_len = strlen(src);
    char *end =0;
    char buf[3] = {0,};
    int i=0;

    for(i=0; i<src_len; i++) {
        strncpy(buf, &src[i*2], 2);
        dst[i] = (char)strtol(buf, &end, 16);
    }

    *dst_len = i/2;

    return;
}


Code Finder

Code Finder

개요

설치

git clone https://github.com/lmk/code_finder.git
cd code_finder
npm install
npm run build

실행

npm run start

내 해더 파일을 사용하는 방법

  • /build/config.json 파일을 수정합니다.
{
  "list": [
    {
      "id" : "5RAcQeaaF4XYtVaAwg4Gtqf2C4xbpU7v",
      "title": "Sample Project",
      "files": [
        "sample-project/error.h",
        "sample-project/type.h"
      ],
      "option": {
        "checked": true
      }
    },
    {
      "id" : "FCg4h39RBah2hL99n6w4MYmLsd0nTslf",
      "title": "openssl",
      "files": [
        "openssl/err.h"
      ]
    }
  ]
}
  • "id": 고유 ID 아무 값이나 상관없습니다. 여기서는 32자리 해시 코드를 사용했습니다.
  • "title": 프로젝트 제목
  • "files": 파싱할 해더파일 목록

Restriction

  • Edge 지원합니다.
  • Chrome 지원합니다.
  • Internet Explorer 지원합니다. (isomorphic-fetch를 사용했습니다.)
  • node v6.0 이상 설치되어 있어야 합니다.
  • python 2.7 설치되어 있어야 합니다.



C 상수 파서

아주 오래전에 만들어 놓은 것을 github에 올리면서 readme 파일을 작성했습니다.

https://github.com/lmk/c_define_parser


개요

c 헤더 파일을 파싱해서 '#define', 'enum' 구문으로 정의된 값, 상수명을 검색합니다.

사용법

error.h 파일의 내용이 아래와 같을 때

#define ERROR_NONE 0x00
#define ERROR_LOGIN 0x01
#define ERROR_FILE 0x02
#define ERROR_DEVICE 0x03
$ find_const.py 1
1 is ERROR_LOGIN

$ find_const.py 0x03
0x03 is ERROR_DEVICE

구현 로직

  1. 모든 파일을 머지한다.
  2. 주석을 제거한다.
  3. enum 구문을 파싱해서 dic_command에 저장한다.
  4. define 구문을 파싱해서 dic_command에 저장한다.
  5. dic_command에 저장된 상수 값을 찾아서 치환 한다.
  6. dic_command의 key, value 값을 바꿔서 dic_command_r에 저장한다.
  7. 값으로 상수를 찾는다.

선행 작업

find_const.py 파일 9번째 줄에 FILE_PATH 배열에 파싱할 헤더파일 목록을 추가합니다.

python에서 ctype을 이용한 c library 연동 예제

python의 ctype 모듈을 이용해서 visual c++로 작성한 동적 dll을 읽는 sample 코드 입니다.

소스 코드 설명

  • /vc/test_dll: 동적 dll 코드 입니다.

    • SET_VALUE: int 형 값을 저장합니다.
    • GET_VALUE: 저장한 int형 값을 읽어 옵니다.
    • SET_CALLBACK: 함수 포인터를 저장합니다. python으로 코딩한 함수를 넘길 것 입니다.
    • RUN_CALLBACK: 저장한 함수 포인터에 정수형 값을 넘겨 실행 합니다. 실행하면 python함수가 실행될 것 입니다.
    • SET_CLASS_CALLBACK: c++ class를 사용해서 함수 포인터를 저정합니다.
    • RUN_CLASS_CALLBACK: c++ class를 사용해서 저장한 함수 포인터를 실행합니다.
    • SET_THREAD_CALLBACK: 함수 포인터를 저장하고, thread 에서 1초 후 함수를 실행합니다.
    • INIT_CLASS: c++ class를 동적으로 생성합니다.
    • SET_D_CLASS_CALLBACK: 동적으로 생성한 class를 사용해서 함수 포인터를 저장합니다.
    • RUN_D_CLASS_CALLBACK: 동적으로 생성한 class를 사용해서 함수 포인터를 실행합니다.
  • /vc/call_dll: test_dll을 LoadLibrary() 해서 순서대로 테스트 하는 c++ 코드 입니다.(test_dll.dll 파일의 경로는 맞게 수정해야 동작합니다)

  • /python: 동적 dll 읽어 순서대로 테스트 하는 python 코드 입니다.(test_dll.dll 파일의 경로는 맞게 수정해야 동작합니다)