어셈블리를 작성할 때 컴파일러에서 진행되는 작업을 확인해야 하는 경우가 있습니다. 쉘코드 문제를 해결하는 경우 명령을 인내심 있고 신중하게 실행해야 합니다.
이 기사에서는 x86_64 Ubuntu 시스템에서 32비트 ARM 쉘코드를 에뮬레이트하는 방법을 살펴봅니다. 대부분의 랩탑과 워크스테이션은 아직 ARM을 실행하지 않기 때문에 여기서 필요한 것은 시스템에서 기본이 아닌 명령을 실행하는 대체 방법입니다. 또한 원시 쉘코드 바이너리는 실행 가능한 형식이 아니며 대부분의 도구에서 실행할 수 없으므로 이러한 파일을 실행하는 다른 방법이 필요합니다.
여기서는 사용하기 쉬운 이진 분석 도구 세트를 통합하는 콘솔 기반 프레임워크인 Radare2를 사용합니다. 이러한 도구를 스크립팅하거나 대화형 명령줄 인터페이스를 사용할 수 있습니다. Ubuntu에서 이를 설정하려면 몇 가지 간단한 명령만 있으면 됩니다.
mkdir ~/github
cd ~/github
git clone https://github.com/radareorg/radare2.git
cd radare2
sys/install.sh
radare2가 설치되어 있는 경우 최신 버전을 실행 중인지 확인하십시오. 이 도구는 적극적으로 유지 관리되며 정기적으로 업데이트됩니다. 또한 2022년 6월 릴리스 이전에 몇 가지 버그가 있었기 때문에 이 평가판을 더 잘 수행할 수 없었습니다.
cd ~/github/radare2
git pull
sys/install.sh
r2 -V
이 기사에서 사용할 쉘코드 바이너리를 복제하기 위해 bash 프롬프트에서 다음을 실행할 수 있습니다.
nemo@hammerhead:~$ echo -n -e '\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x00' > shellcode-696.bin
nemo@hammerhead:~$ md5sum shellcode-696.bin
42ba1c77446594cac3508b940926575d shellcode-696.bin
ESIL 소개
ESIL(Evaluable String Intermediate Language)은 기본 하드웨어를 고려하지 않고 기계 명령을 "실행"할 수 있는 radare2에서 사용하는 하드웨어에서 추상화된 명령입니다. 이는 에뮬레이션 환경에서 네이티브가 아닌 어셈블리 명령을 실행하는 데 이상적입니다.
ESIL을 사용하여 쉘코드를 실행하려면 다음을 수행해야 합니다.
- 쉘코드 바이너리 로드
- 셸코드 바이너리를 올바르게 해석하는 방법을 알 수 있도록 radare2를 구성합니다.
- ESIL 초기화
- 필요에 따라 레지스터 설정
- 조립 지침으로 기능 확인
사이버 보안 학습을 돕기 위해 다음과 같은 전체 정보 세트를 무료로 받을 수 있습니다.
① 사이버 보안 학습 및 성장 경로에 대한 마인드 맵
② 60개 이상의 기존 사이버 보안 툴킷
③ 100개 이상의 SRC 분석 보고서
④ 사이버 보안 공격 및 방어 기술에 관한 150개 이상의 전자책
⑤ 가장 권위 있는 CISSP 자격증 시험 가이드 + 질문 은행
⑥ 1800페이지 이상의 CTF 실기 매뉴얼
⑦ 네트워크 보안 회사의 최신 면접 질문 모음(답변 포함)
⑧ APP 클라이언트 보안 테스트 가이드(Android+IOS)
ESIL로 ARM 쉘코드 실행
- 쉘코드 바이너리 로드
쉘코드 바이너리에서 "파일" 명령을 실행하면 Linux가 파일 형식을 결정할 수 없음을 알 수 있습니다. 마찬가지로, radare2는 그것이 무엇인지 결정할 수 없습니다.
nemo@hammerhead:~/labs/shellcode/asm$ file shellcode-696.bin
shellcode-696.bin: data
이진 파일일 뿐이므로 radare2에 로드하고 보고 싶은 것을 지정해야 합니다. 여기에서 ARM 파일을 올바르게 분석할 수 있도록 일부 소프트웨어 분석 설정을 수정합니다.
nemo@hammerhead:~/labs/shellcode/asm$ r2 shellcode-696.bin
[0x00000000]> e anal.arch = arm
[0x00000000]> e asm.arch = arm
[0x00000000]> e asm.bits = 32
[0x00000000]> e anal.armthumb=true
- 셸코드 바이너리를 올바르게 실행하는 방법을 알 수 있도록 radare2를 구성합니다.
다음으로 어떤 명령어가 ARM이고 어떤 명령어가 THUMB인지 지정하려고 합니다. 이렇게 하려면 명령어 유형이 변경되는 함수를 정의해야 한다는 것을 알게 되었습니다. 이 특정 쉘코드에서는 ARM과 THUMB 명령어 사이를 전환합니다.
[0x00000000]> af
[0x00000000]> pdf
┌ 8: fcn.00000000 ();
│ rg: 0 (vars 0, args 0)
│ bp: 0 (vars 0, args 0)
│ sp: 0 (vars 0, args 0)
│ 0x00000000 01308fe2 add r3, pc, 1
└ 0x00000004 13ff2fe1 bx r3
이 radare2 명령 스니펫에서는 주소가 0인 함수를 분석하고 있습니다. 여기에는 함수가 실제로 존재하지 않지만 "함수"를 ARM 또는 THUMB로 지정할 수 있도록 합니다. "pdf" 명령은 추가 및 bx 명령을 포함하는 함수의 분해 명령을 인쇄합니다.
<p>[0x00000000]> s 8
[0x00000008]> af
[0x00000008]> pdf
┌ 24: fcn.00000008 (int32_t arg1, int32_t arg2);
│ ; arg int32_t arg1 @ r0
│ ; arg int32_t arg2 @ r1
│ 0x00000008 78460c30 andlo r4, ip, r8, ror r6
│ 0x0000000c c0460190 andls r4, r1, r0, asr 13 ; arg2
│ ┌─< 0x00000010 491a921a bne 0xfe48693c
│ │ 0x00000014 0b2701df svcle 0x1270b
│ │ 0x00000018 2f62696e cdpvs p2, 6, c6, c9, c15, 1
└ │ 0x0000001c 2f736800 rsbeq r7, r8, pc, lsr 6
[0x00000008]> afB 16</p>
주소 8에서 시작하는 다음 명령어 세트는 THUMB 명령어입니다. "s 8 " 명령은 파일에서 8바이트를 찾고, 가고 싶은 곳으로 점프하고, 다음 "함수"를 정의합니다. "af"로 함수를 만든 후 "pdf"로 표시하려고 하면 약간 이상하게 보입니다. 이는 도구가 여전히 이러한 명령어를 ARM으로 해석하기 때문입니다.
비트 수를 16으로 설정하여 이 "함수"가 THUMB임을 지정할 수 있습니다. 즉, asm.bits를 16으로 설정하지만 이 기능에만 해당됩니다. 일반 ARM 바이너리에서 radare2는 이 구별을 자동으로 수행하려고 시도하지만 이 쉘코드 명령어 문자열만 있기 때문에 여전히 수동으로 수행해야 합니다.
처음 두 명령어를 삭제
하고 전체 THUMB 쉘코드를 사용할 수 있습니다. 이렇게 하면 함수를 다시 정의할 필요 없이 파일을 열 때 "e asm.bits=16"을 설정할 수 있습니다. 원하는 경우 두 가지 명령 유형을 구분할 수 있습니다.
Radare2에는 "izz" 명령을 사용하여 바이너리 파일의 모든 문자열을 표시하는 더 편리한 방법도 있습니다.
> izz
[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000008 0x00000008 4 5 ascii xF\f0
1 0x00000018 0x00000018 7 8 ascii /bin/sh</p>
이제 쉘코드 바이너리를 제대로 로드할 수 있습니다.
- ESIL 초기화
앞서 언급한 바와 같이 radare2에는 많은 내장 명령이 있으며 명령 접두사에 "?"를 추가하면 모든 관련 명령을 나열할 수 있습니다. "ae?" 명령은 ESIL 및 에뮬레이션과 관련된 명령을 나열합니다.
[0x00000000]> ae?
Usage: ae[idesr?] [arg] ESIL code emulation
| ae [expr] evaluate ESIL expression
| ae? show this help
| ae?? show ESIL help
| aea[f] [count] analyse n esil instructions accesses (regs, mem..)
| aeA[f] [count] analyse n bytes for their esil accesses (regs, mem..)
| aeb ([addr]) emulate block in current or given address
| aeC[arg0 arg1..] @ addr appcall in esil
| aec[?] continue until ^C
| aef [addr] emulate function
| aefa [addr] emulate function to find out args in given or current offset
| aeg [expr] esil data flow graph
| aegf [expr] [register] esil data flow graph filter
| aei[?] initialize ESIL VM state (aei- to deinitialize)
| aek[?] [query] perform sdb query on ESIL.info
| aeL list ESIL plugins
| aep[?] [addr] manage esil pin hooks (see “e cmd.esil.pin”)
| aepc [addr] change esil PC to this address
| aer[?] [..] handle ESIL registers like “ar” or “dr” does
| aes[?] perform emulated debugger step
| aets[?] esil Trace session
| aev [esil] visual esil debugger for the given expression or current instruction
| aex [hex] evaluate opcode expression
여기에서 먼저 "aei" 명령으로 ESIL을 초기화해야 합니다. 그런 다음 스택을 초기화해야 합니다. Radare2는 스택 위치를 자동으로 선택하지만 "aeim" 명령 매개변수를 사용하여 지정할 수도 있습니다.
[0x00000008]> aei
[0x00000008]> aeim
- 필요에 따라 레지스터 설정
쉘코드 명령어는 0부터 시작하므로 "aepc 0" 명령을 사용하여 프로그램 카운터(PC)를 0으로 설정해야 합니다. 오프셋 0 이외의 위치에서 실행을 시작하려면 "aepc
" 시작 주소를 설정합니다.[0x00000008]> aepc 0
쉘코드("subs r1, r1, r1")의 명령은 r1을 0으로 설정합니다. 이 레지스터는 기본적으로 이미 0이므로 0xffff로 설정하여 스테핑 중에 발생하는 변경 사항을 확인할 수 있습니다. 이렇게 하려면 "aer" 명령을 사용해야 합니다.
[0x00000008]> aer r1 = 0xffff
- 설정으로 기능 확인
자, 이제 모든 설정이 완료되었습니다. 시각적 모드로 전환하고 디버거 패널에 들어갈 수 있습니다. 시각적 모드에는 여러 옵션(패널)이 있으므로 올바른 패널에 도달하려면 "p"를 두 번 눌러야 합니다. 언제든지 시각화 모드를 종료하려면 Esc 키를 누르십시오. "?"를 눌러 사용 가능한 명령 목록을 볼 수도 있습니다.
[0x00000008]> V
(hit “p” twice to get to the debugger panel)
그런 다음 상단 근처에 일련의 레지스터가 있음을 알 수 있습니다. 다음과 같이 표시됩니다.
일반적으로 콘솔에 입력되는 모든 r2 명령은 시각적 모드에서도 입력할 수 있습니다. 예를 들어 오프셋 0x18에 문자열을 인쇄하려면 다음 명령을 실행해야 합니다.
# Hit “:” while in visual mode.
> ps @0x18
/bin/sh
> # Hit enter on a blank line to return to visual mode.
이제 "s" 키를 사용하여 어셈블리 명령을 실행할 수 있습니다. 찾아보면 스택 데이터와 함께 최상위 레지스터가 업데이트되는 것을 볼 수 있습니다(첫 번째 그림의 0x00178000에서 시작). 또한 실행할 다음 명령어(일명 PC)의 주소가 어셈블리 명령어(첫 번째 그림의 0x00000010)에서 강조 표시되어 있음을 알 수 있습니다.
위의 그림은 r1 레지스터가 0x0000ffff를 보유하고 있음을 보여줍니다. 또한 다음 명령어, 즉 "subs r1, r1, r1"이 실행된다는 점에 유의하십시오. 이 명령어는 자체에서 r1을 빼서 다시 r1에 저장하여 본질적으로 0으로 만듭니다.
다음 명령으로 이동하려면 "s" 키를 다시 누르십시오.
이제 "svc 1 " 명령으로 데몬을 통해 모든 것이 호출될 준비가 되었습니다. 이제 "execve" 호출을 수행하고 있으므로 r7 레지스터에 0xb를 설정해야 합니다. r0의 첫 번째 매개변수는 실행하려는 바이너리의 경로에 대한 포인터여야 합니다. r0이 0x18을 보유하고 있음을 알 수 있습니다. 다음을 실행하여 가리키는지 확인할 수 있습니다.
# Hit “:” while at the “svc 1” instruction in visual mode.
> ps @r0
/bin/sh
>
이제 "/bin/sh"에 어떤 매개변수도 전달하지 않고 환경 변수도 설정하지 않으므로 r1과 r2가 모두 0으로 설정되어 있음을 알 수 있습니다.
ARM 시스템에서 실행하고 있지 않기 때문에 데몬 호출("svc 1") 명령을 올바르게 사용할 수 없습니다. 더 복잡한 쉘코드를 테스트할 때 이것을 명심하십시오.
요약하다
사용자 지정 쉘코드 문제를 해결하든, 보고 있는 정적 콘텐츠를 확인하든, 지침이 실제로 무엇을 하는지 확인해야 하는 경우가 있습니다. Radare2를 사용하면 알 수 없는 파일 형식(예: 쉘코드 바이너리 또는 펌웨어 이미지)에서 네이티브가 아닌 어셈블리 파일을 로드하고 단계별로 명령을 실행할 수 있습니다.