기초 어셈블리어, Assembly Language

기초 어셈블리어

INC(Increase)
피연산자에 1을 더한다.
연산 결과에 따라 ZF(Zero Flag)나 OF(Overflow Flag)가 세트될 수 있다.
ex. INC reg, INC mem

DEC(Decrease)
피연산자에 1을 뺀다.
연산 결과에 따라 ZF나 OF가 세트될 수 있다.
ex. DEC reg, DEC mem

ADD(Add)
Destination에 Source의 값을 더해서 Destination에 저장한다.
연산 결과에 따라 ZF, OF, CF(Carry Flag)가 세트될 수 있다.
ex. ADD eax(Destination), 100(Source) : eax레지스터에 100을 더해서 eax레지스터에 저장

SUB(Subtract)
Destination에 Source의 값을 빼서 Destination에 저장한다.
연산 결과에 따라 ZF, OF, CF가 세트될 수 있다.
ex. Sub eax(Destination), 100(Source) : eax레지스터에 100을 빼서 eax레지스터에 저장

MUL(Unsigned Integer Multiply)
부호 없는 al, ax, eax의 값을 피연산자와 곱한다. 피연산자가 8비트이면 al과 곱해서 ax에 저장되고 16비트면 ax와 곱하고 dx(상위16비트):ax(하위16비트)에 저장된다. 연산 결과에 따라 OF, ZF 플래그가 세트될 수 있다.
ex. MUL reg

IMUL(Integer Multiplication)
부호 있는 al, ax, eax의 값을 피연산자와 곱한다. 연산결과에 따라 CF, OF가 세트될 수 있다.
ex. IMUL r/m8 : 단일 피연산자이고 피연산자를 al, ax, eax에 곱한다.
IMUL r16(destination), r/m16(value) : value를 al, ax, eax와 곱해서 destination에 저장
IMUL r16(destination), r/m8(value), imm8(value) : value끼리 곱해서 destination에 저장
(연산 결과가 destination 레지스터의 크기보다 크다면 OF, CF가 세트된다.)

DIV(Unsigned Integer Divide)
8, 16, 32비트 부호 없는 정수의 나눗셈을 수행한다. 연산결과에 따라 CF, OF, ZF가 세트될 수 있다.
ex. DIV reg

MOV(Move)
Source에서 Destination으로 데이터를 복사한다.
ex. MOV reg(Destination), mem(Source)

MOVS(Move String)
Source에서 Destination으로 데이터를 복사한다.
ex. MOVS Destination, Source

MOVSB, MOVSW, MOVSE(Move String)
SI 또는 ESI 레지스터에 의해 지정된 메모리 주소의 내용을 DI 또는 EDI 레지스터에 의해 지정되는 메모리 주소로 복사한다.
MOVSB는 BYTE 단위, MOVSW는 WORD 단위, MOVSD는 DWORD 단위로 복사한다. 방향 플래그(DF)가 1로 세트되어 있으면 ESI와 EDI는 복사 시에 감소하게 되고 DF가 0으로 세트되어 있으면 ESI와 EDI는 복사 시에 증가하게 된다.
ex. MOVSB, MOVSW, MOVSD

MOVSX(Move with Sign-Extend)
BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 부호는 그대로 유지.
ex. MOVSX reg32, reg16

MOVZX(Move with Zero-Extend)
BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 남은 비트는 0으로 채운다.
ex. MOVZX reg32, reg16

INT(Interrupt)
소프트웨어 인터럽트를 발생시켜 운영체제의 서브루틴을 호출한다.
ex. INT imm

AND(Logical AND)
Destination과 Source 피연산자의 각 비트가 AND 연산된다.
AND 연산은 각 비트가 모두 1일 때만 결과 값이 1이 된다.
ex. Destination : 10011100
Source      : 11001010
결과          : 10001000
AND reg(Destination), mem(Source) : reg와 mem을 AND 연산한 후 결과를 reg에 저장
AND 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

OR(Inclusive OR)
Destination과 Source 피연산자의 각 비트가 OR 연산된다.
OR 연산은 각 비트가 모두 0이면 결과는 0이고 모두 0이 아니면 결과는 1이 된다.
ex. Destination : 10011100
Source      : 11001010
결과          : 11011110
OR reg(Destination), mem(Source) : reg와 mem을 OR 연산한 후 결과를 reg에 저장
 OR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

