Post

SROP

분명 돼야 하는데 안된다면 i r로 레지스터 모두가 정상인 상태로 설정되어 있는지 확인해본다.

Sigreturn

  1. int instruction을 실행하면, kernel mode로 진입하면서 user mode context를 kernel stack에 push 해놓는다.
  2. signal을 감지하는 것은 kernel mode에서 수행된다. kernel은 수신된 signal이 있는지 확인하고 nonblocked pending signal이 있으면 do\_signal()를 호출한다.
  3. 여기서 signal을 처리하게 되는데, 이 때 signal handler가 등록되어 있는 경우, signal handler를 실행하기 위해 user mode로 나가야한다.

일단 kernel mode에서 벗어나면 kernel stack이 초기화 되기 때문에, 아까 push해 둔 user mode context가 유실되어 어느 user mode로 돌아가야할지 알 수 없게된다.

  1. 따라서, signal handler를 실행하기 위해 user mode로 나가기 전에 kernel stack에 존재하는 user mode context를 user stack에 복사해두고, 나중에 이를 복원하기 위해 ret addr을 sigreturn() syscall 주소로 설정한다.
  2. signal handler를 실행한다.
  3. signal handler 실행을 마치고 리턴하면서 sigreturn()이 호출되며 다시 kernel mode로 진입하게 되며 user stack에 있는 context가 kernel stack에 복원된다.

Note ) sigcontext에 지정된 eip는 최종적으로 kernel mode에서 user mode로 나가게 될 때 실행되는 지점이다. sigreturn()은 syscall 이기 때문에 이미 호출되면서 kernel mode로 넘어간다. * 따라서 sigreturn()iret과는 다르다.

32bit

1
2
3
4
5
#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
26
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
13
$ 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
6
eax : 0xb
ebx : argument string
ecx : 0x00000000
edx : 0x00000000
eip : syscall OR ret

Note) 이 밖의 다른 레지스터들도 적절히 설정해주어야 한다.

cs, ss

반드시 정상일 때의 값으로 설정해주어야 한다! 커널모드인지 유저모드인지는 segment register를 보고 판단하기 때문에. 정상일 때의 값은 보통 다음과 같다.

1
2
3
cs : 0x33
ss : 0x2b

fs, gs

fs, gs 레지스터를 모두 0x0으로 설정했는데도 sigreturn이 실행되고 나면 0x3이 된다. 0x3이어도 실행에는 문제가 없으나, 0xffff같은 큰 값으로 설정하면 0x0으로 만들 수 있다.

*fpstate

반드시 0x0으로 설정한다. eax가 설정해놓은 값 0xb가 되는 것이 아니라 0x0이 되어버리는 경우 이게 이상한 값으로 설정되어 있을 가능성이 크다.

EOF

쉘이 바로 종료되는 경우 EOF가 넘어가기 때문이다.

1
2
( cat exploit ; cat ) | ./srop\_taget

64bit

1
2
3
4
5
6
#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
38
#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
6
rax : 0x3b
rdx : envp ( 0x0 )
rsi : argv ( 0x0 )
rdi : argument string
rip : syscall OR ret

vsyscall’s syscall; ret gadget
1
2
3
4
5
0xffffffffff600000 0xffffffffff601000 r-xp      [vsyscall]
gdb-peda$ x/3i 0xffffffffff600007
0xffffffffff600007:  syscall
0xffffffffff600009:  ret

This post is licensed under CC BY 4.0 by the author.