Reutrn-to-Csu Theory

Return-to-csu

Binary File

64bit ROP 기법을 사용할 때 우리는 gadget을 이용하곤 한다. 하지만 우리가 원하는 pop gadget이 존재하지 않을 경우는 어떻게 할까?
바로 RTC 를 사용하여 우회를 시킬 수 있다.

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-40h]

setvbuf(stdin, 0LL, 2, 0LL);
write(1, "Hey, ROP! What's Up?\n", 0x15uLL);
return read(0, &buf, 0x200uLL);
}
[Problem]
확인하기 위해 본인 사이트에 올라가 있는 문제를 예시로 들겠다. 문제에는 일반적인 ROP처럼 write 함수와 read 함수가 주어져있다.
1
2
3
4
5
6
7
8
9
10
11
12
[INFO] File: rtc
0x00000000004006bc: pop r12; pop r13; pop r14; pop r15; ret;
0x00000000004006be: pop r13; pop r14; pop r15; ret;
0x00000000004006c0: pop r14; pop r15; ret;
0x00000000004006c2: pop r15; ret;
0x000000000040054f: pop rbp; mov edi, 0x601048; jmp rax;
0x00000000004006bb: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x00000000004006bf: pop rbp; pop r14; pop r15; ret;
0x0000000000400560: pop rbp; ret;
0x00000000004006c3: pop rdi; ret;
0x00000000004006c1: pop rsi; pop r15; ret;
0x00000000004006bd: pop rsp; pop r13; pop r14; pop r15; ret;
[gadgets]
gadget 도구를 이용해서 확인해보면 rdi와 rsi를 채울 수 있을만한 gadget이 보이지 않는다. 그래도 괜찮다 우리에겐 csu가 있으니까! _libc_csu_init 이라는 함수를 어셈블리 뷰로 확인해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:0000000000400660 ; void _libc_csu_init(void)
.text:0000000000400660 public __libc_csu_init
.text:0000000000400660 __libc_csu_init proc near
.text:0000000000400660 ; __unwind {
.text:0000000000400660 push r15
.text:0000000000400662 push r14
.text:0000000000400664 mov r15d, edi
.text:0000000000400667 push r13
.text:0000000000400669 push r12
.text:000000000040066B lea r12, __frame_dummy_init_array_entry
.text:0000000000400672 push rbp
.text:0000000000400673 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040067A push rbx
.text:000000000040067B mov r14, rsi
.text:000000000040067E mov r13, rdx
.text:0000000000400681 sub rbp, r12
.text:0000000000400684 sub rsp, 8
.text:0000000000400688 sar rbp, 3
.text:000000000040068C call _init_proc
.text:0000000000400691 test rbp, rbp
.text:0000000000400694 jz short loc_4006B6
[libc_csu_init]
1
2
3
4
5
6
7
8
9
10
11
.text:00000000004006B6 loc_4006B6:
.text:00000000004006B6 add rsp, 8
.text:00000000004006BA pop rbx
.text:00000000004006BB pop rbp
.text:00000000004006BC pop r12
.text:00000000004006BE pop r13
.text:00000000004006C0 pop r14
.text:00000000004006C2 pop r15
.text:00000000004006C4 retn
.text:00000000004006C4 ; } // starts at 400660
.text:00000000004006C4 __libc_csu_ini
[libc_csu_init gadget1]
여기서 우리가 조작할 수 있는 주소는 rbx, rbp, r12, r13, r14, r15 이다. 첫번째, 두번째 인자값을 받는 rdi, rsi가 없는데 어떻게 할 수 있을까란 생각이 들 수 있겠지만 그 부분은 바로 아래의 코드를 확인하면 알 수 있을 것이다.
1
2
3
4
5
6
7
8
.text:00000000004006A0 loc_4006A0:
.text:00000000004006A0 mov rdx, r13
.text:00000000004006A3 mov rsi, r14
.text:00000000004006A6 mov edi, r15d
.text:00000000004006A9 call qword ptr [r12+rbx*8]
.text:00000000004006AD add rbx, 1
.text:00000000004006B1 cmp rbx, rbp
.text:00000000004006B4 jnz short loc_4006A0
[libc_csu_init gadget2]

[gadget1] 코드에서 pop 해줬던 r15,r14,r13 값을 다시 edi,rsi,rdx에 셋팅한다.(보다시피 rdi가 아닌 edi라서 edi 값은 32비트 범위의 한해서만 조작이 가능하다.)
그리고는 r12+rbx*8한 값을 호출하는데 rbx 값을 0으로 셋팅해준다면 0[rbx] * 8이 돼서 최종적으로 r12의 값만 호출할 수 있게끔 한다.
rbx의 값에 1을 더하고 rbp값과 비교연산을 거친다. rbx값을 1로 셋팅해준다면 조건분기문도 성립되어 문제 없이 통과하게 된다.

공유하기