XOR(Exclusive OR)
Destination과 Source 피연산자의 각 비트가 XOR 연산된다.
XOR 연산은 각 비트가 서로 다른 값일 때만 결과가 1이다. 같은 값이라면 결과는 0이 된다.
ex. Destination : 10011100
Source      : 11001010
결과          : 01010110
XOR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.
피연산자의 두 값이 같은 값이라면 결과는 항상 0이 된다.
레지스터를 0으로 초기화시킬때 MOV 명령어를 사용하기보다는 XOR reg, reg으로 많이 사용한다.

TEST(Test)
두 피연산자 사이에 논리적인 AND 연산을 수행하여 플래그 레지스터에 영향을 주지만 결과값은 저장하지 않는다. OF, CF는 항상 0으로 세트되고 TEST 연산 결과값이 0이면 ZF가 1로
세트, 0이 아니면 ZF가 0으로 세트된다.
ex. TEST reg, reg

STC(Set Carry Flag)
캐리 플래그(CF)를 1로 세트한다.
ex. STC

CLC(Clear Carry Flag)
캐리 플래그(CF)를 0으로 세트한다.
ex. CLC

STD(Set Direction Flag)
방향 플래그(DF)를 1로 세트한다.
ex. STD

CLD(Clear Direction Flag)
방향 플래그(DF)를 0으로 세트한다.
ex. CLD

STI(Set Interrupt Flag)
인터럽트 플래그(IF)를 1로 세트한다.
ex. STI

CLI(Clear Interrupt Flag)
인터럽트 플래그(IF)를 0으로 세트한다.
ex. CLI

SHR(Shift Right)
Destination 피연산자를 Source 피연산자의 크기만큼 오른쪽으로 각 비트를 시프트시킨다.
최상위 비트는 0으로 채워지고 최하위 비트는 캐리 플래그(CF)로 복사된다.
ex. SHR reg, imm16

SHL(Shift Left)
Destination 피연산자를 Source 피연산자의 크기만큼 왼쪽으로 각 비트를 시프트시킨다.
최상위 비트는 캐리 플래그(CF)로 복사되고 최하위 비트는 0으로 채워진다.
ex. SHL reg, imm16

PUSH(Push on Stack)
스택에 값을 넣는다.
ESP의 값이 4만큼 줄어들고 이 위치에 새로운 값이 채워진다.
ex. PUSH reg8

PUSHAD(Push All)
EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터의 값을 스택에 PUSH한다.
레지스터들의 값을 보관해야 할 때 사용한다.
ex. PUSHAD

PUSHFD(Push Flags)
플래그 레지스터를 스택에 PUSH한다.
플래그 레지스터의 값을 보관해야 할 때 사용한다.
ex. PUSHFD

POP(Pop from Stack)
ESP 레지스터가 가리키고 있는 위치의 스택 공간에서 4byte 만큼을 Destination 피연산자에 복사하고 ESP 레지스터의 값에 4를 더한다.
ex. POP reg16(Destination)

POPAD(Pop All Flags from Stack)
스택에 존재하는 값을 EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터로 POP한다.
PUSHAD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다.
ex. POPAD

POPFD(Pop Flags from Stack)
스택에 존재하는 값을 플래그 레지스터로 POP한다.
PUSHFD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다.
ex. POPFD

XCHG(Exchange)
두 피연산자의 내용이 서로 교환된다.
XCHG 명령은 imm 값이 피연산자로 올 수 없다.
ex. XCHG reg, mem

NEG(Negate)
피연산자의 2의 보수를 계산하여 결과를 피연산자에 저장한다.
ex. NEG reg

PTR
피연산자의 크기를 재설정한다.
ex. WORD PTR value : value의 크기를 WORD의 크기로 재설정한다.

OFFSET
세그먼트의 시작으로부터 변수가 위치한 거리까지의 상대적 거리를 리턴한다.
ex. OFFSET value : value가 존재하는 위치를 세그먼트 시작 지점으로부터의 상대적 거리를 구한다.

LEA(Load Effective Address)
Source 피연산자의 유효 주소를 계산하여 Destination 피연산자에 복사한다.
간단히 주소를 알아내서 복사하는 명령어다.
ex. LEA reg(Destination), mem(Source)

REP(Repeat String)
ECX 레지스터를 카운터로 사용해서 문자열 관련 명령을 ECX>0인 동안 반복한다.
한번 진행될 때마다 ECX 레지스터값이 -1 된다.
ex. REP MOVS destination, source

