바이너리 분석에 특화된 도구들로 이루어진 리버스 엔지니어링 프레임 워크이다.
Static, Dynamic 분석을 모두 가능하며 지원되는 플랫폼이 매우 많다. (무려 ARM도 지원, 안드로이드 분석에도 활용 가능!)
CLI 기반이고 명령어가 많아 복잡한데 Cutter라는 GUI 버전도 있다. 하지만 아직 CLI의 기능을 모두 옮기지는 못한듯.
다음의 페이지에서 제공하는 튜토리얼을 통해서 간단히 사용법을 익혀보겠다.
https://monosource.gitbooks.io/radare2-explorations/content/tutorials.html
한 가지 주의할 점은, 튜토리얼에서 제공하는 파일은 직접 make
를 실행해서 바이너리를 생성해야 하는데 환경에 따라 PIE가 설정될 수 있다.
따라서 Makefile
을 다음과 같이 수정해야 한다.
#Makefile
patchme: patchme.o
gcc -m32 -o patchme patchme.o -no-pie
바이너리를 실행하면, 패치시켜서 함수호출을 시킬 수 있냐고 물어본다.
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme# ./patchme
Hello there! Can you patch me up to call my function?
원본을 patchme_fix
로 카피한 바이너리를 radare2로 로드시킨다.
r2 -Aw patchme_fix
먼저 main
함수를 보면 앞서 봤던 문자열 출력 코드 다음에 nop 코드가 보인다.
목표 함수를 찾아 이 nop 부분을 call XXX
로 변경시키는 것이 최종 목표이다.
f
나 afl
로 radare2가 분석한 심볼이나 라벨 등을 확인할 수 있다.
[0x08048310]> afl
0x08048310 1 50 entry0
0x08048343 1 4 fcn.08048343
0x080482f0 1 6 sym.imp.__libc_start_main
0x08048370 4 50 -> 41 sym.deregister_tm_clones
0x080483b0 4 58 -> 54 sym.register_tm_clones
0x080483f0 3 34 -> 31
...
...
하지만 이것으로 찾고자 하는 사용자 정의 함수를 명확히 분별하긴 힘들다.
문자열을 검색해본다.(iz
: information strings)
[0x08048310]> iz
[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x0000101c 0x0804a01c 53 54 .data ascii Hello there! Can you patch me up to call my function?
1 0x00001052 0x0804a052 10 11 .data ascii Thank you!
Thank you!라는 문자열이 확인된다.
문자열이 참조되는 위치를 찾는다.
[0x08048310]> axt 0x804a052
(nofunc) 0x8048433 [DATA] push loc.secret
0x804833 seek하는데, 명령어의 결과값을 이용해 인자를 전달할 수 있다.
[0x08048310]> axt 0x804a052~[1]
0x8048433
[0x08048310]> s `axt 0x804a052~[1]`
함수가 아니므로 pdf
로는 확인할 수 없고, Vp
를 입력하여 visual mode disassembler를 확인한다.
현재 위치에서 조금 올라가면 함수 프롤로그를 볼 수 있는데, 함수로 지정되어 있지는 않다.
함수 시작주소에서 uf
를 입력해 함수로 지정해준다.
그리고 ur
로 이름을 callme로 변경한다.
그리고 nop 위치에서 A
를 입력하면 어셈블리 코드를 입력할 수 있다.
call callme를 입력하여 패치한다.
q
를 두 번 입력해 빠져나온 후, 해당 파일을 실행해보면
[0x08048430]> q
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme# ./patchme_fix
Hello there! Can you patch me up to call my function?
Thank you!
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme#
패치되어 callme()
가 실행된 것을 확인할 수 있다.
비밀번호를 입력받고 일치 여부를 출력해주는 프로그램이다.
./xor
Enter the password: 1234
Wrong!
프로그램을 로드한다. -d
는 디버깅 및 트레이싱을 하겠다고 명시해주는 옵션이란다.
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut2-memory# r2 -Ad xor
Process with PID 23957 started...
= attach 23957 23957
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
glibc.fc_offset = 0x00148
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[TOFIX: aaft can't run in debugger mode.ions (aaft)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
-- Use radare2! Lemons included!
izz
는 모든 섹션의 문자열을 출력해준다.
[0x0804859b]> izz
[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000028 0x00000028 4 10 utf16le 4 \t(
1 0x00000154 0x08048154 18 19 .interp ascii /lib/ld-linux.so.2
2 0x0000025d 0x0804825d 9 10 .dynstr ascii libc.so.6
3 0x00000267 0x08048267 14 15 .dynstr ascii _IO_stdin_used
4 0x00000276 0x08048276 12 13 .dynstr ascii __printf_chk
5 0x00000283 0x08048283 7 8 .dynstr ascii strncmp
6 0x0000028b 0x0804828b 14 15 .dynstr ascii __isoc99_scanf
7 0x0000029a 0x0804829a 4 5 .dynstr ascii puts
8 0x0000029f 0x0804829f 17 18 .dynstr ascii __libc_start_main
9 0x000002b1 0x080482b1 16 17 .dynstr ascii __stack_chk_fail
10 0x000002c2 0x080482c2 9 10 .dynstr ascii GLIBC_2.7
11 0x000002cc 0x080482cc 11 12 .dynstr ascii GLIBC_2.3.4
12 0x000002d8 0x080482d8 9 10 .dynstr ascii GLIBC_2.0
...
...
마땅한 정보는 없다.
dcu main
로 main
직전까지 실행시키고 Vp
로 코드를 확인한다.
스택에 순차적으로 저장되어 생성된 어떤 문자열과scanf()
로 입력받은 문자열을 sym.check()
에서 비교한다.
[0xf7fd6c70]> dcu main
Continue until 0x0804859b using 1 bpsize
hit breakpoint at: 804859b
[0xf7fd6c70]> Vp
내용을 확인하기 위해 dcu sym.check
로 코드를 진행하고 sym.check
의 코드를 확인한다.
위 캡쳐화면에는 안나와있지만, 인자가 두 개 전달되는데 하나는 입력값이고 다른 하나는 시리얼이다.
상위에서 arg_4
로 전달되는게 입력값인데, 이것을 edx
에 넣고 eax
와 xor하고 있다.
eax
는 처음에 0x2a로 초기화되고 루프를 돌며 3씩 더해진다. 42, 45, 48, etc... 가 되겠다.
루틴을 확인했으므로 이것에 맞게끔, radare2의 기능을 이용해 xor 연산된 값을 구할 수 있다.
[0x08048660]> woe 42 3 @ esi!32
from 42 to 255 step 3 size 1
[0x08048660]> ps @ esi!32
*-0369<?BEHKNQTWZ]`cfilorux{~\x81\x84\x87
[0x08048660]> p8 32 @ esi
2a2d303336393c3f4245484b4e5154575a5d606366696c6f7275787b7e818487
이것은 esi
가 가리키는 위치에 0x2a부터 3씩 증가되는 값을 나열하여 저장시키는 것이다.
(esi
가 어딜 가리키는지는 상관안하고 진행하는거다. 테스트를 위해서)
그래서 마지막에 보면 p8 32 @ esi
로 esi
에 저장된 값을 확인해보면 0x2a부터 3씩 증가되는 헥스값이 출력되고 있다.
이렇게 초기화한 key 값과 XOR 인코딩된 시리얼과 연산하면, 원본 시리얼을 구할 수 있을 것이다.
XOR 인코딩된 시리얼은 eax
에 저장되어 있다.
(현재 eip
의 위치는 dcu sym.check
를 실행한 상태이므로 sym.check
가 실행되기 직전이다.)
wox
명령어로 XOR 연산을 시킬 수 있다.
[0x08048660]> ps @ eax
gb~|mMT\x0b6\x1a?*{/$%i)\x14\x1a[\x0c\x0dZ\x0b*
J\x19\xe9\xb3\xda
[0x08048660]> wox `p8 32 @ edi` @ eax!32
[0x08048660]> ps @ eax
MONO[th4t_wa5~pr3tty=ea5y_r1gh7]
하지만!
현재 최신 버전의 radare2
에서는 wox
명령어를 저렇게 사용할 수 없었다.
단순히 xor 시킬 1바이트의 헥스값만을 입력할 수 있다.
예를 들어
[0x8048660] wox 90
위 명령어를 실행하면 현재 radare2가 가리키는 위치인 0x8048660의 값과 90을 xor 시킬 것이다.
어쨌든.. 튜토리얼대로 시리얼을 구할 수는 없었다.
직접 다른 언어로 스크립트를 작성해야 한다..