disassemble #1 - how to use gdb?

disassemble을 공부하면서 기본적인 사용법을 적어봅니다.

시작에 앞서..


먼저 assembly language Cpu의 제조사에 따라, 혹은 32bit / 64bit에 따라 그 모습을 달리 합니다.

기본적으로 32bit cpu는 알고 계시듯 메모리를 4GB까지 사용 할 수 있습니다.
2의 23승을 타나내며 값으로는 4,294,967,296 이고,
이는 cpu가 한번에 처리 할 수 있는 값의 범위이며 주소의 범위입니다.

이후 64bit cpu에서는 총 처리할 수 있는 값이 2의 64승으로 늘어났으며, 이는 16엑사바이트 입니다. 1엑사바이트는 1,048,576 테라바이트로 엄청난 숫자임을 이해 할 수 있습니다.

또한 어셈블리에서는 레지스터의 이름도 변하였고, 사용 할 수 있는 레지스터의 수도 증가하였습니다. 이를 감안하여 공부해야 합니다.

기본적인 코드 작성과 gdb 사용



위에 작성한 간단한 프로그램을 gcc를 이용해 컴파일 해줍니다.
(-g옵션을 이용해 디버깅 가능한 컴파일을 합니다.)
gcc -g first.c

그리고 gdb를 이용해 ./a.out를 실행시켜 줍니다.

[root@localhost assem]# gdb ./a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/assem/a.out...done.
(gdb)

아래 명령어를 통해 break point를 main 함수에 걸어주고, 프로그램을 실행 하도록 합니다.

(gdb) break main
Breakpoint 1 at 0x4004cc: file first.c, line 4.
(gdb) run
Starting program: /root/assem/a.out

Breakpoint 1, main () at first.c:4
4 int i = 0;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64

위의 run에서 현재 4번행 int i = 0;을 가르키고 있는 것을 확인 할 수 있습니다.
이상태로 disassemble해보도록 하겠습니다.

(gdb) disassemble
Dump of assembler code for function main:
     0x00000000004004c4 <+0>: push   rbp
     0x00000000004004c5 <+1>: mov    rbp,rsp
     0x00000000004004c8 <+4>: sub    rsp,0x10
=> 0x00000000004004cc <+8>: mov    DWORD PTR [rbp-0x4],0x0
     0x00000000004004d3 <+15>: jmp    0x4004e3 <main+31>
     0x00000000004004d5 <+17>: mov    edi,0x4005e8
     0x00000000004004da <+22>: call   0x4003b8 <puts@plt>
     0x00000000004004df <+27>: add    DWORD PTR [rbp-0x4],0x1
     0x00000000004004e3 <+31>: cmp    DWORD PTR [rbp-0x4],0x1
     0x00000000004004e7 <+35>: jle    0x4004d5 <main+17>
     0x00000000004004e9 <+37>: leave
     0x00000000004004ea <+38>: ret
End of assembler dump.

위와 같은 모습을 보실 수 있고, 다음에 실행될 위치를 (=>) 가르킵니다.

아래 명령어를 통해 현재 rip 레지스터(Instruction pointer register)를 통해 현재 위치를 확인 하겠습니다.

이 레지스터 RIP(EIP - 32bit)는 이후에 실행될 명령어의 위치를 가르키게 됩니다.

(gdb) info register rip
rip            0x4004cc 0x4004cc <main+8>

위를 통해 현재 rip의 주소는 0x4004cc 임을 확인 할 수 있고,

(gdb) x/i 0x4004cc    혹은 (gdb) x/i $rip 를 통해 현재 위치의 명령어를 확인 할 수 있습니다.

(gdb) x/i 0x4004cc
=> 0x4004cc <main+8>: mov    DWORD PTR [rbp-0x4],0x0

어셈블리어는 기본 명령 <목적지>, <근원지> 로 표현되며, 위 코드에선
명령 - mov
목적지 rbp-0x4
근원지 0x0
이 됩니다.

DWORD PTR은 4 바이트의 값을 읽어들인다는 뜻이 됩니다. (Int형 변수)

먼저 위 행을 실행하기 앞서 rbp에 어떤 값이 들어있는지 조사 해 보도록 하겠습니다.

이는 아래와 같이 표현합니다.

(gdb) x/xw $rbp-4
0x7fffffffe2bc: 0x00000000

현재 rbp의 주소는 0x7fffffffe2bc임을 확인 할 수 있고 내부에는 0이 값이 들어 있는 것을 확인 할 수 있습니다.

x/xw의 앞의 x 는 examine 명령어를 나타내며, 메모리 값을 조사 할 때 사용됩니다.

뒤의 x는 진법을 표시하며 아래 알파벳을 이용 할 수 있습니다.

o - 8진법
x - 16진법
u - Unsigned 10진법
t - 2진법

그리고 마지막 w는 내부 값을 조사 할 때 유효한 크기를 나타냅니다.

b - 단일 바이트
h - 2바이트 (하프워드)
w - 4바이트 (워드)
g - 8바이트 (자이언트)

다시 돌아와 mov명령어는 근원지에 있는 값을 목적지에 집어 넣으라는 이야기 입니다.