JMP(Jump Unconditionally to Lable)
피연산자의 위치로 실행 흐름이 변경된다. 피연산자가 가리키는 코드로 점프 뛰어서 실행한다고 생각하면 된다. 피연산자에는 레이블이나 레지스터, 메모리 값이 올 수 있다.
short점프는 -127 ~ 127 byte범위 안에서, near점프는 같은 세그먼트 내부에서, far점프는 현재 세그먼트를 벗어날 때 사용된다. JMP 명령어는 되돌아올 리턴 어드레스 값을 저장하지 않는다.
ex. JMP shortlabel, JMP nearlabel, JMP farlabel

CALL(Call a Procedure)
함수 호출시 사용된다. JMP명렁어 같이 프로그램의 실행 흐름이 변경되지만 JMP명령와 다른 점은 되돌아올 리턴 어드레스(CALL 다음 명령)를 스택에 저장한다는 것이다. 되돌아올 주소를 저장하기 떄문에 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있다. 호출한 함수가 일을 다 마치면 원래 위치에서 다시 프로그램이 실행될 수 있음을 의미한다.
ex. CALL nearlabel, CALL farlabel, CALL mem16, CALL 함수주소,
CALL DWORD PTR[EAX+8], CALL : 특정 api 지목

CMP(Compare)
두 피연산자를 비교하는 작업을 한다. Destination 피연산자에서 Source 피연산자를 묵시적으로 빼서 값을 비교한다. 두 피연산자의 값이 같다면 결과는 0이 되고 제로 플래그(ZF)가 1로 세트된다. 다르다면 제로 플래그(ZF)는 0으로 세트된다.
ex. CMP reg, reg

NOP(No Operation)
아무 일도 하지 않는 명령어이다.
ex. NOP

조건 점프 명령
JMP 명령어와는 다르게 특정 조건이 만족하게 된다면 점프를 수행하게 되는 명령어이다.

 명령어
 명령어의 의미
 명령어가 수행되기 위한
 플래그 레지스터와
 범용 레지스터의 상태 
 JA
 Jump if (unsigned) above
 CF=0 and ZF=0
 JAE
 Jump if (unsigned) above
 or equal 
 CF=0
 JB
 Jump if (unsigned) below
 CF=1
 JBE
 Jump if (unsigned) below
 or equal
 CF=1 or ZF=1
 JC
 Jump if carry flag set
 CF=1
 JCXZ
 Jump if CX is 0
 CX=0
 JE
 Jump if equal
 ZF=1
 JECXZ
 Jump if ECX is 0
 ECX=0
 JG
 Jump if (signed) greater
 ZF=0 and SF=0
 JGE
 Jump if (signed) greater
 or equal
 SF=OF
 JL
 Jump if (signed) less
 SF!=OF
 JLE
 Jump if (signed) less
 or equal
 ZF=1 and OF!=OF
 JNA
 Jump if (unsigned) not 
 above
 CF=1 or ZF=1
 JNAE
 Jump if (unsigned) not
 above or equal 
 CF=1
 JNB
 Jump if (unsigned) not
 below
 CF=0
 JNBE
 Jump if (unsigned) not
 below or equal 
 CF=0 and ZF=0
 JNC
 Jump if carry flag not set
 CF=0
 JNE
 Jump if not equal
 ZF=0
 JNG
 Jump if (signed) not greater
 ZF=1 or SF!=OF
 JNGE
 Jump if (signed) not greater
 or equal 
 SF!=OF
 JNL
 Jump if (signed) not less
 SF=OF
 JNLE
 Jump if (signed) not less
 or equal
 ZF=0 and SF=OF
 JNO
 Jump if overflow flag not set
 OF=0
 JNP
 Jump if parity flag not set 
 PF=0
 JNS
 Jump if sign flag not set
 SF=0
 JNZ
 Jump if not zero
 ZF=0
 JO
 Jump if overflow flag is set
 OF=1
 JP
 Jump if parity flag set
 PF=1
 JPE
 Jump if parity is equal
 PF=1
 JPO
 Jump if parity is odd
 PF=0
 JS
 Jump if sign flag is set
 SF=1
 JZ
 Jump is zero
 ZF=1

출처:dakuo.tistory.com
Read more

METASPLOIT 기본 명령어, 사용법

METASPLOIT 사용법

