SROP
분명 돼야 하는데 안된다면
i r
로 레지스터 모두가 정상인 상태로 설정되어 있는지 확인해본다.
Sigreturn
int
instruction을 실행하면, kernel mode로 진입하면서 user mode context를 kernel stack에 push 해놓는다.- signal을 감지하는 것은 kernel mode에서 수행된다. kernel은 수신된 signal이 있는지 확인하고 nonblocked pending signal이 있으면
do_signal()
를 호출한다. - 여기서 signal을 처리하게 되는데, 이 때 signal handler가 등록되어 있는 경우, signal handler를 실행하기 위해 user mode로 나가야한다.
일단 kernel mode에서 벗어나면 kernel stack이 초기화 되기 때문에, 아까 push해 둔 user mode context가 유실되어 어느 user mode로 돌아가야할지 알 수 없게된다.
- 따라서, signal handler를 실행하기 위해 user mode로 나가기 전에 kernel stack에 존재하는 user mode context를 user stack에 복사해두고, 나중에 이를 복원하기 위해 ret addr을
sigreturn()
syscall 주소로 설정한다. - signal handler를 실행한다.
- signal handler 실행을 마치고 리턴하면서
sigreturn()
이 호출되며 다시 kernel mode로 진입하게 되며 user stack에 있는 context가 kernel stack에 복원된다.
Note ) sigcontext
에 지정된 eip
는 최종적으로 kernel mode에서 user mode로 나가게 될 때 실행되는 지점이다. sigreturn()
은 syscall 이기 때문에 이미 호출되면서 kernel mode로 넘어간다. * 따라서 sigreturn()
은 iret
과는 다르다.
32bit
/usr/include/x86_64-linux-gnu/asm/unistd_32.hlink
1
2
3
4
#define \_\_NR\_read 3
#define \_\_NR\_write 4
#define \_\_NR\_sigreturn 119
#define \_\_NR\_execve 11 0xb
/usr/include/x86_64-linux-gnu/bits/sigcontext.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct sigcontext
{
unsigned short gs, \_\_gsh;
unsigned short fs, \_\_fsh;
unsigned short es, \_\_esh;
unsigned short ds, \_\_dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, \_\_csh;
unsigned long eflags;
unsigned long esp\_at\_signal;
unsigned short ss, \_\_ssh;
struct \_fpstate \* fpstate;
unsigned long oldmask;
unsigned long cr2;
};
* sigreturn으로 리턴하는 위치 바로 아래부터 gs, \__gsh, fs, ...
순으로 위치시키면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
$ objdump -d /lib/i386-linux-gnu/libc.so.6 | grep \<execve\>: -A 20
000b5f40 <execve>:
b5f40: 57 push %edi
b5f41: 53 push %ebx
b5f42: 8b 54 24 14 mov 0x14(%esp),%edx
b5f46: e8 90 08 07 00 call 1267db <\_\_frame\_state\_for+0x35b>
b5f4b: 81 c3 b5 50 0f 00 add $0xf50b5,%ebx
b5f51: 8b 4c 24 10 mov 0x10(%esp),%ecx
b5f55: 8b 7c 24 0c mov 0xc(%esp),%edi
b5f59: 87 df xchg %ebx,%edi
b5f5b: b8 0b 00 00 00 mov $0xb,%eax
b5f60: 65 ff 15 10 00 00 00 call \*%gs:0x10
따라서 레지스터를 다음과 같이 설정한다.
1
2
3
4
5
eax : 0xb
ebx : argument string
ecx : 0x00000000
edx : 0x00000000
eip : syscall OR ret
Note) 이 밖의 다른 레지스터들도 적절히 설정해주어야 한다.
cs, ss
반드시 정상일 때의 값으로 설정해주어야 한다! 커널모드인지 유저모드인지는 segment register를 보고 판단하기 때문에. 정상일 때의 값은 보통 다음과 같다.
1
2
cs : 0x33
ss : 0x2b
fs, gs
fs, gs
레지스터를 모두 0x0
으로 설정했는데도 sigreturn이 실행되고 나면 0x3
이 된다. 0x3
이어도 실행에는 문제가 없으나, 0xffff
같은 큰 값으로 설정하면 0x0
으로 만들 수 있다.
*fpstate
반드시 0x0
으로 설정한다. eax
가 설정해놓은 값 0xb
가 되는 것이 아니라 0x0
이 되어버리는 경우 이게 이상한 값으로 설정되어 있을 가능성이 크다.
EOF
쉘이 바로 종료되는 경우 EOF가 넘어가기 때문이다.
1
( cat exploit ; cat ) | ./srop\_taget
64bit
/usr/include/x86_64-linux-gnu/asm/unistd_64.hlink
1
2
3
4
5
#define \_\_NR\_syncfs 306 // used to set RAX = 0 → \_\_NR\_read
#define \_\_NR\_read 0
#define \_\_NR\_write 1
#define \_\_NR\_rt\_sigreturn 15 0xf
#define \_\_NR\_execve 59 0x3b
/usr/include/x86_64-linux-gnu/bits/sigcontext.h
* rsp
기준으로 parameter를 전달하기 때문에, syscall
하기 직전 상태에서 rsp+0x28 == r8
이 되도록 위치시키면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <signal.h>
struct sigcontext // sizeof(st) == 256 (0x100)
{
\_\_uint64\_t r8; rsp+0x28
\_\_uint64\_t r9;
\_\_uint64\_t r10;
\_\_uint64\_t r11;
\_\_uint64\_t r12;
\_\_uint64\_t r13;
\_\_uint64\_t r14;
\_\_uint64\_t r15;
\_\_uint64\_t rdi; rsp+0x68
\_\_uint64\_t rsi;
\_\_uint64\_t rbp;
\_\_uint64\_t rbx;
\_\_uint64\_t rdx;
\_\_uint64\_t rax;
\_\_uint64\_t rcx;
\_\_uint64\_t rsp; rsp+0xa0
\_\_uint64\_t rip;
\_\_uint64\_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short \_\_pad0; // == ss;
\_\_uint64\_t err;
\_\_uint64\_t trapno;
\_\_uint64\_t oldmask;
\_\_uint64\_t cr2;
\_\_extension\_\_ union // UNION
{
struct \_fpstate \* fpstate;
\_\_uint64\_t \_\_fpstate\_word;
};
\_\_uint64\_t \_\_reserved1 [8];
};
따라서 레지스터를 다음과 같이 설정한다.
1
2
3
4
5
rax : 0x3b
rdx : envp ( 0x0 )
rsi : argv ( 0x0 )
rdi : argument string
rip : syscall OR ret
vsyscall’s syscall; ret gadget
1
2
3
4
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
gdb-peda$ x/3i 0xffffffffff600007
0xffffffffff600007: syscall
0xffffffffff600009: ret