nexti를 통해 한 라인을 실행 하도록 하겠습니다.

(gdb) nexti
0x00000000004004d3 6 for(i=0; i<2; i++){
(gdb) x/i $rip
=> 0x4004d3 <main+15>: jmp    0x4004e3 <main+31>

명령어를 확인하기 앞서 앞에서 봤던 mov명령어가 실행된걸 확인 하면,

(gdb) x/xw $rbp-4
0x7fffffffe2bc: 0x00000000

0이 들어가 있는 것을 확인 할 수 있습니다.

이후 x/i $rip를 통해 다음 명령어를 확인 해 보겠습니다.

jmp는 jump명령어로 0x4004e3번지로 jump하라는 명령입니다.

(gdb) x/i 0x4004e3 을 통해 번지를 조사하면
   0x4004e3 <main+31>: cmp    DWORD PTR [rbp-0x4],0x1
위 명령어로 이동 하라는 것을 확인 할 수 있습니다.

(gdb) nexti
0x00000000004004e3 6 for(i=0; i<2; i++){
(gdb) x/i $rip
=> 0x4004e3 <main+31>: cmp    DWORD PTR [rbp-0x4],0x1

cmp는 비교 명령어 입니다. rbp-0x4에 집어넣엇던

0이라는 값과, for문에서 집어넣었던 i<2라는 값을 비교합니다. (작거나 같은지)

(gdb) nexti
0x00000000004004e7 6 for(i=0; i<2; i++){
(gdb) x/i $rip
=> 0x4004e7 <main+35>: jle    0x4004d5 <main+17>

jle는 작거나 같은경우 jump하라는 명령어로 이 경우엔 윗 줄에서 비교했던
$rbp-4가 1보다 작거나 같으면 17라인으로 jump 하라는 명령입니다.

이 경우 $rbp-4의 값이 0이었으므로 17라인으로 이동하게 될 것입니다.
그렇지 않다면
이 코드에서는 점프 없이 다음 명령어로 통과하게 되어 for문을 빠져나오게 됩니다.

우선 다음으로 넘어가 보도록 하겠습니다.

(gdb) nexti
7 printf("Hello\n");
(gdb) x/i $rip
=> 0x4004d5 <main+17>: mov    edi,0x4005e8

예상대로 17라인으로 이동하는 것을 확인 할 수 있습니다.

여기선 EDI 레지스터에 0x4005e8 이라는 값을 집어넣으라는 이야기를 하고 있는데,

EDI(Extended Destination Index) register는 복사 시에 목적지의 주소가 저장되는 index입니다.

0x4005e8의 값을 확인 해 보기 위해 아래 명령어를 확인 해보도록 하겠습니다.

(gdb) x/xw 0x4005e8
0x4005e8 <__dso_handle+8>: 0x6c6c6548

(gdb) x/6b 0x4005e8
0x4005e8 <__dso_handle+8>: 0x48 0x65 0x6c 0x6c 0x6f 0x00

(gdb) x/6db 0x4005e8
0x4005e8 <__dso_handle+8>: 72 101 108 108 111

먼저 첫 번째 실행 결과로 0x6c6c6548 이란 값을 확인 할 수 있습니다.

위를 바이트 단위로 끊어 보게 되면 두 번째 출력값을 확인 할 수 있는데, 이는 꽤나 눈에 익은 HEX값입니다. 다음으로 세번째 출력 x/6db를 통해 6개의 바이트를 10진수로 꺼내 보면

ASCII(아스키코드)값의 범위에 들어가는 것을 보실 수 있습니다.

이를 아래 명령어를 통해 출력해보면

(gdb) x/6c 0x4005e8
0x4005e8 <__dso_handle+8>:   72 'H'   101 'e'    108 'l'   108 'l'   111 'o'   0 '\000'

각각 H e l l o \0 이란 것을 확인 할 수 있습니다.

더 보기좋게 하기위해 아래와 같은 출력 방법이 가능합니다.

x/s 0x4005e8

(gdb) x/s 0x4005e8
0x4005e8 <__dso_handle+8>: "Hello"


그런데 다시 위의 출력값으로 돌아가보면,

(gdb) x/xw 0x4005e8
0x4005e8 <__dso_handle+8>: 0x6c6c6548

(gdb) x/6b 0x4005e8
0x4005e8 <__dso_handle+8>: 0x48 0x65 0x6c 0x6c 0x6f 0x00

여기서 바이트의 역순(Byte-reversal)이 일어난 것을 확인 할 수 있습니다.

이는 프로세서가 값을 little-endian 바이트 순서로 저장하기 때문이며, 꺼내올때 역순으로 꺼내오기 때문입니다. 이는 cpu가 저장공간에 IP address를 저장할 때에도 같은 현상을 보실 수 있습니다.

이후 진행되는 나머지 코드들은 위 내용의 반복입니다.





linux - If server has multiple interface that specify default gateway device. (Red Hat Enterprise Linux 5, 6)

레드햇 리눅스 에서 서버 이더넷 포트에 다수의 게이트웨이가 있어 route시 default gateway가 재대로 잡히지 않는 현상이 있습니다.

위와 같은 현상이 있다면 아래와 같이 설정 할 수 있습니다.

- Resolution


 # vi /etc/sysconfig/network

위 명령어를 실행 한 뒤 아래 내용을 추가해줍니다.

 GATEWAYDEV=ethX

ethX = 디폴트 게이트웨이로 설정할 이더넷 포트

python - pxssh를 이용한 ssh connect & send command

pxssh를 이용하면 ssh를 이용해 linux에 접속하여 직접 command를 날릴 수 있고, 반환값도 받아 올 수 있습니다.

먼저 pxssh를 이용하기위해 모듈 pexpect를 설치해줍니다.

# wget https://pypi.python.org/packages/source/p/pexpect/pexpect-4.0.1.tar.gz#md5=056df81e6ca7081f1015b4b147b977b7

# tar xvf pexpect-4.0.1.tar.gz

# cd pexpect-4.0.1.tar.gz

# python setup.py install

먼저 모듈을 아래와 같이 로드해줍니다.

from pexpect import pxssh

현재 파이썬 3.x버전을 사용하고 있으며, 간혹 책이나 튜토리얼을 보게되면

import pxssh 와 같이 선언하는 문구를 보실 수 있습니다.

그러나 위와 같이 선언시 import하지 못해 에러가 발생합니다.

s = pxssh.pxssh()를 통해 변수를 초기화 해 준 뒤 아래의 함수들을 이용하여 로그인 및 커맨드를 실행합니다.

s.login(Domain, ID, Password) - 로그인 관련 메소드로 해당 도메인에 해당 아이디와
 패스워드를 이용해 로그인 합니다.

s.sendline(Command) - 명령어를 실행하는 부분으로 Command에 입력된 명령어를 실행합니다.

s.prompt() - 실행된 명령어의 실행(return되는) 값을 받기 위해 사용합니다.

s.before.decode() - s.before에 실행된 명령어를 가지고 있고, 이를 decode()하여 출력합니다.


위와 같은 코드를 완성하였습니다.

Line 4 - 127.0.0.1 도메인으로 root/123456 으로 로그인을 시도합니다.
(로그인 관련 리턴값을 받아 올 수 있습니다.)
Line 6 - uname -v 명령어를 실행합니다.
Line 8 - 받아온 값을 출력합니다.

실행 결과는 아래와 같습니다.

----------------------------------------------------------------
# python3 pxssh.py 
uname -v
#1 SMP Tue Nov 11 17:57:25 UTC 2014
----------------------------------------------------------------



linux - Dell서버 Ethernet device name em -> eth 변경

dell서버에서 centos나 redhat linux설치 시 ethernet 디바이스 이름이 아래와 같이 em으로 등록 되어 있는것을 볼 수 있습니다.

$ ifconfig

em1      Link encap:Ethernet  HWaddr 20:47:47:8B:72:2C  
            UP BROADCAST RUNNING SLAVE MULTICAST  MTU:1500  Metric:1
            RX packets:13287407 errors:0 dropped:0 overruns:0 frame:0
            TX packets:3518085 errors:0 dropped:0 overruns:0 carrier:0
            collisions:0 txqueuelen:1000 
            RX bytes:3979015280 (3.7 GiB)  TX bytes:1456833747 (1.3 GiB)
            Interrupt:41



$ cd /etc/sysconfig/network-scripts

위 경로로 이동해 보시면 디바이스 name이 em으로 되어있는 것을 보실 수 있습니다.

ifcfg-em1
ifcfg-em2
...


먼저 디바이스 name을 바꾸기 위해 패키지를 하나 삭제해야합니다.

rpm 명령어를 통해 패키지를 검색합니다.

$ rpm -qa | grep biosdevname
biosdevname-0.4.1-3.el6.x86_64

검색된 패키지를 rpm 명령어를 통해 삭제해 줍니다.

$ rpm -e biosdevname-0.4.1-3.el6.x86_64

위 명령어를 통해 패키지를 삭제 한 뒤 아래 파일을 수정해줍니다.

$ vi /etc/udev/rules.d/70-persistent-net.rules

위 파일이 존재하지 않는다면 추가 해 줘야 하는데, 첨부파일을 다운받아 수정하셔도 됩니다.

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="xx:xx:xx:xx:xx:xx", ATTR{type}=="1", KERNEL=="*", NAME="em1"

위 내용을 아래와 같이 수정합니다.

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="xx:xx:xx:xx:xx:xx", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

** xx:xx:xx:xx:xx:xx부분엔 포트의 mac-address를 넣어 주시면 됩니다. (ifcfg-emX 파일 참고)

em1 포트는 eth0 포트와 같습니다.

수정 하신 뒤 아래 폴더로 이동합니다.

$ cd /etc/sysconfig/network-scripts

그리고 ifcfg-em(숫자) 를 ifcfg-eth(숫자-1)로 수정 해 줍니다.
ex) ifcfg-em1 -> ifcfg-eth0

그리고 파일 내부를 수정해줍니다.
DEVICE=em1 -> DEVICE=eth0

$ init 6를 통해 시스템을 리부팅 시켜줍니다.