Metasploit은 펄스크립트를 사용하는 침투테스트도구이다.

1. METASPLOIT 경로
metasploit Kali Linux 기본적으로 설치되어 있다. Kali 기준으로 경로는 /usr/share/metasploit-framework/ .

Screen Shot 2015 03 26 at 11 22 55 AM

/user/share/metasploit-framework 경로를 살펴보면 msf* 다양한 실행파일들이 있다. 여기서 우리가 사용할 실행파일은 msfconsole metasploit framework console 약자고 저장되어있는 exploit 코드나 payload등을 간단하게 가져와 사용할수 있고 명령어를 통해 다른 기능들도 사용할 있다. GUI 버젼도 존재하지만 너무 느리고 멋이 없다.

msfconsole 실행하기에 앞서 exploit코드들이 있는 경로를 확인해보자. msfconsole framework 맞게 작성된 외부 파일을 가져와 임포트 하기 위해서는 경로를 알고 있는게 많은 exploit 코드들을 관리하기 편하다.

Screen Shot 2015 03 26 at 11 24 12 AM

/usr/share/metasploit-framework/modules/exploits 경로로 들어가면 exploit 코드들을 여러 분류로 나누어 정리해 놓았다. windows/ftp 관련 exploit 코드들만해도 엄청 많다. 모두 .rb 파일로 metasploit-framework ruby 프레임워크에 맞는 문법에 따라 작성된 코드만 해석할 있다.

msfconsole 이용해 exploit 테스트해 보려는데 msfconsole 있는 exploit코드에 맞는 버젼의 프로그램을 찾기가 귀찮아서 exploit-db 사이트에서 프로그램과 msfconsole exploit코드가 모두 업로드 되어있는 것을 찾았다.


BadBlue 라는 간단한 웹 서버 프로그램으로 더 이상 업데이트도 없고 회사가 없어진 것 같다.
exploit code파일은 16806.rb msfconsole 맞게 작성되어있다. 이제 파일을 msfconsole에서 임포트해야 한다. 
exploit code 모아놓은 폴더중 적당히 맞는 범주의 폴더에 16806.rb 파일을 넣고 msfconsole 실행 시킨다.

Screen Shot 2015 03 26 at 11 28 46 AM

GEEK하다. 재시작하면 계속 다른 ASCII ART 나온다. (http://www.network-science.de/ascii/ 문자를 ASCII ART 바꿔주는 사이트) 처음 화면에서는 metasploit version, 저장되어 있는 exploit, payloads 등등의 정보를 알려준다. exploit code 1420개나 있지만 바로 exploit code 실행시켜 공격에 성공할 있는 별로 없다. (운영체제, 버젼, 언어등 환경의 문제나 잘못된 exploit code)

2. METASPLOIT 명령어

먼저 콘솔 옵션들을 확인해 보자. help라고 치면 콘솔 옵션을 확인할 수 있다.


- 저장된 exploit code확인 명령어
msf > show exploits

Screen Shot 2015 03 26 at 11 38 17 AM

exploit code가 엄청나게 많다. 이름과 날짜와 위험수준 그리고 어떤 취약점을 이용하는 공격인지 어느 프로그램의 몇 버젼을 대상으로 하는지 간단한 설명이 써있다. 이중에서 내가 필요한 exploit code를 찾아 해킹공격을 할수있다.

앞에서 exploit code경로에 추가한 Badblue exploit code msfconsole 임포트하려면 reload_all 치면된다.

- 전체 module 다시 로딩
msf > reload_all

Screen Shot 2015 03 26 at 3 33 28 PM

경로에 추가한 exploit code를 임포트 했으면 이제 제대로 임포트가 되었는지 찾아보자.

- 저장된 자료(exploit, payloads, encoder)에서 문자열로 검색하기
msf > search “문자열

Screen Shot 2015 03 26 at 3 34 39 PM

msfconsole에서는 tab으로 자동완성도 안된다. 귀찮아서 그냥 /exploit/ 경로안에 바로 넣었다.. 추가한 exploit code 16806, BadBlue Buffer Overflow 보인다. 제대로 임포트가 되었다. 옛날에 백트랙인가 칼리인가에서 아무리 exploit 파일을 경로에 추가해도 임포트가 안된적이 있었다. 알고보니 컴퓨터에 metasploit 두개가 깔려 있었고 /bin/ 있는 msfconsole 경로가 아닌 다른곳에 설치되어 있는 msfconsole exploit 파일을 넣었었다. 임포트가 안될 때에는 msfconsole 바로 실행하지말고 ./msfconsole 이용해서 실행해보자.

exploit 장전하자. 

- exploit code 장전
msf > use “EXPLOIT_NAME”

쉘에도 exploit이 장전되었다. 이제 희생자를 조준하는 설정들을 맞춰줄 일이 남아있다. exploit 마다 다 설정들이 다르니 확인하면서 환경에 맞게 추가 해주면 된다.

- exploit option 보기
msf exploit(exploit) > show options

Screen Shot 2015 03 26 at 3 35 43 PM

BadBlue exploit코드의 설정은 몇개 없다. Proxies, RHOST, RPORT, VHOST 있는데 Required 채워주면 공격을 실행할 있고 나머지는 진짜 옵션이다. Description 설명에 맞는 값을 채워 넣어주면 된다.

희생자 (172.16.219.131) : Windows XP Pro SP3, BadBlue Webserver 
공격자 (172.16.219.130) : Kali Linux, msfconsole 

- option 설정하기
msf exploit(exploit) > set “NAME” “VALUE”

Screen Shot 2015 03 26 at 3 37 56 PM

RPORT 디폴트로 80으로 설정되어 있고(보통 타겟 프로그램 디폴트 ) set 명령어로 RHOST 설정해 주었다. show options 확인할 있다.

BadBlue exploit에는 필요 없지만 target, payload 공격자의 입맛에 맞게 수정할 있다.

- payload 선택하기
msf exploit(exploit) > set PAYLOAD “PAYLOAD_NAME”

- target 목록 보기
msf exploit(exploit) > show targets

- target 선택하기
msf exploit(exploit) > set  target “NUM”

이렇게 타겟에 맞게 옵션을 설정 해준다. 설정이 완료되면 설정에 맞게 공격을 수행할 수 있다.

3. EXPLOIT


- exploit 하기
msf exploit(exploit) > exploit

exploit 명령어를 입력하면 .rb 파일의 exploit코드를 실행한다.

Screen Shot 2015 03 26 at 3 41 22 PM

exploit 이 실행되면서 몇개의 로그가 찍히고 Meterprerter session이 성립된다. exploit 코드가정상적으로 실행되어 payload까지 진입하고 payload에 의해서 세션 연결이 되었다. (다른 payload를 사용하면 session을 맺지 않고 공격할수도 있다.) 쉽게 말해 타켓프로그램의 취약점을 이용해서 넷버스의 서버파일을 실행한것과 비슷하다.
여튼 meterpreter > 쉘이 떨어지면 공격에 성공한 것이다. meterpreter 쉘은 다양한 공격 명령들을 수행할 수 있다. 여기서도 help (?) 명령어를 치면 공격 가능한명령어들을 확인할 수 있다.

Screen Shot 2015 03 26 at 3 42 50 PM

굉장히 다양한 공격을 수행할 수있다.
난 희생자의 컴퓨터정보를 얻어오는 sysinfo 명령어와 상대 모니터출력을 흠쳐오는 screenshot 명령어를 수행해 보았다.

Screen Shot 2015 03 26 at 3 53 27 PM

희생자의 컴퓨터 정보를 알아내고 스크린샷도 저장이 되었다.

LtSQfmZa

희생자의 모니터도 흠쳐 볼 수있다.
Read more

scp(Secure Copy) 로 바이너리 다운



pwnable문제를 풀다보면 바이너리가 주어지지 않는 경우가 있다. 이럴경우 scp를 이용해서 로컬로 바이너리를 복사하면 우리의 친구 IDA를 이용해 더욱 편하게 분석을 할 수 있다.

scp -P port ID@IP:원격지경로(서버 절대 경로) 다운받을경로(로컬 절대 경로)
scp -P 1022 cr3denza@192.168.0.41:/home/pwn500/pwn500 /home/cr3denza/
Read more

함수호출규약 (Calling Convention), cdecl, stdcall

Calling Convection


함수호출규약은 함수(subroutine, callee)가 어떻게 인자를 전달받고 결과값을 반환하는지에 대한 로우레벨에서의 규칙이다. 다양하게 구현된 함수호출규약들은 파라미터의 위치, 리턴 값이나 리턴 주소의 위치, 파라미터의 정리등의 방법에서 차이가 있다.
함수호출규약은 프로그래밍언어의 속도, 코드길이 등과 관련있지만 별차이 없다.

대표적인 함수호출규약으로는 cdecl, stdcall, fastcall이 있다. 이중에 우리는 cdecl과 stdcall에 대해서만 확인해 볼것이다.

cdecl, stdcall 모두 스택 상단에 인자를 쌓고 함수를 호출하는 방식으로 인자를 전달하고 리턴 값은 eax 레지스터를 이용해 받아온다. 즉 함수 호출까지는 cdecl, stdcall 모두 동일한 어셈블리어를 갖고 있다. 둘의 차이는 함수가 모두 실행되고 콜러로 돌아올때 발생한다. 인자를 전달하기 위해 할당한 스택 메모리를 정리 할때 cdecl은 콜리가 스택을 정리하고 stdcall은 콜리에서 콜러로 복귀한 후 콜러가 스택을 정리한다.

확인해보자


먼저 인자를 받아 합을 리턴해주는 간단한 cdecl 방식의 함수를 작성하고 컴파일 한다.
버퍼보호기능은 끄고 최적화는 모두 비활성화 시키고 컴파일 하였다. 컴파일러는 visual C++을 사용하였다.



push 2

push 1
call func

부분을 보면 스택의 상단에 2와 1을 쌓고 func함수를 호출하는 것을 알 수 있다. 2와 1은 func함수 안에서 인자로 사용된다. 인자를 push 함으로 스택은 8bytes만큼 자란다.
func 함수가 모두 실행되고 main함수로 돌아오자마자 add esp, 8 명령어를 만난다. esp에 8bytes를 더해 esp를 내린다. push 2, push 1로 8bytes만큼 잡은 인자를 정리하는 것이다.
func함수도 살펴보자.



cdecl 방식의 func 함수 안이다. func 함수를 호출 하기 이전에 main은 인자 전달을 위해 스택의 상단에 인자를 쌓았다. 그리고 func함수가 호출(RET생성)되고 프롤로그가 실행(SFP생성)되면서 RET, SFP가 인자 위로 쌓였을 것이다. 이제 func함수가 실행이 되는데 main함수에서 넘겨준 인자를 ebp+val 의 방식으로 사용을 한다. func함수가 실행된 후에는 몇개의 레지스터를 복원하고 스택프레임을 func진입당시 초기상태로 복원은 하지만 인자를 정리하는 부분은 찾아볼 수 없다.

func함수에서 main함수로 복귀한 후 main함수가 인자를 정리하는 것을 앞에서 확인했다.
cdecl은 콜러가 인자를 정리하는 것을 알 수 있다.

- cdecl 방식은 Caller에서 인자영역 스택을 정리한다
- 일반적으로 코딩으로 함수를 작성하면 cdecl 방식의 함수

cdecl 에서 인자를 할당받았던 스택을 어떻게 정리하는지 알아봤으니 stdcall도 같은 방식으로 확인해보자.

동일한 코드를 동일한 방법으로 func의 호출규약만 stdcall로 수정하고 컴파일 한다.
어셈블리어로 main함수부터 확인해보자.



cdecl방식과 func함수를 호출하기 이전까지는 같고 func함수가 끝나고 나서 add esp, 8 명령어가 없어졌다. 스택정리를 main에서 하지 않는다. func함수를 확인해보자.




stdcall 방식의 func 함수이다. cdecl방식과 다른게 없어 보이지만 자세히보면 retn에 오퍼랜드로 8이 들어가 있는것을 볼 수 있다. retn 하면서 스택의 인자 부분을 정리하는 코드이다.(ret 하면서 add esp, 8 이 된다.)

stdcall 방식에서는 콜리가 인자를 정리하는 것을 확인할 수 있다.

- stdcall 방식은 Callee가 인자영역 스택을 정리한다.
- 일반적으로 DLL안의 함수가 stdcall 방식의 함수

리눅스에서도 확인을 해보았는데 좀 이상하다.
Visual C++과 똑같이 코드를 작성하고 컴파일하였다.
gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -o cdecl cdecl.c



main함수에도 func함수에도 인자를 정리해주는 add esp, 8이 없다. 인자를 스택의 상단에 쌓아주는 push도 없다. 대신에 mov를 이용해 스택의 상단에 값을 넣어준다. 그리고 func함수는 비슷하게 돌아간다. 초반에 지역변수 메모리를 할당할때 아예 인자의 메모리까지 다 할당해버리고 사용을 한다. int형 변수 2개를 만들고 확인해보니 프롤로그 이후에 esp에 값을 뺄때 16bytes를 뺀다.(int형 변수 8bytes, func에 들어갈 인자 8bytes) 그래서 함수 호출, 복귀시 인자부분을 정리하지 않아도 된다. 인자가 많아져봐야 몇바이트 안될것같고 여러 함수가 호출되어도 mov로 계속 인자를 넣어주면 되니 좋을것같다.

stdcall도 확인해봐야겠다.
똑같은 방법으로 stdcall로만 바꾸고 컴파일 하였다.



main함수에서 sub esp, 0x8을 하고 func함수에서는 ret 0x8를 한다.
func함수에서 main함수로 복귀할때 ret 0x8로 스택을 줄이고 main함수에 돌아와서 sub esp, 0x8로 스택을 다시 키운다. 이렇게 되면 main함수 초기의 스택의 모양와 같아진다. 
리눅스에서 stdcall은 왜있는거지.
Read more

콜러(caller)와 콜리(callee)

프로그램은 실행하면서 많은 함수를 호출한다. 함수가 호출되고 호출받은 함수가 실행되려면 레지스터와 스택을 사용해야 한다. 버퍼오버플로우 공격을 공부하면서 호출된 함수가 어떻게 esp (스택을 관리하는 레지스터)를 백업하는지 어떻게 스택을 독립적으로 사용하는지 알 수 있었다. 이번에는 프로그램의 흐름과 레지스터의 백업을 알아본다.


위의 그림을 보면 main() 함수에서 func() 함수를 호출하였다. main()함수를 콜러(호출함)라고하고 func()함수를 콜리(호출당함)라고 한다.
main()함수는 실행하면서 레지스터를 사용한다. 그리고 func()함수가 모두 실행되고 다시 main()함수로 되돌아왔을 때에 func()가 호출되기 이전의 레지스터의 값을 갖고 있어야 한다. 이 때 콜러가 저장하고 보존되도록 신경써야 하는 레지스터를 caller-saved-register라 하고 callee는 이 레지스터들을 자유롭게 사용할 수 있다.

콜러가 저장하는 레지스터 (caller-saved-register)

콜러가 사용하기전 반드시 백업해야 하는 레지스터, 콜리는 사용 가능
값이 보존되지 않아도 되는 경우는 백업하지 않을수 있다.
EAX, ECX, EDX

콜리가 저장하는 레지스터 (callee-saved-register)

콜리가 사용하기전 반드시 백업해야 하는 레지스터, 콜러는 사용 가능
콜리 입장에서는 콜러가 백업을 필요로하는 레지스터가 무엇인지 모르기 때문에 무조건 백업해야한다.
EBX, ESI, EDI, EBP

Read more

Stack 메모리, push, pop 명령어

스택 자료구조는 LIFO(Last in First Out) 구조로 마지막에 들어간 값이 가장 먼저 나오는 구조를 갖고 있다. CPU는 PUSH, POP어셈블리어 명령어와 esp, ebp 레지스터를 이용해서 스택을 관리하고 스택에는 지역변수와 임시 데이터들이 저장된다. EBP는 스택에서 가장 낮은 바닥(bottom)의 주소를 갖고 있고 ESP 는 스택 꼭대기인 탑(top)의 주소를 갖고 있다.



PUSH 명령어는 ESP위에 값을 넣고 ESP가 새로운 값을 가리키게 만든다(그림 상 한칸 올라간다).
POP명령어는 ESP가 가리키는 값을 스택에서 빼고 ESP를 한 칸(4byte) 내린다.

PUSH 0x1010명령어가 실행되면 esp는 한 칸 위로 올라가고(주소는 4byte 감소) 그 곳에 0x1010이 들어간다.위의 그림에서 PUSH 0x1010이 실행되면 ESP는 low address가 적혀있는 곳을 가리키게 되고 그 안에는 0x1010의 값이 들어간다.

POP명령어의 operand는 한 개고 레지스터가 오게 된다. POP eax가 실행되면 ESP가 가리키고 있는 DATA가 eax에 저장되고 ESP는 한 칸(4Byte) 내려온다. 위의 그림에서 POP eax가 실행되면 DATA1 (TOP)이 eax에 저장되고 ESP는 DATA2를 가리키게 된다.
Read more